From: Tim Goya Date: Sun, 16 Dec 2007 22:57:24 +0000 (+0000) Subject: move changes to the right branch X-Git-Url: https://git.octo.it/?a=commitdiff_plain;h=0ca46e18808e20795cd0e8eb7c82cc38cc927ebf;p=supertux.git move changes to the right branch SVN-Revision: 5201 --- diff --git a/src/src/Jamfile b/src/src/Jamfile new file mode 100644 index 000000000..092591a9e --- /dev/null +++ b/src/src/Jamfile @@ -0,0 +1,35 @@ +SubDir TOP src ; + +SubInclude TOP src squirrel ; +SubInclude TOP src scripting ; + +sources = + [ Wildcard *.cpp *.hpp ] + [ Wildcard audio : *.cpp *.hpp ] + [ Wildcard audio/newapi : *.cpp *.hpp ] + [ Wildcard badguy : *.cpp *.hpp ] + [ Wildcard binreloc : *.c *.h ] + [ Wildcard control : *.cpp *.hpp ] + [ Wildcard gui : *.cpp *.hpp ] + [ Wildcard lisp : *.cpp *.hpp ] + [ Wildcard math : *.cpp *.hpp ] + [ Wildcard object : *.cpp *.hpp ] + [ Wildcard physfs : *.cpp *.hpp ] + [ Wildcard sprite : *.cpp *.hpp ] + [ Wildcard tinygettext : *.cpp *.hpp ] + [ Wildcard trigger : *.cpp *.hpp ] + [ Wildcard video : *.cpp *.hpp ] + [ Wildcard worldmap : *.cpp *.hpp ] + [ Wildcard obstack : *.c *.h *.hpp ] +; +TRANSLATABLE_SOURCES += [ SearchSource $(sources) ] ; + +#Application supertux : $(sources) $(wrapper_objects) ; +Application supertux2 : $(sources) $(wrapper_objects) : linkerfile ; +C++Flags supertux2 : -DAPPDATADIR=\'\"$(appdatadir)\"\' ; +LinkWith supertux2 : squirrel ; +ExternalLibs supertux2 : SDL SDLIMAGE GL OPENAL VORBIS VORBISFILE OGG ICONV PHYSFS BINRELOC LIBCURL ; +Help supertux2 : "Build the supertux2 executable" ; +IncludeDir supertux2 : squirrel/include squirrel ; +Package [ Wildcard scripting : *.cpp *.hpp ] ; + diff --git a/src/src/addon.cpp b/src/src/addon.cpp new file mode 100644 index 000000000..1b6dbdc2c --- /dev/null +++ b/src/src/addon.cpp @@ -0,0 +1,102 @@ +// $Id$ +// +// SuperTux - Add-on +// Copyright (C) 2007 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// + +#include +#include +#include "addon.hpp" +#include "addon_manager.hpp" + +void +Addon::install() +{ + AddonManager& adm = AddonManager::get_instance(); + adm.install(*this); +} + +void +Addon::remove() +{ + AddonManager& adm = AddonManager::get_instance(); + adm.remove(*this); +} + +void +Addon::parse(const lisp::Lisp& lisp) +{ + try { + lisp.get("kind", kind); + lisp.get("title", title); + lisp.get("author", author); + lisp.get("license", license); + lisp.get("http-url", http_url); + lisp.get("file", file); + lisp.get("md5", md5); + } catch(std::exception& e) { + std::stringstream msg; + msg << "Problem when parsing addoninfo: " << e.what(); + throw std::runtime_error(msg.str()); + } +} + +void +Addon::parse(std::string fname) +{ + try { + lisp::Parser parser; + const lisp::Lisp* root = parser.parse(fname); + const lisp::Lisp* addon = root->get_lisp("supertux-addoninfo"); + if(!addon) throw std::runtime_error("file is not a supertux-addoninfo file."); + parse(*addon); + } catch(std::exception& e) { + std::stringstream msg; + msg << "Problem when reading addoninfo '" << fname << "': " << e.what(); + throw std::runtime_error(msg.str()); + } +} + +void +Addon::write(lisp::Writer& writer) const +{ + writer.start_list("supertux-addoninfo"); + if (kind != "") writer.write_string("kind", kind); + if (title != "") writer.write_string("title", title); + if (author != "") writer.write_string("author", author); + if (license != "") writer.write_string("license", license); + if (http_url != "") writer.write_string("http-url", http_url); + if (file != "") writer.write_string("file", file); + if (md5 != "") writer.write_string("md5", md5); + writer.end_list("supertux-addoninfo"); +} + +void +Addon::write(std::string fname) const +{ + lisp::Writer writer(fname); + write(writer); +} + +bool +Addon::equals(const Addon& addon2) const +{ + if ((this->md5 == "") || (addon2.md5 == "")) return (this->title == addon2.title); + return (this->md5 == addon2.md5); +} + diff --git a/src/src/addon.hpp b/src/src/addon.hpp new file mode 100644 index 000000000..b65efe704 --- /dev/null +++ b/src/src/addon.hpp @@ -0,0 +1,84 @@ +// $Id$ +// +// SuperTux - Add-on +// Copyright (C) 2007 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +#ifndef ADDON_H +#define ADDON_H + +#include +#include +#include "lisp/parser.hpp" +#include "lisp/lisp.hpp" +#include "lisp/writer.hpp" + +/** + * Represents an (available or installed) Add-on, e.g. a level set + */ +class Addon +{ +public: + std::string kind; + std::string title; + std::string author; + std::string license; + std::string http_url; + std::string file; + std::string md5; + + bool isInstalled; + + /** + * Download and install Add-on + */ + void install(); + + /** + * Physically delete Add-on + */ + void remove(); + + /** + * Read additional information from given contents of a (supertux-addoninfo ...) block + */ + void parse(const lisp::Lisp& lisp); + + /** + * Read additional information from given file + */ + void parse(std::string fname); + + /** + * Writes out Add-on metainformation to a Lisp Writer + */ + void write(lisp::Writer& writer) const; + + /** + * Writes out Add-on metainformation to a file + */ + void write(std::string fname) const; + + /** + * Checks if Add-on is the same as given one. + * If available, checks MD5 sum, else relies on title alone. + */ + bool equals(const Addon& addon2) const; + +}; + +#endif diff --git a/src/src/addon_manager.cpp b/src/src/addon_manager.cpp new file mode 100644 index 000000000..79e353741 --- /dev/null +++ b/src/src/addon_manager.cpp @@ -0,0 +1,295 @@ +// $Id$ +// +// SuperTux - Add-on Manager +// Copyright (C) 2007 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// + +#include +#include +#include +#include +#include +#include +#include "addon_manager.hpp" +#include "config.h" +#include "log.hpp" +#include "lisp/parser.hpp" +#include "lisp/lisp.hpp" +#include "lisp/list_iterator.hpp" + +#ifdef HAVE_LIBCURL +#include +#include +#include +#endif + +#ifdef HAVE_LIBCURL +namespace { + + size_t my_curl_string_append(void *ptr, size_t size, size_t nmemb, void *string_ptr) + { + std::string& s = *static_cast(string_ptr); + std::string buf(static_cast(ptr), size * nmemb); + s += buf; + log_debug << "read " << size * nmemb << " bytes of data..." << std::endl; + return size * nmemb; + } + + size_t my_curl_physfs_write(void *ptr, size_t size, size_t nmemb, void *f_p) + { + PHYSFS_file* f = static_cast(f_p); + PHYSFS_sint64 written = PHYSFS_write(f, ptr, size, nmemb); + log_debug << "read " << size * nmemb << " bytes of data..." << std::endl; + return size * written; + } + +} +#endif + +AddonManager& +AddonManager::get_instance() +{ + static AddonManager instance; + return instance; +} + +AddonManager::AddonManager() +{ +#ifdef HAVE_LIBCURL + curl_global_init(CURL_GLOBAL_ALL); +#endif +} + +AddonManager::~AddonManager() +{ +#ifdef HAVE_LIBCURL + curl_global_cleanup(); +#endif +} + +std::vector +AddonManager::get_installed_addons() const +{ + std::vector addons; + + // iterate over complete search path (i.e. directories and archives) + char **i = PHYSFS_getSearchPath(); + if (!i) throw std::runtime_error("Could not query physfs search path"); + for (; *i != NULL; i++) { + + // get filename of potential archive + std::string fileName = *i; + + // make sure it's in the writeDir + static const std::string writeDir = PHYSFS_getWriteDir(); + if (fileName.compare(0, writeDir.length(), writeDir) != 0) continue; + + // make sure it looks like an archive + static const std::string archiveExt = ".zip"; + if (fileName.compare(fileName.length()-archiveExt.length(), archiveExt.length(), archiveExt) != 0) continue; + + // make sure it exists + struct stat stats; + if (stat(fileName.c_str(), &stats) != 0) continue; + + // make sure it's an actual file + if (!S_ISREG(stats.st_mode)) continue; + + Addon addon; + + // extract nice title as fallback for when the Add-on has no addoninfo file + static const char* dirSep = PHYSFS_getDirSeparator(); + std::string::size_type n = fileName.rfind(dirSep) + 1; + if (n == std::string::npos) n = 0; + addon.title = fileName.substr(n, fileName.length() - n - archiveExt.length()); + std::string shortFileName = fileName.substr(n, fileName.length() - n); + addon.file = shortFileName; + + // read an accompaining .nfo file, if it exists + static const std::string infoExt = ".nfo"; + std::string infoFileName = fileName.substr(n, fileName.length() - n - archiveExt.length()) + infoExt; + if (PHYSFS_exists(infoFileName.c_str())) { + addon.parse(infoFileName); + if (addon.file != shortFileName) { + log_warning << "Add-on \"" << addon.title << "\", contained in file \"" << shortFileName << "\" is accompained by an addoninfo file that specifies \"" << addon.file << "\" as the Add-on's file name. Skipping." << std::endl; + } + } + + // make sure the Add-on's file name does not contain weird characters + if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) { + log_warning << "Add-on \"" << addon.title << "\" contains unsafe file name. Skipping." << std::endl; + continue; + } + + addon.isInstalled = true; + addons.push_back(addon); + } + + return addons; +} + +std::vector +AddonManager::get_available_addons() const +{ + std::vector addons; + +#ifdef HAVE_LIBCURL + + char error_buffer[CURL_ERROR_SIZE+1]; + + const char* baseUrl = "http://supertux.lethargik.org/addons/index.nfo"; + std::string addoninfos = ""; + + CURL *curl_handle; + curl_handle = curl_easy_init(); + curl_easy_setopt(curl_handle, CURLOPT_URL, baseUrl); + curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL"); + curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, my_curl_string_append); + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &addoninfos); + curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, error_buffer); + curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1); + curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1); + CURLcode result = curl_easy_perform(curl_handle); + curl_easy_cleanup(curl_handle); + + if (result != CURLE_OK) { + std::string why = error_buffer[0] ? error_buffer : "unhandled error"; + throw std::runtime_error("Downloading Add-on list failed: " + why); + } + + try { + lisp::Parser parser; + std::stringstream addoninfos_stream(addoninfos); + const lisp::Lisp* root = parser.parse(addoninfos_stream, "supertux-addons"); + + const lisp::Lisp* addons_lisp = root->get_lisp("supertux-addons"); + if(!addons_lisp) throw std::runtime_error("Downloaded file is not an Add-on list"); + + lisp::ListIterator iter(addons_lisp); + while(iter.next()) { + const std::string& token = iter.item(); + if(token == "supertux-addoninfo") { + Addon addon; + addon.parse(*(iter.lisp())); + + // make sure the Add-on's file name does not contain weird characters + if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) { + log_warning << "Add-on \"" << addon.title << "\" contains unsafe file name. Skipping." << std::endl; + continue; + } + + addon.isInstalled = false; + addons.push_back(addon); + } else { + log_warning << "Unknown token '" << token << "' in Add-on list" << std::endl; + } + } + } catch(std::exception& e) { + std::stringstream msg; + msg << "Problem when reading Add-on list: " << e.what(); + throw std::runtime_error(msg.str()); + } + +#endif + + return addons; +} + + +void +AddonManager::install(const Addon& addon) +{ + // make sure the Add-on's file name does not contain weird characters + if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) { + throw std::runtime_error("Add-on has unsafe file name (\""+addon.file+"\")"); + } + +#ifdef HAVE_LIBCURL + + char error_buffer[CURL_ERROR_SIZE+1]; + + char* url = (char*)malloc(addon.http_url.length() + 1); + strncpy(url, addon.http_url.c_str(), addon.http_url.length() + 1); + + PHYSFS_file* f = PHYSFS_openWrite(addon.file.c_str()); + + log_debug << "Downloading \"" << url << "\"" << std::endl; + + CURL *curl_handle; + curl_handle = curl_easy_init(); + curl_easy_setopt(curl_handle, CURLOPT_URL, url); + curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL"); + curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, my_curl_physfs_write); + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, f); + curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, error_buffer); + curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1); + curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1); + CURLcode result = curl_easy_perform(curl_handle); + curl_easy_cleanup(curl_handle); + + PHYSFS_close(f); + + free(url); + + if (result != CURLE_OK) { + PHYSFS_delete(addon.file.c_str()); + std::string why = error_buffer[0] ? error_buffer : "unhandled error"; + throw std::runtime_error("Downloading Add-on failed: " + why); + } + + // write an accompaining .nfo file + static const std::string archiveExt = ".zip"; + static const std::string infoExt = ".nfo"; + std::string infoFileName = addon.file.substr(0, addon.file.length()-archiveExt.length()) + infoExt; + addon.write(infoFileName); + + static const std::string writeDir = PHYSFS_getWriteDir(); + static const std::string dirSep = PHYSFS_getDirSeparator(); + std::string fullFilename = writeDir + dirSep + addon.file; + log_debug << "Finished downloading \"" << fullFilename << "\"" << std::endl; + PHYSFS_addToSearchPath(fullFilename.c_str(), 1); +#else + (void) addon; +#endif + +} + +void +AddonManager::remove(const Addon& addon) +{ + // make sure the Add-on's file name does not contain weird characters + if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) { + throw std::runtime_error("Add-on has unsafe file name (\""+addon.file+"\")"); + } + + log_debug << "deleting file \"" << addon.file << "\"" << std::endl; + PHYSFS_removeFromSearchPath(addon.file.c_str()); + PHYSFS_delete(addon.file.c_str()); + + // remove an accompaining .nfo file + static const std::string archiveExt = ".zip"; + static const std::string infoExt = ".nfo"; + std::string infoFileName = addon.file.substr(0, addon.file.length()-archiveExt.length()) + infoExt; + if (PHYSFS_exists(infoFileName.c_str())) { + log_debug << "deleting file \"" << infoFileName << "\"" << std::endl; + PHYSFS_delete(infoFileName.c_str()); + } +} + diff --git a/src/src/addon_manager.hpp b/src/src/addon_manager.hpp new file mode 100644 index 000000000..23b3415c7 --- /dev/null +++ b/src/src/addon_manager.hpp @@ -0,0 +1,61 @@ +// $Id$ +// +// SuperTux - Add-on Manager +// Copyright (C) 2007 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +#ifndef ADDON_MANAGER_H +#define ADDON_MANAGER_H + +#include +#include +#include "addon.hpp" + +/** + * Checks for, installs and removes Add-ons + */ +class AddonManager +{ +public: + /** + * returns a list of installed Add-ons + */ + std::vector get_installed_addons() const; + + /** + * returns a list of available Add-ons + */ + std::vector get_available_addons() const; + + /** + * Download and install Add-on + */ + void install(const Addon& addon); + + /** + * Physically delete Add-on + */ + void remove(const Addon& addon); + + static AddonManager& get_instance(); + +protected: + AddonManager(); + ~AddonManager(); +}; + +#endif diff --git a/src/src/audio/dummy_sound_source.cpp b/src/src/audio/dummy_sound_source.cpp new file mode 100644 index 000000000..1a9c274e0 --- /dev/null +++ b/src/src/audio/dummy_sound_source.cpp @@ -0,0 +1,81 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#include + +#include "dummy_sound_source.hpp" + +class DummySoundSource : public SoundSource +{ +public: + DummySoundSource() + {} + virtual ~DummySoundSource() + {} + + virtual void play() + { + is_playing = true; + } + + virtual void stop() + { + is_playing = false; + } + + virtual bool playing() + { + return is_playing; + } + + virtual void set_looping(bool ) + { + } + + virtual void set_gain(float ) + { + } + + virtual void set_pitch(float ) + { + } + + virtual void set_position(const Vector& ) + { + } + + virtual void set_velocity(const Vector& ) + { + } + + virtual void set_reference_distance(float ) + { + } + + virtual void set_rollof_factor(float ) + { + } + +private: + bool is_playing; +}; + +SoundSource* create_dummy_sound_source() +{ + return new DummySoundSource(); +} diff --git a/src/src/audio/dummy_sound_source.hpp b/src/src/audio/dummy_sound_source.hpp new file mode 100644 index 000000000..458c1e834 --- /dev/null +++ b/src/src/audio/dummy_sound_source.hpp @@ -0,0 +1,26 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#ifndef __DUMMY_SOUND_SOURCE_HPP__ +#define __DUMMY_SOUND_SOURCE_HPP__ + +#include "sound_source.hpp" + +SoundSource* create_dummy_sound_source(); + +#endif diff --git a/src/src/audio/openal_sound_source.cpp b/src/src/audio/openal_sound_source.cpp new file mode 100644 index 000000000..252cc6ee5 --- /dev/null +++ b/src/src/audio/openal_sound_source.cpp @@ -0,0 +1,105 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#include + +#include "openal_sound_source.hpp" +#include "sound_manager.hpp" + +OpenALSoundSource::OpenALSoundSource() +{ + alGenSources(1, &source); + SoundManager::check_al_error("Couldn't create audio source: "); + set_reference_distance(128); +} + +OpenALSoundSource::~OpenALSoundSource() +{ + stop(); + alDeleteSources(1, &source); +} + +void +OpenALSoundSource::stop() +{ + alSourceStop(source); + alSourcei(source, AL_BUFFER, AL_NONE); + SoundManager::check_al_error("Problem stopping audio source: "); +} + +void +OpenALSoundSource::play() +{ + alSourcePlay(source); + SoundManager::check_al_error("Couldn't start audio source: "); +} + +bool +OpenALSoundSource::playing() +{ + ALint state = AL_PLAYING; + alGetSourcei(source, AL_SOURCE_STATE, &state); + return state != AL_STOPPED; +} + +void +OpenALSoundSource::update() +{ +} + +void +OpenALSoundSource::set_looping(bool looping) +{ + alSourcei(source, AL_LOOPING, looping ? AL_TRUE : AL_FALSE); +} + +void +OpenALSoundSource::set_position(const Vector& position) +{ + alSource3f(source, AL_POSITION, position.x, position.y, 0); +} + +void +OpenALSoundSource::set_velocity(const Vector& velocity) +{ + alSource3f(source, AL_VELOCITY, velocity.x, velocity.y, 0); +} + +void +OpenALSoundSource::set_gain(float gain) +{ + alSourcef(source, AL_GAIN, gain); +} + +void +OpenALSoundSource::set_pitch(float pitch) +{ + alSourcef(source, AL_PITCH, pitch); +} + +void +OpenALSoundSource::set_reference_distance(float distance) +{ + alSourcef(source, AL_REFERENCE_DISTANCE, distance); +} + +void +OpenALSoundSource::set_rollof_factor(float factor) +{ + alSourcef(source, AL_ROLLOFF_FACTOR, factor); +} diff --git a/src/src/audio/openal_sound_source.hpp b/src/src/audio/openal_sound_source.hpp new file mode 100644 index 000000000..4b03ed6d7 --- /dev/null +++ b/src/src/audio/openal_sound_source.hpp @@ -0,0 +1,57 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#ifndef __OPENAL_SOUND_SOURCE_H__ +#define __OPENAL_SOUND_SOURCE_H__ + +#ifndef MACOSX +#include +#else +#include +#endif + +#include "math/vector.hpp" +#include "sound_source.hpp" + +class OpenALSoundSource : public SoundSource +{ +public: + OpenALSoundSource(); + virtual ~OpenALSoundSource(); + + virtual void play(); + virtual void stop(); + virtual bool playing(); + + virtual void update(); + + virtual void set_looping(bool looping); + virtual void set_gain(float gain); + virtual void set_pitch(float pitch); + virtual void set_position(const Vector& position); + virtual void set_velocity(const Vector& position); + virtual void set_reference_distance(float distance); + virtual void set_rollof_factor(float factor); + +protected: + friend class SoundManager; + + ALuint source; +}; + +#endif diff --git a/src/src/audio/sound_file.cpp b/src/src/audio/sound_file.cpp new file mode 100644 index 000000000..2495bbbb0 --- /dev/null +++ b/src/src/audio/sound_file.cpp @@ -0,0 +1,422 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +/** Used SDL_mixer and glest source as reference */ +#include + +#include "sound_file.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "log.hpp" +#include "lisp/parser.hpp" +#include "lisp/lisp.hpp" +#include "file_system.hpp" + +class WavSoundFile : public SoundFile +{ +public: + WavSoundFile(PHYSFS_file* file); + ~WavSoundFile(); + + size_t read(void* buffer, size_t buffer_size); + void reset(); + +private: + PHYSFS_file* file; + + PHYSFS_sint64 datastart; +}; + +static inline uint32_t read32LE(PHYSFS_file* file) +{ + uint32_t result; + if(PHYSFS_readULE32(file, &result) == 0) + throw std::runtime_error("file too short"); + + return result; +} + +static inline uint16_t read16LE(PHYSFS_file* file) +{ + uint16_t result; + if(PHYSFS_readULE16(file, &result) == 0) + throw std::runtime_error("file too short"); + + return result; +} + +WavSoundFile::WavSoundFile(PHYSFS_file* file) +{ + this->file = file; + + char magic[4]; + if(PHYSFS_read(file, magic, sizeof(magic), 1) != 1) + throw std::runtime_error("Couldn't read file magic (not a wave file)"); + if(strncmp(magic, "RIFF", 4) != 0) { + log_debug << "MAGIC: " << magic << std::endl; + throw std::runtime_error("file is not a RIFF wav file"); + } + + uint32_t wavelen = read32LE(file); + (void) wavelen; + + if(PHYSFS_read(file, magic, sizeof(magic), 1) != 1) + throw std::runtime_error("Couldn't read chunk header (not a wav file?)"); + if(strncmp(magic, "WAVE", 4) != 0) + throw std::runtime_error("file is not a valid RIFF/WAVE file"); + + char chunkmagic[4]; + uint32_t chunklen; + + // search audio data format chunk + do { + if(PHYSFS_read(file, chunkmagic, sizeof(chunkmagic), 1) != 1) + throw std::runtime_error("EOF while searching format chunk"); + chunklen = read32LE(file); + + if(strncmp(chunkmagic, "fmt ", 4) == 0) + break; + + if(strncmp(chunkmagic, "fact", 4) == 0 + || strncmp(chunkmagic, "LIST", 4) == 0) { + // skip chunk + if(PHYSFS_seek(file, PHYSFS_tell(file) + chunklen) == 0) + throw std::runtime_error("EOF while searching fmt chunk"); + } else { + throw std::runtime_error("complex WAVE files not supported"); + } + } while(true); + + if(chunklen < 16) + throw std::runtime_error("Format chunk too short"); + + // parse format + uint16_t encoding = read16LE(file); + if(encoding != 1) + throw std::runtime_error("only PCM encoding supported"); + channels = read16LE(file); + rate = read32LE(file); + uint32_t byterate = read32LE(file); + (void) byterate; + uint16_t blockalign = read16LE(file); + (void) blockalign; + bits_per_sample = read16LE(file); + + if(chunklen > 16) { + if(PHYSFS_seek(file, PHYSFS_tell(file) + (chunklen-16)) == 0) + throw std::runtime_error("EOF while reading reast of format chunk"); + } + + // set file offset to DATA chunk data + do { + if(PHYSFS_read(file, chunkmagic, sizeof(chunkmagic), 1) != 1) + throw std::runtime_error("EOF while searching data chunk"); + chunklen = read32LE(file); + + if(strncmp(chunkmagic, "data", 4) == 0) + break; + + // skip chunk + if(PHYSFS_seek(file, PHYSFS_tell(file) + chunklen) == 0) + throw std::runtime_error("EOF while searching fmt chunk"); + } while(true); + + datastart = PHYSFS_tell(file); + size = static_cast (chunklen); +} + +WavSoundFile::~WavSoundFile() +{ + PHYSFS_close(file); +} + +void +WavSoundFile::reset() +{ + if(PHYSFS_seek(file, datastart) == 0) + throw std::runtime_error("Couldn't seek to data start"); +} + +size_t +WavSoundFile::read(void* buffer, size_t buffer_size) +{ + PHYSFS_sint64 end = datastart + size; + PHYSFS_sint64 cur = PHYSFS_tell(file); + if(cur >= end) + return 0; + + size_t readsize = std::min(static_cast (end - cur), buffer_size); + if(PHYSFS_read(file, buffer, readsize, 1) != 1) + throw std::runtime_error("read error while reading samples"); + +#ifdef WORDS_BIGENDIAN + if (bits_per_sample != 16) + return readsize; + char *tmp = (char*)buffer; + + size_t i; + char c; + for (i = 0; i < readsize / 2; i++) + { + c = tmp[2*i]; + tmp[2*i] = tmp[2*i+1]; + tmp[2*i+1] = c; + } + + buffer = tmp; +#endif + + return readsize; +} + +//--------------------------------------------------------------------------- + +class OggSoundFile : public SoundFile +{ +public: + OggSoundFile(PHYSFS_file* file, double loop_begin, double loop_at); + ~OggSoundFile(); + + size_t read(void* buffer, size_t buffer_size); + void reset(); + +private: + static size_t cb_read(void* ptr, size_t size, size_t nmemb, void* source); + static int cb_seek(void* source, ogg_int64_t offset, int whence); + static int cb_close(void* source); + static long cb_tell(void* source); + + PHYSFS_file* file; + OggVorbis_File vorbis_file; + ogg_int64_t loop_begin; + ogg_int64_t loop_at; + size_t normal_buffer_loop; +}; + +OggSoundFile::OggSoundFile(PHYSFS_file* file, double loop_begin, double loop_at) +{ + this->file = file; + + ov_callbacks callbacks = { cb_read, cb_seek, cb_close, cb_tell }; + ov_open_callbacks(file, &vorbis_file, 0, 0, callbacks); + + vorbis_info* vi = ov_info(&vorbis_file, -1); + + channels = vi->channels; + rate = vi->rate; + bits_per_sample = 16; + size = static_cast (ov_pcm_total(&vorbis_file, -1) * 2); + + double sample_len = 1.0f / rate; + double samples_begin = loop_begin / sample_len; + double sample_loop = loop_at / sample_len; + + this->loop_begin = (ogg_int64_t) samples_begin; + if(loop_begin < 0) { + this->loop_at = (ogg_int64_t) -1; + } else { + this->loop_at = (ogg_int64_t) sample_loop; + } +} + +OggSoundFile::~OggSoundFile() +{ + ov_clear(&vorbis_file); +} + +size_t +OggSoundFile::read(void* _buffer, size_t buffer_size) +{ + char* buffer = reinterpret_cast (_buffer); + int section = 0; + size_t totalBytesRead = 0; + + while(buffer_size>0) { +#ifdef WORDS_BIGENDIAN + int bigendian = 1; +#else + int bigendian = 0; +#endif + + size_t bytes_to_read = buffer_size; + if(loop_at > 0) { + size_t bytes_per_sample = 2; + ogg_int64_t time = ov_pcm_tell(&vorbis_file); + ogg_int64_t samples_left_till_loop = loop_at - time; + ogg_int64_t bytes_left_till_loop + = samples_left_till_loop * bytes_per_sample; + if(bytes_left_till_loop <= 4) + break; + + if(bytes_left_till_loop < (ogg_int64_t) bytes_to_read) { + bytes_to_read = (size_t) bytes_left_till_loop; + } + } + + long bytesRead + = ov_read(&vorbis_file, buffer, bytes_to_read, bigendian, + 2, 1, §ion); + if(bytesRead == 0) { + break; + } + buffer_size -= bytesRead; + buffer += bytesRead; + totalBytesRead += bytesRead; + } + + return totalBytesRead; +} + +void +OggSoundFile::reset() +{ + ov_pcm_seek(&vorbis_file, loop_begin); +} + +size_t +OggSoundFile::cb_read(void* ptr, size_t size, size_t nmemb, void* source) +{ + PHYSFS_file* file = reinterpret_cast (source); + + PHYSFS_sint64 res + = PHYSFS_read(file, ptr, static_cast (size), + static_cast (nmemb)); + if(res <= 0) + return 0; + + return static_cast (res); +} + +int +OggSoundFile::cb_seek(void* source, ogg_int64_t offset, int whence) +{ + PHYSFS_file* file = reinterpret_cast (source); + + switch(whence) { + case SEEK_SET: + if(PHYSFS_seek(file, static_cast (offset)) == 0) + return -1; + break; + case SEEK_CUR: + if(PHYSFS_seek(file, PHYSFS_tell(file) + offset) == 0) + return -1; + break; + case SEEK_END: + if(PHYSFS_seek(file, PHYSFS_fileLength(file) + offset) == 0) + return -1; + break; + default: +#ifdef DEBUG + assert(false); +#else + return -1; +#endif + } + return 0; +} + +int +OggSoundFile::cb_close(void* source) +{ + PHYSFS_file* file = reinterpret_cast (source); + PHYSFS_close(file); + return 0; +} + +long +OggSoundFile::cb_tell(void* source) +{ + PHYSFS_file* file = reinterpret_cast (source); + return static_cast (PHYSFS_tell(file)); +} + +//--------------------------------------------------------------------------- + +SoundFile* load_music_file(const std::string& filename) +{ + lisp::Parser parser(false); + const lisp::Lisp* root = parser.parse(filename); + const lisp::Lisp* music = root->get_lisp("supertux-music"); + if(music == NULL) + throw std::runtime_error("file is not a supertux-music file."); + + std::string raw_music_file; + float loop_begin = 0; + float loop_at = -1; + + music->get("file", raw_music_file); + music->get("loop-begin", loop_begin); + music->get("loop-at", loop_at); + + if(loop_begin < 0) { + throw std::runtime_error("can't loop from negative value"); + } + + std::string basedir = FileSystem::dirname(filename); + raw_music_file = FileSystem::normalize(basedir + raw_music_file); + + PHYSFS_file* file = PHYSFS_openRead(raw_music_file.c_str()); + if(!file) { + std::stringstream msg; + msg << "Couldn't open '" << raw_music_file << "': " << PHYSFS_getLastError(); + throw std::runtime_error(msg.str()); + } + + return new OggSoundFile(file, loop_begin, loop_at); +} + +SoundFile* load_sound_file(const std::string& filename) +{ + if(filename.length() > 6 + && filename.compare(filename.length()-6, 6, ".music") == 0) { + return load_music_file(filename); + } + + PHYSFS_file* file = PHYSFS_openRead(filename.c_str()); + if(!file) { + std::stringstream msg; + msg << "Couldn't open '" << filename << "': " << PHYSFS_getLastError(); + throw std::runtime_error(msg.str()); + } + + try { + char magic[4]; + if(PHYSFS_read(file, magic, sizeof(magic), 1) != 1) + throw std::runtime_error("Couldn't read magic, file too short"); + PHYSFS_seek(file, 0); + if(strncmp(magic, "RIFF", 4) == 0) + return new WavSoundFile(file); + else if(strncmp(magic, "OggS", 4) == 0) + return new OggSoundFile(file, 0, -1); + else + throw std::runtime_error("Unknown file format"); + } catch(std::exception& e) { + std::stringstream msg; + msg << "Couldn't read '" << filename << "': " << e.what(); + throw std::runtime_error(msg.str()); + } +} diff --git a/src/src/audio/sound_file.hpp b/src/src/audio/sound_file.hpp new file mode 100644 index 000000000..fd777b22a --- /dev/null +++ b/src/src/audio/sound_file.hpp @@ -0,0 +1,44 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __SOUND_FILE_H__ +#define __SOUND_FILE_H__ + +#include +#include + +class SoundFile +{ +public: + virtual ~SoundFile() + { } + + virtual size_t read(void* buffer, size_t buffer_size) = 0; + virtual void reset() = 0; + + int channels; + int rate; + int bits_per_sample; + /// size in bytes + size_t size; +}; + +SoundFile* load_sound_file(const std::string& filename); + +#endif diff --git a/src/src/audio/sound_manager.cpp b/src/src/audio/sound_manager.cpp new file mode 100644 index 000000000..7d285ffb3 --- /dev/null +++ b/src/src/audio/sound_manager.cpp @@ -0,0 +1,430 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#include + +#include "sound_manager.hpp" + +#include +#include +#include +#include +#include +#include + +#include "sound_file.hpp" +#include "sound_source.hpp" +#include "openal_sound_source.hpp" +#include "stream_sound_source.hpp" +#include "dummy_sound_source.hpp" +#include "log.hpp" +#include "timer.hpp" + +#ifndef DEBUG + /** Older openal versions often miss this function and it isn't that vital for + * supertux... + */ +#ifdef alcGetString +#undef alcGetString +#endif +#define alcGetString(x,y) "" +#endif + +SoundManager* sound_manager = 0; + +SoundManager::SoundManager() + : device(0), context(0), sound_enabled(false), music_source(0), + music_enabled(false) +{ + try { + device = alcOpenDevice(0); + if (device == NULL) { + throw std::runtime_error("Couldn't open audio device."); + } + + int attributes[] = { 0 }; + context = alcCreateContext(device, attributes); + check_alc_error("Couldn't create audio context: "); + alcMakeContextCurrent(context); + check_alc_error("Couldn't select audio context: "); + + check_al_error("Audio error after init: "); + sound_enabled = true; + music_enabled = true; + } catch(std::exception& e) { + if(context != NULL) + alcDestroyContext(context); + context = NULL; + if(device != NULL) + alcCloseDevice(device); + device = NULL; + log_warning << "Couldn't initialize audio device: " << e.what() << std::endl; + print_openal_version(); + } +} + +SoundManager::~SoundManager() +{ + delete music_source; + + for(SoundSources::iterator i = sources.begin(); i != sources.end(); ++i) { + delete *i; + } + + for(SoundBuffers::iterator i = buffers.begin(); i != buffers.end(); ++i) { + ALuint buffer = i->second; + alDeleteBuffers(1, &buffer); + } + + if(context != NULL) { + alcDestroyContext(context); + } + if(device != NULL) { + alcCloseDevice(device); + } +} + +ALuint +SoundManager::load_file_into_buffer(SoundFile* file) +{ + ALenum format = get_sample_format(file); + ALuint buffer; + alGenBuffers(1, &buffer); + check_al_error("Couldn't create audio buffer: "); + char* samples = new char[file->size]; + try { + file->read(samples, file->size); + alBufferData(buffer, format, samples, + static_cast (file->size), + static_cast (file->rate)); + check_al_error("Couldn't fill audio buffer: "); + } catch(...) { + delete[] samples; + throw; + } + delete[] samples; + + return buffer; +} + +OpenALSoundSource* +SoundManager::intern_create_sound_source(const std::string& filename) +{ + if(!sound_enabled) + throw std::runtime_error("sound disabled"); + + std::auto_ptr source (new OpenALSoundSource()); + + ALuint buffer; + + // reuse an existing static sound buffer + SoundBuffers::iterator i = buffers.find(filename); + if(i != buffers.end()) { + buffer = i->second; + } else { + // Load sound file + std::auto_ptr file (load_sound_file(filename)); + + if(file->size < 100000) { + buffer = load_file_into_buffer(file.get()); + buffers.insert(std::make_pair(filename, buffer)); + } else { + StreamSoundSource* source = new StreamSoundSource(); + source->set_sound_file(file.release()); + return source; + } + } + + alSourcei(source->source, AL_BUFFER, buffer); + return source.release(); +} + +SoundSource* +SoundManager::create_sound_source(const std::string& filename) +{ + if(!sound_enabled) + return create_dummy_sound_source(); + + try { + return intern_create_sound_source(filename); + } catch(std::exception &e) { + log_warning << "Couldn't create audio source: " << e.what() << std::endl; + return create_dummy_sound_source(); + } +} + +void +SoundManager::preload(const std::string& filename) +{ + if(!sound_enabled) + return; + + SoundBuffers::iterator i = buffers.find(filename); + // already loaded? + if(i != buffers.end()) + return; + + std::auto_ptr file (load_sound_file(filename)); + // only keep small files + if(file->size >= 100000) + return; + + ALuint buffer = load_file_into_buffer(file.get()); + buffers.insert(std::make_pair(filename, buffer)); +} + +void +SoundManager::play(const std::string& filename, const Vector& pos) +{ + if(!sound_enabled) + return; + + try { + std::auto_ptr source + (intern_create_sound_source(filename)); + + if(pos == Vector(-1, -1)) { + source->set_rollof_factor(0); + } else { + source->set_position(pos); + } + source->play(); + sources.push_back(source.release()); + } catch(std::exception& e) { + log_warning << "Couldn't play sound " << filename << ": " << e.what() << std::endl; + } +} + +void +SoundManager::manage_source(SoundSource* source) +{ + assert(source != NULL); + + OpenALSoundSource* openal_source = dynamic_cast (source); + if(openal_source != NULL) { + sources.push_back(openal_source); + } +} + +void +SoundManager::register_for_update( StreamSoundSource* sss ){ + if( sss != NULL ){ + update_list.push_back( sss ); + } +} + +void +SoundManager::remove_from_update( StreamSoundSource* sss ){ + if( sss != NULL ){ + StreamSoundSources::iterator i = update_list.begin(); + while( i != update_list.end() ){ + if( *i == sss ){ + i = update_list.erase(i); + } else { + i++; + } + } + } +} + +void +SoundManager::enable_sound(bool enable) +{ + if(device == NULL) + return; + + sound_enabled = enable; +} + +void +SoundManager::enable_music(bool enable) +{ + if(device == NULL) + return; + + music_enabled = enable; + if(music_enabled) { + play_music(current_music); + } else { + if(music_source) { + delete music_source; + music_source = 0; + } + } +} + +void +SoundManager::stop_music(float fadetime) +{ + if(fadetime > 0) { + if(music_source + && music_source->get_fade_state() != StreamSoundSource::FadingOff) + music_source->set_fading(StreamSoundSource::FadingOff, fadetime); + } else { + delete music_source; + music_source = NULL; + } + current_music = ""; +} + +void +SoundManager::play_music(const std::string& filename, bool fade) +{ + if(filename == current_music && music_source != NULL) + return; + current_music = filename; + if(!music_enabled) + return; + + if(filename == "") { + delete music_source; + music_source = NULL; + return; + } + + try { + std::auto_ptr newmusic (new StreamSoundSource()); + alSourcef(newmusic->source, AL_ROLLOFF_FACTOR, 0); + newmusic->set_sound_file(load_sound_file(filename)); + newmusic->set_looping(true); + if(fade) + newmusic->set_fading(StreamSoundSource::FadingOn, .5f); + newmusic->play(); + + delete music_source; + music_source = newmusic.release(); + } catch(std::exception& e) { + log_warning << "Couldn't play music file '" << filename << "': " << e.what() << std::endl; + } +} + +void +SoundManager::set_listener_position(const Vector& pos) +{ + static Uint32 lastticks = SDL_GetTicks(); + + Uint32 current_ticks = SDL_GetTicks(); + if(current_ticks - lastticks < 300) + return; + lastticks = current_ticks; + + alListener3f(AL_POSITION, pos.x, pos.y, 0); +} + +void +SoundManager::set_listener_velocity(const Vector& vel) +{ + alListener3f(AL_VELOCITY, vel.x, vel.y, 0); +} + +void +SoundManager::update() +{ + static Uint32 lasttime = SDL_GetTicks(); + Uint32 now = SDL_GetTicks(); + + if(now - lasttime < 300) + return; + lasttime = now; + + // update and check for finished sound sources + for(SoundSources::iterator i = sources.begin(); i != sources.end(); ) { + OpenALSoundSource* source = *i; + + source->update(); + + if(!source->playing()) { + delete source; + i = sources.erase(i); + } else { + ++i; + } + } + // check streaming sounds + if(music_source) { + music_source->update(); + } + + if (context) + { + alcProcessContext(context); + check_alc_error("Error while processing audio context: "); + } + + //run update() for stream_sound_source + StreamSoundSources::iterator s = update_list.begin(); + while( s != update_list.end() ){ + (*s)->update(); + s++; + } +} + +ALenum +SoundManager::get_sample_format(SoundFile* file) +{ + if(file->channels == 2) { + if(file->bits_per_sample == 16) { + return AL_FORMAT_STEREO16; + } else if(file->bits_per_sample == 8) { + return AL_FORMAT_STEREO8; + } else { + throw std::runtime_error("Only 16 and 8 bit samples supported"); + } + } else if(file->channels == 1) { + if(file->bits_per_sample == 16) { + return AL_FORMAT_MONO16; + } else if(file->bits_per_sample == 8) { + return AL_FORMAT_MONO8; + } else { + throw std::runtime_error("Only 16 and 8 bit samples supported"); + } + } + + throw std::runtime_error("Only 1 and 2 channel samples supported"); +} + +void +SoundManager::print_openal_version() +{ + log_info << "OpenAL Vendor: " << alGetString(AL_VENDOR) << std::endl; + log_info << "OpenAL Version: " << alGetString(AL_VERSION) << std::endl; + log_info << "OpenAL Renderer: " << alGetString(AL_RENDERER) << std::endl; + log_info << "OpenAl Extensions: " << alGetString(AL_EXTENSIONS) << std::endl; +} + +void +SoundManager::check_alc_error(const char* message) +{ + int err = alcGetError(device); + if(err != ALC_NO_ERROR) { + std::stringstream msg; + msg << message << alcGetString(device, err); + throw std::runtime_error(msg.str()); + } +} + +void +SoundManager::check_al_error(const char* message) +{ + int err = alGetError(); + if(err != AL_NO_ERROR) { + std::stringstream msg; + msg << message << alGetString(err); + throw std::runtime_error(msg.str()); + } +} diff --git a/src/src/audio/sound_manager.hpp b/src/src/audio/sound_manager.hpp new file mode 100644 index 000000000..12f19447c --- /dev/null +++ b/src/src/audio/sound_manager.hpp @@ -0,0 +1,125 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#ifndef __SOUND_MANAGER_H__ +#define __SOUND_MANAGER_H__ + +#include +#include +#include + +#ifndef MACOSX +#include +#include +#else +#include +#include +#endif + +#include "math/vector.hpp" + +class SoundFile; +class SoundSource; +class StreamSoundSource; +class OpenALSoundSource; + +class SoundManager +{ +public: + SoundManager(); + virtual ~SoundManager(); + + void enable_sound(bool sound_enabled); + /** + * Creates a new sound source object which plays the specified soundfile. + * You are responsible for deleting the sound source later (this will stop the + * sound). + * This function never throws exceptions, but might return a DummySoundSource + */ + SoundSource* create_sound_source(const std::string& filename); + /** + * Convenience function to simply play a sound at a given position. + */ + void play(const std::string& name, const Vector& pos = Vector(-1, -1)); + /** + * Adds the source to the list of managed sources (= the source gets deleted + * when it finished playing) + */ + void manage_source(SoundSource* source); + /// preloads a sound, so that you don't get a lag later when playing it + void preload(const std::string& name); + + void set_listener_position(const Vector& position); + void set_listener_velocity(const Vector& velocity); + + void enable_music(bool music_enabled); + void play_music(const std::string& filename, bool fade = false); + void stop_music(float fadetime = 0); + + bool is_music_enabled() { return music_enabled; } + bool is_sound_enabled() { return sound_enabled; } + + bool is_audio_enabled() { + return device != 0 && context != 0; + } + + void update(); + + /* + * Tell soundmanager to call update() for stream_sound_source. + */ + void register_for_update( StreamSoundSource* sss ); + /* + * Unsubscribe from updates for stream_sound_source. + */ + void remove_from_update( StreamSoundSource* sss ); + +private: + friend class OpenALSoundSource; + friend class StreamSoundSource; + + /** creates a new sound source, might throw exceptions, never returns NULL */ + OpenALSoundSource* intern_create_sound_source(const std::string& filename); + static ALuint load_file_into_buffer(SoundFile* file); + static ALenum get_sample_format(SoundFile* file); + + void print_openal_version(); + void check_alc_error(const char* message); + static void check_al_error(const char* message); + + ALCdevice* device; + ALCcontext* context; + bool sound_enabled; + + typedef std::map SoundBuffers; + SoundBuffers buffers; + typedef std::vector SoundSources; + SoundSources sources; + + typedef std::vector StreamSoundSources; + StreamSoundSources update_list; + + StreamSoundSource* music_source; + + bool music_enabled; + std::string current_music; +}; + +extern SoundManager* sound_manager; + +#endif diff --git a/src/src/audio/sound_source.hpp b/src/src/audio/sound_source.hpp new file mode 100644 index 000000000..48c3f287a --- /dev/null +++ b/src/src/audio/sound_source.hpp @@ -0,0 +1,49 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#ifndef __SOUND_SOURCE_H__ +#define __SOUND_SOURCE_H__ + +#include "math/vector.hpp" + +/** + * A sound source represents the source of audio output. You can place + * sources at certain points in your world or set their velocity to produce + * doppler effects + */ +class SoundSource +{ +public: + virtual ~SoundSource() + { } + + virtual void play() = 0; + virtual void stop() = 0; + virtual bool playing() = 0; + + virtual void set_looping(bool looping) = 0; + /// Set volume (0.0 is silent, 1.0 is normal) + virtual void set_gain(float gain) = 0; + virtual void set_pitch(float pitch) = 0; + virtual void set_position(const Vector& position) = 0; + virtual void set_velocity(const Vector& position) = 0; + virtual void set_reference_distance(float distance) = 0; + virtual void set_rollof_factor(float factor) = 0; +}; + +#endif diff --git a/src/src/audio/stream_sound_source.cpp b/src/src/audio/stream_sound_source.cpp new file mode 100644 index 000000000..5a0172362 --- /dev/null +++ b/src/src/audio/stream_sound_source.cpp @@ -0,0 +1,144 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include +#include + +#include + +#include "stream_sound_source.hpp" +#include "sound_manager.hpp" +#include "sound_file.hpp" +#include "timer.hpp" +#include "log.hpp" + +StreamSoundSource::StreamSoundSource() + : file(0), fade_state(NoFading), looping(false) +{ + alGenBuffers(STREAMFRAGMENTS, buffers); + SoundManager::check_al_error("Couldn't allocate audio buffers: "); + //add me to update list + sound_manager->register_for_update( this ); +} + +StreamSoundSource::~StreamSoundSource() +{ + //don't update me any longer + sound_manager->remove_from_update( this ); + delete file; + stop(); + alDeleteBuffers(STREAMFRAGMENTS, buffers); + SoundManager::check_al_error("Couldn't delete audio buffers: "); +} + +void +StreamSoundSource::set_sound_file(SoundFile* newfile) +{ + delete file; + file = newfile; + + ALint queued; + alGetSourcei(source, AL_BUFFERS_QUEUED, &queued); + for(size_t i = 0; i < STREAMFRAGMENTS - queued; ++i) { + if(fillBufferAndQueue(buffers[i]) == false) + break; + } +} + +void +StreamSoundSource::update() +{ + ALint processed = 0; + alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed); + for(ALint i = 0; i < processed; ++i) { + ALuint buffer; + alSourceUnqueueBuffers(source, 1, &buffer); + SoundManager::check_al_error("Couldn't unqueu audio buffer: "); + + if(fillBufferAndQueue(buffer) == false) + break; + } + + if(!playing()) { + if(processed == 0 || !looping) + return; + + // we might have to restart the source if we had a buffer underrun + log_info << "Restarting audio source because of buffer underrun" << std::endl; + play(); + } + + if(fade_state == FadingOn) { + float time = real_time - fade_start_time; + if(time >= fade_time) { + set_gain(1.0); + fade_state = NoFading; + } else { + set_gain(time / fade_time); + } + } else if(fade_state == FadingOff) { + float time = real_time - fade_start_time; + if(time >= fade_time) { + stop(); + fade_state = NoFading; + } else { + set_gain( (fade_time-time) / fade_time); + } + } +} + +void +StreamSoundSource::set_fading(FadeState state, float fade_time) +{ + this->fade_state = state; + this->fade_time = fade_time; + this->fade_start_time = real_time; +} + +bool +StreamSoundSource::fillBufferAndQueue(ALuint buffer) +{ + // fill buffer + char* bufferdata = new char[STREAMFRAGMENTSIZE]; + size_t bytesread = 0; + do { + bytesread += file->read(bufferdata + bytesread, + STREAMFRAGMENTSIZE - bytesread); + // end of sound file + if(bytesread < STREAMFRAGMENTSIZE) { + if(looping) + file->reset(); + else + break; + } + } while(bytesread < STREAMFRAGMENTSIZE); + + if(bytesread > 0) { + ALenum format = SoundManager::get_sample_format(file); + alBufferData(buffer, format, bufferdata, bytesread, file->rate); + SoundManager::check_al_error("Couldn't refill audio buffer: "); + + alSourceQueueBuffers(source, 1, &buffer); + SoundManager::check_al_error("Couldn't queue audio buffer: "); + } + delete[] bufferdata; + + // return false if there aren't more buffers to fill + return bytesread >= STREAMFRAGMENTSIZE; +} diff --git a/src/src/audio/stream_sound_source.hpp b/src/src/audio/stream_sound_source.hpp new file mode 100644 index 000000000..aa621b39f --- /dev/null +++ b/src/src/audio/stream_sound_source.hpp @@ -0,0 +1,71 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __STREAM_SOUND_SOURCE_H__ +#define __STREAM_SOUND_SOURCE_H__ + +#include +#include +#include "openal_sound_source.hpp" + +class SoundFile; + +class StreamSoundSource : public OpenALSoundSource +{ +public: + StreamSoundSource(); + virtual ~StreamSoundSource(); + + void set_sound_file(SoundFile* file); + + enum FadeState { NoFading, FadingOn, FadingOff }; + + void set_fading(FadeState state, float fadetime); + FadeState get_fade_state() const + { + return fade_state; + } + void update(); + + void set_looping(bool looping) + { + this->looping = looping; + } + bool get_looping() const + { + return looping; + } + +private: + static const size_t STREAMBUFFERSIZE = 1024 * 500; + static const size_t STREAMFRAGMENTS = 5; + static const size_t STREAMFRAGMENTSIZE + = STREAMBUFFERSIZE / STREAMFRAGMENTS; + + bool fillBufferAndQueue(ALuint buffer); + SoundFile* file; + ALuint buffers[STREAMFRAGMENTS]; + + FadeState fade_state; + float fade_start_time; + float fade_time; + bool looping; +}; + +#endif diff --git a/src/src/badguy/angrystone.cpp b/src/src/badguy/angrystone.cpp new file mode 100644 index 000000000..865e43061 --- /dev/null +++ b/src/src/badguy/angrystone.cpp @@ -0,0 +1,177 @@ +// $Id$ +// +// AngryStone - A spiked block that charges towards the player +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#include + +#include "angrystone.hpp" + +static const float SPEED = 240; + +static const float CHARGE_TIME = .5; +static const float ATTACK_TIME = 1; +static const float RECOVER_TIME = .5; + +AngryStone::AngryStone(const lisp::Lisp& reader) + : BadGuy(reader, "images/creatures/angrystone/angrystone.sprite"), state(IDLE) +{ +} + +void +AngryStone::write(lisp::Writer& writer) +{ + writer.start_list("angrystone"); + + writer.write_float("x", start_position.x); + writer.write_float("y", start_position.y); + + writer.end_list("angrystone"); +} + +void +AngryStone::activate() +{ + physic.set_velocity_x(0); + physic.set_velocity_y(0); + physic.enable_gravity(true); + sprite->set_action("idle"); +} + +void +AngryStone::collision_solid(const CollisionHit& hit) +{ + // TODO + (void) hit; +#if 0 + if ((state == ATTACKING) && + (hit.normal.x == -attackDirection.x) && (hit.normal.y == attackDirection.y)) { + state = IDLE; + sprite->set_action("idle"); + physic.set_velocity_x(0); + physic.set_velocity_y(0); + physic.enable_gravity(true); + oldWallDirection.x = attackDirection.x; + oldWallDirection.y = attackDirection.y; + } +#endif +} + +void +AngryStone::kill_fall() +{ + //prevents AngryStone from getting killed by other enemies or the player +} + +HitResponse +AngryStone::collision_badguy(BadGuy& badguy, const CollisionHit& ) +{ + if (state == ATTACKING) { + badguy.kill_fall(); + return FORCE_MOVE; + } + + return FORCE_MOVE; +} + +void +AngryStone::active_update(float elapsed_time) { + BadGuy::active_update(elapsed_time); + + if (state == IDLE) { + MovingObject* player = this->get_nearest_player(); + MovingObject* badguy = this; + const Vector playerPos = player->get_pos(); + const Vector badguyPos = badguy->get_pos(); + float dx = (playerPos.x - badguyPos.x); + float dy = (playerPos.y - badguyPos.y); + + float playerHeight = player->get_bbox().p2.y - player->get_bbox().p1.y; + float badguyHeight = badguy->get_bbox().p2.y - badguy->get_bbox().p1.y; + + float playerWidth = player->get_bbox().p2.x - player->get_bbox().p1.x; + float badguyWidth = badguy->get_bbox().p2.x - badguy->get_bbox().p1.x; + + if ((dx > -playerWidth) && (dx < badguyWidth)) { + if (dy > 0) { + attackDirection.x = 0; + attackDirection.y = 1; + } else { + attackDirection.x = 0; + attackDirection.y = -1; + } + if ((attackDirection.x != oldWallDirection.x) || (attackDirection.y != oldWallDirection.y)) { + sprite->set_action("charging"); + timer.start(CHARGE_TIME); + state = CHARGING; + } + } else + if ((dy > -playerHeight) && (dy < badguyHeight)) { + if (dx > 0) { + attackDirection.x = 1; + attackDirection.y = 0; + } else { + attackDirection.x = -1; + attackDirection.y = 0; + } + if ((attackDirection.x != oldWallDirection.x) || (attackDirection.y != oldWallDirection.y)) { + sprite->set_action("charging"); + timer.start(CHARGE_TIME); + state = CHARGING; + } + } + + } + + if (state == CHARGING) { + if (timer.check()) { + sprite->set_action("attacking"); + timer.start(ATTACK_TIME); + state = ATTACKING; + physic.enable_gravity(false); + physic.set_velocity_x(SPEED * attackDirection.x); + physic.set_velocity_y(SPEED * attackDirection.y); + oldWallDirection.x = 0; + oldWallDirection.y = 0; + } + } + + if (state == ATTACKING) { + if (timer.check()) { + timer.start(RECOVER_TIME); + state = RECOVERING; + sprite->set_action("idle"); + physic.enable_gravity(true); + physic.set_velocity_x(0); + physic.set_velocity_y(0); + } + } + + if (state == RECOVERING) { + if (timer.check()) { + state = IDLE; + sprite->set_action("idle"); + physic.enable_gravity(true); + physic.set_velocity_x(0); + physic.set_velocity_y(0); + } + } + +} + +IMPLEMENT_FACTORY(AngryStone, "angrystone") diff --git a/src/src/badguy/angrystone.hpp b/src/src/badguy/angrystone.hpp new file mode 100644 index 000000000..7abd8f18f --- /dev/null +++ b/src/src/badguy/angrystone.hpp @@ -0,0 +1,56 @@ +// $Id$ +// +// AngryStone - A spiked block that charges towards the player +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#ifndef __ANGRYSTONE_H__ +#define __ANGRYSTONE_H__ + +#include "badguy.hpp" + +class AngryStone : public BadGuy +{ +public: + AngryStone(const lisp::Lisp& reader); + + void activate(); + void write(lisp::Writer& writer); + void collision_solid(const CollisionHit& hit); + HitResponse collision_badguy(BadGuy& badguy, const CollisionHit& hit); + void active_update(float elapsed_time); + void kill_fall(); + + virtual AngryStone* clone() const { return new AngryStone(*this); } + +protected: + Vector attackDirection; /**< 1-normalized vector of current attack direction */ + Vector oldWallDirection; /**< if wall was hit during last attack: 1-normalized vector of last attack direction, (0,0) otherwise */ + + Timer timer; + + enum AngryStoneState { + IDLE, + CHARGING, + ATTACKING, + RECOVERING + }; + AngryStoneState state; + +}; + +#endif diff --git a/src/src/badguy/badguy.cpp b/src/src/badguy/badguy.cpp new file mode 100644 index 000000000..19e8d8b49 --- /dev/null +++ b/src/src/badguy/badguy.cpp @@ -0,0 +1,580 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#include + +#include "badguy.hpp" +#include "object/camera.hpp" +#include "object/tilemap.hpp" +#include "tile.hpp" +#include "statistics.hpp" +#include "game_session.hpp" +#include "log.hpp" +#include "level.hpp" +#include "object/bullet.hpp" +#include "main.hpp" +#include "object/particles.hpp" +#include "random_generator.hpp" + +static const float SQUISH_TIME = 2; +static const float X_OFFSCREEN_DISTANCE = 1600; +static const float Y_OFFSCREEN_DISTANCE = 1200; + +BadGuy::BadGuy(const Vector& pos, const std::string& sprite_name, int layer) + : MovingSprite(pos, sprite_name, layer, COLGROUP_DISABLED), countMe(true), + dir(LEFT), start_dir(AUTO), frozen(false), ignited(false), + state(STATE_INIT), on_ground_flag(false) +{ + start_position = bbox.p1; + + sound_manager->preload("sounds/squish.wav"); + sound_manager->preload("sounds/fall.wav"); +} + +BadGuy::BadGuy(const Vector& pos, Direction direction, const std::string& sprite_name, int layer) + : MovingSprite(pos, sprite_name, layer, COLGROUP_DISABLED), countMe(true), + dir(direction), start_dir(direction), frozen(false), ignited(false), + state(STATE_INIT), on_ground_flag(false) +{ + start_position = bbox.p1; + + sound_manager->preload("sounds/squish.wav"); + sound_manager->preload("sounds/fall.wav"); +} + +BadGuy::BadGuy(const lisp::Lisp& reader, const std::string& sprite_name, int layer) + : MovingSprite(reader, sprite_name, layer, COLGROUP_DISABLED), countMe(true), dir(LEFT), start_dir(AUTO), frozen(false), ignited(false), state(STATE_INIT), on_ground_flag(false) +{ + start_position = bbox.p1; + + std::string dir_str = "auto"; + reader.get("direction", dir_str); + start_dir = str2dir( dir_str ); + dir = start_dir; + + reader.get("dead-script", dead_script); + + sound_manager->preload("sounds/squish.wav"); + sound_manager->preload("sounds/fall.wav"); +} + +void +BadGuy::draw(DrawingContext& context) +{ + if(!sprite) + return; + if(state == STATE_INIT || state == STATE_INACTIVE) + return; + if(state == STATE_FALLING) { + DrawingEffect old_effect = context.get_drawing_effect(); + context.set_drawing_effect((DrawingEffect) (old_effect | VERTICAL_FLIP)); + sprite->draw(context, get_pos(), layer); + context.set_drawing_effect(old_effect); + } else { + sprite->draw(context, get_pos(), layer); + } +} + +void +BadGuy::update(float elapsed_time) +{ + if(!Sector::current()->inside(bbox)) { + remove_me(); + return; + } + if(is_offscreen()) { + if (state == STATE_ACTIVE) deactivate(); + set_state(STATE_INACTIVE); + } + + switch(state) { + case STATE_ACTIVE: + active_update(elapsed_time); + break; + case STATE_INIT: + case STATE_INACTIVE: + inactive_update(elapsed_time); + try_activate(); + break; + case STATE_SQUISHED: + if(state_timer.check()) { + remove_me(); + break; + } + movement = physic.get_movement(elapsed_time); + break; + case STATE_FALLING: + movement = physic.get_movement(elapsed_time); + break; + } + + on_ground_flag = false; +} + +Direction +BadGuy::str2dir( std::string dir_str ) +{ + if( dir_str == "auto" ) + return AUTO; + if( dir_str == "left" ) + return LEFT; + if( dir_str == "right" ) + return RIGHT; + + //default to "auto" + log_warning << "Badguy::str2dir: unknown direction \"" << dir_str << "\"" << std::endl;; + return AUTO; +} + +void +BadGuy::activate() +{ +} + +void +BadGuy::deactivate() +{ +} + +void +BadGuy::write(lisp::Writer& ) +{ + log_warning << "tried to write out a generic badguy" << std::endl; +} + +void +BadGuy::active_update(float elapsed_time) +{ + movement = physic.get_movement(elapsed_time); +} + +void +BadGuy::inactive_update(float ) +{ +} + +void +BadGuy::collision_tile(uint32_t tile_attributes) +{ + if(tile_attributes & Tile::HURTS) { + if (tile_attributes & Tile::FIRE) { + if (is_flammable()) ignite(); + } + else if (tile_attributes & Tile::ICE) { + if (is_freezable()) freeze(); + } + else { + kill_fall(); + } + } +} + +HitResponse +BadGuy::collision(GameObject& other, const CollisionHit& hit) +{ + switch(state) { + case STATE_INIT: + case STATE_INACTIVE: + return ABORT_MOVE; + case STATE_ACTIVE: { + BadGuy* badguy = dynamic_cast (&other); + if(badguy && badguy->state == STATE_ACTIVE && badguy->get_group() == COLGROUP_MOVING) { + + // hit from above? + if (badguy->get_bbox().p2.y < (bbox.p1.y + 16)) { + if(collision_squished(*badguy)) { + return ABORT_MOVE; + } + } + + return collision_badguy(*badguy, hit); + } + + Player* player = dynamic_cast (&other); + if(player) { + + // hit from above? + if (player->get_bbox().p2.y < (bbox.p1.y + 16)) { + if(collision_squished(*player)) { + return ABORT_MOVE; + } + } + + return collision_player(*player, hit); + } + + Bullet* bullet = dynamic_cast (&other); + if(bullet) + return collision_bullet(*bullet, hit); + + return FORCE_MOVE; + } + case STATE_SQUISHED: + return FORCE_MOVE; + case STATE_FALLING: + return FORCE_MOVE; + } + + return ABORT_MOVE; +} + +void +BadGuy::collision_solid(const CollisionHit& hit) +{ + physic.set_velocity_x(0); + physic.set_velocity_y(0); + update_on_ground_flag(hit); +} + +HitResponse +BadGuy::collision_player(Player& player, const CollisionHit& ) +{ + if(player.is_invincible()) { + kill_fall(); + return ABORT_MOVE; + } + + if(frozen) + unfreeze(); + player.kill(false); + return FORCE_MOVE; +} + +HitResponse +BadGuy::collision_badguy(BadGuy& , const CollisionHit& ) +{ + return FORCE_MOVE; +} + +bool +BadGuy::collision_squished(GameObject& ) +{ + return false; +} + +HitResponse +BadGuy::collision_bullet(Bullet& bullet, const CollisionHit& hit) +{ + if (is_frozen()) { + if(bullet.get_type() == FIRE_BONUS) { + // fire bullet thaws frozen badguys + unfreeze(); + bullet.remove_me(); + return ABORT_MOVE; + } else { + // other bullets ricochet + bullet.ricochet(*this, hit); + return FORCE_MOVE; + } + } + else if (is_ignited()) { + if(bullet.get_type() == ICE_BONUS) { + // ice bullets extinguish ignited badguys + extinguish(); + bullet.remove_me(); + return ABORT_MOVE; + } else { + // other bullets are absorbed by ignited badguys + bullet.remove_me(); + return FORCE_MOVE; + } + } + else if(bullet.get_type() == FIRE_BONUS && is_flammable()) { + // fire bullets ignite flammable badguys + ignite(); + bullet.remove_me(); + return ABORT_MOVE; + } + else if(bullet.get_type() == ICE_BONUS && is_freezable()) { + // ice bullets freeze freezable badguys + freeze(); + bullet.remove_me(); + return ABORT_MOVE; + } + else { + // in all other cases, bullets ricochet + bullet.ricochet(*this, hit); + return FORCE_MOVE; + } +} + +void +BadGuy::kill_squished(GameObject& object) +{ + sound_manager->play("sounds/squish.wav", get_pos()); + physic.enable_gravity(true); + physic.set_velocity_x(0); + physic.set_velocity_y(0); + set_state(STATE_SQUISHED); + set_group(COLGROUP_MOVING_ONLY_STATIC); + Player* player = dynamic_cast(&object); + if (player) { + if (countMe) Sector::current()->get_level()->stats.badguys++; + player->bounce(*this); + } + + // start dead-script + if(dead_script != "") { + std::istringstream stream(dead_script); + Sector::current()->run_script(stream, "dead-script"); + } +} + +void +BadGuy::kill_fall() +{ + sound_manager->play("sounds/fall.wav", get_pos()); + if (countMe) Sector::current()->get_level()->stats.badguys++; + physic.set_velocity_y(0); + physic.enable_gravity(true); + set_state(STATE_FALLING); + + // start dead-script + if(dead_script != "") { + std::istringstream stream(dead_script); + Sector::current()->run_script(stream, "dead-script"); + } +} + +void +BadGuy::run_dead_script() +{ + if (countMe) + Sector::current()->get_level()->stats.badguys++; + + // start dead-script + if(dead_script != "") { + std::istringstream stream(dead_script); + Sector::current()->run_script(stream, "dead-script"); + } +} + +void +BadGuy::set_state(State state) +{ + if(this->state == state) + return; + + State laststate = this->state; + this->state = state; + switch(state) { + case STATE_SQUISHED: + state_timer.start(SQUISH_TIME); + break; + case STATE_ACTIVE: + set_group(COLGROUP_MOVING); + bbox.set_pos(start_position); + break; + case STATE_INACTIVE: + // was the badguy dead anyway? + if(laststate == STATE_SQUISHED || laststate == STATE_FALLING) { + remove_me(); + } + set_group(COLGROUP_DISABLED); + break; + case STATE_FALLING: + set_group(COLGROUP_DISABLED); + break; + default: + break; + } +} + +bool +BadGuy::is_offscreen() +{ + float scroll_x = Sector::current()->camera->get_translation().x; + float scroll_y = Sector::current()->camera->get_translation().y; + + if(bbox.p2.x < scroll_x - X_OFFSCREEN_DISTANCE + || bbox.p1.x > scroll_x + X_OFFSCREEN_DISTANCE + SCREEN_WIDTH + || bbox.p2.y < scroll_y - Y_OFFSCREEN_DISTANCE + || bbox.p1.y > scroll_y + Y_OFFSCREEN_DISTANCE + SCREEN_HEIGHT) + return true; + + return false; +} + +void +BadGuy::try_activate() +{ + float scroll_x = Sector::current()->camera->get_translation().x; + float scroll_y = Sector::current()->camera->get_translation().y; + + /* Activate badguys if they're just around the screen to avoid + * the effect of having badguys suddenly popping up from nowhere. + */ + //Badguy left of screen + if (start_position.x > scroll_x - X_OFFSCREEN_DISTANCE && + start_position.x < scroll_x - bbox.get_width() && + start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE && + start_position.y < scroll_y + SCREEN_HEIGHT + Y_OFFSCREEN_DISTANCE) { + if (start_dir != AUTO) dir = start_dir; else dir = RIGHT; + set_state(STATE_ACTIVE); + activate(); + //Badguy right of screen + } else if (start_position.x > scroll_x + SCREEN_WIDTH && + start_position.x < scroll_x + SCREEN_WIDTH + X_OFFSCREEN_DISTANCE && + start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE && + start_position.y < scroll_y + SCREEN_HEIGHT + Y_OFFSCREEN_DISTANCE) { + if (start_dir != AUTO) dir = start_dir; else dir = LEFT; + set_state(STATE_ACTIVE); + activate(); + //Badguy over or under screen + } else if (start_position.x > scroll_x - X_OFFSCREEN_DISTANCE && + start_position.x < scroll_x + SCREEN_WIDTH + X_OFFSCREEN_DISTANCE && + ((start_position.y > scroll_y + SCREEN_HEIGHT && + start_position.y < scroll_y + SCREEN_HEIGHT + Y_OFFSCREEN_DISTANCE) || + (start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE && + start_position.y < scroll_y - bbox.get_height() ))) { + if (start_dir != AUTO) dir = start_dir; + else{ + // if nearest player is to our right, start facing right + Player* player = get_nearest_player(); + if (player && (player->get_bbox().p1.x > get_bbox().p2.x)) { + dir = RIGHT; + } else { + dir = LEFT; + } + } + set_state(STATE_ACTIVE); + activate(); + } else if(state == STATE_INIT + && start_position.x > scroll_x - X_OFFSCREEN_DISTANCE + && start_position.x < scroll_x + X_OFFSCREEN_DISTANCE + SCREEN_WIDTH + && start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE + && start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE + SCREEN_HEIGHT ) { + if (start_dir != AUTO) { + dir = start_dir; + } else { + // if nearest player is to our right, start facing right + Player* player = get_nearest_player(); + if (player && (player->get_bbox().p1.x > get_bbox().p2.x)) { + dir = RIGHT; + } else { + dir = LEFT; + } + } + set_state(STATE_ACTIVE); + activate(); + } +} + +bool +BadGuy::might_fall(int height) +{ + // make sure we check for at least a 1-pixel fall + assert(height > 0); + + float x1; + float x2; + float y1 = bbox.p2.y + 1; + float y2 = bbox.p2.y + 1 + height; + if (dir == LEFT) { + x1 = bbox.p1.x - 1; + x2 = bbox.p1.x - 1; + } else { + x1 = bbox.p2.x + 1; + x2 = bbox.p2.x + 1; + } + return Sector::current()->is_free_of_statics(Rect(x1, y1, x2, y2)); +} + +Player* +BadGuy::get_nearest_player() +{ + // FIXME: does not really return nearest player + + std::vector players = Sector::current()->get_players(); + for (std::vector::iterator playerIter = players.begin(); playerIter != players.end(); ++playerIter) { + Player* player = *playerIter; + return player; + } + + return 0; +} + +void +BadGuy::update_on_ground_flag(const CollisionHit& hit) +{ + if (hit.bottom) { + on_ground_flag = true; + floor_normal = hit.slope_normal; + } +} + +bool +BadGuy::on_ground() +{ + return on_ground_flag; +} + +Vector +BadGuy::get_floor_normal() +{ + return floor_normal; +} + +void +BadGuy::freeze() +{ + set_group(COLGROUP_MOVING_STATIC); + frozen = true; +} + +void +BadGuy::unfreeze() +{ + set_group(COLGROUP_MOVING); + frozen = false; +} + +bool +BadGuy::is_freezable() const +{ + return false; +} + +bool +BadGuy::is_frozen() const +{ + return frozen; +} + +void +BadGuy::ignite() +{ + kill_fall(); +} + +void +BadGuy::extinguish() +{ +} + +bool +BadGuy::is_flammable() const +{ + return true; +} + +bool +BadGuy::is_ignited() const +{ + return ignited; +} diff --git a/src/src/badguy/badguy.hpp b/src/src/badguy/badguy.hpp new file mode 100644 index 000000000..6c1816612 --- /dev/null +++ b/src/src/badguy/badguy.hpp @@ -0,0 +1,243 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __BADGUY_H__ +#define __BADGUY_H__ + +// moved them here to make it less typing when implementing new badguys +#include +#include "timer.hpp" +#include "object/moving_sprite.hpp" +#include "physic.hpp" +#include "object/player.hpp" +#include "serializable.hpp" +#include "resources.hpp" +#include "sector.hpp" +#include "direction.hpp" +#include "object_factory.hpp" +#include "lisp/parser.hpp" +#include "lisp/lisp.hpp" +#include "lisp/writer.hpp" +#include "video/drawing_context.hpp" +#include "audio/sound_manager.hpp" +#include "audio/sound_source.hpp" + +class BadGuy : public MovingSprite, protected UsesPhysic, public Serializable +{ +public: + BadGuy(const Vector& pos, const std::string& sprite_name, int layer = LAYER_OBJECTS); + BadGuy(const Vector& pos, Direction direction, const std::string& sprite_name, int layer = LAYER_OBJECTS); + BadGuy(const lisp::Lisp& reader, const std::string& sprite_name, int layer = LAYER_OBJECTS); + + /** Called when the badguy is drawn. The default implementation simply draws + * the badguy sprite on screen + */ + virtual void draw(DrawingContext& context); + /** Called each frame. The default implementation checks badguy state and + * calls active_update and inactive_update + */ + virtual void update(float elapsed_time); + /** Called when a collision with another object occured. The default + * implemetnation calls collision_player, collision_solid, collision_badguy + * and collision_squished + */ + virtual HitResponse collision(GameObject& other, const CollisionHit& hit); + + /** Called when a collision with tile with special attributes occured */ + virtual void collision_tile(uint32_t tile_attributes); + + /** Set the badguy to kill/falling state, which makes him falling of the + * screen (his sprite is turned upside-down) + */ + virtual void kill_fall(); + + /** Call this, if you use custom kill_fall() or kill_squashed(GameObject& object) */ + virtual void run_dead_script(); + + /** Writes out the badguy into the included lisp::Writer. Useful e.g. when + * converting an old-format level to the new format. + */ + virtual void write(lisp::Writer& writer); + + /** + * True if this badguy can break bricks or open bonusblocks in his current form. + */ + virtual bool can_break() + { + return false; + } + + Vector get_start_position() const + { + return start_position; + } + void set_start_position(const Vector& vec) + { + start_position = vec; + } + + /** Count this badguy to the statistics? This value should not be changed + * during runtime. */ + bool countMe; + + /** + * Called when hit by a fire bullet, and is_flammable() returns true + */ + virtual void ignite(); + + /** + * Called to revert a badguy when is_ignited() returns true + */ + virtual void extinguish(); + + /** + * Returns whether to call ignite() when a badguy gets hit by a fire bullet + */ + virtual bool is_flammable() const; + + /** + * Returns whether this badguys is currently on fire + */ + bool is_ignited() const; + + /** + * Called when hit by an ice bullet, and is_freezable() returns true. + */ + virtual void freeze(); + + /** + * Called to unfreeze the badguy. + */ + virtual void unfreeze(); + + virtual bool is_freezable() const; + + bool is_frozen() const; + +protected: + enum State { + STATE_INIT, + STATE_INACTIVE, + STATE_ACTIVE, + STATE_SQUISHED, + STATE_FALLING + }; + + /** Called when the badguy collided with a player */ + virtual HitResponse collision_player(Player& player, const CollisionHit& hit); + /** Called when the badguy collided with solid ground */ + virtual void collision_solid(const CollisionHit& hit); + /** Called when the badguy collided with another badguy */ + virtual HitResponse collision_badguy(BadGuy& other, const CollisionHit& hit); + + /** Called when the player hit the badguy from above. You should return true + * if the badguy was squished, false if squishing wasn't possible + */ + virtual bool collision_squished(GameObject& object); + + /** Called when the badguy collided with a bullet */ + virtual HitResponse collision_bullet(Bullet& bullet, const CollisionHit& hit); + + /** called each frame when the badguy is activated. */ + virtual void active_update(float elapsed_time); + /** called each frame when the badguy is not activated. */ + virtual void inactive_update(float elapsed_time); + + /** + * called when the badguy has been activated. (As a side effect the dir + * variable might have been changed so that it faces towards the player. + */ + virtual void activate(); + /** called when the badguy has been deactivated */ + virtual void deactivate(); + + void kill_squished(GameObject& object); + + void set_state(State state); + State get_state() const + { return state; } + + /** + * returns a pointer to the nearest player or 0 if no player is available + */ + Player* get_nearest_player(); + + /// is the enemy activated + bool activated; + /** + * initial position of the enemy. Also the position where enemy respawns when + * after being deactivated. + */ + bool is_offscreen(); + /** + * Returns true if we might soon fall at least @c height pixels. Minimum + * value for height is 1 pixel + */ + bool might_fall(int height = 1); + + Vector start_position; + + /** + * The direction we currently face in + */ + Direction dir; + + /** + * The direction we initially faced in + */ + Direction start_dir; + + /** + * Get Direction from String. + */ + Direction str2dir( std::string dir_str ); + + /** + * Update on_ground_flag judging by solid collision @c hit. + * This gets called from the base implementation of collision_solid, so call this when overriding collision_solid's default behaviour. + */ + void update_on_ground_flag(const CollisionHit& hit); + + /** + * Returns true if we touched ground in the past frame + * This only works if update_on_ground_flag() gets called in collision_solid. + */ + bool on_ground(); + + /** + * Returns floor normal stored the last time when update_on_ground_flag was called and we touched something solid from above. + */ + Vector get_floor_normal(); + + bool frozen; + bool ignited; /**< true if this badguy is currently on fire */ + + std::string dead_script; /**< script to execute when badguy is killed */ + +private: + void try_activate(); + + State state; + Timer state_timer; + bool on_ground_flag; /**< true if we touched something solid from above and update_on_ground_flag was called last frame */ + Vector floor_normal; /**< floor normal stored the last time when update_on_ground_flag was called and we touched something solid from above */ + +}; + +#endif diff --git a/src/src/badguy/bomb.cpp b/src/src/badguy/bomb.cpp new file mode 100644 index 000000000..cbc96c0a0 --- /dev/null +++ b/src/src/badguy/bomb.cpp @@ -0,0 +1,104 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "bomb.hpp" +#include "random_generator.hpp" +#include "object/explosion.hpp" + +Bomb::Bomb(const Vector& pos, Direction dir, std::string custom_sprite /*= "images/creatures/mr_bomb/mr_bomb.sprite"*/ ) + : BadGuy( pos, dir, custom_sprite ) +{ + state = STATE_TICKING; + set_action(dir == LEFT ? "ticking-left" : "ticking-right", 1); + countMe = false; + + ticking.reset(sound_manager->create_sound_source("sounds/fizz.wav")); + ticking->set_position(get_pos()); + ticking->set_looping(true); + ticking->set_gain(2.0); + ticking->set_reference_distance(32); + ticking->play(); +} + +Bomb::Bomb(const Bomb& other) + : BadGuy(other), state(other.state) +{ + if (state == STATE_TICKING) { + ticking.reset(sound_manager->create_sound_source("sounds/fizz.wav")); + ticking->set_position(get_pos()); + ticking->set_looping(true); + ticking->set_gain(2.0); + ticking->set_reference_distance(32); + ticking->play(); + } +} + +void +Bomb::write(lisp::Writer& ) +{ + // bombs are only temporarily so don't write them out... +} + +void +Bomb::collision_solid(const CollisionHit& hit) +{ + if(hit.bottom) + physic.set_velocity_y(0); +} + +HitResponse +Bomb::collision_player(Player& , const CollisionHit& ) +{ + return ABORT_MOVE; +} + +HitResponse +Bomb::collision_badguy(BadGuy& , const CollisionHit& ) +{ + return ABORT_MOVE; +} + +void +Bomb::active_update(float ) +{ + ticking->set_position(get_pos()); + if(sprite->animation_done()) { + explode(); + } +} + +void +Bomb::explode() +{ + ticking->stop(); + + remove_me(); + Explosion* explosion = new Explosion(get_bbox().get_middle()); + Sector::current()->add_object(explosion); + + run_dead_script(); +} + +void +Bomb::kill_fall() +{ + explode(); +} diff --git a/src/src/badguy/bomb.hpp b/src/src/badguy/bomb.hpp new file mode 100644 index 000000000..e75a32f80 --- /dev/null +++ b/src/src/badguy/bomb.hpp @@ -0,0 +1,49 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __BOMB_H__ +#define __BOMB_H__ + +#include "badguy.hpp" + +class Bomb : public BadGuy +{ +public: + Bomb(const Vector& pos, Direction dir, std::string custom_sprite = "images/creatures/mr_bomb/bomb.sprite" ); + Bomb(const Bomb& bomb); + + void write(lisp::Writer& writer); + void collision_solid(const CollisionHit& hit); + HitResponse collision_player(Player& player, const CollisionHit& hit); + HitResponse collision_badguy(BadGuy& badguy, const CollisionHit& hit); + void active_update(float elapsed_time); + void kill_fall(); + void explode(); + +private: + enum State { + STATE_TICKING + }; + + State state; + + std::auto_ptr ticking; +}; + +#endif diff --git a/src/src/badguy/bouncing_snowball.cpp b/src/src/badguy/bouncing_snowball.cpp new file mode 100644 index 000000000..f84561b74 --- /dev/null +++ b/src/src/badguy/bouncing_snowball.cpp @@ -0,0 +1,90 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "bouncing_snowball.hpp" + +static const float JUMPSPEED = -450; +static const float WALKSPEED = 80; + +BouncingSnowball::BouncingSnowball(const lisp::Lisp& reader) + : BadGuy(reader, "images/creatures/bouncing_snowball/bouncing_snowball.sprite") +{ +} + +BouncingSnowball::BouncingSnowball(const Vector& pos, Direction d) + : BadGuy(pos, d, "images/creatures/bouncing_snowball/bouncing_snowball.sprite") +{ +} + +void +BouncingSnowball::write(lisp::Writer& writer) +{ + writer.start_list("bouncingsnowball"); + + writer.write_float("x", start_position.x); + writer.write_float("y", start_position.y); + + writer.end_list("bouncingsnowball"); +} + +void +BouncingSnowball::activate() +{ + physic.set_velocity_x(dir == LEFT ? -WALKSPEED : WALKSPEED); + sprite->set_action(dir == LEFT ? "left" : "right"); +} + +bool +BouncingSnowball::collision_squished(GameObject& object) +{ + sprite->set_action("squished"); + kill_squished(object); + return true; +} + +void +BouncingSnowball::collision_solid(const CollisionHit& hit) +{ + if(hit.bottom) { + if(get_state() == STATE_ACTIVE) { + physic.set_velocity_y(JUMPSPEED); + } else { + physic.set_velocity_y(0); + } + } else if(hit.top) { + physic.set_velocity_y(0); + } + + if(hit.left || hit.right) { // left or right collision + dir = dir == LEFT ? RIGHT : LEFT; + sprite->set_action(dir == LEFT ? "left" : "right"); + physic.set_velocity_x(-physic.get_velocity_x()); + } +} + +HitResponse +BouncingSnowball::collision_badguy(BadGuy& , const CollisionHit& hit) +{ + collision_solid(hit); + return CONTINUE; +} + +IMPLEMENT_FACTORY(BouncingSnowball, "bouncingsnowball") diff --git a/src/src/badguy/bouncing_snowball.hpp b/src/src/badguy/bouncing_snowball.hpp new file mode 100644 index 000000000..ed2c5cb16 --- /dev/null +++ b/src/src/badguy/bouncing_snowball.hpp @@ -0,0 +1,42 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __BOUNCING_SNOWBALL_H__ +#define __BOUNCING_SNOWBALL_H__ + +#include "badguy.hpp" + +class BouncingSnowball : public BadGuy +{ +public: + BouncingSnowball(const lisp::Lisp& reader); + BouncingSnowball(const Vector& pos, Direction d); + + void activate(); + void write(lisp::Writer& writer); + void collision_solid(const CollisionHit& hit); + HitResponse collision_badguy(BadGuy& badguy, const CollisionHit& hit); + + virtual BouncingSnowball* clone() const { return new BouncingSnowball(*this); } + +protected: + bool collision_squished(GameObject& object); +}; + +#endif diff --git a/src/src/badguy/dart.cpp b/src/src/badguy/dart.cpp new file mode 100644 index 000000000..393474e31 --- /dev/null +++ b/src/src/badguy/dart.cpp @@ -0,0 +1,136 @@ +// $Id$ +// +// Dart - Your average poison dart +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#include + +#include "dart.hpp" + +namespace { + const float SPEED = 200; +} + +static const std::string SOUNDFILE = "sounds/flame.wav"; + +Dart::Dart(const lisp::Lisp& reader) + : BadGuy(reader, "images/creatures/dart/dart.sprite"), parent(0) +{ + physic.enable_gravity(false); + countMe = false; + sound_manager->preload("sounds/darthit.wav"); + sound_manager->preload("sounds/stomp.wav"); +} + +Dart::Dart(const Vector& pos, Direction d, const BadGuy* parent = 0) + : BadGuy(pos, d, "images/creatures/dart/dart.sprite"), parent(parent) +{ + physic.enable_gravity(false); + countMe = false; + sound_manager->preload("sounds/darthit.wav"); + sound_manager->preload("sounds/stomp.wav"); +} + +Dart::Dart(const Dart& other) + : BadGuy(other), parent(other.parent) +{ + sound_source.reset(sound_manager->create_sound_source(SOUNDFILE)); + sound_manager->preload("sounds/darthit.wav"); + sound_manager->preload("sounds/stomp.wav"); +} + +Dart::~Dart() +{ +} + +bool +Dart::updatePointers(const GameObject* from_object, GameObject* to_object) +{ + if (from_object == parent) { + parent = dynamic_cast(to_object); + return true; + } + return false; +} + +void +Dart::write(lisp::Writer& writer) +{ + writer.start_list("dart"); + writer.write_float("x", start_position.x); + writer.write_float("y", start_position.y); + writer.end_list("dart"); +} + +void +Dart::activate() +{ + physic.set_velocity_x(dir == LEFT ? -::SPEED : ::SPEED); + sprite->set_action(dir == LEFT ? "flying-left" : "flying-right"); + + sound_source.reset(sound_manager->create_sound_source(SOUNDFILE)); + sound_source->set_position(get_pos()); + sound_source->set_looping(true); + sound_source->set_gain(1.0); + sound_source->set_reference_distance(32); + sound_source->play(); +} + +void +Dart::deactivate() +{ + sound_source.reset(); + remove_me(); +} + +void +Dart::active_update(float elapsed_time) +{ + BadGuy::active_update(elapsed_time); + sound_source->set_position(get_pos()); +} + +void +Dart::collision_solid(const CollisionHit& ) +{ + sound_manager->play("sounds/darthit.wav", get_pos()); + remove_me(); +} + +HitResponse +Dart::collision_badguy(BadGuy& badguy, const CollisionHit& ) +{ + // ignore collisions with parent + if (&badguy == parent) { + return FORCE_MOVE; + } + sound_manager->play("sounds/stomp.wav", get_pos()); + remove_me(); + badguy.kill_fall(); + return ABORT_MOVE; +} + +HitResponse +Dart::collision_player(Player& player, const CollisionHit& hit) +{ + sound_manager->play("sounds/stomp.wav", get_pos()); + remove_me(); + return BadGuy::collision_player(player, hit); +} + +IMPLEMENT_FACTORY(Dart, "dart") diff --git a/src/src/badguy/dart.hpp b/src/src/badguy/dart.hpp new file mode 100644 index 000000000..bbab1df0f --- /dev/null +++ b/src/src/badguy/dart.hpp @@ -0,0 +1,56 @@ +// $Id$ +// +// Dart - Your average poison dart +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#ifndef __DART_H__ +#define __DART_H__ + +#include "badguy.hpp" + +/** + * Badguy "Dart" - Your average poison dart + */ +class Dart : public BadGuy +{ +public: + Dart(const lisp::Lisp& reader); + Dart(const Vector& pos, Direction d, const BadGuy* parent); + Dart(const Dart& dart); + ~Dart(); + + void activate(); + void deactivate(); + void write(lisp::Writer& writer); + + void active_update(float elapsed_time); + + void collision_solid(const CollisionHit& hit); + HitResponse collision_badguy(BadGuy& badguy, const CollisionHit& hit); + HitResponse collision_player(Player& player, const CollisionHit& hit); + + virtual Dart* clone() const { return new Dart(*this); } + + virtual bool updatePointers(const GameObject* from_object, GameObject* to_object); + +protected: + const BadGuy* parent; /**< collisions with this BadGuy will be ignored */ + std::auto_ptr sound_source; /**< SoundSource for ambient sound */ +}; + +#endif diff --git a/src/src/badguy/darttrap.cpp b/src/src/badguy/darttrap.cpp new file mode 100644 index 000000000..7c6a84b47 --- /dev/null +++ b/src/src/badguy/darttrap.cpp @@ -0,0 +1,107 @@ +// $Id$ +// +// DartTrap - Shoots a Dart at regular intervals +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "darttrap.hpp" +#include "dart.hpp" + +namespace { + const float MUZZLE_Y = 25; /**< [px] muzzle y-offset from top */ +} + +DartTrap::DartTrap(const lisp::Lisp& reader) + : BadGuy(reader, "images/creatures/darttrap/darttrap.sprite", LAYER_TILES-1), initial_delay(0), fire_delay(2), ammo(-1), state(IDLE) +{ + reader.get("initial-delay", initial_delay); + reader.get("fire-delay", fire_delay); + reader.get("ammo", ammo); + countMe = false; + sound_manager->preload("sounds/dartfire.wav"); + if (start_dir == AUTO) log_warning << "Setting a DartTrap's direction to AUTO is no good idea" << std::endl; +} + +void +DartTrap::write(lisp::Writer& writer) +{ + writer.start_list("darttrap"); + writer.write_float("x", start_position.x); + writer.write_float("y", start_position.y); + writer.write_float("initial-delay", initial_delay); + writer.write_float("fire-delay", fire_delay); + writer.write_int("ammo", ammo); + writer.end_list("darttrap"); +} + +void +DartTrap::activate() +{ + state = IDLE; + sprite->set_action(dir == LEFT ? "idle-left" : "idle-right"); + set_group(COLGROUP_DISABLED); + + if (initial_delay == 0) initial_delay = 0.1f; + fire_timer.start(initial_delay); +} + +HitResponse +DartTrap::collision_player(Player& , const CollisionHit& ) +{ + return ABORT_MOVE; +} + +void +DartTrap::active_update(float ) +{ + if (state == IDLE) { + if ((ammo != 0) && (fire_timer.check())) { + if (ammo > 0) ammo--; + load(); + fire_timer.start(fire_delay); + } + } + if (state == LOADING) { + if (sprite->animation_done()) { + fire(); + } + } +} + +void +DartTrap::load() +{ + state = LOADING; + sprite->set_action(dir == LEFT ? "loading-left" : "loading-right", 1); +} + +void +DartTrap::fire() +{ + float px = get_pos().x; + if (dir == RIGHT) px += 5; + float py = get_pos().y; + py += MUZZLE_Y; + + sound_manager->play("sounds/dartfire.wav", get_pos()); + Sector::current()->add_object(new Dart(Vector(px, py), dir, this)); + state = IDLE; + sprite->set_action(dir == LEFT ? "idle-left" : "idle-right"); +} + +IMPLEMENT_FACTORY(DartTrap, "darttrap") diff --git a/src/src/badguy/darttrap.hpp b/src/src/badguy/darttrap.hpp new file mode 100644 index 000000000..7f2b80ebc --- /dev/null +++ b/src/src/badguy/darttrap.hpp @@ -0,0 +1,57 @@ +// $Id$ +// +// DartTrap - Shoots a Dart at regular intervals +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __DARTTRAP_H__ +#define __DARTTRAP_H__ + +#include "badguy.hpp" +#include "timer.hpp" + +/** + * Badguy "DartTrap" - Shoots a Dart at regular intervals + */ +class DartTrap : public BadGuy +{ +public: + DartTrap(const lisp::Lisp& reader); + + void activate(); + void write(lisp::Writer& writer); + void active_update(float elapsed_time); + HitResponse collision_player(Player& player, const CollisionHit& hit); + + virtual DartTrap* clone() const { return new DartTrap(*this); } + +protected: + enum State { + IDLE, LOADING + }; + + void load(); /**< load a shot */ + void fire(); /**< fire a shot */ + + float initial_delay; /**< time to wait before firing first shot */ + float fire_delay; /**< reload time */ + int ammo; /**< ammo left (-1 means unlimited) */ + + State state; /**< current state */ + Timer fire_timer; /**< time until new shot is fired */ +}; + +#endif diff --git a/src/src/badguy/dispenser.cpp b/src/src/badguy/dispenser.cpp new file mode 100644 index 000000000..123e4aa74 --- /dev/null +++ b/src/src/badguy/dispenser.cpp @@ -0,0 +1,153 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "dispenser.hpp" +#include "badguy/bouncing_snowball.hpp" +#include "badguy/snowball.hpp" +#include "badguy/mrbomb.hpp" +#include "badguy/mriceblock.hpp" +#include "badguy/mrrocket.hpp" +#include "badguy/poisonivy.hpp" +#include "badguy/snail.hpp" +#include "badguy/skullyhop.hpp" +#include "random_generator.hpp" + +Dispenser::Dispenser(const lisp::Lisp& reader) + : BadGuy(reader, "images/creatures/dispenser/dispenser.sprite") +{ + reader.get("cycle", cycle); + reader.get("badguy", badguy); + if (badguy == "mrrocket") { + if (start_dir == AUTO) log_warning << "Setting a Dispenser's direction to AUTO is no good idea" << std::endl; + sprite->set_action(dir == LEFT ? "working-left" : "working-right"); + } + else {sprite->set_action("dropper");} + bbox.set_size(sprite->get_current_hitbox_width(), sprite->get_current_hitbox_height()); + countMe = false; +} + +void +Dispenser::write(lisp::Writer& writer) +{ + writer.start_list("dispenser"); + + writer.write_float("x", start_position.x); + writer.write_float("y", start_position.y); + writer.write_float("cycle", cycle); + writer.write_string("badguy", badguy); + + writer.end_list("dispenser"); +} + +void +Dispenser::activate() +{ + if(frozen) + return; + dispense_timer.start(cycle, true); + launch_badguy(); +} + +void +Dispenser::deactivate() +{ + dispense_timer.stop(); +} + +//TODO: Add launching velocity to certain badguys +bool +Dispenser::collision_squished(GameObject& object) +{ + //TODO: Should it act like a normal tile when killed? + sprite->set_action(dir == LEFT ? "broken-left" : "broken-right"); + dispense_timer.start(0); + Player* player = dynamic_cast(&object); + if (player) player->bounce(*this); + kill_squished(object); + return true; +} + +void +Dispenser::active_update(float ) +{ + if (dispense_timer.check()) { + launch_badguy(); + } +} + +// Add themed randomizer +void +Dispenser::launch_badguy() +{ + //FIXME: Does is_offscreen() work right here? + if (!is_offscreen()) { + if (badguy == "snowball") + Sector::current()->add_object(new SnowBall(Vector(get_pos().x, get_pos().y+32), dir)); + else if (badguy == "bouncingsnowball") + Sector::current()->add_object(new BouncingSnowball(Vector(get_pos().x, get_pos().y+32), dir)); + else if (badguy == "mrbomb") + Sector::current()->add_object(new MrBomb(Vector(get_pos().x, get_pos().y+32), dir)); + else if (badguy == "mriceblock") + Sector::current()->add_object(new MrIceBlock(Vector(get_pos().x, get_pos().y+32), dir)); + else if (badguy == "snail") + Sector::current()->add_object(new Snail(Vector(get_pos().x, get_pos().y+32), dir)); + else if (badguy == "mrrocket") { + Sector::current()->add_object(new MrRocket(Vector(get_pos().x+(dir == LEFT ? -32 : 32), get_pos().y), dir));} + else if (badguy == "poisonivy") + Sector::current()->add_object(new PoisonIvy(Vector(get_pos().x, get_pos().y+32), dir)); + else if (badguy == "skullyhop") + Sector::current()->add_object(new SkullyHop(Vector(get_pos().x, get_pos().y+44), dir)); + else if (badguy == "random") + { + switch (systemRandom.rand(7)) + { + case 0: Sector::current()->add_object(new SnowBall(Vector(get_pos().x, get_pos().y+32), dir)); break; + case 1: Sector::current()->add_object(new BouncingSnowball(Vector(get_pos().x, get_pos().y+32), dir)); break; + case 2: Sector::current()->add_object(new MrBomb(Vector(get_pos().x, get_pos().y+32), dir)); break; + case 3: Sector::current()->add_object(new MrIceBlock(Vector(get_pos().x, get_pos().y+32), dir)); break; + case 4: Sector::current()->add_object(new PoisonIvy(Vector(get_pos().x, get_pos().y+32), dir)); break; + case 5: Sector::current()->add_object(new Snail(Vector(get_pos().x, get_pos().y+32), dir)); break; + case 6: Sector::current()->add_object(new SkullyHop(Vector(get_pos().x, get_pos().y+44), dir)); break; + } + } + } +} + +void +Dispenser::freeze() +{ + BadGuy::freeze(); + dispense_timer.stop(); +} + +void +Dispenser::unfreeze() +{ + BadGuy::unfreeze(); + activate(); +} + +bool +Dispenser::is_freezable() const +{ + return true; +} +IMPLEMENT_FACTORY(Dispenser, "dispenser") diff --git a/src/src/badguy/dispenser.hpp b/src/src/badguy/dispenser.hpp new file mode 100644 index 000000000..e553d545d --- /dev/null +++ b/src/src/badguy/dispenser.hpp @@ -0,0 +1,50 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __DISPENSER_H__ +#define __DISPENSER_H__ + +#include "badguy.hpp" +#include "timer.hpp" + +class Dispenser : public BadGuy +{ +public: + Dispenser(const lisp::Lisp& reader); + + void activate(); + void deactivate(); + void write(lisp::Writer& writer); + void active_update(float elapsed_time); + + void freeze(); + void unfreeze(); + bool is_freezable() const; + + virtual Dispenser* clone() const { return new Dispenser(*this); } + +protected: + bool collision_squished(GameObject& object); + void launch_badguy(); + float cycle; + std::string badguy; + Timer dispense_timer; +}; + +#endif diff --git a/src/src/badguy/fish.cpp b/src/src/badguy/fish.cpp new file mode 100644 index 000000000..803512096 --- /dev/null +++ b/src/src/badguy/fish.cpp @@ -0,0 +1,162 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "fish.hpp" +#include "tile.hpp" +#include "object/tilemap.hpp" +#include "log.hpp" + +static const float FISH_JUMP_POWER = -600; +static const float FISH_WAIT_TIME = 1; + +Fish::Fish(const lisp::Lisp& reader) + : BadGuy(reader, "images/creatures/fish/fish.sprite", LAYER_TILES-1), stop_y(0) +{ + physic.enable_gravity(true); +} + +Fish::Fish(const Vector& pos) + : BadGuy(pos, "images/creatures/fish/fish.sprite", LAYER_TILES-1), stop_y(0) +{ + physic.enable_gravity(true); +} + +void +Fish::write(lisp::Writer& writer) +{ + writer.start_list("fish"); + + writer.write_float("x", start_position.x); + writer.write_float("y", start_position.y); + + writer.end_list("fish"); +} + +void +Fish::collision_solid(const CollisionHit& chit) +{ + hit(chit); +} + +HitResponse +Fish::collision_badguy(BadGuy& , const CollisionHit& chit) +{ + return hit(chit); +} + +void +Fish::draw(DrawingContext& context) +{ + if(waiting.started()) + return; + + BadGuy::draw(context); +} + +HitResponse +Fish::hit(const CollisionHit& hit) +{ + if(hit.top) { + physic.set_velocity_y(0); + } + + return CONTINUE; +} + +void +Fish::collision_tile(uint32_t tile_attributes) +{ + if ((tile_attributes & Tile::WATER) && (physic.get_velocity_y() >= 0)) { + + // initialize stop position if uninitialized + if (stop_y == 0) stop_y = get_pos().y + get_bbox().get_height(); + + // stop when we have reached the stop position + if (get_pos().y >= stop_y) { + if(!frozen) + start_waiting(); + movement = Vector(0, 0); + } + + } +} + +void +Fish::active_update(float elapsed_time) +{ + BadGuy::active_update(elapsed_time); + + // waited long enough? + if(waiting.check()) { + jump(); + } + + // set sprite + if(!frozen) + sprite->set_action(physic.get_velocity_y() < 0 ? "normal" : "down"); + + // we can't afford flying out of the tilemap, 'cause the engine would remove us. + if ((get_pos().y - 31.8) < 0) // too high, let us fall + { + physic.set_velocity_y(0); + physic.enable_gravity(true); + } +} + +void +Fish::start_waiting() +{ + waiting.start(FISH_WAIT_TIME); + set_group(COLGROUP_DISABLED); + physic.enable_gravity(false); + physic.set_velocity_y(0); +} + +void +Fish::jump() +{ + physic.set_velocity_y(FISH_JUMP_POWER); + physic.enable_gravity(true); + set_group(COLGROUP_MOVING); +} + +void +Fish::freeze() +{ + BadGuy::freeze(); + sprite->set_action(physic.get_velocity_y() < 0 ? "iced" : "iced-down"); + waiting.stop(); +} + +void +Fish::unfreeze() +{ // does this happen at all? (or do fishes die when they fall frozen?) + BadGuy::unfreeze(); + start_waiting(); +} + +bool +Fish::is_freezable() const +{ + return true; +} + +IMPLEMENT_FACTORY(Fish, "fish") diff --git a/src/src/badguy/fish.hpp b/src/src/badguy/fish.hpp new file mode 100644 index 000000000..fa36dee97 --- /dev/null +++ b/src/src/badguy/fish.hpp @@ -0,0 +1,55 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __FISH_H__ +#define __FISH_H__ + +#include "badguy.hpp" + +class Fish : public BadGuy +{ +public: + Fish(const lisp::Lisp& ); + Fish(const Vector& pos); + + void draw(DrawingContext& context); + + void collision_solid(const CollisionHit& hit); + HitResponse collision_badguy(BadGuy& , const CollisionHit& ); + void collision_tile(uint32_t tile_attributes); + + void write(lisp::Writer& ); + void active_update(float); + + void freeze(); + void unfreeze(); + bool is_freezable() const; + + virtual Fish* clone() const { return new Fish(*this); } + +private: + HitResponse hit(const CollisionHit& ); + void start_waiting(); + void jump(); + + Timer waiting; + float stop_y; /**< y-coordinate to stop at */ +}; + +#endif diff --git a/src/src/badguy/flame.cpp b/src/src/badguy/flame.cpp new file mode 100644 index 000000000..7f8d3562e --- /dev/null +++ b/src/src/badguy/flame.cpp @@ -0,0 +1,86 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "flame.hpp" +#include "log.hpp" + +static const std::string SOUNDFILE = "sounds/flame.wav"; + +Flame::Flame(const lisp::Lisp& reader) + : BadGuy(reader, "images/creatures/flame/flame.sprite", LAYER_FLOATINGOBJECTS), angle(0), radius(100), speed(2) +{ + reader.get("radius", radius); + reader.get("speed", speed); + bbox.set_pos(Vector(start_position.x + cos(angle) * radius, + start_position.y + sin(angle) * radius)); + countMe = false; + sound_manager->preload(SOUNDFILE); +} + +void +Flame::write(lisp::Writer& writer) +{ + writer.start_list("flame"); + + writer.write_float("x", start_position.x); + writer.write_float("y", start_position.y); + writer.write_float("radius", radius); + writer.write_float("speed", speed); + + writer.end_list("flame"); +} + +void +Flame::active_update(float elapsed_time) +{ + angle = fmodf(angle + elapsed_time * speed, (float) (2*M_PI)); + Vector newpos(start_position.x + cos(angle) * radius, + start_position.y + sin(angle) * radius); + movement = newpos - get_pos(); + + sound_source->set_position(get_pos()); +} + +void +Flame::activate() +{ + set_group(COLGROUP_TOUCHABLE); + + sound_source.reset(sound_manager->create_sound_source(SOUNDFILE)); + sound_source->set_position(get_pos()); + sound_source->set_looping(true); + sound_source->set_gain(2.0); + sound_source->set_reference_distance(32); + sound_source->play(); +} + +void +Flame::deactivate() +{ + sound_source.reset(); +} + +void +Flame::kill_fall() +{ +} + +IMPLEMENT_FACTORY(Flame, "flame") diff --git a/src/src/badguy/flame.hpp b/src/src/badguy/flame.hpp new file mode 100644 index 000000000..ac8a8fc7b --- /dev/null +++ b/src/src/badguy/flame.hpp @@ -0,0 +1,45 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#ifndef __FLAME_H__ +#define __FLAME_H__ + +#include "badguy.hpp" + +class Flame : public BadGuy +{ +public: + Flame(const lisp::Lisp& reader); + Flame(const Flame& flame); + + void activate(); + void deactivate(); + + void write(lisp::Writer& write); + void active_update(float elapsed_time); + void kill_fall(); + +private: + float angle; + float radius; + float speed; + + std::auto_ptr sound_source; +}; + +#endif diff --git a/src/src/badguy/flyingsnowball.cpp b/src/src/badguy/flyingsnowball.cpp new file mode 100644 index 000000000..a0abf233a --- /dev/null +++ b/src/src/badguy/flyingsnowball.cpp @@ -0,0 +1,127 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include + +#include "flyingsnowball.hpp" +#include "random_generator.hpp" +#include "object/sprite_particle.hpp" + +static const float FLYTIME = 1.0f; +static const float FLYSPEED = -100.0f; + +namespace { + const float PUFF_PROBABILITY = 0.1f; /**< chanche of puffs being spawned in the current cycle */ + const float PUFF_INTERVAL_MIN = 0.1f; /**< spawn new puff of smoke at most that often */ + const float PUFF_INTERVAL_MAX = 1.1f; /**< spawn new puff of smoke at least that often */ +} + +FlyingSnowBall::FlyingSnowBall(const lisp::Lisp& reader) + : BadGuy(reader, "images/creatures/flying_snowball/flying_snowball.sprite") +{ + physic.enable_gravity(false); +} + +FlyingSnowBall::FlyingSnowBall(const Vector& pos) + : BadGuy(pos, "images/creatures/flying_snowball/flying_snowball.sprite") +{ + physic.enable_gravity(false); +} + +void +FlyingSnowBall::write(lisp::Writer& writer) +{ + writer.start_list("flyingsnowball"); + + writer.write_float("x", start_position.x); + writer.write_float("y", start_position.y); + + writer.end_list("flyingsnowball"); +} + +void +FlyingSnowBall::activate() +{ + sprite->set_action(dir == LEFT ? "left" : "right"); + mode = FLY_UP; + physic.set_velocity_y(FLYSPEED); + timer.start(FLYTIME/2); + puff_timer.start(systemRandom.randf(PUFF_INTERVAL_MIN, PUFF_INTERVAL_MAX)); +} + +bool +FlyingSnowBall::collision_squished(GameObject& object) +{ + sprite->set_action(dir == LEFT ? "squished-left" : "squished-right"); + kill_squished(object); + return true; +} + +void +FlyingSnowBall::collision_solid(const CollisionHit& hit) +{ + if(hit.top || hit.bottom) { + physic.set_velocity_y(0); + } +} + +void +FlyingSnowBall::active_update(float elapsed_time) +{ + if(timer.check()) { + if(mode == FLY_UP) { + mode = FLY_DOWN; + physic.set_velocity_y(-FLYSPEED); + + // stop puffing + puff_timer.stop(); + + } else if(mode == FLY_DOWN) { + mode = FLY_UP; + physic.set_velocity_y(FLYSPEED); + + // roll a dice whether to start puffing + if (systemRandom.randf(0, 1) < PUFF_PROBABILITY) { + puff_timer.start(systemRandom.randf(PUFF_INTERVAL_MIN, PUFF_INTERVAL_MAX)); + } + + } + timer.start(FLYTIME); + } + movement=physic.get_movement(elapsed_time); + + Player* player = this->get_nearest_player(); + if (player) { + dir = (player->get_pos().x > get_pos().x) ? RIGHT : LEFT; + sprite->set_action(dir == LEFT ? "left" : "right"); + } + + // spawn smoke puffs + if (puff_timer.check()) { + Vector ppos = bbox.get_middle(); + Vector pspeed = Vector(systemRandom.randf(-10, 10), 150); + Vector paccel = Vector(0,0); + Sector::current()->add_object(new SpriteParticle("images/objects/particles/smoke.sprite", "default", ppos, ANCHOR_MIDDLE, pspeed, paccel, LAYER_OBJECTS-1)); + puff_timer.start(systemRandom.randf(PUFF_INTERVAL_MIN, PUFF_INTERVAL_MAX)); + } +} + +IMPLEMENT_FACTORY(FlyingSnowBall, "flyingsnowball") diff --git a/src/src/badguy/flyingsnowball.hpp b/src/src/badguy/flyingsnowball.hpp new file mode 100644 index 000000000..43e932494 --- /dev/null +++ b/src/src/badguy/flyingsnowball.hpp @@ -0,0 +1,50 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __FLYINGSNOWBALL_H__ +#define __FLYINGSNOWBALL_H__ + +#include "badguy.hpp" + +class FlyingSnowBall : public BadGuy +{ +public: + FlyingSnowBall(const lisp::Lisp& reader); + FlyingSnowBall(const Vector& pos); + + void activate(); + void write(lisp::Writer& writer); + void active_update(float elapsed_time); + void collision_solid(const CollisionHit& hit); + + virtual FlyingSnowBall* clone() const { return new FlyingSnowBall(*this); } + +protected: + enum FlyingSnowballMode { + FLY_UP, + FLY_DOWN + }; + FlyingSnowballMode mode; + bool collision_squished(GameObject& object); +private: + Timer timer; + Timer puff_timer; /**< time until the next smoke puff is spawned */ +}; + +#endif diff --git a/src/src/badguy/ghosttree.cpp b/src/src/badguy/ghosttree.cpp new file mode 100644 index 000000000..973afa86b --- /dev/null +++ b/src/src/badguy/ghosttree.cpp @@ -0,0 +1,253 @@ +// $Id$ +// +// SuperTux - Boss "GhostTree" +// Copyright (C) 2007 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#include + +#include "ghosttree.hpp" +#include "treewillowisp.hpp" +#include "sprite/sprite_manager.hpp" +#include "root.hpp" +#include "random_generator.hpp" +#include "object/lantern.hpp" + +static const size_t WILLOWISP_COUNT = 10; +static const float ROOT_TOP_OFFSET = 64; +static const float WILLOWISP_TOP_OFFSET = -64; +static const Vector SUCK_TARGET_OFFSET = Vector(-16,-16); +static const float SUCK_TARGET_SPREAD = 8; + +GhostTree::GhostTree(const lisp::Lisp& lisp) + : BadGuy(lisp, "images/creatures/ghosttree/ghosttree.sprite", + LAYER_OBJECTS - 10), mystate(STATE_IDLE), + willo_spawn_y(0), willo_radius(200), willo_speed(1.8f), willo_color(0), + treecolor(0), suck_lantern(0) +{ + glow_sprite.reset(sprite_manager->create("images/creatures/ghosttree/ghosttree-glow.sprite")); +} + +GhostTree::~GhostTree() +{ +} + +void +GhostTree::die() +{ + mystate = STATE_DYING; + sprite->set_action("dying", 1); + glow_sprite->set_action("dying", 1); + + std::vector::iterator iter; + for(iter = willowisps.begin(); iter != willowisps.end(); ++iter) { + TreeWillOWisp *willo = *iter; + willo->vanish(); + } +} + +void +GhostTree::activate() +{ + willowisp_timer.start(1.0f, true); + colorchange_timer.start(13, true); + root_timer.start(5, true); + set_group(COLGROUP_TOUCHABLE); +} + +void +GhostTree::active_update(float elapsed_time) +{ + (void) elapsed_time; + + if (mystate == STATE_IDLE) { + if(colorchange_timer.check()) { + sound_manager->play("sounds/tree_howling.ogg", get_pos()); + suck_timer.start(3); + treecolor = (treecolor + 1) % 3; + + Color col; + switch(treecolor) { + case 0: col = Color(1, 0, 0); break; + case 1: col = Color(0, 1, 0); break; + case 2: col = Color(0, 0, 1); break; + case 3: col = Color(1, 1, 0); break; + case 4: col = Color(1, 0, 1); break; + case 5: col = Color(0, 1, 1); break; + default: assert(false); + } + glow_sprite->set_color(col); + } + + if(suck_timer.check()) { + Color col = glow_sprite->get_color(); + sound_manager->play("sounds/tree_suck.ogg", get_pos()); + std::vector::iterator iter; + for(iter = willowisps.begin(); iter != willowisps.end(); ++iter) { + TreeWillOWisp *willo = *iter; + if(willo->get_color() == col) { + willo->start_sucking(get_bbox().get_middle() + SUCK_TARGET_OFFSET + Vector(systemRandom.randf(-SUCK_TARGET_SPREAD, SUCK_TARGET_SPREAD), systemRandom.randf(-SUCK_TARGET_SPREAD, SUCK_TARGET_SPREAD))); + } + } + mystate = STATE_SUCKING; + } + + if(willowisp_timer.check()) { + if(willowisps.size() < WILLOWISP_COUNT) { + Vector pos = Vector(bbox.get_width() / 2, bbox.get_height() / 2 + willo_spawn_y + WILLOWISP_TOP_OFFSET); + TreeWillOWisp *willowisp + = new TreeWillOWisp(this, pos, 200 + willo_radius, willo_speed); + + Sector::current()->add_object(willowisp); + willowisps.push_back(willowisp); + + willo_spawn_y -= 40; + if(willo_spawn_y < -160) + willo_spawn_y = 0; + + willo_radius += 20; + if(willo_radius > 120) + willo_radius = 0; + + if(willo_speed == 1.8f) { + willo_speed = 1.5f; + } else { + willo_speed = 1.8f; + } + + do { + willo_color = (willo_color + 1) % 3; + } while(willo_color == treecolor); + + switch(willo_color) { + case 0: willowisp->set_color(Color(1, 0, 0)); break; + case 1: willowisp->set_color(Color(0, 1, 0)); break; + case 2: willowisp->set_color(Color(0, 0, 1)); break; + case 3: willowisp->set_color(Color(1, 1, 0)); break; + case 4: willowisp->set_color(Color(1, 0, 1)); break; + case 5: willowisp->set_color(Color(0, 1, 1)); break; + default: assert(false); + } + } + } + + if(root_timer.check()) { + /* TODO indicate root with an animation */ + Player* player = get_nearest_player(); + Root* root = new Root(Vector(player->get_bbox().get_left(), get_bbox().get_bottom()+ROOT_TOP_OFFSET)); + Sector::current()->add_object(root); + } + } else if (mystate == STATE_SWALLOWING) { + if (suck_lantern) { + // suck in lantern + assert (suck_lantern); + Vector pos = suck_lantern->get_pos(); + Vector delta = get_bbox().get_middle() + SUCK_TARGET_OFFSET - pos; + Vector dir = delta.unit(); + if (delta.norm() < 1) { + dir = delta; + suck_lantern->ungrab(*this, RIGHT); + suck_lantern->remove_me(); + suck_lantern = 0; + sprite->set_action("swallow", 1); + } else { + pos += dir; + suck_lantern->grab(*this, pos, RIGHT); + } + } else { + // wait until lantern is swallowed + if (sprite->animation_done()) { + if (is_color_deadly(suck_lantern_color)) { + die(); + } else { + sprite->set_action("default"); + mystate = STATE_IDLE; + spawn_lantern(); + } + } + } + } +} + +bool +GhostTree::is_color_deadly(Color color) const { + if (color == Color(0,0,0)) return false; + Color my_color = glow_sprite->get_color(); + return ((my_color.red != color.red) || (my_color.green != color.green) || (my_color.blue != color.blue)); +} + +void +GhostTree::willowisp_died(TreeWillOWisp *willowisp) +{ + if ((mystate == STATE_SUCKING) && (willowisp->was_sucked)) { + mystate = STATE_IDLE; + } + willowisps.erase(std::find(willowisps.begin(), willowisps.end(), willowisp)); +} + +void +GhostTree::draw(DrawingContext& context) +{ + BadGuy::draw(context); + + context.push_target(); + context.push_transform(); + context.set_target(DrawingContext::LIGHTMAP); + if (mystate == STATE_SUCKING) { + context.set_alpha(0.5 + fmodf(game_time, 0.5)); + } else { + context.set_alpha(0.5); + } + glow_sprite->draw(context, get_pos(), layer); + context.pop_transform(); + context.pop_target(); +} + +bool +GhostTree::collides(GameObject& other, const CollisionHit& ) { + if (mystate != STATE_SUCKING) return false; + if (dynamic_cast(&other)) return true; + if (dynamic_cast(&other)) return true; + return false; +} + +HitResponse +GhostTree::collision(GameObject& other, const CollisionHit& ) { + if(mystate != STATE_SUCKING) return ABORT_MOVE; + + Player* player = dynamic_cast(&other); + if (player) { + player->kill(false); + } + + Lantern* lantern = dynamic_cast(&other); + if (lantern) { + suck_lantern = lantern; + suck_lantern->grab(*this, suck_lantern->get_pos(), RIGHT); + suck_lantern_color = lantern->get_color(); + mystate = STATE_SWALLOWING; + } + + return ABORT_MOVE; +} + +void +GhostTree::spawn_lantern() { + Lantern* lantern = new Lantern(get_bbox().get_middle() + SUCK_TARGET_OFFSET); + Sector::current()->add_object(lantern); +} + +IMPLEMENT_FACTORY(GhostTree, "ghosttree"); + diff --git a/src/src/badguy/ghosttree.hpp b/src/src/badguy/ghosttree.hpp new file mode 100644 index 000000000..7e57d5c19 --- /dev/null +++ b/src/src/badguy/ghosttree.hpp @@ -0,0 +1,75 @@ +// $Id$ +// +// SuperTux - Boss "GhostTree" +// Copyright (C) 2007 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#ifndef __GHOSTTREE_H__ +#define __GHOSTTREE_H__ + +#include +#include "badguy.hpp" + +class TreeWillOWisp; +class Lantern; + +class GhostTree : public BadGuy +{ +public: + GhostTree(const lisp::Lisp& lisp); + ~GhostTree(); + + virtual bool is_flammable() const { return false; } + virtual bool is_freezable() const { return false; } + virtual void kill_fall() { } + + void activate(); + void active_update(float elapsed_time); + void willowisp_died(TreeWillOWisp* willowisp); + virtual void draw(DrawingContext& context); + + virtual bool collides(GameObject& other, const CollisionHit& hit); + virtual HitResponse collision(GameObject& other, const CollisionHit& hit); + + void die(); + +private: + enum MyState { + STATE_IDLE, STATE_SUCKING, STATE_SWALLOWING, STATE_DYING + }; + MyState mystate; + Timer willowisp_timer; + float willo_spawn_y; + float willo_radius; + float willo_speed; + int willo_color; + + std::auto_ptr glow_sprite; + Timer colorchange_timer; + Timer suck_timer; + Timer root_timer; + int treecolor; + Color suck_lantern_color; + + Lantern* suck_lantern; /**< Lantern that is currently being sucked in */ + + std::vector willowisps; + + bool is_color_deadly(Color color) const; + void spawn_lantern(); +}; + +#endif + diff --git a/src/src/badguy/igel.cpp b/src/src/badguy/igel.cpp new file mode 100644 index 000000000..b8450d226 --- /dev/null +++ b/src/src/badguy/igel.cpp @@ -0,0 +1,127 @@ +// $Id$ +// +// SuperTux - Badguy "Igel" +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "igel.hpp" +#include "object/block.hpp" +#include "sector.hpp" +#include "object/bullet.hpp" + +namespace { + const float WALKSPEED = 80; /**< speed at which we walk around */ + const float TURN_RECOVER_TIME = 0.5; /**< seconds before we will again turn around when shot at */ + const float RANGE_OF_VISION = 256; /**< range in px at which we can see bullets */ +} + +Igel::Igel(const lisp::Lisp& reader) + : WalkingBadguy(reader, "images/creatures/igel/igel.sprite", "walking-left", "walking-right") +{ + walk_speed = WALKSPEED; + max_drop_height = 16; +} + +Igel::Igel(const Vector& pos, Direction d) + : WalkingBadguy(pos, d, "images/creatures/igel/igel.sprite", "walking-left", "walking-right") +{ + walk_speed = WALKSPEED; + max_drop_height = 16; +} + +void +Igel::write(lisp::Writer& writer) +{ + writer.start_list("igel"); + WalkingBadguy::write(writer); + writer.end_list("igel"); +} + +void +Igel::be_normal() +{ + activate(); +} + +void +Igel::turn_around() +{ + WalkingBadguy::turn_around(); + turn_recover_timer.start(TURN_RECOVER_TIME); +} + +bool +Igel::can_see(const MovingObject& o) +{ + Rect mb = get_bbox(); + Rect ob = o.get_bbox(); + + bool inReach_left = ((ob.p2.x < mb.p1.x) && (ob.p2.x >= mb.p1.x-((dir == LEFT) ? RANGE_OF_VISION : 0))); + bool inReach_right = ((ob.p1.x > mb.p2.x) && (ob.p1.x <= mb.p2.x+((dir == RIGHT) ? RANGE_OF_VISION : 0))); + bool inReach_top = (ob.p2.y >= mb.p1.y); + bool inReach_bottom = (ob.p1.y <= mb.p2.y); + + return ((inReach_left || inReach_right) && inReach_top && inReach_bottom); +} + +void +Igel::active_update(float elapsed_time) +{ + bool wants_to_flee = false; + + // check if we see a fire bullet + Sector* sector = Sector::current(); + for (Sector::GameObjects::iterator i = sector->gameobjects.begin(); i != sector->gameobjects.end(); ++i) { + Bullet* bullet = dynamic_cast(*i); + if (!bullet) continue; + if (bullet->get_type() != FIRE_BONUS) continue; + if (can_see(*bullet)) wants_to_flee = true; + } + + // if we flee, handle this ourselves + if (wants_to_flee && (!turn_recover_timer.started())) { + turn_around(); + BadGuy::active_update(elapsed_time); + return; + } + + // else adhere to default behaviour + WalkingBadguy::active_update(elapsed_time); +} + +HitResponse +Igel::collision_bullet(Bullet& bullet, const CollisionHit& hit) +{ + // default reaction if hit on front side + if (((dir == LEFT) && hit.left) || ((dir == RIGHT) && hit.right)) { + return BadGuy::collision_bullet(bullet, hit); + } + + // else make bullet ricochet and ignore the hit + bullet.ricochet(*this, hit); + return FORCE_MOVE; +} + +bool +Igel::collision_squished(GameObject& ) +{ + // this will hurt + return false; +} + +IMPLEMENT_FACTORY(Igel, "igel") diff --git a/src/src/badguy/igel.hpp b/src/src/badguy/igel.hpp new file mode 100644 index 000000000..4d2b82f79 --- /dev/null +++ b/src/src/badguy/igel.hpp @@ -0,0 +1,53 @@ +// $Id$ +// +// SuperTux - Badguy "Igel" +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __IGEL_H__ +#define __IGEL_H__ + +#include "walking_badguy.hpp" +#include "moving_object.hpp" + +/** + * Badguy "Igel" - a hedgehog that can absorb bullets + */ +class Igel : public WalkingBadguy +{ +public: + Igel(const lisp::Lisp& reader); + Igel(const Vector& pos, Direction d); + + void write(lisp::Writer& writer); + HitResponse collision_bullet(Bullet& bullet, const CollisionHit& hit); + + void active_update(float elapsed_time); + + virtual Igel* clone() const { return new Igel(*this); } + +protected: + bool collision_squished(GameObject& object); + void be_normal(); /**< switch to state STATE_NORMAL */ + void turn_around(); /**< reverse direction, assumes we are in STATE_NORMAL */ + bool can_see(const MovingObject& o); /**< check if we can see o */ + +private: + Timer turn_recover_timer; /**< wait time until we will turn around again when shot at */ + +}; + +#endif diff --git a/src/src/badguy/jumpy.cpp b/src/src/badguy/jumpy.cpp new file mode 100644 index 000000000..706a7d7bb --- /dev/null +++ b/src/src/badguy/jumpy.cpp @@ -0,0 +1,119 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "jumpy.hpp" + +static const float JUMPSPEED=-600; +static const float JUMPY_MID_TOLERANCE=4; +static const float JUMPY_LOW_TOLERANCE=2; + +Jumpy::Jumpy(const lisp::Lisp& reader) + : BadGuy(reader, "images/creatures/jumpy/jumpy.sprite"), groundhit_pos_set(false) +{ +} + +void +Jumpy::write(lisp::Writer& writer) +{ + writer.start_list("jumpy"); + + writer.write_float("x", start_position.x); + writer.write_float("y", start_position.y); + + writer.end_list("jumpy"); +} + +void +Jumpy::collision_solid(const CollisionHit& chit) +{ + hit(chit); +} + +HitResponse +Jumpy::collision_badguy(BadGuy& , const CollisionHit& chit) +{ + return hit(chit); +} + +HitResponse +Jumpy::hit(const CollisionHit& chit) +{ + if(chit.bottom) { + if (!groundhit_pos_set) + { + pos_groundhit = get_pos(); + groundhit_pos_set = true; + } + + physic.set_velocity_y(frozen ? 0 : JUMPSPEED); + // TODO create a nice sound for this... + //sound_manager->play("sounds/skid.wav"); + } else if(chit.top) { + physic.set_velocity_y(0); + } + + return CONTINUE; +} + +void +Jumpy::active_update(float elapsed_time) +{ + BadGuy::active_update(elapsed_time); + + if(frozen) + return; + + Player* player = this->get_nearest_player(); + if (player) + { + dir = (player->get_pos().x > get_pos().x) ? RIGHT : LEFT; + } + + if (!groundhit_pos_set) + { + sprite->set_action(dir == LEFT ? "left-middle" : "right-middle"); + return; + } + + if ( get_pos().y < (pos_groundhit.y - JUMPY_MID_TOLERANCE ) ) + sprite->set_action(dir == LEFT ? "left-up" : "right-up"); + else if ( get_pos().y >= (pos_groundhit.y - JUMPY_MID_TOLERANCE) && + get_pos().y < (pos_groundhit.y - JUMPY_LOW_TOLERANCE) ) + sprite->set_action(dir == LEFT ? "left-middle" : "right-middle"); + else + sprite->set_action(dir == LEFT ? "left-down" : "right-down"); +} + +void +Jumpy::freeze() +{ + BadGuy::freeze(); + physic.set_velocity_y(std::max(0.0f, physic.get_velocity_y())); + sprite->set_action(dir == LEFT ? "left-iced" : "right-iced"); +} + +bool +Jumpy::is_freezable() const +{ + return true; +} + +IMPLEMENT_FACTORY(Jumpy, "jumpy") diff --git a/src/src/badguy/jumpy.hpp b/src/src/badguy/jumpy.hpp new file mode 100644 index 000000000..b0f9e3ebd --- /dev/null +++ b/src/src/badguy/jumpy.hpp @@ -0,0 +1,47 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __JUMPY_H__ +#define __JUMPY_H__ + +#include "badguy.hpp" + +class Jumpy : public BadGuy +{ +public: + Jumpy(const lisp::Lisp& reader); + + void collision_solid(const CollisionHit& hit); + HitResponse collision_badguy(BadGuy& other, const CollisionHit& hit); + + void write(lisp::Writer& writer); + void active_update(float); + + void freeze(); + bool is_freezable() const; + + virtual Jumpy* clone() const { return new Jumpy(*this); } + +private: + HitResponse hit(const CollisionHit& hit); + Vector pos_groundhit; + bool groundhit_pos_set; +}; + +#endif diff --git a/src/src/badguy/kugelblitz.cpp b/src/src/badguy/kugelblitz.cpp new file mode 100644 index 000000000..cbfdd9986 --- /dev/null +++ b/src/src/badguy/kugelblitz.cpp @@ -0,0 +1,216 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "kugelblitz.hpp" +#include "object/tilemap.hpp" +#include "object/camera.hpp" +#include "tile.hpp" +#include "random_generator.hpp" + +#define LIFETIME 5 +#define MOVETIME 0.75 +#define BASE_SPEED 200 +#define RAND_SPEED 150 + +static const float X_OFFSCREEN_DISTANCE = 1600; +static const float Y_OFFSCREEN_DISTANCE = 1200; + +Kugelblitz::Kugelblitz(const lisp::Lisp& reader) + : BadGuy(Vector(0,0), "images/creatures/kugelblitz/kugelblitz.sprite"), groundhit_pos_set(false) +{ + reader.get("x", start_position.x); + sprite->set_action("falling"); + physic.enable_gravity(false); +} + +void +Kugelblitz::write(lisp::Writer& writer) +{ + writer.start_list("kugelblitz"); + + writer.write_float("x", start_position.x); + + writer.end_list("kugelblitz"); +} + +void +Kugelblitz::activate() +{ + physic.set_velocity_y(300); + physic.set_velocity_x(-20); //fall a little to the left + direction = 1; + dying = false; +} + +void +Kugelblitz::collision_solid(const CollisionHit& chit) +{ + hit(chit); +} + +HitResponse +Kugelblitz::collision_player(Player& player, const CollisionHit& ) +{ + if(player.is_invincible()) { + explode(); + return ABORT_MOVE; + } + // hit from above? + if(player.get_movement().y - get_movement().y > 0 && player.get_bbox().p2.y < + (get_bbox().p1.y + get_bbox().p2.y) / 2) { + // if it's not is it possible to squish us, then this will hurt + if(!collision_squished(player)) + player.kill(false); + explode(); + return FORCE_MOVE; + } + player.kill(false); + explode(); + return FORCE_MOVE; +} + +HitResponse +Kugelblitz::collision_badguy(BadGuy& other , const CollisionHit& chit) +{ + //Let the Kugelblitz explode, too? The problem with that is that + //two Kugelblitzes would cancel each other out on contact... + other.kill_fall(); + return hit(chit); +} + +HitResponse +Kugelblitz::hit(const CollisionHit& hit) +{ + // hit floor? + if(hit.bottom) { + if (!groundhit_pos_set) + { + pos_groundhit = get_pos(); + groundhit_pos_set = true; + } + sprite->set_action("flying"); + physic.set_velocity_y(0); + //Set random initial speed and direction + direction = systemRandom.rand(2)? 1: -1; + int speed = (BASE_SPEED + (systemRandom.rand(RAND_SPEED))) * direction; + physic.set_velocity_x(speed); + movement_timer.start(MOVETIME); + lifetime.start(LIFETIME); + + } else if(hit.top) { // bumped on roof + physic.set_velocity_y(0); + } + + return CONTINUE; +} + +void +Kugelblitz::active_update(float elapsed_time) +{ + if (lifetime.check()) { + explode(); + } + else { + if (groundhit_pos_set) { + if (movement_timer.check()) { + if (direction == 1) direction = -1; else direction = 1; + int speed = (BASE_SPEED + (systemRandom.rand(RAND_SPEED))) * direction; + physic.set_velocity_x(speed); + movement_timer.start(MOVETIME); + } + } + /* + if (Sector::current()->solids->get_tile_at(get_pos())->getAttributes() == 16) { + //HIT WATER + Sector::current()->add_object(new Electrifier(75,1421,1.5)); + Sector::current()->add_object(new Electrifier(76,1422,1.5)); + explode(); + } + if (Sector::current()->solids->get_tile_at(get_pos())->getAttributes() == 48) { + //HIT ELECTRIFIED WATER + explode(); + } + */ + } + BadGuy::active_update(elapsed_time); +} + +void +Kugelblitz::kill_fall() +{ +} + +void +Kugelblitz::explode() +{ + if (!dying) { + sprite->set_action("pop"); + lifetime.start(0.2f); + dying = true; + } + else remove_me(); +} + +void +Kugelblitz::try_activate() +{ + //FIXME: Don't activate Kugelblitz before it's on-screen + float scroll_x = Sector::current()->camera->get_translation().x; + float scroll_y = Sector::current()->camera->get_translation().y; + + /* Activate badguys if they're just around the screen to avoid + * the effect of having badguys suddenly popping up from nowhere. + */ + if (start_position.x > scroll_x - X_OFFSCREEN_DISTANCE && + start_position.x < scroll_x - bbox.get_width() && + start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE && + start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) { + dir = RIGHT; + set_state(STATE_ACTIVE); + activate(); + } else if (start_position.x > scroll_x && + start_position.x < scroll_x + X_OFFSCREEN_DISTANCE && + start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE && + start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) { + dir = LEFT; + set_state(STATE_ACTIVE); + activate(); + } else if (start_position.x > scroll_x - X_OFFSCREEN_DISTANCE && + start_position.x < scroll_x + X_OFFSCREEN_DISTANCE && + ((start_position.y > scroll_y && + start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) || + (start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE && + start_position.y < scroll_y))) { + dir = start_position.x < scroll_x ? RIGHT : LEFT; + set_state(STATE_ACTIVE); + activate(); + } else if(state == STATE_INIT + && start_position.x > scroll_x - X_OFFSCREEN_DISTANCE + && start_position.x < scroll_x + X_OFFSCREEN_DISTANCE + && start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE + && start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) { + dir = LEFT; + set_state(STATE_ACTIVE); + activate(); + } +} + +IMPLEMENT_FACTORY(Kugelblitz, "kugelblitz") diff --git a/src/src/badguy/kugelblitz.hpp b/src/src/badguy/kugelblitz.hpp new file mode 100644 index 000000000..5f327f29e --- /dev/null +++ b/src/src/badguy/kugelblitz.hpp @@ -0,0 +1,56 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __KUGELBLITZ_H__ +#define __KUGELBLITZ_H__ + +#include "badguy.hpp" +#include "timer.hpp" +#include "object/electrifier.hpp" + +class Kugelblitz : public BadGuy +{ +public: + Kugelblitz(const lisp::Lisp& reader); + + void activate(); + HitResponse collision_badguy(BadGuy& other, const CollisionHit& hit); + void collision_solid(const CollisionHit& hit); + HitResponse collision_player(Player& player, const CollisionHit& hit); + + void write(lisp::Writer& writer); + void active_update(float); + void kill_fall(); + void explode(); + + virtual Kugelblitz* clone() const { return new Kugelblitz(*this); } + +private: + void try_activate(); + HitResponse hit(const CollisionHit& hit); + Vector pos_groundhit; + bool groundhit_pos_set; + bool dying; + Timer movement_timer; + Timer lifetime; + int direction; + State state; +}; + +#endif diff --git a/src/src/badguy/mole.cpp b/src/src/badguy/mole.cpp new file mode 100644 index 000000000..513274352 --- /dev/null +++ b/src/src/badguy/mole.cpp @@ -0,0 +1,168 @@ +// $Id$ +// +// SuperTux - Mole Badguy +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "mole.hpp" +#include "mole_rock.hpp" +#include "tile.hpp" +#include "object/tilemap.hpp" +#include "random_generator.hpp" +#include "log.hpp" +#include "level.hpp" + +static const float IDLE_TIME = 0.2f; /**< time to wait before and after throwing */ +static const float THROW_TIME = 4.6f; /**< time to spend throwing */ +static const float THROW_INTERVAL = 1; /**< time between two thrown rocks */ +static const float THROW_VELOCITY = 400; /**< initial velocity of thrown rocks */ + +Mole::Mole(const lisp::Lisp& reader) + : BadGuy(reader, "images/creatures/mole/mole.sprite", LAYER_TILES-1), state(PRE_THROWING) +{ + physic.enable_gravity(false); +} + +Mole::Mole(const Vector& pos) + : BadGuy(pos, "images/creatures/mole/mole.sprite", LAYER_TILES-1), state(PRE_THROWING) +{ + physic.enable_gravity(false); +} + +void +Mole::write(lisp::Writer& writer) +{ + writer.start_list("mole"); + writer.write_float("x", start_position.x); + writer.write_float("y", start_position.y); + writer.end_list("mole"); +} + +void +Mole::activate() +{ + if (state != DEAD) set_state(PRE_THROWING); +} + +void +Mole::kill_fall() +{ + set_state(DEAD); + sound_manager->play("sounds/fall.wav", get_pos()); + if (countMe) Sector::current()->get_level()->stats.badguys++; +} + +HitResponse +Mole::collision_badguy(BadGuy& , const CollisionHit& ) +{ + return FORCE_MOVE; +} + +bool +Mole::collision_squished(GameObject& ) +{ + set_state(DEAD); + sound_manager->play("sounds/squish.wav", get_pos()); + if (countMe) Sector::current()->get_level()->stats.badguys++; + return true; +} + +void +Mole::throw_rock() +{ + float px = get_bbox().get_middle().x; + float py = get_bbox().get_middle().y; + + float angle = systemRandom.rand(90 - 15, 90 + 15) * (M_PI / 180); + float vx = cos(angle) * THROW_VELOCITY; + float vy = -sin(angle) * THROW_VELOCITY; + + sound_manager->play("sounds/dartfire.wav", get_pos()); + Sector::current()->add_object(new MoleRock(Vector(px, py), Vector(vx, vy), this)); +} + +void +Mole::active_update(float elapsed_time) +{ + BadGuy::active_update(elapsed_time); + + switch (state) { + case PRE_THROWING: + if (timer.check()) { + set_state(THROWING); + } + break; + case THROWING: + if (throw_timer.check()) { + throw_rock(); + throw_timer.start(THROW_INTERVAL); + } + if (timer.check()) { + set_state(POST_THROWING); + } + break; + case POST_THROWING: + if (timer.check()) { + set_state(PEEKING); + } + break; + case PEEKING: + if (sprite->animation_done()) { + set_state(PRE_THROWING); + } + break; + case DEAD: + break; + } + +} + +void +Mole::set_state(MoleState new_state) +{ + switch (new_state) { + case PRE_THROWING: + sprite->set_action("idle"); + set_group(COLGROUP_DISABLED); + timer.start(IDLE_TIME); + break; + case THROWING: + sprite->set_action("idle"); + set_group(COLGROUP_DISABLED); + timer.start(THROW_TIME); + throw_timer.start(THROW_INTERVAL); + break; + case POST_THROWING: + sprite->set_action("idle"); + set_group(COLGROUP_DISABLED); + timer.start(IDLE_TIME); + break; + case PEEKING: + sprite->set_action("peeking", 1); + set_group(COLGROUP_STATIC); + break; + case DEAD: + sprite->set_action("idle"); + set_group(COLGROUP_DISABLED); + break; + } + + state = new_state; +} + +IMPLEMENT_FACTORY(Mole, "mole") diff --git a/src/src/badguy/mole.hpp b/src/src/badguy/mole.hpp new file mode 100644 index 000000000..2a1fa0f39 --- /dev/null +++ b/src/src/badguy/mole.hpp @@ -0,0 +1,59 @@ +// $Id$ +// +// SuperTux - Mole Badguy +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __MOLE_H__ +#define __MOLE_H__ + +#include "badguy.hpp" + +class Mole : public BadGuy +{ +public: + Mole(const lisp::Lisp& ); + Mole(const Vector& pos); + + void kill_fall(); + HitResponse collision_badguy(BadGuy& , const CollisionHit& ); + bool collision_squished(GameObject& object); + + void activate(); + void write(lisp::Writer& ); + void active_update(float); + + virtual Mole* clone() const { return new Mole(*this); } + +private: + enum MoleState { + PRE_THROWING, + THROWING, + POST_THROWING, + PEEKING, + DEAD + }; + + MoleState state; + Timer timer; + Timer throw_timer; + + void set_state(MoleState new_state); + void throw_rock(); + +}; + +#endif diff --git a/src/src/badguy/mole_rock.cpp b/src/src/badguy/mole_rock.cpp new file mode 100644 index 000000000..c9252ff05 --- /dev/null +++ b/src/src/badguy/mole_rock.cpp @@ -0,0 +1,114 @@ +// $Id$ +// +// MoleRock - Rock thrown by "Mole" Badguy +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#include + +#include "mole_rock.hpp" + +MoleRock::MoleRock(const lisp::Lisp& reader) + : BadGuy(reader, "images/creatures/mole/mole_rock.sprite", LAYER_TILES - 2), parent(0), initial_velocity(Vector(0, -400)) +{ + physic.enable_gravity(true); + countMe = false; +} + +MoleRock::MoleRock(const Vector& pos, const Vector& velocity, const BadGuy* parent = 0) + : BadGuy(pos, LEFT, "images/creatures/mole/mole_rock.sprite", LAYER_TILES - 2), parent(parent), initial_velocity(velocity) +{ + physic.enable_gravity(true); + countMe = false; +} + +MoleRock::MoleRock(const MoleRock& other) + : BadGuy(other), parent(other.parent), initial_velocity(Vector(0, -400)) +{ +} + +MoleRock::~MoleRock() +{ +} + +bool +MoleRock::updatePointers(const GameObject* from_object, GameObject* to_object) +{ + if (from_object == parent) { + parent = dynamic_cast(to_object); + return true; + } + return false; +} + +void +MoleRock::write(lisp::Writer& writer) +{ + writer.start_list("mole_rock"); + writer.write_float("x", start_position.x); + writer.write_float("y", start_position.y); + writer.end_list("mole_rock"); +} + +void +MoleRock::activate() +{ + physic.set_velocity(initial_velocity); + sprite->set_action("default"); +} + +void +MoleRock::deactivate() +{ + remove_me(); +} + +void +MoleRock::active_update(float elapsed_time) +{ + BadGuy::active_update(elapsed_time); +} + +void +MoleRock::collision_solid(const CollisionHit& ) +{ + sound_manager->play("sounds/darthit.wav", get_pos()); + remove_me(); +} + +HitResponse +MoleRock::collision_badguy(BadGuy& badguy, const CollisionHit& ) +{ + // ignore collisions with parent + if (&badguy == parent) { + return FORCE_MOVE; + } + sound_manager->play("sounds/stomp.wav", get_pos()); + remove_me(); + badguy.kill_fall(); + return ABORT_MOVE; +} + +HitResponse +MoleRock::collision_player(Player& player, const CollisionHit& hit) +{ + sound_manager->play("sounds/stomp.wav", get_pos()); + remove_me(); + return BadGuy::collision_player(player, hit); +} + +IMPLEMENT_FACTORY(MoleRock, "mole_rock") diff --git a/src/src/badguy/mole_rock.hpp b/src/src/badguy/mole_rock.hpp new file mode 100644 index 000000000..42a7e05e4 --- /dev/null +++ b/src/src/badguy/mole_rock.hpp @@ -0,0 +1,56 @@ +// $Id$ +// +// MoleRock - Rock thrown by "Mole" Badguy +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#ifndef __MOLE_ROCK_H__ +#define __MOLE_ROCK_H__ + +#include "badguy.hpp" + +/** + * Badguy "MoleRock" - Rock thrown by "Mole" Badguy + */ +class MoleRock : public BadGuy +{ +public: + MoleRock(const lisp::Lisp& reader); + MoleRock(const Vector& pos, const Vector& velocity, const BadGuy* parent); + MoleRock(const MoleRock& mole_rock); + ~MoleRock(); + + void activate(); + void deactivate(); + void write(lisp::Writer& writer); + + void active_update(float elapsed_time); + + void collision_solid(const CollisionHit& hit); + HitResponse collision_badguy(BadGuy& badguy, const CollisionHit& hit); + HitResponse collision_player(Player& player, const CollisionHit& hit); + + virtual MoleRock* clone() const { return new MoleRock(*this); } + + virtual bool updatePointers(const GameObject* from_object, GameObject* to_object); + +protected: + const BadGuy* parent; /**< collisions with this BadGuy will be ignored */ + const Vector initial_velocity; /**< velocity at time of creation */ +}; + +#endif diff --git a/src/src/badguy/mrbomb.cpp b/src/src/badguy/mrbomb.cpp new file mode 100644 index 000000000..93a58c2f2 --- /dev/null +++ b/src/src/badguy/mrbomb.cpp @@ -0,0 +1,149 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "mrbomb.hpp" +#include "bomb.hpp" +#include "object/explosion.hpp" +#include "sprite/sprite_manager.hpp" +#include "audio/sound_manager.hpp" + +MrBomb::MrBomb(const lisp::Lisp& reader) + : WalkingBadguy(reader, "images/creatures/mr_bomb/mr_bomb.sprite", "left", "right") +{ + walk_speed = 80; + max_drop_height = 16; + grabbed = false; + + //Prevent stutter when Tux jumps on Mr Bomb + sound_manager->preload("sounds/explosion.wav"); + + //Check if we need another sprite + if( !reader.get( "sprite", sprite_name ) ){ + return; + } + if( sprite_name == "" ){ + sprite_name = "images/creatures/mr_bomb/mr_bomb.sprite"; + return; + } + //Replace sprite + sprite = sprite_manager->create( sprite_name ); +} + +/* MrBomb created by a despencer always gets default sprite atm.*/ +MrBomb::MrBomb(const Vector& pos, Direction d) + : WalkingBadguy(pos, d, "images/creatures/mr_bomb/mr_bomb.sprite", "left", "right") +{ + walk_speed = 80; + max_drop_height = 16; + grabbed = false; + sound_manager->preload("sounds/explosion.wav"); +} + +void +MrBomb::write(lisp::Writer& writer) +{ + writer.start_list("mrbomb"); + WalkingBadguy::write(writer); + writer.end_list("mrbomb"); +} + +HitResponse +MrBomb::collision(GameObject& object, const CollisionHit& hit) +{ + if(grabbed) + return FORCE_MOVE; + return WalkingBadguy::collision(object, hit); +} + +HitResponse +MrBomb::collision_player(Player& player, const CollisionHit& hit) +{ + if(grabbed) + return FORCE_MOVE; + return WalkingBadguy::collision_player(player, hit); +} + +bool +MrBomb::collision_squished(GameObject& object) +{ + remove_me(); + Sector::current()->add_object(new Bomb(get_pos(), dir, sprite_name )); + kill_squished(object); + return true; +} + +void +MrBomb::active_update(float elapsed_time) +{ + if(grabbed) + return; + WalkingBadguy::active_update(elapsed_time); +} + +void +MrBomb::kill_fall() +{ + remove_me(); + Explosion* explosion = new Explosion(get_bbox().get_middle()); + Sector::current()->add_object(explosion); + + run_dead_script(); +} + +void +MrBomb::grab(MovingObject&, const Vector& pos, Direction dir) +{ + assert(frozen); + movement = pos - get_pos(); + this->dir = dir; + sprite->set_action(dir == LEFT ? "iced-left" : "iced-right"); + set_group(COLGROUP_DISABLED); + grabbed = true; +} + +void +MrBomb::ungrab(MovingObject& , Direction dir) +{ + this->dir = dir; + set_group(COLGROUP_MOVING); + grabbed = false; +} + +void +MrBomb::freeze() +{ + WalkingBadguy::freeze(); + sprite->set_action(dir == LEFT ? "iced-left" : "iced-right"); +} + +bool +MrBomb::is_freezable() const +{ + return true; +} + +bool +MrBomb::is_portable() const +{ + return frozen; +} + +IMPLEMENT_FACTORY(MrBomb, "mrbomb") diff --git a/src/src/badguy/mrbomb.hpp b/src/src/badguy/mrbomb.hpp new file mode 100644 index 000000000..14f326dec --- /dev/null +++ b/src/src/badguy/mrbomb.hpp @@ -0,0 +1,55 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __MRBOMB_H__ +#define __MRBOMB_H__ + +#include "walking_badguy.hpp" +#include "object/portable.hpp" + +class MrBomb : public WalkingBadguy, public Portable +{ +public: + MrBomb(const lisp::Lisp& reader); + MrBomb(const Vector& pos, Direction d); + + void write(lisp::Writer& writer); + void kill_fall(); + HitResponse collision(GameObject& object, const CollisionHit& hit); + HitResponse collision_player(Player& player, const CollisionHit& hit); + + void active_update(float elapsed_time); + + void grab(MovingObject& object, const Vector& pos, Direction dir); + void ungrab(MovingObject& object, Direction dir); + bool is_portable() const; + + void freeze(); + bool is_freezable() const; + + virtual MrBomb* clone() const { return new MrBomb(*this); } + +protected: + bool collision_squished(GameObject& object); + +private: + bool grabbed; +}; + +#endif diff --git a/src/src/badguy/mriceblock.cpp b/src/src/badguy/mriceblock.cpp new file mode 100644 index 000000000..cb801b300 --- /dev/null +++ b/src/src/badguy/mriceblock.cpp @@ -0,0 +1,279 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "mriceblock.hpp" +#include "object/block.hpp" + +namespace { + const float KICKSPEED = 500; + const int MAXSQUISHES = 10; +} + +MrIceBlock::MrIceBlock(const lisp::Lisp& reader) + : WalkingBadguy(reader, "images/creatures/mr_iceblock/mr_iceblock.sprite", "left", "right"), ice_state(ICESTATE_NORMAL), squishcount(0) +{ + walk_speed = 80; + max_drop_height = 600; + sound_manager->preload("sounds/iceblock_bump.wav"); + sound_manager->preload("sounds/stomp.wav"); + sound_manager->preload("sounds/kick.wav"); +} + +MrIceBlock::MrIceBlock(const Vector& pos, Direction d) + : WalkingBadguy(pos, d, "images/creatures/mr_iceblock/mr_iceblock.sprite", "left", "right"), ice_state(ICESTATE_NORMAL), squishcount(0) +{ + walk_speed = 80; + max_drop_height = 600; + sound_manager->preload("sounds/iceblock_bump.wav"); + sound_manager->preload("sounds/stomp.wav"); + sound_manager->preload("sounds/kick.wav"); +} + +void +MrIceBlock::write(lisp::Writer& writer) +{ + writer.start_list("mriceblock"); + WalkingBadguy::write(writer); + writer.end_list("mriceblock"); +} + +void +MrIceBlock::activate() +{ + WalkingBadguy::activate(); + set_state(ICESTATE_NORMAL); +} + +void +MrIceBlock::active_update(float elapsed_time) +{ + if(ice_state == ICESTATE_GRABBED) + return; + + if(ice_state == ICESTATE_FLAT && flat_timer.check()) { + set_state(ICESTATE_NORMAL); + } + + if (ice_state == ICESTATE_NORMAL) + { + WalkingBadguy::active_update(elapsed_time); + return; + } + + BadGuy::active_update(elapsed_time); +} + +bool +MrIceBlock::can_break(){ + return ice_state == ICESTATE_KICKED; +} + +void +MrIceBlock::collision_solid(const CollisionHit& hit) +{ + update_on_ground_flag(hit); + + if(hit.top || hit.bottom) { // floor or roof + physic.set_velocity_y(0); + return; + } + + // hit left or right + switch(ice_state) { + case ICESTATE_NORMAL: + WalkingBadguy::collision_solid(hit); + break; + case ICESTATE_KICKED: { + if(hit.right && dir == RIGHT) { + dir = LEFT; + sound_manager->play("sounds/iceblock_bump.wav", get_pos()); + physic.set_velocity_x(-KICKSPEED); + } else if(hit.left && dir == LEFT) { + dir = RIGHT; + sound_manager->play("sounds/iceblock_bump.wav", get_pos()); + physic.set_velocity_x(KICKSPEED); + } + sprite->set_action(dir == LEFT ? "flat-left" : "flat-right"); + break; + } + case ICESTATE_FLAT: + physic.set_velocity_x(0); + break; + case ICESTATE_GRABBED: + break; + } +} + +HitResponse +MrIceBlock::collision(GameObject& object, const CollisionHit& hit) +{ + if(ice_state == ICESTATE_GRABBED) + return FORCE_MOVE; + + return BadGuy::collision(object, hit); +} + +HitResponse +MrIceBlock::collision_player(Player& player, const CollisionHit& hit) +{ + if(ice_state == ICESTATE_GRABBED) + return FORCE_MOVE; + + if(dir == UP) { + return FORCE_MOVE; + } + + // handle kicks from left or right side + if(ice_state == ICESTATE_FLAT && get_state() == STATE_ACTIVE) { + if(hit.left) { + dir = RIGHT; + player.kick(); + set_state(ICESTATE_KICKED); + return FORCE_MOVE; + } else if(hit.right) { + dir = LEFT; + player.kick(); + set_state(ICESTATE_KICKED); + return FORCE_MOVE; + } + } + + return BadGuy::collision_player(player, hit); +} + +HitResponse +MrIceBlock::collision_badguy(BadGuy& badguy, const CollisionHit& hit) +{ + switch(ice_state) { + case ICESTATE_NORMAL: + return WalkingBadguy::collision_badguy(badguy, hit); + case ICESTATE_FLAT: + return FORCE_MOVE; + case ICESTATE_KICKED: + badguy.kill_fall(); + return FORCE_MOVE; + default: + assert(false); + } + + return ABORT_MOVE; +} + +bool +MrIceBlock::collision_squished(GameObject& object) +{ + switch(ice_state) { + case ICESTATE_KICKED: + case ICESTATE_NORMAL: + squishcount++; + if(squishcount >= MAXSQUISHES) { + kill_fall(); + return true; + } + + set_state(ICESTATE_FLAT); + break; + case ICESTATE_FLAT: + { + MovingObject* movingobject = dynamic_cast(&object); + if (movingobject && (movingobject->get_pos().x < get_pos().x)) { + dir = RIGHT; + } else { + dir = LEFT; + } + } + set_state(ICESTATE_KICKED); + break; + case ICESTATE_GRABBED: + assert(false); + break; + } + + Player* player = dynamic_cast(&object); + if (player) player->bounce(*this); + return true; +} + +void +MrIceBlock::set_state(IceState state) +{ + if(ice_state == state) + return; + + switch(state) { + case ICESTATE_NORMAL: + WalkingBadguy::activate(); + break; + case ICESTATE_FLAT: + if(dir == UP) { + physic.set_velocity_y(-KICKSPEED); + bbox.set_size(34, 31.8f); + } else { + sound_manager->play("sounds/stomp.wav", get_pos()); + physic.set_velocity_x(0); + physic.set_velocity_y(0); + + sprite->set_action(dir == LEFT ? "flat-left" : "flat-right"); + } + flat_timer.start(4); + break; + case ICESTATE_KICKED: + sound_manager->play("sounds/kick.wav", get_pos()); + + physic.set_velocity_x(dir == LEFT ? -KICKSPEED : KICKSPEED); + sprite->set_action(dir == LEFT ? "flat-left" : "flat-right"); + // we should slide above 1 block holes now... + bbox.set_size(34, 31.8f); + break; + case ICESTATE_GRABBED: + flat_timer.stop(); + break; + default: + assert(false); + } + ice_state = state; +} + +void +MrIceBlock::grab(MovingObject&, const Vector& pos, Direction dir) +{ + movement = pos - get_pos(); + this->dir = dir; + sprite->set_action(dir == LEFT ? "flat-left" : "flat-right"); + set_state(ICESTATE_GRABBED); + set_group(COLGROUP_DISABLED); +} + +void +MrIceBlock::ungrab(MovingObject& , Direction dir) +{ + this->dir = dir; + set_state(dir == UP ? ICESTATE_FLAT : ICESTATE_KICKED); + set_group(COLGROUP_MOVING); +} + +bool +MrIceBlock::is_portable() const +{ + return ice_state == ICESTATE_FLAT; +} + +IMPLEMENT_FACTORY(MrIceBlock, "mriceblock") diff --git a/src/src/badguy/mriceblock.hpp b/src/src/badguy/mriceblock.hpp new file mode 100644 index 000000000..a00c5e734 --- /dev/null +++ b/src/src/badguy/mriceblock.hpp @@ -0,0 +1,67 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __MRICEBLOCK_H__ +#define __MRICEBLOCK_H__ + +#include "walking_badguy.hpp" +#include "object/portable.hpp" + +class MrIceBlock : public WalkingBadguy, public Portable +{ +public: + MrIceBlock(const lisp::Lisp& reader); + MrIceBlock(const Vector& pos, Direction d); + + void activate(); + void write(lisp::Writer& writer); + HitResponse collision(GameObject& object, const CollisionHit& hit); + void collision_solid(const CollisionHit& hit); + HitResponse collision_badguy(BadGuy& badguy, const CollisionHit& hit); + HitResponse collision_player(Player& player, const CollisionHit& hit); + + void active_update(float elapsed_time); + + void grab(MovingObject& object, const Vector& pos, Direction dir); + void ungrab(MovingObject& object, Direction dir); + bool is_portable() const; + + bool can_break(); + + virtual MrIceBlock* clone() const { return new MrIceBlock(*this); } + +protected: + bool collision_squished(GameObject& object); + +private: + enum IceState { + ICESTATE_NORMAL, + ICESTATE_FLAT, + ICESTATE_GRABBED, + ICESTATE_KICKED + }; + + void set_state(IceState state); + + IceState ice_state; + Timer flat_timer; + int squishcount; +}; + +#endif diff --git a/src/src/badguy/mrrocket.cpp b/src/src/badguy/mrrocket.cpp new file mode 100644 index 000000000..4ac7045a0 --- /dev/null +++ b/src/src/badguy/mrrocket.cpp @@ -0,0 +1,90 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "mrrocket.hpp" +#include "object/explosion.hpp" + +static const float SPEED = 200; + +MrRocket::MrRocket(const lisp::Lisp& reader) + : BadGuy(reader, "images/creatures/mr_rocket/mr_rocket.sprite") +{ +} + +MrRocket::MrRocket(const Vector& pos, Direction d) + : BadGuy(pos, d, "images/creatures/mr_rocket/mr_rocket.sprite") +{ +} + +void +MrRocket::write(lisp::Writer& writer) +{ + writer.start_list("mrrocket"); + + writer.write_float("x", start_position.x); + writer.write_float("y", start_position.y); + + writer.end_list("mrrocket"); +} + +void +MrRocket::activate() +{ + physic.set_velocity_x(dir == LEFT ? -SPEED : SPEED); + physic.enable_gravity(false); + sprite->set_action(dir == LEFT ? "left" : "right"); +} + +void +MrRocket::active_update(float elapsed_time) +{ + if (collision_timer.check()) { + Sector::current()->add_object(new Explosion(get_bbox().get_middle())); + remove_me(); + } + else if (!collision_timer.started()) { + movement=physic.get_movement(elapsed_time); + sprite->set_action(dir == LEFT ? "left" : "right"); + } +} + +bool +MrRocket::collision_squished(GameObject& object) +{ + sprite->set_action(dir == LEFT ? "squished-left" : "squished-right"); + kill_squished(object); + kill_fall(); + return true; +} + +void +MrRocket::collision_solid(const CollisionHit& hit) +{ + if(hit.top || hit.bottom) { + physic.set_velocity_y(0); + } else if(hit.left || hit.right) { + sprite->set_action(dir == LEFT ? "collision-left" : "collision-right"); + physic.set_velocity_x(0); + collision_timer.start(0.2f, true); + } +} + +IMPLEMENT_FACTORY(MrRocket, "mrrocket") diff --git a/src/src/badguy/mrrocket.hpp b/src/src/badguy/mrrocket.hpp new file mode 100644 index 000000000..628df377d --- /dev/null +++ b/src/src/badguy/mrrocket.hpp @@ -0,0 +1,44 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __MRROCKET_H__ +#define __MRROCKET_H__ + +#include "badguy.hpp" +#include "timer.hpp" + +class MrRocket : public BadGuy +{ +public: + MrRocket(const lisp::Lisp& reader); + MrRocket(const Vector& pos, Direction d); + + void activate(); + void active_update(float elapsed_time); + void write(lisp::Writer& writer); + void collision_solid(const CollisionHit& hit); + + virtual MrRocket* clone() const { return new MrRocket(*this); } + +protected: + bool collision_squished(GameObject& object); + Timer collision_timer; +}; + +#endif diff --git a/src/src/badguy/mrtree.cpp b/src/src/badguy/mrtree.cpp new file mode 100644 index 000000000..ddcc43aa3 --- /dev/null +++ b/src/src/badguy/mrtree.cpp @@ -0,0 +1,102 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "mrtree.hpp" +#include "stumpy.hpp" +#include "poisonivy.hpp" +#include "random_generator.hpp" +#include "object/sprite_particle.hpp" +#include "sector.hpp" + +static const float WALKSPEED = 100; + +static const float POISONIVY_WIDTH = 32; +static const float POISONIVY_HEIGHT = 32; +static const float POISONIVY_Y_OFFSET = 24; + + +MrTree::MrTree(const lisp::Lisp& reader) + : WalkingBadguy(reader, "images/creatures/mr_tree/mr_tree.sprite","left","right") +{ + walk_speed = WALKSPEED; + max_drop_height = 16; + sound_manager->preload("sounds/mr_tree.ogg"); +} + +void +MrTree::write(lisp::Writer& writer) +{ + writer.start_list("mrtree"); + WalkingBadguy::write(writer); + writer.end_list("mrtree"); +} + +bool +MrTree::collision_squished(GameObject& object) +{ + // replace with Stumpy + Vector stumpy_pos = get_pos(); + stumpy_pos.x += 20; + stumpy_pos.y += 25; + Stumpy* stumpy = new Stumpy(stumpy_pos, dir); + remove_me(); + Sector::current()->add_object(stumpy); + + // give Feedback + sound_manager->play("sounds/mr_tree.ogg", get_pos()); + Player* player = dynamic_cast(&object); + if (player) player->bounce(*this); + + // spawn some particles + // TODO: provide convenience function in MovingSprite or MovingObject? + for (int px = (int)stumpy->get_bbox().p1.x; px < (int)stumpy->get_bbox().p2.x; px+=10) { + Vector ppos = Vector(px, stumpy->get_bbox().p1.y-5); + float angle = systemRandom.randf(-M_PI_2, M_PI_2); + float velocity = systemRandom.randf(45, 90); + float vx = sin(angle)*velocity; + float vy = -cos(angle)*velocity; + Vector pspeed = Vector(vx, vy); + Vector paccel = Vector(0, 100); + Sector::current()->add_object(new SpriteParticle("images/objects/particles/leaf.sprite", "default", ppos, ANCHOR_MIDDLE, pspeed, paccel, LAYER_OBJECTS-1)); + } + + // spawn PoisonIvy + Vector leaf1_pos = Vector(stumpy_pos.x - POISONIVY_WIDTH - 1, stumpy_pos.y - POISONIVY_Y_OFFSET); + Rect leaf1_bbox = Rect(leaf1_pos.x, leaf1_pos.y, leaf1_pos.x + POISONIVY_WIDTH, leaf1_pos.y + POISONIVY_HEIGHT); + if (Sector::current()->is_free_of_movingstatics(leaf1_bbox, this)) { + PoisonIvy* leaf1 = new PoisonIvy(leaf1_bbox.p1, LEFT); + leaf1 = leaf1; + Sector::current()->add_object(leaf1); + } + + // spawn PoisonIvy + Vector leaf2_pos = Vector(stumpy_pos.x + sprite->get_current_hitbox_width() + 1, stumpy_pos.y - POISONIVY_Y_OFFSET); + Rect leaf2_bbox = Rect(leaf2_pos.x, leaf2_pos.y, leaf2_pos.x + POISONIVY_WIDTH, leaf2_pos.y + POISONIVY_HEIGHT); + if (Sector::current()->is_free_of_movingstatics(leaf2_bbox, this)) { + PoisonIvy* leaf2 = new PoisonIvy(leaf2_bbox.p1, RIGHT); + leaf2 = leaf2; + Sector::current()->add_object(leaf2); + } + + return true; +} + +IMPLEMENT_FACTORY(MrTree, "mrtree") diff --git a/src/src/badguy/mrtree.hpp b/src/src/badguy/mrtree.hpp new file mode 100644 index 000000000..0b884a1ce --- /dev/null +++ b/src/src/badguy/mrtree.hpp @@ -0,0 +1,37 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __MRTREE_H__ +#define __MRTREE_H__ + +#include "walking_badguy.hpp" + +class MrTree : public WalkingBadguy +{ +public: + MrTree(const lisp::Lisp& reader); + void write(lisp::Writer& writer); + virtual MrTree* clone() const { return new MrTree(*this); } + +protected: + bool collision_squished(GameObject& object); + +}; + +#endif diff --git a/src/src/badguy/plant.cpp b/src/src/badguy/plant.cpp new file mode 100644 index 000000000..5830d2b01 --- /dev/null +++ b/src/src/badguy/plant.cpp @@ -0,0 +1,117 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "plant.hpp" + +static const float WALKSPEED = 80; +static const float WAKE_TIME = .5; + +Plant::Plant(const lisp::Lisp& reader) + : BadGuy(reader, "images/creatures/plant/plant.sprite") +{ + state = PLANT_SLEEPING; +} + +void +Plant::write(lisp::Writer& writer) +{ + writer.start_list("plant"); + + writer.write_float("x", start_position.x); + writer.write_float("y", start_position.y); + + writer.end_list("plant"); +} + +void +Plant::activate() +{ + //FIXME: turns sspiky around for debugging + dir = dir == LEFT ? RIGHT : LEFT; + + state = PLANT_SLEEPING; + physic.set_velocity_x(0); + sprite->set_action(dir == LEFT ? "sleeping-left" : "sleeping-right"); +} + +void +Plant::collision_solid(const CollisionHit& hit) +{ + if(hit.top || hit.bottom) { + physic.set_velocity_y(0); + } else if(hit.left || hit.right) { + dir = dir == LEFT ? RIGHT : LEFT; + sprite->set_action(dir == LEFT ? "left" : "right"); + physic.set_velocity_x(-physic.get_velocity_x()); + } +} + +HitResponse +Plant::collision_badguy(BadGuy& , const CollisionHit& hit) +{ + if(state != PLANT_WALKING) return CONTINUE; + + if(hit.left || hit.right) { + dir = dir == LEFT ? RIGHT : LEFT; + sprite->set_action(dir == LEFT ? "left" : "right"); + physic.set_velocity_x(-physic.get_velocity_x()); + } + + return CONTINUE; +} + +void +Plant::active_update(float elapsed_time) { + BadGuy::active_update(elapsed_time); + + if(state == PLANT_SLEEPING) { + + Player* player = this->get_nearest_player(); + if (player) { + Rect mb = this->get_bbox(); + Rect pb = player->get_bbox(); + + bool inReach_left = (pb.p2.x >= mb.p2.x-((dir == LEFT) ? 256 : 0)); + bool inReach_right = (pb.p1.x <= mb.p1.x+((dir == RIGHT) ? 256 : 0)); + bool inReach_top = (pb.p2.y >= mb.p2.y); + bool inReach_bottom = (pb.p1.y <= mb.p1.y); + + if (inReach_left && inReach_right && inReach_top && inReach_bottom) { + // wake up + sprite->set_action(dir == LEFT ? "waking-left" : "waking-right"); + if(!timer.started()) timer.start(WAKE_TIME); + state = PLANT_WAKING; + } + } + } + + if(state == PLANT_WAKING) { + if(timer.check()) { + // start walking + sprite->set_action(dir == LEFT ? "left" : "right"); + physic.set_velocity_x(dir == LEFT ? -WALKSPEED : WALKSPEED); + state = PLANT_WALKING; + } + } + +} + +IMPLEMENT_FACTORY(Plant, "plant") diff --git a/src/src/badguy/plant.hpp b/src/src/badguy/plant.hpp new file mode 100644 index 000000000..7216514e6 --- /dev/null +++ b/src/src/badguy/plant.hpp @@ -0,0 +1,50 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __PLANT_H__ +#define __PLANT_H__ + +#include "badguy.hpp" + +class Plant : public BadGuy +{ +public: + Plant(const lisp::Lisp& reader); + + void activate(); + void write(lisp::Writer& writer); + void collision_solid(const CollisionHit& hit); + HitResponse collision_badguy(BadGuy& badguy, const CollisionHit& hit); + void active_update(float elapsed_time); + + virtual Plant* clone() const { return new Plant(*this); } + +protected: + Timer timer; + + enum PlantState { + PLANT_SLEEPING, + PLANT_WAKING, + PLANT_WALKING + }; + PlantState state; + +}; + +#endif diff --git a/src/src/badguy/poisonivy.cpp b/src/src/badguy/poisonivy.cpp new file mode 100644 index 000000000..67b4c7f06 --- /dev/null +++ b/src/src/badguy/poisonivy.cpp @@ -0,0 +1,66 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "poisonivy.hpp" +#include "random_generator.hpp" +#include "object/sprite_particle.hpp" + +PoisonIvy::PoisonIvy(const lisp::Lisp& reader) + : WalkingBadguy(reader, "images/creatures/poison_ivy/poison_ivy.sprite", "left", "right") +{ + walk_speed = 80; +} + +PoisonIvy::PoisonIvy(const Vector& pos, Direction d) + : WalkingBadguy(pos, d, "images/creatures/poison_ivy/poison_ivy.sprite", "left", "right") +{ + walk_speed = 80; +} + +void +PoisonIvy::write(lisp::Writer& writer) +{ + writer.start_list("poisonivy"); + WalkingBadguy::write(writer); + writer.end_list("poisonivy"); +} + +bool +PoisonIvy::collision_squished(GameObject& object) +{ + sprite->set_action(dir == LEFT ? "squished-left" : "squished-right"); + // spawn some particles + // TODO: provide convenience function in MovingSprite or MovingObject? + for (int i = 0; i < 3; i++) { + Vector ppos = bbox.get_middle(); + float angle = systemRandom.randf(-M_PI_2, M_PI_2); + float velocity = systemRandom.randf(350, 400); + float vx = sin(angle)*velocity; + float vy = -cos(angle)*velocity; + Vector pspeed = Vector(vx, vy); + Vector paccel = Vector(0, 100); + Sector::current()->add_object(new SpriteParticle("images/objects/particles/poisonivy.sprite", "default", ppos, ANCHOR_MIDDLE, pspeed, paccel, LAYER_OBJECTS-1)); + } + kill_squished(object); + return true; +} + +IMPLEMENT_FACTORY(PoisonIvy, "poisonivy") diff --git a/src/src/badguy/poisonivy.hpp b/src/src/badguy/poisonivy.hpp new file mode 100644 index 000000000..783f23254 --- /dev/null +++ b/src/src/badguy/poisonivy.hpp @@ -0,0 +1,39 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __POISONIVY_H__ +#define __POISONIVY_H__ + +#include "walking_badguy.hpp" + +class PoisonIvy : public WalkingBadguy +{ +public: + PoisonIvy(const lisp::Lisp& reader); + PoisonIvy(const Vector& pos, Direction d); + + void write(lisp::Writer& writer); + virtual PoisonIvy* clone() const { return new PoisonIvy(*this); } + +protected: + bool collision_squished(GameObject& object); + +}; + +#endif diff --git a/src/src/badguy/root.cpp b/src/src/badguy/root.cpp new file mode 100644 index 000000000..cad33cbc0 --- /dev/null +++ b/src/src/badguy/root.cpp @@ -0,0 +1,98 @@ +// $Id$ +// +// SuperTux - "Will-O-Wisp" Badguy +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +#include + +#include "root.hpp" +#include "sprite/sprite_manager.hpp" +#include "timer.hpp" + +static const float SPEED_GROW = 256; +static const float SPEED_SHRINK = 128; +static const float HATCH_TIME = 0.75; + +Root::Root(const Vector& pos) + : BadGuy(pos, "images/creatures/ghosttree/root.sprite", LAYER_TILES-1), + mystate(STATE_APPEARING), offset_y(0) +{ + base_sprite.reset(sprite_manager->create("images/creatures/ghosttree/root-base.sprite")); + base_sprite->set_action("appearing", 1); + base_sprite->set_animation_loops(1); // TODO: necessary because set_action ignores loops for default action + physic.enable_gravity(false); +} + +Root::~Root() +{ +} + +void +Root::activate() +{ + set_group(COLGROUP_TOUCHABLE); +} + +void +Root::deactivate() +{ + remove_me(); +} + +void +Root::active_update(float elapsed_time) +{ + if (mystate == STATE_APPEARING) { + if (base_sprite->animation_done()) { + hatch_timer.start(HATCH_TIME); + mystate = STATE_HATCHING; + } + } + if (mystate == STATE_HATCHING) { + if (!hatch_timer.started()) mystate = STATE_GROWING; + } + else if (mystate == STATE_GROWING) { + offset_y -= elapsed_time * SPEED_GROW; + if (offset_y < -sprite->get_height()) { + offset_y = -sprite->get_height(); + mystate = STATE_SHRINKING; + } + set_pos(start_position + Vector(0, offset_y)); + } + else if (mystate == STATE_SHRINKING) { + offset_y += elapsed_time * SPEED_SHRINK; + if (offset_y > 0) { + offset_y = 0; + mystate = STATE_VANISHING; + base_sprite->set_action("vanishing", 2); + base_sprite->set_animation_loops(2); // TODO: doesn't seem to work for loops=1 + } + set_pos(start_position + Vector(0, offset_y)); + } + else if (mystate == STATE_VANISHING) { + if (base_sprite->animation_done()) remove_me(); + } + BadGuy::active_update(elapsed_time); +} + +void +Root::draw(DrawingContext& context) +{ + base_sprite->draw(context, start_position, LAYER_TILES+1); + if ((mystate != STATE_APPEARING) && (mystate != STATE_VANISHING)) BadGuy::draw(context); +} + diff --git a/src/src/badguy/root.hpp b/src/src/badguy/root.hpp new file mode 100644 index 000000000..d693f232d --- /dev/null +++ b/src/src/badguy/root.hpp @@ -0,0 +1,51 @@ +// $Id$ +// +// SuperTux - Boss "GhostTree" +// Copyright (C) 2007 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#ifndef __ROOT_H__ +#define __ROOT_H__ + +#include +#include "badguy.hpp" + +class Timer; + +class Root : public BadGuy +{ +public: + Root(const Vector& pos); + ~Root(); + + void activate(); + void deactivate(); + void active_update(float elapsed_time); + virtual void draw(DrawingContext& context); + virtual bool is_flammable() const { return false; } + virtual bool is_freezable() const { return false; } + virtual void kill_fall() { } + +protected: + enum MyState { + STATE_APPEARING, STATE_HATCHING, STATE_GROWING, STATE_SHRINKING, STATE_VANISHING + }; + MyState mystate; + std::auto_ptr base_sprite; + float offset_y; + Timer hatch_timer; +}; + +#endif diff --git a/src/src/badguy/skullyhop.cpp b/src/src/badguy/skullyhop.cpp new file mode 100644 index 000000000..2b086eae4 --- /dev/null +++ b/src/src/badguy/skullyhop.cpp @@ -0,0 +1,150 @@ +// $Id$ +// +// SkullyHop - A Hopping Skull +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +#include + +#include "skullyhop.hpp" +#include "random_generator.hpp" + +namespace { + const float VERTICAL_SPEED = -450; /**< y-speed when jumping */ + const float HORIZONTAL_SPEED = 220; /**< x-speed when jumping */ + const float MIN_RECOVER_TIME = 0.1f; /**< minimum time to stand still before starting a (new) jump */ + const float MAX_RECOVER_TIME = 1.0f; /**< maximum time to stand still before starting a (new) jump */ + static const std::string HOP_SOUND = "sounds/hop.ogg"; +} + +SkullyHop::SkullyHop(const lisp::Lisp& reader) + : BadGuy(reader, "images/creatures/skullyhop/skullyhop.sprite") +{ + sound_manager->preload( HOP_SOUND ); +} + +SkullyHop::SkullyHop(const Vector& pos, Direction d) + : BadGuy(pos, d, "images/creatures/skullyhop/skullyhop.sprite") +{ + sound_manager->preload( HOP_SOUND ); +} + +void +SkullyHop::write(lisp::Writer& writer) +{ + writer.start_list("skullyhop"); + writer.write_float("x", start_position.x); + writer.write_float("y", start_position.y); + writer.end_list("skullyhop"); +} + +void +SkullyHop::activate() +{ + // initial state is JUMPING, because we might start airborne + state = JUMPING; + sprite->set_action(dir == LEFT ? "jumping-left" : "jumping-right"); +} + +void +SkullyHop::set_state(SkullyHopState newState) +{ + if (newState == STANDING) { + physic.set_velocity_x(0); + physic.set_velocity_y(0); + sprite->set_action(dir == LEFT ? "standing-left" : "standing-right"); + + float recover_time = systemRandom.randf(MIN_RECOVER_TIME,MAX_RECOVER_TIME); + recover_timer.start(recover_time); + } else + if (newState == CHARGING) { + sprite->set_action(dir == LEFT ? "charging-left" : "charging-right", 1); + } else + if (newState == JUMPING) { + sprite->set_action(dir == LEFT ? "jumping-left" : "jumping-right"); + physic.set_velocity_x(dir == LEFT ? -HORIZONTAL_SPEED : HORIZONTAL_SPEED); + physic.set_velocity_y(VERTICAL_SPEED); + sound_manager->play( HOP_SOUND, get_pos()); + } + + state = newState; +} + +bool +SkullyHop::collision_squished(GameObject& object) +{ + sprite->set_action(dir == LEFT ? "squished-left" : "squished-right"); + kill_squished(object); + return true; +} + +void +SkullyHop::collision_solid(const CollisionHit& hit) +{ + // just default behaviour (i.e. stop at floor/walls) when squished + if (BadGuy::get_state() == STATE_SQUISHED) { + BadGuy::collision_solid(hit); + } + + // ignore collisions while standing still + if(state != JUMPING) + return; + + // check if we hit the floor while falling + if(hit.bottom && physic.get_velocity_y() > 0 ) { + set_state(STANDING); + } + // check if we hit the roof while climbing + if(hit.top) { + physic.set_velocity_y(0); + } + + // check if we hit left or right while moving in either direction + if(hit.left || hit.right) { + dir = dir == LEFT ? RIGHT : LEFT; + sprite->set_action(dir == LEFT ? "jumping-left" : "jumping-right"); + physic.set_velocity_x(-0.25*physic.get_velocity_x()); + } +} + +HitResponse +SkullyHop::collision_badguy(BadGuy& , const CollisionHit& hit) +{ + // behaviour for badguy collisions is the same as for collisions with solids + collision_solid(hit); + + return CONTINUE; +} + +void +SkullyHop::active_update(float elapsed_time) +{ + BadGuy::active_update(elapsed_time); + + // charge when fully recovered + if ((state == STANDING) && (recover_timer.check())) { + set_state(CHARGING); + return; + } + + // jump as soon as charging animation completed + if ((state == CHARGING) && (sprite->animation_done())) { + set_state(JUMPING); + return; + } +} + +IMPLEMENT_FACTORY(SkullyHop, "skullyhop") diff --git a/src/src/badguy/skullyhop.hpp b/src/src/badguy/skullyhop.hpp new file mode 100644 index 000000000..0569722dc --- /dev/null +++ b/src/src/badguy/skullyhop.hpp @@ -0,0 +1,57 @@ +// $Id$ +// +// SkullyHop - A Hopping Skull +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#ifndef __SKULLYHOP_H__ +#define __SKULLYHOP_H__ + +#include "badguy.hpp" + +/** + * Badguy "SkullyHop" - A Hopping Skull + */ +class SkullyHop : public BadGuy +{ +public: + SkullyHop(const lisp::Lisp& reader); + SkullyHop(const Vector& pos, Direction d); + + void activate(); + void write(lisp::Writer& writer); + void collision_solid(const CollisionHit& hit); + HitResponse collision_badguy(BadGuy& badguy, const CollisionHit& hit); + bool collision_squished(GameObject& object); + void active_update(float elapsed_time); + + virtual SkullyHop* clone() const { return new SkullyHop(*this); } + +protected: + enum SkullyHopState { + STANDING, + CHARGING, + JUMPING + }; + + Timer recover_timer; + SkullyHopState state; + + void set_state(SkullyHopState newState); +}; + +#endif diff --git a/src/src/badguy/snail.cpp b/src/src/badguy/snail.cpp new file mode 100644 index 000000000..a2695334d --- /dev/null +++ b/src/src/badguy/snail.cpp @@ -0,0 +1,248 @@ +// $Id$ +// +// SuperTux - Badguy "Snail" +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "snail.hpp" +#include "object/block.hpp" + +namespace { + const float KICKSPEED = 500; + const int MAXSQUISHES = 10; + const float KICKSPEED_Y = -500; /**< y-velocity gained when kicked */ +} + +Snail::Snail(const lisp::Lisp& reader) + : WalkingBadguy(reader, "images/creatures/snail/snail.sprite", "left", "right"), state(STATE_NORMAL), squishcount(0) +{ + walk_speed = 80; + max_drop_height = 600; + sound_manager->preload("sounds/iceblock_bump.wav"); + sound_manager->preload("sounds/stomp.wav"); + sound_manager->preload("sounds/kick.wav"); +} + +Snail::Snail(const Vector& pos, Direction d) + : WalkingBadguy(pos, d, "images/creatures/snail/snail.sprite", "left", "right"), state(STATE_NORMAL), squishcount(0) +{ + walk_speed = 80; + max_drop_height = 600; + sound_manager->preload("sounds/iceblock_bump.wav"); + sound_manager->preload("sounds/stomp.wav"); + sound_manager->preload("sounds/kick.wav"); +} + +void +Snail::write(lisp::Writer& writer) +{ + writer.start_list("snail"); + WalkingBadguy::write(writer); + writer.end_list("snail"); +} + +void +Snail::activate() +{ + WalkingBadguy::activate(); + be_normal(); +} + +void +Snail::be_normal() +{ + if (state == STATE_NORMAL) return; + + state = STATE_NORMAL; + WalkingBadguy::activate(); +} + +void +Snail::be_flat() +{ + state = STATE_FLAT; + sprite->set_action(dir == LEFT ? "flat-left" : "flat-right"); + sprite->set_fps(64); + + physic.set_velocity_x(0); + physic.set_velocity_y(0); + + flat_timer.start(4); +} + +void +Snail::be_kicked() +{ + state = STATE_KICKED_DELAY; + sprite->set_action(dir == LEFT ? "flat-left" : "flat-right"); + sprite->set_fps(64); + + physic.set_velocity_x(0); + physic.set_velocity_y(0); + + // start a timer to delay addition of upward movement until we are (hopefully) out from under the player + kicked_delay_timer.start(0.05f); +} + +bool +Snail::can_break(){ + return state == STATE_KICKED; +} + +void +Snail::active_update(float elapsed_time) +{ + switch (state) { + + case STATE_NORMAL: + WalkingBadguy::active_update(elapsed_time); + break; + + case STATE_FLAT: + if (flat_timer.started()) { + sprite->set_fps(64 - 15 * flat_timer.get_timegone()); + } + if (flat_timer.check()) { + be_normal(); + } + BadGuy::active_update(elapsed_time); + break; + + case STATE_KICKED_DELAY: + if (kicked_delay_timer.check()) { + physic.set_velocity_x(dir == LEFT ? -KICKSPEED : KICKSPEED); + physic.set_velocity_y(KICKSPEED_Y); + state = STATE_KICKED; + } + BadGuy::active_update(elapsed_time); + break; + + case STATE_KICKED: + physic.set_velocity_x(physic.get_velocity_x() * pow(0.99, elapsed_time/0.02)); + if (fabsf(physic.get_velocity_x()) < walk_speed) be_normal(); + BadGuy::active_update(elapsed_time); + break; + + } +} + +void +Snail::collision_solid(const CollisionHit& hit) +{ + update_on_ground_flag(hit); + + switch (state) { + case STATE_NORMAL: + WalkingBadguy::collision_solid(hit); + break; + case STATE_FLAT: + if(hit.top || hit.bottom) { + physic.set_velocity_y(0); + } + if(hit.left || hit.right) { + } + break; + case STATE_KICKED_DELAY: + if(hit.top || hit.bottom) { + physic.set_velocity_y(0); + } + if(hit.left || hit.right) { + physic.set_velocity_x(0); + } + break; + case STATE_KICKED: + if(hit.top || hit.bottom) { + physic.set_velocity_y(0); + } + if(hit.left || hit.right) { + sound_manager->play("sounds/iceblock_bump.wav", get_pos()); + + if( ( dir == LEFT && hit.left ) || ( dir == RIGHT && hit.right) ){ + dir = (dir == LEFT) ? RIGHT : LEFT; + sprite->set_action(dir == LEFT ? "flat-left" : "flat-right"); + + physic.set_velocity_x(-physic.get_velocity_x()*0.75); + if (fabsf(physic.get_velocity_x()) < walk_speed) be_normal(); + } + + } + break; + } + +} + +HitResponse +Snail::collision_badguy(BadGuy& badguy, const CollisionHit& hit) +{ + switch(state) { + case STATE_NORMAL: + return WalkingBadguy::collision_badguy(badguy, hit); + case STATE_FLAT: + case STATE_KICKED_DELAY: + return FORCE_MOVE; + case STATE_KICKED: + badguy.kill_fall(); + return FORCE_MOVE; + default: + assert(false); + } + + return ABORT_MOVE; +} + +bool +Snail::collision_squished(GameObject& object) +{ + switch(state) { + + case STATE_KICKED: + case STATE_NORMAL: + squishcount++; + if(squishcount >= MAXSQUISHES) { + kill_fall(); + return true; + } + + sound_manager->play("sounds/stomp.wav", get_pos()); + be_flat(); + break; + + case STATE_FLAT: + sound_manager->play("sounds/kick.wav", get_pos()); + { + MovingObject* movingobject = dynamic_cast(&object); + if (movingobject && (movingobject->get_pos().x < get_pos().x)) { + dir = RIGHT; + } else { + dir = LEFT; + } + } + be_kicked(); + break; + + case STATE_KICKED_DELAY: + break; + + } + + Player* player = dynamic_cast(&object); + if (player) player->bounce(*this); + return true; +} + +IMPLEMENT_FACTORY(Snail, "snail") diff --git a/src/src/badguy/snail.hpp b/src/src/badguy/snail.hpp new file mode 100644 index 000000000..642df55b2 --- /dev/null +++ b/src/src/badguy/snail.hpp @@ -0,0 +1,63 @@ +// $Id$ +// +// SuperTux - Badguy "Snail" +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __SNAIL_H__ +#define __SNAIL_H__ + +#include "walking_badguy.hpp" + +/** + * Badguy "Snail" - a snail-like creature that can be flipped and tossed around at an angle + */ +class Snail : public WalkingBadguy +{ +public: + Snail(const lisp::Lisp& reader); + Snail(const Vector& pos, Direction d); + + void activate(); + void write(lisp::Writer& writer); + void collision_solid(const CollisionHit& hit); + HitResponse collision_badguy(BadGuy& badguy, const CollisionHit& hit); + bool can_break(); + + void active_update(float elapsed_time); + + virtual Snail* clone() const { return new Snail(*this); } + +protected: + bool collision_squished(GameObject& object); + void be_normal(); /**< switch to state STATE_NORMAL */ + void be_flat(); /**< switch to state STATE_FLAT */ + void be_kicked(); /**< switch to state STATE_KICKED_DELAY */ + +private: + enum State { + STATE_NORMAL, /**< walking around */ + STATE_FLAT, /**< flipped upside-down */ + STATE_KICKED_DELAY, /**< short delay before being launched */ + STATE_KICKED /**< launched */ + }; + State state; + Timer flat_timer; /**< wait time until flipping right-side-up again */ + Timer kicked_delay_timer; /**< wait time until switching from STATE_KICKED_DELAY to STATE_KICKED */ + int squishcount; +}; + +#endif diff --git a/src/src/badguy/snowball.cpp b/src/src/badguy/snowball.cpp new file mode 100644 index 000000000..8e690cbed --- /dev/null +++ b/src/src/badguy/snowball.cpp @@ -0,0 +1,52 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "snowball.hpp" + +SnowBall::SnowBall(const lisp::Lisp& reader) + : WalkingBadguy(reader, "images/creatures/snowball/snowball.sprite", "left", "right") +{ + walk_speed = 80; +} + +SnowBall::SnowBall(const Vector& pos, Direction d) + : WalkingBadguy(pos, d, "images/creatures/snowball/snowball.sprite", "left", "right") +{ + walk_speed = 80; +} + +void +SnowBall::write(lisp::Writer& writer) +{ + writer.start_list("snowball"); + WalkingBadguy::write(writer); + writer.end_list("snowball"); +} + +bool +SnowBall::collision_squished(GameObject& object) +{ + sprite->set_action(dir == LEFT ? "squished-left" : "squished-right"); + kill_squished(object); + return true; +} + +IMPLEMENT_FACTORY(SnowBall, "snowball") diff --git a/src/src/badguy/snowball.hpp b/src/src/badguy/snowball.hpp new file mode 100644 index 000000000..ef32818c3 --- /dev/null +++ b/src/src/badguy/snowball.hpp @@ -0,0 +1,39 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __SNOWBALL_H__ +#define __SNOWBALL_H__ + +#include "walking_badguy.hpp" + +class SnowBall : public WalkingBadguy +{ +public: + SnowBall(const lisp::Lisp& reader); + SnowBall(const Vector& pos, Direction d); + + void write(lisp::Writer& writer); + virtual SnowBall* clone() const { return new SnowBall(*this); } + +protected: + bool collision_squished(GameObject& object); + +}; + +#endif diff --git a/src/src/badguy/spidermite.cpp b/src/src/badguy/spidermite.cpp new file mode 100644 index 000000000..96a90a0ee --- /dev/null +++ b/src/src/badguy/spidermite.cpp @@ -0,0 +1,98 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include +#include + +#include "spidermite.hpp" + +static const float FLYTIME = 1.2f; +static const float FLYSPEED = -100.0f; + +SpiderMite::SpiderMite(const lisp::Lisp& reader) + : BadGuy(reader, "images/creatures/spidermite/spidermite.sprite") +{ + physic.enable_gravity(false); +} + +SpiderMite::SpiderMite(const Vector& pos) + : BadGuy(pos, "images/creatures/spidermite/spidermite.sprite") +{ + physic.enable_gravity(false); +} + +void +SpiderMite::write(lisp::Writer& writer) +{ + writer.start_list("spidermite"); + + writer.write_float("x", start_position.x); + writer.write_float("y", start_position.y); + + writer.end_list("spidermite"); +} + +void +SpiderMite::activate() +{ + sprite->set_action(dir == LEFT ? "left" : "right"); + mode = FLY_UP; + physic.set_velocity_y(FLYSPEED); + timer.start(FLYTIME/2); +} + +bool +SpiderMite::collision_squished(GameObject& object) +{ + sprite->set_action(dir == LEFT ? "squished-left" : "squished-right"); + kill_squished(object); + return true; +} + +void +SpiderMite::collision_solid(const CollisionHit& hit) +{ + if(hit.top || hit.bottom) { // hit floor or roof? + physic.set_velocity_y(0); + } +} + +void +SpiderMite::active_update(float elapsed_time) +{ + if(timer.check()) { + if(mode == FLY_UP) { + mode = FLY_DOWN; + physic.set_velocity_y(-FLYSPEED); + } else if(mode == FLY_DOWN) { + mode = FLY_UP; + physic.set_velocity_y(FLYSPEED); + } + timer.start(FLYTIME); + } + movement=physic.get_movement(elapsed_time); + + Player* player = this->get_nearest_player(); + if (player) { + dir = (player->get_pos().x > get_pos().x) ? RIGHT : LEFT; + sprite->set_action(dir == LEFT ? "left" : "right"); + } +} + +IMPLEMENT_FACTORY(SpiderMite, "spidermite") diff --git a/src/src/badguy/spidermite.hpp b/src/src/badguy/spidermite.hpp new file mode 100644 index 000000000..ee36b5b20 --- /dev/null +++ b/src/src/badguy/spidermite.hpp @@ -0,0 +1,49 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __SPIDERMITE_H__ +#define __SPIDERMITE_H__ + +#include "badguy.hpp" + +class SpiderMite : public BadGuy +{ +public: + SpiderMite(const lisp::Lisp& reader); + SpiderMite(const Vector& pos); + + void activate(); + void write(lisp::Writer& writer); + void active_update(float elapsed_time); + void collision_solid(const CollisionHit& hit); + + virtual SpiderMite* clone() const { return new SpiderMite(*this); } + +protected: + enum SpiderMiteMode { + FLY_UP, + FLY_DOWN + }; + SpiderMiteMode mode; + bool collision_squished(GameObject& object); +private: + Timer timer; +}; + +#endif diff --git a/src/src/badguy/spiky.cpp b/src/src/badguy/spiky.cpp new file mode 100644 index 000000000..eebf19a1d --- /dev/null +++ b/src/src/badguy/spiky.cpp @@ -0,0 +1,52 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "spiky.hpp" + +Spiky::Spiky(const lisp::Lisp& reader) + : WalkingBadguy(reader, "images/creatures/spiky/spiky.sprite", "left", "right") +{ + walk_speed = 80; + max_drop_height = 600; +} + +void +Spiky::write(lisp::Writer& writer) +{ + writer.start_list("spiky"); + WalkingBadguy::write(writer); + writer.end_list("spiky"); +} + +void +Spiky::freeze() +{ + WalkingBadguy::freeze(); + sprite->set_action(dir == LEFT ? "iced-left" : "iced-right"); +} + +bool +Spiky::is_freezable() const +{ + return true; +} + +IMPLEMENT_FACTORY(Spiky, "spiky") diff --git a/src/src/badguy/spiky.hpp b/src/src/badguy/spiky.hpp new file mode 100644 index 000000000..b90a0ecca --- /dev/null +++ b/src/src/badguy/spiky.hpp @@ -0,0 +1,39 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __SPIKY_H__ +#define __SPIKY_H__ + +#include "walking_badguy.hpp" + +class Spiky : public WalkingBadguy +{ +public: + Spiky(const lisp::Lisp& reader); + + void write(lisp::Writer& writer); + virtual Spiky* clone() const { return new Spiky(*this); } + + void freeze(); + bool is_freezable() const; + +private: +}; + +#endif diff --git a/src/src/badguy/sspiky.cpp b/src/src/badguy/sspiky.cpp new file mode 100644 index 000000000..ad80b8de8 --- /dev/null +++ b/src/src/badguy/sspiky.cpp @@ -0,0 +1,123 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "sspiky.hpp" + +static const float WALKSPEED = 80; + +SSpiky::SSpiky(const lisp::Lisp& reader) + : WalkingBadguy(reader, "images/creatures/spiky/sleepingspiky.sprite", "left", "right"), state(SSPIKY_SLEEPING) +{ + walk_speed = WALKSPEED; + max_drop_height = -1; +} + +void +SSpiky::write(lisp::Writer& writer) +{ + writer.start_list("sspiky"); + WalkingBadguy::write(writer); + writer.end_list("sspiky"); +} + +void +SSpiky::activate() +{ + state = SSPIKY_SLEEPING; + physic.set_velocity_x(0); + sprite->set_action(dir == LEFT ? "sleeping-left" : "sleeping-right"); +} + +void +SSpiky::collision_solid(const CollisionHit& hit) +{ + if(state != SSPIKY_WALKING) { + BadGuy::collision_solid(hit); + return; + } + WalkingBadguy::collision_solid(hit); +} + +HitResponse +SSpiky::collision_badguy(BadGuy& badguy, const CollisionHit& hit) +{ + if(state != SSPIKY_WALKING) { + return BadGuy::collision_badguy(badguy, hit); + } + return WalkingBadguy::collision_badguy(badguy, hit); +} + +void +SSpiky::active_update(float elapsed_time) { + + if(state == SSPIKY_WALKING) { + WalkingBadguy::active_update(elapsed_time); + return; + } + + if(state == SSPIKY_SLEEPING) { + + Player* player = this->get_nearest_player(); + if (player) { + Rect mb = this->get_bbox(); + Rect pb = player->get_bbox(); + + bool inReach_left = (pb.p2.x >= mb.p2.x-((dir == LEFT) ? 256 : 0)); + bool inReach_right = (pb.p1.x <= mb.p1.x+((dir == RIGHT) ? 256 : 0)); + bool inReach_top = (pb.p2.y >= mb.p1.y); + bool inReach_bottom = (pb.p1.y <= mb.p2.y); + + if (inReach_left && inReach_right && inReach_top && inReach_bottom) { + // wake up + sprite->set_action(dir == LEFT ? "waking-left" : "waking-right", 1); + state = SSPIKY_WAKING; + } + } + + BadGuy::active_update(elapsed_time); + } + + if(state == SSPIKY_WAKING) { + if(sprite->animation_done()) { + // start walking + state = SSPIKY_WALKING; + WalkingBadguy::activate(); + } + + BadGuy::active_update(elapsed_time); + } +} + +void +SSpiky::freeze() +{ + WalkingBadguy::freeze(); + sprite->set_action(dir == LEFT ? "iced-left" : "iced-right"); + state = SSPIKY_WALKING; // if we get hit while sleeping, wake up :) +} + +bool +SSpiky::is_freezable() const +{ + return true; +} + +IMPLEMENT_FACTORY(SSpiky, "sspiky") diff --git a/src/src/badguy/sspiky.hpp b/src/src/badguy/sspiky.hpp new file mode 100644 index 000000000..8e346cdfe --- /dev/null +++ b/src/src/badguy/sspiky.hpp @@ -0,0 +1,50 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __SSPIKY_H__ +#define __SSPIKY_H__ + +#include "walking_badguy.hpp" + +class SSpiky : public WalkingBadguy +{ +public: + SSpiky(const lisp::Lisp& reader); + + void activate(); + void write(lisp::Writer& writer); + void collision_solid(const CollisionHit& hit); + HitResponse collision_badguy(BadGuy& badguy, const CollisionHit& hit); + void active_update(float elapsed_time); + + void freeze(); + bool is_freezable() const; + + virtual SSpiky* clone() const { return new SSpiky(*this); } + +protected: + enum SSpikyState { + SSPIKY_SLEEPING, + SSPIKY_WAKING, + SSPIKY_WALKING + }; + SSpikyState state; +}; + +#endif diff --git a/src/src/badguy/stalactite.cpp b/src/src/badguy/stalactite.cpp new file mode 100644 index 000000000..8bde2fbd3 --- /dev/null +++ b/src/src/badguy/stalactite.cpp @@ -0,0 +1,153 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "stalactite.hpp" +#include "random_generator.hpp" + +static const int SHAKE_RANGE_X = 40; +static const float SHAKE_TIME = .8f; +static const float SQUISH_TIME = 2; +static const float SHAKE_RANGE_Y = 400; + +Stalactite::Stalactite(const lisp::Lisp& lisp) + : BadGuy(lisp, "images/creatures/stalactite/stalactite.sprite", LAYER_TILES - 1), state(STALACTITE_HANGING) +{ + countMe = false; +} + +void +Stalactite::write(lisp::Writer& writer) +{ + writer.start_list("stalactite"); + writer.write_float("x", start_position.x); + writer.write_float("y", start_position.y); + writer.end_list("stalactite"); +} + +void +Stalactite::active_update(float elapsed_time) +{ + if(state == STALACTITE_HANGING) { + Player* player = this->get_nearest_player(); + if (player) { + if(player->get_bbox().p2.x > bbox.p1.x - SHAKE_RANGE_X + && player->get_bbox().p1.x < bbox.p2.x + SHAKE_RANGE_X + && player->get_bbox().p2.y > bbox.p1.y + && player->get_bbox().p1.y < bbox.p2.y + SHAKE_RANGE_Y) { + timer.start(SHAKE_TIME); + state = STALACTITE_SHAKING; + } + } + } else if(state == STALACTITE_SHAKING) { + if(timer.check()) { + state = STALACTITE_FALLING; + physic.enable_gravity(true); + } + } else if(state == STALACTITE_FALLING || state == STALACTITE_SQUISHED) { + movement = physic.get_movement(elapsed_time); + if(state == STALACTITE_SQUISHED && timer.check()) + remove_me(); + } +} + +void +Stalactite::squish() +{ + state = STALACTITE_SQUISHED; + set_group(COLGROUP_MOVING_ONLY_STATIC); + sprite->set_action("squished"); + if(!timer.started()) + timer.start(SQUISH_TIME); +} + +void +Stalactite::collision_solid(const CollisionHit& hit) +{ + if(state == STALACTITE_FALLING) { + if (hit.bottom) squish(); + } + if(state == STALACTITE_SQUISHED) { + physic.set_velocity_y(0); + } +} + +HitResponse +Stalactite::collision_player(Player& player) +{ + if(state != STALACTITE_SQUISHED) { + player.kill(false); + } + + return FORCE_MOVE; +} + +HitResponse +Stalactite::collision_badguy(BadGuy& other, const CollisionHit& hit) +{ + if (state == STALACTITE_SQUISHED) return FORCE_MOVE; + if (state != STALACTITE_FALLING) return BadGuy::collision_badguy(other, hit); + + // ignore other Stalactites + if (dynamic_cast(&other)) return FORCE_MOVE; + + if (other.is_freezable()) { + other.freeze(); + } else { + other.kill_fall(); + } + + remove_me(); + + return FORCE_MOVE; +} + +void +Stalactite::kill_fall() +{ +} + +void +Stalactite::draw(DrawingContext& context) +{ + if(get_state() != STATE_ACTIVE) + return; + + + if(state == STALACTITE_SQUISHED) { + sprite->draw(context, get_pos(), LAYER_OBJECTS); + return; + } + + if(state == STALACTITE_SHAKING) { + sprite->draw(context, get_pos() + Vector(systemRandom.rand(-3,3), 0), layer); + } else { + sprite->draw(context, get_pos(), layer); + } +} + +void +Stalactite::deactivate() +{ + if(state != STALACTITE_HANGING) + remove_me(); +} + +IMPLEMENT_FACTORY(Stalactite, "stalactite") diff --git a/src/src/badguy/stalactite.hpp b/src/src/badguy/stalactite.hpp new file mode 100644 index 000000000..bf9484bfd --- /dev/null +++ b/src/src/badguy/stalactite.hpp @@ -0,0 +1,56 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __STALACTITE_H__ +#define __STALACTITE_H__ + +#include "badguy.hpp" + +class Stalactite : public BadGuy +{ +public: + Stalactite(const lisp::Lisp& reader); + + void active_update(float elapsed_time); + void write(lisp::Writer& writer); + void collision_solid(const CollisionHit& hit); + HitResponse collision_player(Player& player); + HitResponse collision_badguy(BadGuy& other, const CollisionHit& hit); + + void kill_fall(); + void draw(DrawingContext& context); + void deactivate(); + + virtual Stalactite* clone() const { return new Stalactite(*this); } + + void squish(); + +protected: + Timer timer; + + enum StalactiteState { + STALACTITE_HANGING, + STALACTITE_SHAKING, + STALACTITE_FALLING, + STALACTITE_SQUISHED + }; + StalactiteState state; +}; + +#endif diff --git a/src/src/badguy/stumpy.cpp b/src/src/badguy/stumpy.cpp new file mode 100644 index 000000000..c53fe39d3 --- /dev/null +++ b/src/src/badguy/stumpy.cpp @@ -0,0 +1,167 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "stumpy.hpp" +#include "poisonivy.hpp" +#include "random_generator.hpp" +#include "object/sprite_particle.hpp" + +static const float WALKSPEED = 120; +static const float INVINCIBLE_TIME = 1; + +Stumpy::Stumpy(const lisp::Lisp& reader) + : WalkingBadguy(reader, "images/creatures/mr_tree/stumpy.sprite","left","right"), mystate(STATE_NORMAL) +{ + walk_speed = WALKSPEED; + max_drop_height = 16; + sound_manager->preload("sounds/mr_tree.ogg"); + sound_manager->preload("sounds/mr_treehit.ogg"); +} + +Stumpy::Stumpy(const Vector& pos, Direction d) + : WalkingBadguy(pos, d, "images/creatures/mr_tree/stumpy.sprite","left","right"), mystate(STATE_INVINCIBLE) +{ + walk_speed = WALKSPEED; + max_drop_height = 16; + sound_manager->preload("sounds/mr_treehit.ogg"); + invincible_timer.start(INVINCIBLE_TIME); +} + + +void +Stumpy::write(lisp::Writer& writer) +{ + writer.start_list("stumpy"); + WalkingBadguy::write(writer); + writer.end_list("stumpy"); +} + +void +Stumpy::activate() +{ + switch (mystate) { + case STATE_INVINCIBLE: + sprite->set_action(dir == LEFT ? "dizzy-left" : "dizzy-right"); + bbox.set_size(sprite->get_current_hitbox_width(), sprite->get_current_hitbox_height()); + physic.set_velocity_x(0); + break; + case STATE_NORMAL: + WalkingBadguy::activate(); + break; + } +} + +void +Stumpy::active_update(float elapsed_time) +{ + switch (mystate) { + case STATE_INVINCIBLE: + if (invincible_timer.check()) { + mystate = STATE_NORMAL; + WalkingBadguy::activate(); + } + BadGuy::active_update(elapsed_time); + break; + case STATE_NORMAL: + WalkingBadguy::active_update(elapsed_time); + break; + } +} + +bool +Stumpy::collision_squished(GameObject& object) +{ + + // if we're still invincible, we ignore the hit + if (mystate == STATE_INVINCIBLE) { + sound_manager->play("sounds/mr_treehit.ogg", get_pos()); + Player* player = dynamic_cast(&object); + if (player) player->bounce(*this); + return true; + } + + // if we can die, we do + if (mystate == STATE_NORMAL) { + sprite->set_action(dir == LEFT ? "squished-left" : "squished-right"); + set_size(sprite->get_current_hitbox_width(), sprite->get_current_hitbox_height()); + kill_squished(object); + // spawn some particles + // TODO: provide convenience function in MovingSprite or MovingObject? + for (int i = 0; i < 25; i++) { + Vector ppos = bbox.get_middle(); + float angle = systemRandom.randf(-M_PI_2, M_PI_2); + float velocity = systemRandom.randf(45, 90); + float vx = sin(angle)*velocity; + float vy = -cos(angle)*velocity; + Vector pspeed = Vector(vx, vy); + Vector paccel = Vector(0, 100); + Sector::current()->add_object(new SpriteParticle("images/objects/particles/bark.sprite", "default", ppos, ANCHOR_MIDDLE, pspeed, paccel, LAYER_OBJECTS-1)); + } + + return true; + + } + + //TODO: exception? + return true; +} + +void +Stumpy::collision_solid(const CollisionHit& hit) +{ + update_on_ground_flag(hit); + + switch (mystate) { + case STATE_INVINCIBLE: + if(hit.top || hit.bottom) { + physic.set_velocity_y(0); + } + if(hit.left || hit.right) { + physic.set_velocity_x(0); + } + break; + case STATE_NORMAL: + WalkingBadguy::collision_solid(hit); + break; + } +} + +HitResponse +Stumpy::collision_badguy(BadGuy& badguy, const CollisionHit& hit) +{ + switch (mystate) { + case STATE_INVINCIBLE: + if(hit.top || hit.bottom) { + physic.set_velocity_y(0); + } + if(hit.left || hit.right) { + physic.set_velocity_x(0); + } + return CONTINUE; + break; + case STATE_NORMAL: + return WalkingBadguy::collision_badguy(badguy, hit); + break; + } + return CONTINUE; +} + +IMPLEMENT_FACTORY(Stumpy, "stumpy") diff --git a/src/src/badguy/stumpy.hpp b/src/src/badguy/stumpy.hpp new file mode 100644 index 000000000..80117feb5 --- /dev/null +++ b/src/src/badguy/stumpy.hpp @@ -0,0 +1,50 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __STUMPY_H__ +#define __STUMPY_H__ + +#include "walking_badguy.hpp" + +class Stumpy : public WalkingBadguy +{ +public: + Stumpy(const lisp::Lisp& reader); + Stumpy(const Vector& pos, Direction d); + + void activate(); + void active_update(float elapsed_time); + void write(lisp::Writer& writer); + void collision_solid(const CollisionHit& hit); + HitResponse collision_badguy(BadGuy& badguy, const CollisionHit& hit); + + virtual Stumpy* clone() const { return new Stumpy(*this); } + +protected: + enum MyState { + STATE_INVINCIBLE, STATE_NORMAL + }; + MyState mystate; + + Timer invincible_timer; + + bool collision_squished(GameObject& object); +}; + +#endif diff --git a/src/src/badguy/toad.cpp b/src/src/badguy/toad.cpp new file mode 100644 index 000000000..26da7e347 --- /dev/null +++ b/src/src/badguy/toad.cpp @@ -0,0 +1,164 @@ +// $Id$ +// +// Toad - A jumping toad +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +#include + +#include "toad.hpp" +#include "random_generator.hpp" + +namespace { + const float VERTICAL_SPEED = -450; /**< y-speed when jumping */ + const float HORIZONTAL_SPEED = 320; /**< x-speed when jumping */ + const float RECOVER_TIME = 0.5; /**< time to stand still before starting a (new) jump */ + static const std::string HOP_SOUND = "sounds/hop.ogg"; +} + +Toad::Toad(const lisp::Lisp& reader) + : BadGuy(reader, "images/creatures/toad/toad.sprite") +{ + sound_manager->preload(HOP_SOUND); +} + +Toad::Toad(const Vector& pos, Direction d) + : BadGuy(pos, d, "images/creatures/toad/toad.sprite") +{ + sound_manager->preload(HOP_SOUND); +} + +void +Toad::write(lisp::Writer& writer) +{ + writer.start_list("toad"); + writer.write_float("x", start_position.x); + writer.write_float("y", start_position.y); + writer.end_list("toad"); +} + +void +Toad::activate() +{ + // initial state is JUMPING, because we might start airborne + state = JUMPING; + sprite->set_action(dir == LEFT ? "jumping-left" : "jumping-right"); +} + +void +Toad::set_state(ToadState newState) +{ + if (newState == IDLE) { + physic.set_velocity_x(0); + physic.set_velocity_y(0); + sprite->set_action(dir == LEFT ? "idle-left" : "idle-right"); + + recover_timer.start(RECOVER_TIME); + } else + if (newState == JUMPING) { + sprite->set_action(dir == LEFT ? "jumping-left" : "jumping-right"); + physic.set_velocity_x(dir == LEFT ? -HORIZONTAL_SPEED : HORIZONTAL_SPEED); + physic.set_velocity_y(VERTICAL_SPEED); + sound_manager->play( HOP_SOUND, get_pos()); + } else + if (newState == FALLING) { + Player* player = get_nearest_player(); + // face player + if (player && (player->get_bbox().p2.x < get_bbox().p1.x) && (dir == RIGHT)) dir = LEFT; + if (player && (player->get_bbox().p1.x > get_bbox().p2.x) && (dir == LEFT)) dir = RIGHT; + sprite->set_action(dir == LEFT ? "idle-left" : "idle-right"); + } + + state = newState; +} + +bool +Toad::collision_squished(GameObject& object) +{ + sprite->set_action(dir == LEFT ? "squished-left" : "squished-right"); + kill_squished(object); + return true; +} + +void +Toad::collision_solid(const CollisionHit& hit) +{ + // just default behaviour (i.e. stop at floor/walls) when squished + if (BadGuy::get_state() == STATE_SQUISHED) { + BadGuy::collision_solid(hit); + return; + } + + // ignore collisions while standing still + if(state == IDLE) { + return; + } + + // check if we hit left or right while moving in either direction + if(((physic.get_velocity_x() < 0) && hit.left) || ((physic.get_velocity_x() > 0) && hit.right)) { + /* + dir = dir == LEFT ? RIGHT : LEFT; + if (state == JUMPING) { + sprite->set_action(dir == LEFT ? "jumping-left" : "jumping-right"); + } else { + sprite->set_action(dir == LEFT ? "idle-left" : "idle-right"); + } + */ + physic.set_velocity_x(-0.25*physic.get_velocity_x()); + } + + // check if we hit the floor while falling + if ((state == FALLING) && hit.bottom) { + set_state(IDLE); + return; + } + + // check if we hit the roof while climbing + if ((state == JUMPING) && hit.top) { + physic.set_velocity_y(0); + } + +} + +HitResponse +Toad::collision_badguy(BadGuy& , const CollisionHit& hit) +{ + // behaviour for badguy collisions is the same as for collisions with solids + collision_solid(hit); + + return CONTINUE; +} + +void +Toad::active_update(float elapsed_time) +{ + BadGuy::active_update(elapsed_time); + + // change sprite when we are falling + if ((state == JUMPING) && (physic.get_velocity_y() > 0)) { + set_state(FALLING); + return; + } + + // jump when fully recovered + if ((state == IDLE) && (recover_timer.check())) { + set_state(JUMPING); + return; + } + +} + +IMPLEMENT_FACTORY(Toad, "toad") diff --git a/src/src/badguy/toad.hpp b/src/src/badguy/toad.hpp new file mode 100644 index 000000000..806716260 --- /dev/null +++ b/src/src/badguy/toad.hpp @@ -0,0 +1,57 @@ +// $Id$ +// +// Toad - A jumping toad +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#ifndef __TOAD_H__ +#define __TOAD_H__ + +#include "badguy.hpp" + +/** + * Badguy "Toad" - A jumping toad + */ +class Toad : public BadGuy +{ +public: + Toad(const lisp::Lisp& reader); + Toad(const Vector& pos, Direction d); + + void activate(); + void write(lisp::Writer& writer); + void collision_solid(const CollisionHit& hit); + HitResponse collision_badguy(BadGuy& badguy, const CollisionHit& hit); + bool collision_squished(GameObject& object); + void active_update(float elapsed_time); + + virtual Toad* clone() const { return new Toad(*this); } + +protected: + enum ToadState { + IDLE, + JUMPING, + FALLING + }; + + Timer recover_timer; + ToadState state; + + void set_state(ToadState newState); +}; + +#endif diff --git a/src/src/badguy/totem.cpp b/src/src/badguy/totem.cpp new file mode 100644 index 000000000..139aa14df --- /dev/null +++ b/src/src/badguy/totem.cpp @@ -0,0 +1,283 @@ +// $Id$ +// +// SuperTux - "Totem" Badguy +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#include + +#include "totem.hpp" +#include "log.hpp" + +static const float WALKSPEED = 100; +static const float JUMP_ON_SPEED_Y = -400; +static const float JUMP_OFF_SPEED_Y = -500; +static const std::string LAND_ON_TOTEM_SOUND = "sounds/totem.ogg"; + +Totem::Totem(const lisp::Lisp& reader) + : BadGuy(reader, "images/creatures/totem/totem.sprite") +{ + carrying = 0; + carried_by = 0; + sound_manager->preload( LAND_ON_TOTEM_SOUND ); +} + +Totem::Totem(const Totem& other) + : BadGuy(other), carrying(other.carrying), carried_by(other.carried_by) +{ + sound_manager->preload( LAND_ON_TOTEM_SOUND ); +} + +Totem::~Totem() +{ + if (carrying) carrying->jump_off(); + if (carried_by) jump_off(); +} + +bool +Totem::updatePointers(const GameObject* from_object, GameObject* to_object) +{ + if (from_object == carrying) { + carrying = dynamic_cast(to_object); + return true; + } + if (from_object == carried_by) { + carried_by = dynamic_cast(to_object); + return true; + } + return false; +} + +void +Totem::write(lisp::Writer& writer) +{ + writer.start_list("totem"); + + writer.write_float("x", start_position.x); + writer.write_float("y", start_position.y); + + writer.end_list("totem"); +} + +void +Totem::activate() +{ + if (!carried_by) { + physic.set_velocity_x(dir == LEFT ? -WALKSPEED : WALKSPEED); + sprite->set_action(dir == LEFT ? "walking-left" : "walking-right"); + return; + } else { + synchronize_with(carried_by); + sprite->set_action(dir == LEFT ? "stacked-left" : "stacked-right"); + return; + } +} + +void +Totem::active_update(float elapsed_time) +{ + BadGuy::active_update(elapsed_time); + + if (!carried_by) { + if (on_ground() && might_fall()) + { + dir = (dir == LEFT ? RIGHT : LEFT); + activate(); + } + + Sector* s = Sector::current(); + if (s) { + // jump a bit if we find a suitable totem + for (std::vector::iterator i = s->moving_objects.begin(); i != s->moving_objects.end(); i++) { + Totem* t = dynamic_cast(*i); + if (!t) continue; + + // skip if we are not approaching each other + if (!((this->dir == LEFT) && (t->dir == RIGHT))) continue; + + Vector p1 = this->get_pos(); + Vector p2 = t->get_pos(); + + // skip if not on same height + float dy = (p1.y - p2.y); + if (fabsf(dy - 0) > 2) continue; + + // skip if too far away + float dx = (p1.x - p2.x); + if (fabsf(dx - 128) > 2) continue; + + physic.set_velocity_y(JUMP_ON_SPEED_Y); + p1.y -= 1; + this->set_pos(p1); + break; + } + } + } + + if (carried_by) { + this->synchronize_with(carried_by); + } + + if (carrying) { + carrying->synchronize_with(this); + } + +} + +bool +Totem::collision_squished(GameObject& object) +{ + if (carrying) carrying->jump_off(); + if (carried_by) { + Player* player = dynamic_cast(&object); + if (player) player->bounce(*this); + jump_off(); + } + + sprite->set_action(dir == LEFT ? "squished-left" : "squished-right"); + bbox.set_size(sprite->get_current_hitbox_width(), sprite->get_current_hitbox_height()); + + kill_squished(object); + return true; +} + +void +Totem::collision_solid(const CollisionHit& hit) +{ + update_on_ground_flag(hit); + + // if we are being carried around, pass event to bottom of stack and ignore it + if (carried_by) { + carried_by->collision_solid(hit); + return; + } + + // If we hit something from above or below: stop moving in this direction + if (hit.top || hit.bottom) { + physic.set_velocity_y(0); + } + + // If we are hit from the direction we are facing: turn around + if (hit.left && (dir == LEFT)) { + dir = RIGHT; + activate(); + } + if (hit.right && (dir == RIGHT)) { + dir = LEFT; + activate(); + } +} + +HitResponse +Totem::collision_badguy(BadGuy& badguy, const CollisionHit& hit) +{ + // if we are being carried around, pass event to bottom of stack and ignore it + if (carried_by) { + carried_by->collision_badguy(badguy, hit); + return CONTINUE; + } + + // if we hit a Totem that is not from our stack: have our base jump on its top + Totem* totem = dynamic_cast(&badguy); + if (totem) { + Totem* thisBase = this; while (thisBase->carried_by) thisBase=thisBase->carried_by; + Totem* srcBase = totem; while (srcBase->carried_by) srcBase=srcBase->carried_by; + Totem* thisTop = this; while (thisTop->carrying) thisTop=thisTop->carrying; + if (srcBase != thisBase) { + srcBase->jump_on(thisTop); + } + } + + // If we are hit from the direction we are facing: turn around + if(hit.left && (dir == LEFT)) { + dir = RIGHT; + activate(); + } + if(hit.right && (dir == RIGHT)) { + dir = LEFT; + activate(); + } + + return CONTINUE; +} + +void +Totem::kill_fall() +{ + if (carrying) carrying->jump_off(); + if (carried_by) jump_off(); + + BadGuy::kill_fall(); +} + +void +Totem::jump_on(Totem* target) +{ + if (target->carrying) { + log_warning << "target is already carrying someone" << std::endl; + return; + } + + target->carrying = this; + + this->carried_by = target; + this->activate(); + bbox.set_size(sprite->get_current_hitbox_width(), sprite->get_current_hitbox_height()); + + sound_manager->play( LAND_ON_TOTEM_SOUND , get_pos()); + + + this->synchronize_with(target); +} + +void +Totem::jump_off() { + if (!carried_by) { + log_warning << "not carried by anyone" << std::endl; + return; + } + + carried_by->carrying = 0; + + this->carried_by = 0; + + this->activate(); + bbox.set_size(sprite->get_current_hitbox_width(), sprite->get_current_hitbox_height()); + + + physic.set_velocity_y(JUMP_OFF_SPEED_Y); +} + +void +Totem::synchronize_with(Totem* base) +{ + + if (dir != base->dir) { + dir = base->dir; + sprite->set_action(dir == LEFT ? "stacked-left" : "stacked-right"); + } + + Vector pos = base->get_pos(); + pos.y -= sprite->get_current_hitbox_height(); + set_pos(pos); + + physic.set_velocity_x(base->physic.get_velocity_x()); + physic.set_velocity_y(base->physic.get_velocity_y()); +} + + +IMPLEMENT_FACTORY(Totem, "totem") diff --git a/src/src/badguy/totem.hpp b/src/src/badguy/totem.hpp new file mode 100644 index 000000000..682fe456c --- /dev/null +++ b/src/src/badguy/totem.hpp @@ -0,0 +1,58 @@ +// $Id$ +// +// SuperTux - "Totem" Badguy +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#ifndef __TOTEM_H__ +#define __TOTEM_H__ + +#include "badguy.hpp" + +/** + * "Totem" Badguy - A variable-height stack of wooden blocks + */ +class Totem : public BadGuy +{ +public: + Totem(const lisp::Lisp& reader); + Totem(const Totem& totem); + ~Totem(); + + void activate(); + void active_update(float elapsed_time); + void write(lisp::Writer& writer); + void collision_solid(const CollisionHit& hit); + HitResponse collision_badguy(BadGuy& badguy, const CollisionHit& hit); + + virtual Totem* clone() const { return new Totem(*this); } + virtual bool updatePointers(const GameObject* from_object, GameObject* to_object); + +protected: + Totem* carrying; /**< Totem we are currently carrying (or 0) */ + Totem* carried_by; /**< Totem by which we are currently carried (or 0) */ + + bool collision_squished(GameObject& object); + void kill_fall(); + + void jump_on(Totem* target); /**< jump on target */ + void jump_off(); /**< jump off current base */ + + void synchronize_with(Totem* baseTotem); /**< synchronize position and movement with baseTotem */ +}; + +#endif diff --git a/src/src/badguy/treewillowisp.cpp b/src/src/badguy/treewillowisp.cpp new file mode 100644 index 000000000..e1eb02721 --- /dev/null +++ b/src/src/badguy/treewillowisp.cpp @@ -0,0 +1,157 @@ +// $Id$ +// +// SuperTux - "Will-O-Wisp" Badguy +// Copyright (C) 2007 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +#include + +#include "treewillowisp.hpp" +#include "ghosttree.hpp" +#include "object/lantern.hpp" + +static const std::string SOUNDFILE = "sounds/willowisp.wav"; +static const float SUCKSPEED = 25; + +TreeWillOWisp::TreeWillOWisp(GhostTree* tree, const Vector& pos, + float radius, float speed) + : BadGuy(Vector(0, 0), "images/creatures/willowisp/willowisp.sprite", + LAYER_OBJECTS - 20), was_sucked(false), mystate(STATE_DEFAULT), tree(tree) +{ + treepos_delta = pos; + sound_manager->preload(SOUNDFILE); + + this->radius = radius; + this->angle = 0; + this->speed = speed; + start_position = tree->get_pos() + treepos_delta; +} + +TreeWillOWisp::~TreeWillOWisp() +{ +} + +void +TreeWillOWisp::activate() +{ + sound_source.reset(sound_manager->create_sound_source(SOUNDFILE)); + sound_source->set_position(get_pos()); + sound_source->set_looping(true); + sound_source->set_gain(2.0); + sound_source->set_reference_distance(32); + sound_source->play(); + + set_group(COLGROUP_MOVING); +} + +void +TreeWillOWisp::vanish() +{ + mystate = STATE_VANISHING; + sprite->set_action("vanishing", 1); + set_group(COLGROUP_DISABLED); +} + +void +TreeWillOWisp::start_sucking(Vector suck_target) +{ + mystate = STATE_SUCKED; + this->suck_target = suck_target; + was_sucked = true; +} + +HitResponse +TreeWillOWisp::collision_player(Player& player, const CollisionHit& hit) +{ + //TODO: basically a no-op. Remove if this doesn't change. + return BadGuy::collision_player(player, hit); +} + +bool +TreeWillOWisp::collides(GameObject& other, const CollisionHit& ) { + Lantern* lantern = dynamic_cast(&other); + if (lantern && lantern->is_open()) + return true; + if (dynamic_cast(&other)) + return true; + + return false; +} + +void +TreeWillOWisp::draw(DrawingContext& context) +{ + sprite->draw(context, get_pos(), layer); + + context.push_target(); + context.set_target(DrawingContext::LIGHTMAP); + + sprite->draw(context, get_pos(), layer); + + context.pop_target(); +} + +void +TreeWillOWisp::active_update(float elapsed_time) +{ + // remove TreeWillOWisp if it has completely vanished + if (mystate == STATE_VANISHING) { + if(sprite->animation_done()) { + remove_me(); + tree->willowisp_died(this); + } + return; + } + + if (mystate == STATE_SUCKED) { + Vector dir = suck_target - get_pos(); + if(dir.norm() < 5) { + vanish(); + return; + } + Vector newpos = get_pos() + dir * elapsed_time; + movement = newpos - get_pos(); + return; + } + + angle = fmodf(angle + elapsed_time * speed, (float) (2*M_PI)); + Vector newpos(tree->get_pos() + treepos_delta + Vector(sin(angle) * radius, 0)); + movement = newpos - get_pos(); + float sizemod = cos(angle) * 0.8f; + /* TODO: modify sprite size */ + + sound_source->set_position(get_pos()); + + if(sizemod < 0) { + layer = LAYER_OBJECTS + 5; + } else { + layer = LAYER_OBJECTS - 20; + } +} + +void +TreeWillOWisp::set_color(const Color& color) +{ + this->color = color; + sprite->set_color(color); +} + +Color +TreeWillOWisp::get_color() const +{ + return color; +} + diff --git a/src/src/badguy/treewillowisp.hpp b/src/src/badguy/treewillowisp.hpp new file mode 100644 index 000000000..c95cbc864 --- /dev/null +++ b/src/src/badguy/treewillowisp.hpp @@ -0,0 +1,76 @@ +// $Id$ +// +// SuperTux - "Will-O-Wisp" Badguy +// Copyright (C) 2007 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#ifndef __TREEWILLOWISP_H__ +#define __TREEWILLOWISP_H__ + +#include "badguy.hpp" + +class GhostTree; + +class TreeWillOWisp : public BadGuy +{ +public: + TreeWillOWisp(GhostTree* tree, const Vector& pos, float radius, float speed); + virtual ~TreeWillOWisp(); + + void activate(); + + /** + * make TreeWillOWisp vanish + */ + void vanish(); + void start_sucking(Vector suck_target); + bool was_sucked; + + void active_update(float elapsed_time); + void set_color(const Color& color); + Color get_color() const; + + virtual bool is_flammable() const { return false; } + virtual bool is_freezable() const { return false; } + virtual void kill_fall() { vanish(); } + + virtual void draw(DrawingContext& context); + +protected: + virtual bool collides(GameObject& other, const CollisionHit& hit); + HitResponse collision_player(Player& player, const CollisionHit& hit); + +private: + enum MyState { + STATE_DEFAULT, STATE_VANISHING, STATE_SUCKED + }; + MyState mystate; + + Color color; + float angle; + float radius; + float speed; + + std::auto_ptr sound_source; + Vector treepos_delta; + GhostTree* tree; + + Vector suck_target; +}; + +#endif + diff --git a/src/src/badguy/walking_badguy.cpp b/src/src/badguy/walking_badguy.cpp new file mode 100644 index 000000000..3b8f7ab34 --- /dev/null +++ b/src/src/badguy/walking_badguy.cpp @@ -0,0 +1,146 @@ +// $Id$ +// +// SuperTux - WalkingBadguy +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "walking_badguy.hpp" +#include "log.hpp" +#include "timer.hpp" + +WalkingBadguy::WalkingBadguy(const Vector& pos, const std::string& sprite_name, const std::string& walk_left_action, const std::string& walk_right_action, int layer) + : BadGuy(pos, sprite_name, layer), walk_left_action(walk_left_action), walk_right_action(walk_right_action), walk_speed(80), max_drop_height(-1) +{ +} + +WalkingBadguy::WalkingBadguy(const Vector& pos, Direction direction, const std::string& sprite_name, const std::string& walk_left_action, const std::string& walk_right_action, int layer) + : BadGuy(pos, direction, sprite_name, layer), walk_left_action(walk_left_action), walk_right_action(walk_right_action), walk_speed(80), max_drop_height(-1) +{ +} + +WalkingBadguy::WalkingBadguy(const lisp::Lisp& reader, const std::string& sprite_name, const std::string& walk_left_action, const std::string& walk_right_action, int layer) + : BadGuy(reader, sprite_name, layer), walk_left_action(walk_left_action), walk_right_action(walk_right_action), walk_speed(80), max_drop_height(-1) +{ +} + +void +WalkingBadguy::write(lisp::Writer& writer) +{ + writer.write_float("x", start_position.x); + writer.write_float("y", start_position.y); +} + +void +WalkingBadguy::activate() +{ + if(frozen) + return; + sprite->set_action(dir == LEFT ? walk_left_action : walk_right_action); + bbox.set_size(sprite->get_current_hitbox_width(), sprite->get_current_hitbox_height()); + physic.set_velocity_x(dir == LEFT ? -walk_speed : walk_speed); +} + +void +WalkingBadguy::active_update(float elapsed_time) +{ + BadGuy::active_update(elapsed_time); + + if (max_drop_height > -1) { + if (on_ground() && might_fall(max_drop_height+1)) + { + turn_around(); + } + } + +} + +void +WalkingBadguy::collision_solid(const CollisionHit& hit) +{ + + update_on_ground_flag(hit); + + if (hit.top) { + if (physic.get_velocity_y() < 0) physic.set_velocity_y(0); + } + if (hit.bottom) { + if (physic.get_velocity_y() > 0) physic.set_velocity_y(0); + } + + if ((hit.left && (hit.slope_normal.y == 0) && (dir == LEFT)) || (hit.right && (hit.slope_normal.y == 0) && (dir == RIGHT))) { + turn_around(); + } + +} + +HitResponse +WalkingBadguy::collision_badguy(BadGuy& , const CollisionHit& hit) +{ + + if ((hit.left && (dir == LEFT)) || (hit.right && (dir == RIGHT))) { + turn_around(); + } + + return CONTINUE; +} + +void +WalkingBadguy::turn_around() +{ + if(frozen) + return; + dir = dir == LEFT ? RIGHT : LEFT; + sprite->set_action(dir == LEFT ? walk_left_action : walk_right_action); + physic.set_velocity_x(-physic.get_velocity_x()); + + // if we get dizzy, we fall off the screen + if (turn_around_timer.started()) { + if (turn_around_counter++ > 10) kill_fall(); + } else { + turn_around_timer.start(1); + turn_around_counter = 0; + } + +} + +void +WalkingBadguy::freeze() +{ + BadGuy::freeze(); + physic.set_velocity_x(0); +} + +void +WalkingBadguy::unfreeze() +{ + BadGuy::unfreeze(); + WalkingBadguy::activate(); +} + + +float +WalkingBadguy::get_velocity_y() const +{ + return physic.get_velocity_y(); +} + +void +WalkingBadguy::set_velocity_y(float vy) +{ + physic.set_velocity_y(vy); +} diff --git a/src/src/badguy/walking_badguy.hpp b/src/src/badguy/walking_badguy.hpp new file mode 100644 index 000000000..5d8503988 --- /dev/null +++ b/src/src/badguy/walking_badguy.hpp @@ -0,0 +1,59 @@ +// $Id$ +// +// SuperTux - WalkingBadguy +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __WALKING_BADGUY_H__ +#define __WALKING_BADGUY_H__ + +#include "badguy.hpp" + +class Timer; + +/** + * Baseclass for a Badguy that just walks around. + */ +class WalkingBadguy : public BadGuy +{ +public: + WalkingBadguy(const Vector& pos, const std::string& sprite_name, const std::string& walk_left_action, const std::string& walk_right_action, int layer = LAYER_OBJECTS); + WalkingBadguy(const Vector& pos, Direction direction, const std::string& sprite_name, const std::string& walk_left_action, const std::string& walk_right_action, int layer = LAYER_OBJECTS); + WalkingBadguy(const lisp::Lisp& reader, const std::string& sprite_name, const std::string& walk_left_action, const std::string& walk_right_action, int layer = LAYER_OBJECTS); + + void activate(); + void write(lisp::Writer& writer); + void active_update(float elapsed_time); + void collision_solid(const CollisionHit& hit); + HitResponse collision_badguy(BadGuy& badguy, const CollisionHit& hit); + void freeze(); + void unfreeze(); + + float get_velocity_y() const; + void set_velocity_y(float vy); + +protected: + void turn_around(); + + std::string walk_left_action; + std::string walk_right_action; + float walk_speed; + int max_drop_height; /**< Maximum height of drop before we will turn around, or -1 to just drop from any ledge */ + Timer turn_around_timer; + int turn_around_counter; /**< counts number of turns since turn_around_timer was started */ +}; + +#endif diff --git a/src/src/badguy/walkingleaf.cpp b/src/src/badguy/walkingleaf.cpp new file mode 100644 index 000000000..a05744111 --- /dev/null +++ b/src/src/badguy/walkingleaf.cpp @@ -0,0 +1,48 @@ +// $Id$ +// +// SuperTux - Walking Leaf +// Copyright (C) 2006 Wolfgang Becker +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "walkingleaf.hpp" +#include "random_generator.hpp" +#include "object/sprite_particle.hpp" + +WalkingLeaf::WalkingLeaf(const lisp::Lisp& reader) + : WalkingBadguy(reader, "images/creatures/walkingleaf/walkingleaf.sprite", "left", "right") +{ + walk_speed = 60; + max_drop_height = 16; +} + +WalkingLeaf::WalkingLeaf(const Vector& pos, Direction d) + : WalkingBadguy(pos, d, "images/creatures/walkingleaf/walkingleaf.sprite", "left", "right") +{ + walk_speed = 60; + max_drop_height = 16; +} + +bool +WalkingLeaf::collision_squished(GameObject& object) +{ + sprite->set_action(dir == LEFT ? "squished-left" : "squished-right"); + kill_squished(object); + return true; +} + +IMPLEMENT_FACTORY(WalkingLeaf, "walkingleaf") diff --git a/src/src/badguy/walkingleaf.hpp b/src/src/badguy/walkingleaf.hpp new file mode 100644 index 000000000..e2b84b1e8 --- /dev/null +++ b/src/src/badguy/walkingleaf.hpp @@ -0,0 +1,41 @@ +// $Id$ +// +// SuperTux - Walking Leaf +// Copyright (C) 2006 Wolfgang Becker +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __WALKINGLEAF_H__ +#define __WALKINGLEAF_H__ + +#include "walking_badguy.hpp" + +/* + * Easy to kill badguy that does not jump down from it's ledge. + */ +class WalkingLeaf : public WalkingBadguy +{ +public: + WalkingLeaf(const lisp::Lisp& reader); + WalkingLeaf(const Vector& pos, Direction d); + + virtual WalkingLeaf* clone() const { return new WalkingLeaf(*this); } + +protected: + bool collision_squished(GameObject& object); + +}; + +#endif diff --git a/src/src/badguy/willowisp.cpp b/src/src/badguy/willowisp.cpp new file mode 100644 index 000000000..cc50d6bcd --- /dev/null +++ b/src/src/badguy/willowisp.cpp @@ -0,0 +1,279 @@ +// $Id$ +// +// SuperTux - "Will-O-Wisp" Badguy +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +#include + +#include "willowisp.hpp" +#include "log.hpp" +#include "game_session.hpp" +#include "object/lantern.hpp" +#include "object/player.hpp" +#include "scripting/squirrel_util.hpp" + +static const float FLYSPEED = 64; /**< speed in px per second */ +static const float TRACK_RANGE = 384; /**< at what distance to start tracking the player */ +static const float VANISH_RANGE = 512; /**< at what distance to stop tracking and vanish */ +static const std::string SOUNDFILE = "sounds/willowisp.wav"; + +WillOWisp::WillOWisp(const lisp::Lisp& reader) + : BadGuy(reader, "images/creatures/willowisp/willowisp.sprite", LAYER_FLOATINGOBJECTS), mystate(STATE_IDLE), target_sector("main"), target_spawnpoint("main") +{ + bool running = false; + flyspeed = FLYSPEED; + track_range = TRACK_RANGE; + vanish_range = VANISH_RANGE; + + reader.get("sector", target_sector); + reader.get("spawnpoint", target_spawnpoint); + reader.get("name", name); + reader.get("flyspeed", flyspeed); + reader.get("track-range", track_range); + reader.get("vanish-range", vanish_range); + reader.get("hit-script", hit_script); + reader.get("running", running); + + const lisp::Lisp* pathLisp = reader.get_lisp("path"); + if(pathLisp != NULL) { + path.reset(new Path()); + path->read(*pathLisp); + walker.reset(new PathWalker(path.get(), running)); + if(running) + mystate = STATE_PATHMOVING_TRACK; + } + + countMe = false; + sound_manager->preload(SOUNDFILE); +} + +void +WillOWisp::draw(DrawingContext& context) +{ + sprite->draw(context, get_pos(), layer); + + context.push_target(); + context.set_target(DrawingContext::LIGHTMAP); + + sprite->draw(context, get_pos(), layer); + + context.pop_target(); +} + +void +WillOWisp::active_update(float elapsed_time) +{ + Player* player = get_nearest_player(); + if (!player) return; + Vector p1 = this->get_pos() + (this->get_bbox().p2 - this->get_bbox().p1) / 2; + Vector p2 = player->get_pos() + (player->get_bbox().p2 - player->get_bbox().p1) / 2; + Vector dist = (p2 - p1); + + switch(mystate) { + case STATE_STOPPED: + break; + + case STATE_IDLE: + if (dist.norm() <= track_range) { + mystate = STATE_TRACKING; + } + break; + + case STATE_TRACKING: + if (dist.norm() <= vanish_range) { + Vector dir = dist.unit(); + movement = dir * elapsed_time * flyspeed; + } else { + vanish(); + } + sound_source->set_position(get_pos()); + break; + + case STATE_WARPING: + if(sprite->animation_done()) { + remove_me(); + } + + case STATE_VANISHING: { + Vector dir = dist.unit(); + movement = dir * elapsed_time * flyspeed; + if(sprite->animation_done()) { + remove_me(); + } + break; + } + + case STATE_PATHMOVING: + case STATE_PATHMOVING_TRACK: + if(walker.get() == NULL) + return; + movement = walker->advance(elapsed_time) - get_pos(); + if(mystate == STATE_PATHMOVING_TRACK && dist.norm() <= track_range) { + mystate = STATE_TRACKING; + } + break; + + default: + assert(false); + } +} + +void +WillOWisp::activate() +{ + sprite->set_action("idle"); + + sound_source.reset(sound_manager->create_sound_source(SOUNDFILE)); + sound_source->set_position(get_pos()); + sound_source->set_looping(true); + sound_source->set_gain(2.0); + sound_source->set_reference_distance(32); + sound_source->play(); +} + +void +WillOWisp::deactivate() +{ + sound_source.reset(NULL); + + switch (mystate) { + case STATE_STOPPED: + case STATE_IDLE: + case STATE_PATHMOVING: + case STATE_PATHMOVING_TRACK: + break; + case STATE_TRACKING: + mystate = STATE_IDLE; + break; + case STATE_WARPING: + case STATE_VANISHING: + remove_me(); + break; + } +} + +void +WillOWisp::vanish() +{ + mystate = STATE_VANISHING; + sprite->set_action("vanishing", 1); + set_group(COLGROUP_DISABLED); +} + +bool +WillOWisp::collides(GameObject& other, const CollisionHit& ) { + Lantern* lantern = dynamic_cast(&other); + + if (lantern && lantern->is_open()) + return true; + + if (dynamic_cast(&other)) + return true; + + return false; +} + +HitResponse +WillOWisp::collision_player(Player& player, const CollisionHit& ) { + if(player.is_invincible()) + return ABORT_MOVE; + + if (mystate != STATE_TRACKING) + return ABORT_MOVE; + + mystate = STATE_WARPING; + sprite->set_action("warping", 1); + + if(hit_script != "") { + std::istringstream stream(hit_script); + Sector::current()->run_script(stream, "hit-script"); + } else { + GameSession::current()->respawn(target_sector, target_spawnpoint); + } + sound_manager->play("sounds/warp.wav"); + + return CONTINUE; +} + +void +WillOWisp::goto_node(int node_no) +{ + walker->goto_node(node_no); + if(mystate != STATE_PATHMOVING && mystate != STATE_PATHMOVING_TRACK) { + mystate = STATE_PATHMOVING; + } +} + +void +WillOWisp::start_moving() +{ + walker->start_moving(); +} + +void +WillOWisp::stop_moving() +{ + walker->stop_moving(); +} + +void +WillOWisp::set_state(const std::string& new_state) +{ + if(new_state == "stopped") { + mystate = STATE_STOPPED; + } else if(new_state == "idle") { + mystate = STATE_IDLE; + } else if(new_state == "move_path") { + mystate = STATE_PATHMOVING; + walker->start_moving(); + } else if(new_state == "move_path_track") { + mystate = STATE_PATHMOVING_TRACK; + walker->start_moving(); + } else if(new_state == "normal") { + mystate = STATE_IDLE; + } else if(new_state == "vanish") { + vanish(); + } else { + std::ostringstream msg; + msg << "Can't set unknown willowisp state '" << new_state << "', should " + "be stopped, move_path, move_path_track or normal"; + throw new std::runtime_error(msg.str()); + } +} + +void +WillOWisp::expose(HSQUIRRELVM vm, SQInteger table_idx) +{ + if (name.empty()) + return; + + std::cout << "Expose me '" << name << "'\n"; + Scripting::WillOWisp* interface = static_cast (this); + expose_object(vm, table_idx, interface, name); +} + +void +WillOWisp::unexpose(HSQUIRRELVM vm, SQInteger table_idx) +{ + if (name.empty()) + return; + + std::cout << "UnExpose me '" << name << "'\n"; + Scripting::unexpose_object(vm, table_idx, name); +} + +IMPLEMENT_FACTORY(WillOWisp, "willowisp") diff --git a/src/src/badguy/willowisp.hpp b/src/src/badguy/willowisp.hpp new file mode 100644 index 000000000..2ccbc685b --- /dev/null +++ b/src/src/badguy/willowisp.hpp @@ -0,0 +1,84 @@ +// $Id$ +// +// SuperTux - "Will-O-Wisp" Badguy +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#ifndef __WILLOWISP_H__ +#define __WILLOWISP_H__ + +#include "badguy.hpp" +#include "object/path.hpp" +#include "object/path_walker.hpp" +#include "script_interface.hpp" +#include "scripting/willowisp.hpp" + +class WillOWisp : public BadGuy, public Scripting::WillOWisp, + public ScriptInterface +{ +public: + WillOWisp(const lisp::Lisp& reader); + + void activate(); + void deactivate(); + + void active_update(float elapsed_time); + virtual bool is_flammable() const { return false; } + virtual bool is_freezable() const { return false; } + virtual void kill_fall() { vanish(); } + + /** + * make WillOWisp vanish + */ + void vanish(); + + virtual void draw(DrawingContext& context); + + virtual void goto_node(int node_no); + virtual void set_state(const std::string& state); + virtual void start_moving(); + virtual void stop_moving(); + + virtual void expose(HSQUIRRELVM vm, SQInteger table_idx); + virtual void unexpose(HSQUIRRELVM vm, SQInteger table_idx); + +protected: + virtual bool collides(GameObject& other, const CollisionHit& hit); + HitResponse collision_player(Player& player, const CollisionHit& hit); + +private: + enum MyState { + STATE_STOPPED, STATE_IDLE, STATE_TRACKING, STATE_VANISHING, STATE_WARPING, + STATE_PATHMOVING, STATE_PATHMOVING_TRACK + }; + MyState mystate; + + std::string target_sector; + std::string target_spawnpoint; + std::string hit_script; + + std::auto_ptr sound_source; + + std::auto_ptr path; + std::auto_ptr walker; + + float flyspeed; + float track_range; + float vanish_range; +}; + +#endif diff --git a/src/src/badguy/yeti.cpp b/src/src/badguy/yeti.cpp new file mode 100644 index 000000000..79959257d --- /dev/null +++ b/src/src/badguy/yeti.cpp @@ -0,0 +1,324 @@ +// $Id$ +// +// SuperTux - Boss "Yeti" +// Copyright (C) 2005 Matthias Braun +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +#include + +#include +#include +#include +#include "yeti.hpp" +#include "object/camera.hpp" +#include "yeti_stalactite.hpp" +#include "bouncing_snowball.hpp" +#include "game_session.hpp" +#include "level.hpp" + +namespace { + const float JUMP_DOWN_VX = 250; /**< horizontal speed while jumping off the dais */ + const float JUMP_DOWN_VY = -250; /**< vertical speed while jumping off the dais */ + + const float RUN_VX = 350; /**< horizontal speed while running */ + + const float JUMP_UP_VX = 350; /**< horizontal speed while jumping on the dais */ + const float JUMP_UP_VY = -800; /**< vertical speed while jumping on the dais */ + + const float STOMP_VY = -250; /** vertical speed while stomping on the dais */ + + const float LEFT_STAND_X = 16; /**< x-coordinate of left dais' end position */ + const float RIGHT_STAND_X = 800-60-16; /**< x-coordinate of right dais' end position */ + const float LEFT_JUMP_X = LEFT_STAND_X+224; /**< x-coordinate of from where to jump on the left dais */ + const float RIGHT_JUMP_X = RIGHT_STAND_X-224; /**< x-coordinate of from where to jump on the right dais */ + const float STOMP_WAIT = .5; /**< time we stay on the dais before jumping again */ + const float SAFE_TIME = .5; /**< the time we are safe when tux just hit us */ + const int INITIAL_HITPOINTS = 3; /**< number of hits we can take */ + + const float SQUISH_TIME = 5; +} + +Yeti::Yeti(const lisp::Lisp& reader) + : BadGuy(reader, "images/creatures/yeti/yeti.sprite") +{ + hit_points = INITIAL_HITPOINTS; + countMe = false; + sound_manager->preload("sounds/yeti_gna.wav"); + sound_manager->preload("sounds/yeti_roar.wav"); + hud_head.reset(new Surface("images/creatures/yeti/hudlife.png")); +} + +Yeti::~Yeti() +{ +} + +void +Yeti::activate() +{ + dir = RIGHT; + jump_down(); +} + +void +Yeti::draw(DrawingContext& context) +{ + // we blink when we are safe + if(safe_timer.started() && size_t(game_time*40)%2) + return; + + draw_hit_points(context); + + BadGuy::draw(context); +} + +void +Yeti::draw_hit_points(DrawingContext& context) +{ + int i; + + Surface *hh = hud_head.get(); + if (!hh) + return; + + context.push_transform(); + context.set_translation(Vector(0, 0)); + + for (i = 0; i < hit_points; ++i) + { + context.draw_surface(hh, Vector(BORDER_X + (i * hh->get_width()), BORDER_Y + 1), LAYER_FOREGROUND1); + } + + context.pop_transform(); +} + +void +Yeti::active_update(float elapsed_time) +{ + switch(state) { + case JUMP_DOWN: + physic.set_velocity_x((dir==RIGHT)?+JUMP_DOWN_VX:-JUMP_DOWN_VX); + break; + case RUN: + physic.set_velocity_x((dir==RIGHT)?+RUN_VX:-RUN_VX); + if (((dir == RIGHT) && (get_pos().x >= RIGHT_JUMP_X)) || ((dir == LEFT) && (get_pos().x <= LEFT_JUMP_X))) jump_up(); + break; + case JUMP_UP: + physic.set_velocity_x((dir==RIGHT)?+JUMP_UP_VX:-JUMP_UP_VX); + if (((dir == RIGHT) && (get_pos().x >= RIGHT_STAND_X)) || ((dir == LEFT) && (get_pos().x <= LEFT_STAND_X))) be_angry(); + break; + case BE_ANGRY: + if(state_timer.check()) { + sound_manager->play("sounds/yeti_gna.wav"); + physic.set_velocity_y(STOMP_VY); + sprite->set_action((dir==RIGHT)?"stomp-right":"stomp-left"); + } + break; + case SQUISHED: + if (state_timer.check()) { + remove_me(); + } + break; + } + + movement = physic.get_movement(elapsed_time); +} + +void +Yeti::jump_down() +{ + sprite->set_action((dir==RIGHT)?"jump-right":"jump-left"); + physic.set_velocity_x((dir==RIGHT)?(+JUMP_DOWN_VX):(-JUMP_DOWN_VX)); + physic.set_velocity_y(JUMP_DOWN_VY); + state = JUMP_DOWN; +} + +void +Yeti::run() +{ + sprite->set_action((dir==RIGHT)?"run-right":"run-left"); + physic.set_velocity_x((dir==RIGHT)?(+RUN_VX):(-RUN_VX)); + physic.set_velocity_y(0); + state = RUN; +} + +void +Yeti::jump_up() +{ + sprite->set_action((dir==RIGHT)?"jump-right":"jump-left"); + physic.set_velocity_x((dir==RIGHT)?(+JUMP_UP_VX):(-JUMP_UP_VX)); + physic.set_velocity_y(JUMP_UP_VY); + state = JUMP_UP; +} + +void +Yeti::be_angry() +{ + //turn around + dir = (dir==RIGHT)?LEFT:RIGHT; + + sprite->set_action((dir==RIGHT)?"stand-right":"stand-left"); + physic.set_velocity_x(0); + physic.set_velocity_y(0); + if (hit_points < INITIAL_HITPOINTS) summon_snowball(); + stomp_count = 0; + state = BE_ANGRY; + state_timer.start(STOMP_WAIT); +} + +void +Yeti::summon_snowball() +{ + Sector::current()->add_object(new BouncingSnowball(Vector(get_pos().x+(dir == RIGHT ? 64 : -64), get_pos().y), dir)); +} + +bool +Yeti::collision_squished(GameObject& object) +{ + kill_squished(object); + + return true; +} + +void +Yeti::kill_squished(GameObject& object) +{ + Player* player = dynamic_cast(&object); + if (player) { + player->bounce(*this); + take_hit(*player); + } +} + +void Yeti::take_hit(Player& ) +{ + if(safe_timer.started()) + return; + + sound_manager->play("sounds/yeti_roar.wav"); + hit_points--; + + if(hit_points <= 0) { + // We're dead + physic.enable_gravity(true); + physic.set_velocity_x(0); + physic.set_velocity_y(0); + + state = SQUISHED; + state_timer.start(SQUISH_TIME); + set_group(COLGROUP_MOVING_ONLY_STATIC); + sprite->set_action("dead"); + + if (countMe) Sector::current()->get_level()->stats.badguys++; + + if(dead_script != "") { + std::istringstream stream(dead_script); + Sector::current()->run_script(stream, "Yeti - dead-script"); + } + } + else { + safe_timer.start(SAFE_TIME); + } +} + +void +Yeti::kill_fall() +{ + // shooting bullets or being invincible won't work :) + take_hit(*get_nearest_player()); // FIXME: debug only(?) +} + +void +Yeti::write(lisp::Writer& writer) +{ + writer.start_list("yeti"); + + writer.write_float("x", start_position.x); + writer.write_float("y", start_position.y); + + writer.end_list("yeti"); +} + +void +Yeti::drop_stalactite() +{ + // make a stalactite falling down and shake camera a bit + Sector::current()->camera->shake(.1f, 0, 10); + + YetiStalactite* nearest = 0; + float dist = FLT_MAX; + + Player* player = this->get_nearest_player(); + if (!player) return; + + Sector* sector = Sector::current(); + for(Sector::GameObjects::iterator i = sector->gameobjects.begin(); + i != sector->gameobjects.end(); ++i) { + YetiStalactite* stalactite = dynamic_cast (*i); + if(stalactite && stalactite->is_hanging()) { + float sdist + = fabsf(stalactite->get_pos().x - player->get_pos().x); + if(sdist < dist) { + nearest = stalactite; + dist = sdist; + } + } + } + + if(nearest) + nearest->start_shaking(); +} + +void +Yeti::collision_solid(const CollisionHit& hit) +{ + if(hit.top || hit.bottom) { + // hit floor or roof + physic.set_velocity_y(0); + switch (state) { + case JUMP_DOWN: + run(); + break; + case RUN: + break; + case JUMP_UP: + break; + case BE_ANGRY: + // we just landed + if(!state_timer.started()) { + sprite->set_action((dir==RIGHT)?"stand-right":"stand-left"); + stomp_count++; + drop_stalactite(); + + // go to other side after 3 jumps + if(stomp_count == 3) { + jump_down(); + } else { + // jump again + state_timer.start(STOMP_WAIT); + } + } + break; + case SQUISHED: + break; + } + } else if(hit.left || hit.right) { + // hit wall + jump_up(); + } +} + +IMPLEMENT_FACTORY(Yeti, "yeti") diff --git a/src/src/badguy/yeti.hpp b/src/src/badguy/yeti.hpp new file mode 100644 index 000000000..cfc6d055d --- /dev/null +++ b/src/src/badguy/yeti.hpp @@ -0,0 +1,72 @@ +// $Id$ +// +// SuperTux - Boss "Yeti" +// Copyright (C) 2005 Matthias Braun +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __YETI_H__ +#define __YETI_H__ + +#include + +#include "badguy.hpp" + +class Yeti : public BadGuy +{ +public: + Yeti(const lisp::Lisp& lisp); + ~Yeti(); + + void draw(DrawingContext& context); + void write(lisp::Writer& writer); + void activate(); + void active_update(float elapsed_time); + void collision_solid(const CollisionHit& hit); + bool collision_squished(GameObject& object); + void kill_squished(GameObject& object); + void kill_fall(); + + virtual Yeti* clone() const { return new Yeti((Yeti&)*this); } + +private: + void run(); + void jump_up(); + void be_angry(); + void drop_stalactite(); + void summon_snowball(); + void jump_down(); + + void draw_hit_points(DrawingContext& context); + + void take_hit(Player& player); + + enum YetiState { + JUMP_DOWN, + RUN, + JUMP_UP, + BE_ANGRY, + SQUISHED + }; + YetiState state; + Timer state_timer; + Timer safe_timer; + int stomp_count; + int hit_points; + std::auto_ptr hud_head; +}; + +#endif diff --git a/src/src/badguy/yeti_stalactite.cpp b/src/src/badguy/yeti_stalactite.cpp new file mode 100644 index 000000000..de27da329 --- /dev/null +++ b/src/src/badguy/yeti_stalactite.cpp @@ -0,0 +1,64 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include "yeti_stalactite.hpp" + +static const float SHAKE_TIME = .8f; + +YetiStalactite::YetiStalactite(const lisp::Lisp& lisp) + : Stalactite(lisp) +{ +} + +YetiStalactite::~YetiStalactite() +{ +} + +void +YetiStalactite::write(lisp::Writer& writer) +{ + writer.start_list("yeti_stalactite"); + writer.write_float("x", start_position.x); + writer.write_float("y", start_position.y); + writer.end_list("yeti_stalactite"); +} + +void +YetiStalactite::start_shaking() +{ + timer.start(SHAKE_TIME); + state = STALACTITE_SHAKING; +} + +bool +YetiStalactite::is_hanging() +{ + return state == STALACTITE_HANGING; +} + +void +YetiStalactite::active_update(float elapsed_time) +{ + if(state == STALACTITE_HANGING) + return; + + Stalactite::active_update(elapsed_time); +} + +IMPLEMENT_FACTORY(YetiStalactite, "yeti_stalactite") diff --git a/src/src/badguy/yeti_stalactite.hpp b/src/src/badguy/yeti_stalactite.hpp new file mode 100644 index 000000000..04897c0e8 --- /dev/null +++ b/src/src/badguy/yeti_stalactite.hpp @@ -0,0 +1,40 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +#ifndef __YETI_STALACTITE_H__ +#define __YETI_STALACTITE_H__ + +#include "stalactite.hpp" + +class YetiStalactite : public Stalactite +{ +public: + YetiStalactite(const lisp::Lisp& lisp); + virtual ~YetiStalactite(); + + void write(lisp::Writer& ); + void active_update(float elapsed_time); + void start_shaking(); + bool is_hanging(); + + virtual YetiStalactite* clone() const { return new YetiStalactite(*this); } +}; + +#endif diff --git a/src/src/badguy/zeekling.cpp b/src/src/badguy/zeekling.cpp new file mode 100644 index 000000000..64a45669e --- /dev/null +++ b/src/src/badguy/zeekling.cpp @@ -0,0 +1,196 @@ +// $Id$ +// +// Zeekling - flyer that swoops down when she spots the player +// Copyright (C) 2005 Matthias Braun +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#include +#include + +#include "zeekling.hpp" +#include "random_generator.hpp" + +Zeekling::Zeekling(const lisp::Lisp& reader) + : BadGuy(reader, "images/creatures/zeekling/zeekling.sprite"), last_player(0) +{ + state = FLYING; +} + +Zeekling::Zeekling(const Vector& pos, Direction d) + : BadGuy(pos, d, "images/creatures/zeekling/zeekling.sprite"), last_player(0) +{ + state = FLYING; +} + +void +Zeekling::write(lisp::Writer& writer) +{ + writer.start_list("zeekling"); + + writer.write_float("x", start_position.x); + writer.write_float("y", start_position.y); + + writer.end_list("zeekling"); +} + +void +Zeekling::activate() +{ + speed = systemRandom.rand(130, 171); + physic.set_velocity_x(dir == LEFT ? -speed : speed); + physic.enable_gravity(false); + sprite->set_action(dir == LEFT ? "left" : "right"); +} + +bool +Zeekling::collision_squished(GameObject& object) +{ + sprite->set_action(dir == LEFT ? "squished-left" : "squished-right"); + kill_squished(object); + kill_fall(); + return true; +} + +void +Zeekling::onBumpHorizontal() { + if (state == FLYING) { + dir = (dir == LEFT ? RIGHT : LEFT); + sprite->set_action(dir == LEFT ? "left" : "right"); + physic.set_velocity_x(dir == LEFT ? -speed : speed); + } else + if (state == DIVING) { + dir = (dir == LEFT ? RIGHT : LEFT); + state = FLYING; + sprite->set_action(dir == LEFT ? "left" : "right"); + physic.set_velocity_x(dir == LEFT ? -speed : speed); + physic.set_velocity_y(0); + } else + if (state == CLIMBING) { + dir = (dir == LEFT ? RIGHT : LEFT); + sprite->set_action(dir == LEFT ? "left" : "right"); + physic.set_velocity_x(dir == LEFT ? -speed : speed); + } else { + assert(false); + } +} + +void +Zeekling::onBumpVertical() { + if (state == FLYING) { + physic.set_velocity_y(0); + } else + if (state == DIVING) { + state = CLIMBING; + physic.set_velocity_y(-speed); + sprite->set_action(dir == LEFT ? "left" : "right"); + } else + if (state == CLIMBING) { + state = FLYING; + physic.set_velocity_y(0); + } +} + +void +Zeekling::collision_solid(const CollisionHit& hit) +{ + if(hit.top || hit.bottom) { + onBumpVertical(); + } else if(hit.left || hit.right) { + onBumpHorizontal(); + } +} + +/** + * linear prediction of player and badguy positions to decide if we should enter the DIVING state + */ +bool +Zeekling::should_we_dive() { + + const MovingObject* player = this->get_nearest_player(); + if (player && last_player && (player == last_player)) { + + // get positions, calculate movement + const Vector player_pos = player->get_pos(); + const Vector player_mov = (player_pos - last_player_pos); + const Vector self_pos = this->get_pos(); + const Vector self_mov = (self_pos - last_self_pos); + + // new vertical speed to test with + float vy = 2*fabsf(self_mov.x); + + // do not dive if we are not above the player + float height = player_pos.y - self_pos.y; + if (height <= 0) return false; + + // do not dive if we are too far above the player + if (height > 512) return false; + + // do not dive if we would not descend faster than the player + float relSpeed = vy - player_mov.y; + if (relSpeed <= 0) return false; + + // guess number of frames to descend to same height as player + float estFrames = height / relSpeed; + + // guess where the player would be at this time + float estPx = (player_pos.x + (estFrames * player_mov.x)); + + // guess where we would be at this time + float estBx = (self_pos.x + (estFrames * self_mov.x)); + + // near misses are OK, too + if (fabsf(estPx - estBx) < 8) return true; + } + + // update last player tracked, as well as our positions + last_player = player; + if (player) { + last_player_pos = player->get_pos(); + last_self_pos = this->get_pos(); + } + + return false; +} + +void +Zeekling::active_update(float elapsed_time) { + if (state == FLYING) { + if (should_we_dive()) { + state = DIVING; + physic.set_velocity_y(2*fabsf(physic.get_velocity_x())); + sprite->set_action(dir == LEFT ? "diving-left" : "diving-right"); + } + BadGuy::active_update(elapsed_time); + return; + } else if (state == DIVING) { + BadGuy::active_update(elapsed_time); + return; + } else if (state == CLIMBING) { + // stop climbing when we're back at initial height + if (get_pos().y <= start_position.y) { + state = FLYING; + physic.set_velocity_y(0); + } + BadGuy::active_update(elapsed_time); + return; + } else { + assert(false); + } +} + +IMPLEMENT_FACTORY(Zeekling, "zeekling") diff --git a/src/src/badguy/zeekling.hpp b/src/src/badguy/zeekling.hpp new file mode 100644 index 000000000..a130179fd --- /dev/null +++ b/src/src/badguy/zeekling.hpp @@ -0,0 +1,63 @@ +// $Id$ +// +// Zeekling - flyer that swoops down when she spots the player +// Copyright (C) 2005 Matthias Braun +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#ifndef __ZEEKLING_H__ +#define __ZEEKLING_H__ + +#include "badguy.hpp" + +class Zeekling : public BadGuy +{ +public: + Zeekling(const lisp::Lisp& reader); + Zeekling(const Vector& pos, Direction d); + + void activate(); + void write(lisp::Writer& writer); + void collision_solid(const CollisionHit& hit); + void active_update(float elapsed_time); + + virtual Zeekling* clone() const { return new Zeekling(*this); } + +protected: + bool collision_squished(GameObject& object); + float speed; + + Timer diveRecoverTimer; + + enum ZeeklingState { + FLYING, + DIVING, + CLIMBING + }; + ZeeklingState state; + +private: + const MovingObject* last_player; /**< last player we tracked */ + Vector last_player_pos; /**< position we last spotted the player at */ + Vector last_self_pos; /**< position we last were at */ + + bool should_we_dive(); + void onBumpHorizontal(); + void onBumpVertical(); +}; + +#endif diff --git a/src/src/binreloc/binreloc.c b/src/src/binreloc/binreloc.c new file mode 100644 index 000000000..17df59e9d --- /dev/null +++ b/src/src/binreloc/binreloc.c @@ -0,0 +1,770 @@ +/* + * BinReloc - a library for creating relocatable executables + * Written by: Hongli Lai + * http://autopackage.org/ + * + * This source code is public domain. You can relicense this code + * under whatever license you want. + * + * See http://autopackage.org/docs/binreloc/ for + * more information and how to use this. + */ + +#ifndef __BINRELOC_C__ +#define __BINRELOC_C__ + +// [Christoph] use config.h, which defines ENABLE_BINRELOC +#include + +#ifdef ENABLE_BINRELOC + #include + #include + #include +#endif /* ENABLE_BINRELOC */ +#include +#include +#include +#include +#include "binreloc.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + + +/** @internal + * Find the canonical filename of the executable. Returns the filename + * (which must be freed) or NULL on error. If the parameter 'error' is + * not NULL, the error code will be stored there, if an error occured. + */ +static char * +_br_find_exe (BrInitError *error) +{ +#ifndef ENABLE_BINRELOC + if (error) + *error = BR_INIT_ERROR_DISABLED; + return NULL; +#else + char *path, *path2, *line, *result; + size_t buf_size; + ssize_t size; + struct stat stat_buf; + FILE *f; + + /* Read from /proc/self/exe (symlink) */ + if (sizeof (path) > SSIZE_MAX) + buf_size = SSIZE_MAX - 1; + else + buf_size = PATH_MAX - 1; + path = (char *) malloc (buf_size); + if (path == NULL) { + /* Cannot allocate memory. */ + if (error) + *error = BR_INIT_ERROR_NOMEM; + return NULL; + } + path2 = (char *) malloc (buf_size); + if (path2 == NULL) { + /* Cannot allocate memory. */ + if (error) + *error = BR_INIT_ERROR_NOMEM; + free (path); + return NULL; + } + + strncpy (path2, "/proc/self/exe", buf_size - 1); + + while (1) { + int i; + + size = readlink (path2, path, buf_size - 1); + if (size == -1) { + /* Error. */ + free (path2); + break; + } + + /* readlink() success. */ + path[size] = '\0'; + + /* Check whether the symlink's target is also a symlink. + * We want to get the final target. */ + i = stat (path, &stat_buf); + if (i == -1) { + /* Error. */ + free (path2); + break; + } + + /* stat() success. */ + if (!S_ISLNK (stat_buf.st_mode)) { + /* path is not a symlink. Done. */ + free (path2); + return path; + } + + /* path is a symlink. Continue loop and resolve this. */ + strncpy (path, path2, buf_size - 1); + } + + + /* readlink() or stat() failed; this can happen when the program is + * running in Valgrind 2.2. Read from /proc/self/maps as fallback. */ + + buf_size = PATH_MAX + 128; + line = (char *) realloc (path, buf_size); + if (line == NULL) { + /* Cannot allocate memory. */ + free (path); + if (error) + *error = BR_INIT_ERROR_NOMEM; + return NULL; + } + + f = fopen ("/proc/self/maps", "r"); + if (f == NULL) { + free (line); + if (error) + *error = BR_INIT_ERROR_OPEN_MAPS; + return NULL; + } + + /* The first entry should be the executable name. */ + result = fgets (line, (int) buf_size, f); + if (result == NULL) { + fclose (f); + free (line); + if (error) + *error = BR_INIT_ERROR_READ_MAPS; + return NULL; + } + + /* Get rid of newline character. */ + buf_size = strlen (line); + if (buf_size <= 0) { + /* Huh? An empty string? */ + fclose (f); + free (line); + if (error) + *error = BR_INIT_ERROR_INVALID_MAPS; + return NULL; + } + if (line[buf_size - 1] == 10) + line[buf_size - 1] = 0; + + /* Extract the filename; it is always an absolute path. */ + path = strchr (line, '/'); + + /* Sanity check. */ + if (strstr (line, " r-xp ") == NULL || path == NULL) { + fclose (f); + free (line); + if (error) + *error = BR_INIT_ERROR_INVALID_MAPS; + return NULL; + } + + path = strdup (path); + free (line); + fclose (f); + return path; +#endif /* ENABLE_BINRELOC */ +} + + +/** @internal + * Find the canonical filename of the executable which owns symbol. + * Returns a filename which must be freed, or NULL on error. + */ +static char * +_br_find_exe_for_symbol (const void *symbol, BrInitError *error) +{ + symbol = symbol; // [Christoph] mark it as used +#ifndef ENABLE_BINRELOC + if (error) + *error = BR_INIT_ERROR_DISABLED; + return (char *) NULL; +#else + #define SIZE PATH_MAX + 100 + FILE *f; + size_t address_string_len; + char *address_string, line[SIZE], *found; + + if (symbol == NULL) + return (char *) NULL; + + f = fopen ("/proc/self/maps", "r"); + if (f == NULL) + return (char *) NULL; + + address_string_len = 4; + address_string = (char *) malloc (address_string_len); + found = (char *) NULL; + + while (!feof (f)) { + char *start_addr, *end_addr, *end_addr_end, *file; + void *start_addr_p, *end_addr_p; + size_t len; + + if (fgets (line, SIZE, f) == NULL) + break; + + /* Sanity check. */ + if (strstr (line, " r-xp ") == NULL || strchr (line, '/') == NULL) + continue; + + /* Parse line. */ + start_addr = line; + end_addr = strchr (line, '-'); + file = strchr (line, '/'); + + /* More sanity check. */ + if (!(file > end_addr && end_addr != NULL && end_addr[0] == '-')) + continue; + + end_addr[0] = '\0'; + end_addr++; + end_addr_end = strchr (end_addr, ' '); + if (end_addr_end == NULL) + continue; + + end_addr_end[0] = '\0'; + len = strlen (file); + if (len == 0) + continue; + if (file[len - 1] == '\n') + file[len - 1] = '\0'; + + /* Get rid of "(deleted)" from the filename. */ + len = strlen (file); + if (len > 10 && strcmp (file + len - 10, " (deleted)") == 0) + file[len - 10] = '\0'; + + /* I don't know whether this can happen but better safe than sorry. */ + len = strlen (start_addr); + if (len != strlen (end_addr)) + continue; + + + /* Transform the addresses into a string in the form of 0xdeadbeef, + * then transform that into a pointer. */ + if (address_string_len < len + 3) { + address_string_len = len + 3; + address_string = (char *) realloc (address_string, address_string_len); + } + + memcpy (address_string, "0x", 2); + memcpy (address_string + 2, start_addr, len); + address_string[2 + len] = '\0'; + sscanf (address_string, "%p", &start_addr_p); + + memcpy (address_string, "0x", 2); + memcpy (address_string + 2, end_addr, len); + address_string[2 + len] = '\0'; + sscanf (address_string, "%p", &end_addr_p); + + + if (symbol >= start_addr_p && symbol < end_addr_p) { + found = file; + break; + } + } + + free (address_string); + fclose (f); + + if (found == NULL) + return (char *) NULL; + else + return strdup (found); +#endif /* ENABLE_BINRELOC */ +} + + +#ifndef BINRELOC_RUNNING_DOXYGEN + #undef NULL + #define NULL ((void *) 0) /* typecasted as char* for C++ type safeness */ +#endif + +static char *exe = (char *) NULL; + + +/** Initialize the BinReloc library (for applications). + * + * This function must be called before using any other BinReloc functions. + * It attempts to locate the application's canonical filename. + * + * @note If you want to use BinReloc for a library, then you should call + * br_init_lib() instead. + * + * @param error If BinReloc failed to initialize, then the error code will + * be stored in this variable. Set to NULL if you want to + * ignore this. See #BrInitError for a list of error codes. + * + * @returns 1 on success, 0 if BinReloc failed to initialize. + */ +int +br_init (BrInitError *error) +{ + exe = _br_find_exe (error); + return exe != NULL; +} + + +/** Initialize the BinReloc library (for libraries). + * + * This function must be called before using any other BinReloc functions. + * It attempts to locate the calling library's canonical filename. + * + * @note The BinReloc source code MUST be included in your library, or this + * function won't work correctly. + * + * @param error If BinReloc failed to initialize, then the error code will + * be stored in this variable. Set to NULL if you want to + * ignore this. See #BrInitError for a list of error codes. + * + * @returns 1 on success, 0 if a filename cannot be found. + */ +int +br_init_lib (BrInitError *error) +{ + exe = _br_find_exe_for_symbol ((const void *) "", error); + return exe != NULL; +} + + +/** Find the canonical filename of the current application. + * + * @param default_exe A default filename which will be used as fallback. + * @returns A string containing the application's canonical filename, + * which must be freed when no longer necessary. If BinReloc is + * not initialized, or if br_init() failed, then a copy of + * default_exe will be returned. If default_exe is NULL, then + * NULL will be returned. + */ +char * +br_find_exe (const char *default_exe) +{ + if (exe == (char *) NULL) { + /* BinReloc is not initialized. */ + if (default_exe != (const char *) NULL) + return strdup (default_exe); + else + return (char *) NULL; + } + return strdup (exe); +} + + +/** Locate the directory in which the current application is installed. + * + * The prefix is generated by the following pseudo-code evaluation: + * \code + * dirname(exename) + * \endcode + * + * @param default_dir A default directory which will used as fallback. + * @return A string containing the directory, which must be freed when no + * longer necessary. If BinReloc is not initialized, or if the + * initialization function failed, then a copy of default_dir + * will be returned. If default_dir is NULL, then NULL will be + * returned. + */ +char * +br_find_exe_dir (const char *default_dir) +{ + if (exe == NULL) { + /* BinReloc not initialized. */ + if (default_dir != NULL) + return strdup (default_dir); + else + return NULL; + } + + return br_dirname (exe); +} + + +/** Locate the prefix in which the current application is installed. + * + * The prefix is generated by the following pseudo-code evaluation: + * \code + * dirname(dirname(exename)) + * \endcode + * + * @param default_prefix A default prefix which will used as fallback. + * @return A string containing the prefix, which must be freed when no + * longer necessary. If BinReloc is not initialized, or if + * the initialization function failed, then a copy of default_prefix + * will be returned. If default_prefix is NULL, then NULL will be returned. + */ +char * +br_find_prefix (const char *default_prefix) +{ + char *dir1, *dir2; + + if (exe == (char *) NULL) { + /* BinReloc not initialized. */ + if (default_prefix != (const char *) NULL) + return strdup (default_prefix); + else + return (char *) NULL; + } + + dir1 = br_dirname (exe); + dir2 = br_dirname (dir1); + free (dir1); + return dir2; +} + + +/** Locate the application's binary folder. + * + * The path is generated by the following pseudo-code evaluation: + * \code + * prefix + "/bin" + * \endcode + * + * @param default_bin_dir A default path which will used as fallback. + * @return A string containing the bin folder's path, which must be freed when + * no longer necessary. If BinReloc is not initialized, or if + * the initialization function failed, then a copy of default_bin_dir will + * be returned. If default_bin_dir is NULL, then NULL will be returned. + */ +char * +br_find_bin_dir (const char *default_bin_dir) +{ + char *prefix, *dir; + + prefix = br_find_prefix ((const char *) NULL); + if (prefix == (char *) NULL) { + /* BinReloc not initialized. */ + if (default_bin_dir != (const char *) NULL) + return strdup (default_bin_dir); + else + return (char *) NULL; + } + + dir = br_build_path (prefix, "bin"); + free (prefix); + return dir; +} + + +/** Locate the application's superuser binary folder. + * + * The path is generated by the following pseudo-code evaluation: + * \code + * prefix + "/sbin" + * \endcode + * + * @param default_sbin_dir A default path which will used as fallback. + * @return A string containing the sbin folder's path, which must be freed when + * no longer necessary. If BinReloc is not initialized, or if the + * initialization function failed, then a copy of default_sbin_dir will + * be returned. If default_bin_dir is NULL, then NULL will be returned. + */ +char * +br_find_sbin_dir (const char *default_sbin_dir) +{ + char *prefix, *dir; + + prefix = br_find_prefix ((const char *) NULL); + if (prefix == (char *) NULL) { + /* BinReloc not initialized. */ + if (default_sbin_dir != (const char *) NULL) + return strdup (default_sbin_dir); + else + return (char *) NULL; + } + + dir = br_build_path (prefix, "sbin"); + free (prefix); + return dir; +} + + +/** Locate the application's data folder. + * + * The path is generated by the following pseudo-code evaluation: + * \code + * prefix + "/share" + * \endcode + * + * @param default_data_dir A default path which will used as fallback. + * @return A string containing the data folder's path, which must be freed when + * no longer necessary. If BinReloc is not initialized, or if the + * initialization function failed, then a copy of default_data_dir + * will be returned. If default_data_dir is NULL, then NULL will be + * returned. + */ +char * +br_find_data_dir (const char *default_data_dir) +{ + char *prefix, *dir; + + prefix = br_find_prefix ((const char *) NULL); + if (prefix == (char *) NULL) { + /* BinReloc not initialized. */ + if (default_data_dir != (const char *) NULL) + return strdup (default_data_dir); + else + return (char *) NULL; + } + + dir = br_build_path (prefix, "share"); + free (prefix); + return dir; +} + + +/** Locate the application's localization folder. + * + * The path is generated by the following pseudo-code evaluation: + * \code + * prefix + "/share/locale" + * \endcode + * + * @param default_locale_dir A default path which will used as fallback. + * @return A string containing the localization folder's path, which must be freed when + * no longer necessary. If BinReloc is not initialized, or if the + * initialization function failed, then a copy of default_locale_dir will be returned. + * If default_locale_dir is NULL, then NULL will be returned. + */ +char * +br_find_locale_dir (const char *default_locale_dir) +{ + char *data_dir, *dir; + + data_dir = br_find_data_dir ((const char *) NULL); + if (data_dir == (char *) NULL) { + /* BinReloc not initialized. */ + if (default_locale_dir != (const char *) NULL) + return strdup (default_locale_dir); + else + return (char *) NULL; + } + + dir = br_build_path (data_dir, "locale"); + free (data_dir); + return dir; +} + + +/** Locate the application's library folder. + * + * The path is generated by the following pseudo-code evaluation: + * \code + * prefix + "/lib" + * \endcode + * + * @param default_lib_dir A default path which will used as fallback. + * @return A string containing the library folder's path, which must be freed when + * no longer necessary. If BinReloc is not initialized, or if the initialization + * function failed, then a copy of default_lib_dir will be returned. + * If default_lib_dir is NULL, then NULL will be returned. + */ +char * +br_find_lib_dir (const char *default_lib_dir) +{ + char *prefix, *dir; + + prefix = br_find_prefix ((const char *) NULL); + if (prefix == (char *) NULL) { + /* BinReloc not initialized. */ + if (default_lib_dir != (const char *) NULL) + return strdup (default_lib_dir); + else + return (char *) NULL; + } + + dir = br_build_path (prefix, "lib"); + free (prefix); + return dir; +} + + +/** Locate the application's libexec folder. + * + * The path is generated by the following pseudo-code evaluation: + * \code + * prefix + "/libexec" + * \endcode + * + * @param default_libexec_dir A default path which will used as fallback. + * @return A string containing the libexec folder's path, which must be freed when + * no longer necessary. If BinReloc is not initialized, or if the initialization + * function failed, then a copy of default_libexec_dir will be returned. + * If default_libexec_dir is NULL, then NULL will be returned. + */ +char * +br_find_libexec_dir (const char *default_libexec_dir) +{ + char *prefix, *dir; + + prefix = br_find_prefix ((const char *) NULL); + if (prefix == (char *) NULL) { + /* BinReloc not initialized. */ + if (default_libexec_dir != (const char *) NULL) + return strdup (default_libexec_dir); + else + return (char *) NULL; + } + + dir = br_build_path (prefix, "libexec"); + free (prefix); + return dir; +} + + +/** Locate the application's configuration files folder. + * + * The path is generated by the following pseudo-code evaluation: + * \code + * prefix + "/etc" + * \endcode + * + * @param default_etc_dir A default path which will used as fallback. + * @return A string containing the etc folder's path, which must be freed when + * no longer necessary. If BinReloc is not initialized, or if the initialization + * function failed, then a copy of default_etc_dir will be returned. + * If default_etc_dir is NULL, then NULL will be returned. + */ +char * +br_find_etc_dir (const char *default_etc_dir) +{ + char *prefix, *dir; + + prefix = br_find_prefix ((const char *) NULL); + if (prefix == (char *) NULL) { + /* BinReloc not initialized. */ + if (default_etc_dir != (const char *) NULL) + return strdup (default_etc_dir); + else + return (char *) NULL; + } + + dir = br_build_path (prefix, "etc"); + free (prefix); + return dir; +} + + +/*********************** + * Utility functions + ***********************/ + +/** Concatenate str1 and str2 to a newly allocated string. + * + * @param str1 A string. + * @param str2 Another string. + * @returns A newly-allocated string. This string should be freed when no longer needed. + */ +char * +br_strcat (const char *str1, const char *str2) +{ + char *result; + size_t len1, len2; + + if (str1 == NULL) + str1 = ""; + if (str2 == NULL) + str2 = ""; + + len1 = strlen (str1); + len2 = strlen (str2); + + result = (char *) malloc (len1 + len2 + 1); + memcpy (result, str1, len1); + memcpy (result + len1, str2, len2); + result[len1 + len2] = '\0'; + + return result; +} + + +char * +br_build_path (const char *dir, const char *file) +{ + char *dir2, *result; + size_t len; + int must_free = 0; + + len = strlen (dir); + if (len > 0 && dir[len - 1] != '/') { + dir2 = br_strcat (dir, "/"); + must_free = 1; + } else + dir2 = (char *) dir; + + result = br_strcat (dir2, file); + if (must_free) + free (dir2); + return result; +} + + +/* Emulates glibc's strndup() */ +static char * +br_strndup (const char *str, size_t size) +{ + char *result = (char *) NULL; + size_t len; + + if (str == (const char *) NULL) + return (char *) NULL; + + len = strlen (str); + if (len == 0) + return strdup (""); + if (size > len) + size = len; + + result = (char *) malloc (len + 1); + memcpy (result, str, size); + result[size] = '\0'; + return result; +} + + +/** Extracts the directory component of a path. + * + * Similar to g_dirname() or the dirname commandline application. + * + * Example: + * \code + * br_dirname ("/usr/local/foobar"); --> Returns: "/usr/local" + * \endcode + * + * @param path A path. + * @returns A directory name. This string should be freed when no longer needed. + */ +char * +br_dirname (const char *path) +{ + char *end, *result; + + if (path == (const char *) NULL) + return (char *) NULL; + + end = strrchr (path, '/'); + if (end == (const char *) NULL) + return strdup ("."); + + while (end > path && *end == '/') + end--; + result = br_strndup (path, end - path + 1); + if (result[0] == 0) { + free (result); + return strdup ("/"); + } else + return result; +} + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __BINRELOC_C__ */ diff --git a/src/src/binreloc/binreloc.h b/src/src/binreloc/binreloc.h new file mode 100644 index 000000000..592d99826 --- /dev/null +++ b/src/src/binreloc/binreloc.h @@ -0,0 +1,80 @@ +/* + * BinReloc - a library for creating relocatable executables + * Written by: Hongli Lai + * http://autopackage.org/ + * + * This source code is public domain. You can relicense this code + * under whatever license you want. + * + * See http://autopackage.org/docs/binreloc/ for + * more information and how to use this. + */ + +#ifndef __BINRELOC_H__ +#define __BINRELOC_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/** These error codes can be returned by br_init(), br_init_lib(), gbr_init() or gbr_init_lib(). */ +typedef enum { + /** Cannot allocate memory. */ + BR_INIT_ERROR_NOMEM, + /** Unable to open /proc/self/maps; see errno for details. */ + BR_INIT_ERROR_OPEN_MAPS, + /** Unable to read from /proc/self/maps; see errno for details. */ + BR_INIT_ERROR_READ_MAPS, + /** The file format of /proc/self/maps is invalid; kernel bug? */ + BR_INIT_ERROR_INVALID_MAPS, + /** BinReloc is disabled (the ENABLE_BINRELOC macro is not defined). */ + BR_INIT_ERROR_DISABLED +} BrInitError; + + +#ifndef BINRELOC_RUNNING_DOXYGEN +/* Mangle symbol names to avoid symbol collisions with other ELF objects. */ + #define br_init PTeH3518859728963_br_init + #define br_init_lib PTeH3518859728963_br_init_lib + #define br_find_exe PTeH3518859728963_br_find_exe + #define br_find_exe_dir PTeH3518859728963_br_find_exe_dir + #define br_find_prefix PTeH3518859728963_br_find_prefix + #define br_find_bin_dir PTeH3518859728963_br_find_bin_dir + #define br_find_sbin_dir PTeH3518859728963_br_find_sbin_dir + #define br_find_data_dir PTeH3518859728963_br_find_data_dir + #define br_find_locale_dir PTeH3518859728963_br_find_locale_dir + #define br_find_lib_dir PTeH3518859728963_br_find_lib_dir + #define br_find_libexec_dir PTeH3518859728963_br_find_libexec_dir + #define br_find_etc_dir PTeH3518859728963_br_find_etc_dir + #define br_strcat PTeH3518859728963_br_strcat + #define br_build_path PTeH3518859728963_br_build_path + #define br_dirname PTeH3518859728963_br_dirname + + +#endif +int br_init (BrInitError *error); +int br_init_lib (BrInitError *error); + +char *br_find_exe (const char *default_exe); +char *br_find_exe_dir (const char *default_dir); +char *br_find_prefix (const char *default_prefix); +char *br_find_bin_dir (const char *default_bin_dir); +char *br_find_sbin_dir (const char *default_sbin_dir); +char *br_find_data_dir (const char *default_data_dir); +char *br_find_locale_dir (const char *default_locale_dir); +char *br_find_lib_dir (const char *default_lib_dir); +char *br_find_libexec_dir (const char *default_libexec_dir); +char *br_find_etc_dir (const char *default_etc_dir); + +/* Utility functions */ +char *br_strcat (const char *str1, const char *str2); +char *br_build_path (const char *dir, const char *file); +char *br_dirname (const char *path); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __BINRELOC_H__ */ diff --git a/src/src/collision.cpp b/src/src/collision.cpp new file mode 100644 index 000000000..f96fdea53 --- /dev/null +++ b/src/src/collision.cpp @@ -0,0 +1,188 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +#include + +#include "collision.hpp" + +#include +#include +#include +#include +#include +#include "math/vector.hpp" +#include "math/aatriangle.hpp" +#include "math/rect.hpp" +#include "collision_hit.hpp" +#include "log.hpp" + +namespace collision +{ + +bool intersects(const Rect& r1, const Rect& r2) +{ + if(r1.p2.x < r2.p1.x || r1.p1.x > r2.p2.x) + return false; + if(r1.p2.y < r2.p1.y || r1.p1.y > r2.p2.y) + return false; + + return true; +} + +//--------------------------------------------------------------------------- + +namespace { + inline void makePlane(const Vector& p1, const Vector& p2, Vector& n, float& c) + { + n = Vector(p2.y-p1.y, p1.x-p2.x); + c = -(p2 * n); + float nval = n.norm(); + n /= nval; + c /= nval; + } + + static const float DELTA = .0001f; +} + +bool rectangle_aatriangle(Constraints* constraints, const Rect& rect, + const AATriangle& triangle) +{ + if(!intersects(rect, (const Rect&) triangle)) + return false; + + Vector normal; + float c; + Vector p1; + Rect area; + switch(triangle.dir & AATriangle::DEFORM_MASK) { + case 0: + area.p1 = triangle.p1; + area.p2 = triangle.p2; + break; + case AATriangle::DEFORM1: + area.p1 = Vector(triangle.p1.x, triangle.p1.y + triangle.get_height()/2); + area.p2 = triangle.p2; + break; + case AATriangle::DEFORM2: + area.p1 = triangle.p1; + area.p2 = Vector(triangle.p2.x, triangle.p1.y + triangle.get_height()/2); + break; + case AATriangle::DEFORM3: + area.p1 = triangle.p1; + area.p2 = Vector(triangle.p1.x + triangle.get_width()/2, triangle.p2.y); + break; + case AATriangle::DEFORM4: + area.p1 = Vector(triangle.p1.x + triangle.get_width()/2, triangle.p1.y); + area.p2 = triangle.p2; + break; + default: + assert(false); + } + + switch(triangle.dir & AATriangle::DIRECTION_MASK) { + case AATriangle::SOUTHWEST: + p1 = Vector(rect.p1.x, rect.p2.y); + makePlane(area.p1, area.p2, normal, c); + break; + case AATriangle::NORTHEAST: + p1 = Vector(rect.p2.x, rect.p1.y); + makePlane(area.p2, area.p1, normal, c); + break; + case AATriangle::SOUTHEAST: + p1 = rect.p2; + makePlane(Vector(area.p1.x, area.p2.y), + Vector(area.p2.x, area.p1.y), normal, c); + break; + case AATriangle::NORTHWEST: + p1 = rect.p1; + makePlane(Vector(area.p2.x, area.p1.y), + Vector(area.p1.x, area.p2.y), normal, c); + break; + default: + assert(false); + } + + float n_p1 = -(normal * p1); + float depth = n_p1 - c; + if(depth < 0) + return false; + +#if 0 + std::cout << "R: " << rect << " Tri: " << triangle << "\n"; + std::cout << "Norm: " << normal << " Depth: " << depth << "\n"; +#endif + + Vector outvec = normal * (depth + 0.2f); + + const float RDELTA = 3; + if(p1.x < area.p1.x - RDELTA || p1.x > area.p2.x + RDELTA + || p1.y < area.p1.y - RDELTA || p1.y > area.p2.y + RDELTA) { + set_rectangle_rectangle_constraints(constraints, rect, area); + constraints->hit.left = false; + constraints->hit.right = false; + } else { + if(outvec.x < 0) { + constraints->right = rect.get_right() + outvec.x; + } else { + constraints->left = rect.get_left() + outvec.x; + } + + if(outvec.y < 0) { + constraints->bottom = rect.get_bottom() + outvec.y; + constraints->hit.bottom = true; + } else { + constraints->top = rect.get_top() + outvec.y; + constraints->hit.top = true; + } + constraints->hit.slope_normal = normal; + } + + return true; +} + +void set_rectangle_rectangle_constraints(Constraints* constraints, + const Rect& r1, const Rect& r2) +{ + float itop = r1.get_bottom() - r2.get_top(); + float ibottom = r2.get_bottom() - r1.get_top(); + float ileft = r1.get_right() - r2.get_left(); + float iright = r2.get_right() - r1.get_left(); + + float vert_penetration = std::min(itop, ibottom); + float horiz_penetration = std::min(ileft, iright); + if(vert_penetration < horiz_penetration) { + if(itop < ibottom) { + constraints->bottom = std::min(constraints->bottom, r2.get_top()); + constraints->hit.bottom = true; + } else { + constraints->top = std::max(constraints->top, r2.get_bottom()); + constraints->hit.top = true; + } + } else { + if(ileft < iright) { + constraints->right = std::min(constraints->right, r2.get_left()); + constraints->hit.right = true; + } else { + constraints->left = std::max(constraints->left, r2.get_right()); + constraints->hit.left = true; + } + } +} + +} diff --git a/src/src/collision.hpp b/src/src/collision.hpp new file mode 100644 index 000000000..ba6b6f8d9 --- /dev/null +++ b/src/src/collision.hpp @@ -0,0 +1,73 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +#ifndef __COLLISION_H__ +#define __COLLISION_H__ + +#include +#include "collision_hit.hpp" +#include + +class Vector; +class Rect; +class AATriangle; + +namespace collision +{ + +class Constraints +{ +public: + Constraints() { + float infinity = (std::numeric_limits::has_infinity ? std::numeric_limits::infinity() : std::numeric_limits::max()); + left = -infinity; + right = infinity; + top = -infinity; + bottom = infinity; + } + + bool has_constraints() const { + float infinity = (std::numeric_limits::has_infinity ? std::numeric_limits::infinity() : std::numeric_limits::max()); + return left > -infinity || right < infinity + || top > -infinity || bottom < infinity; + } + + float left; + float right; + float top; + float bottom; + Vector ground_movement; + CollisionHit hit; +}; + +/** checks if 2 rectangle intersect each other */ +bool intersects(const Rect& r1, const Rect& r2); + +/** does collision detection between a rectangle and an axis aligned triangle + * Returns true in case of a collision and fills in the hit structure then. + */ +bool rectangle_aatriangle(Constraints* constraints, const Rect& rect, + const AATriangle& triangle); + +void set_rectangle_rectangle_constraints(Constraints* constraints, + const Rect& r1, const Rect& r2); + +} + +#endif diff --git a/src/src/collision_hit.hpp b/src/src/collision_hit.hpp new file mode 100644 index 000000000..02879039a --- /dev/null +++ b/src/src/collision_hit.hpp @@ -0,0 +1,69 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#ifndef SUPERTUX_COLLISION_HIT_H +#define SUPERTUX_COLLISION_HIT_H + +#include +#include +#include "math/vector.hpp" + +/** + * Used as return value for the collision functions, to indicate how the + * collision should be handled + */ +enum HitResponse +{ + /// don't move the object + ABORT_MOVE = 0, + /// move object out of collision and check for collisions again + /// if this happens to often then the move will just be aborted + CONTINUE, + /// do the move ignoring the collision + FORCE_MOVE, + /// passes movement to collided object + PASS_MOVEMENT, + + /// the object should not appear solid + PASSTHROUGH, + /// the object should appear solid + SOLID, +}; + +/** + * This class collects data about a collision + */ +class CollisionHit +{ +public: + CollisionHit() { + left = false; + right = false; + top = false; + bottom = false; + crush = false; + } + + bool left, right; + bool top, bottom; + bool crush; + + Vector slope_normal; +}; + +#endif diff --git a/src/src/console.cpp b/src/src/console.cpp new file mode 100644 index 000000000..28ea36ce7 --- /dev/null +++ b/src/src/console.cpp @@ -0,0 +1,525 @@ +// $Id$ +// +// SuperTux - Console +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#include + +#include +#include +#include +#include +#include "console.hpp" +#include "video/drawing_context.hpp" +#include "video/surface.hpp" +#include "scripting/squirrel_error.hpp" +#include "scripting/squirrel_util.hpp" +#include "physfs/physfs_stream.hpp" +#include "player_status.hpp" +#include "main.hpp" +#include "log.hpp" +#include "resources.hpp" +#include "gameconfig.hpp" + +/// speed (pixels/s) the console closes +static const float FADE_SPEED = 1; + +Console::Console() + : history_position(history.end()), vm(NULL), backgroundOffset(0), + height(0), alpha(1.0), offset(0), focused(false), stayOpen(0) { + fontheight = 8; +} + +Console::~Console() +{ + if(vm != NULL) { + sq_release(Scripting::global_vm, &vm_object); + } +} + +void +Console::init_graphics() +{ + font.reset(new Font(Font::FIXED, + "images/engine/fonts/andale12.png", + "images/engine/fonts/andale12-shadow.png", 7, 14, 1)); + fontheight = font->get_height(); + background.reset(new Surface("images/engine/console.png")); + background2.reset(new Surface("images/engine/console2.png")); +} + +void +Console::flush(ConsoleStreamBuffer* buffer) +{ + if (buffer == &outputBuffer) { + std::string s = outputBuffer.str(); + if ((s.length() > 0) && ((s[s.length()-1] == '\n') || (s[s.length()-1] == '\r'))) { + while ((s[s.length()-1] == '\n') || (s[s.length()-1] == '\r')) s.erase(s.length()-1); + addLines(s); + outputBuffer.str(std::string()); + } + } +} + +void +Console::ready_vm() +{ + if(vm == NULL) { + vm = Scripting::global_vm; + HSQUIRRELVM new_vm = sq_newthread(vm, 16); + if(new_vm == NULL) + throw Scripting::SquirrelError(vm, "Couldn't create new VM thread for console"); + + // store reference to thread + sq_resetobject(&vm_object); + if(SQ_FAILED(sq_getstackobj(vm, -1, &vm_object))) + throw Scripting::SquirrelError(vm, "Couldn't get vm object for console"); + sq_addref(vm, &vm_object); + sq_pop(vm, 1); + + // create new roottable for thread + sq_newtable(new_vm); + sq_pushroottable(new_vm); + if(SQ_FAILED(sq_setdelegate(new_vm, -2))) + throw Scripting::SquirrelError(new_vm, "Couldn't set console_table delegate"); + + sq_setroottable(new_vm); + + vm = new_vm; + + try { + std::string filename = "scripts/console.nut"; + IFileStream stream(filename); + Scripting::compile_and_run(vm, stream, filename); + } catch(std::exception& e) { + log_warning << "Couldn't load console.nut: " << e.what() << std::endl; + } + } +} + +void +Console::execute_script(const std::string& command) +{ + using namespace Scripting; + + ready_vm(); + + SQInteger oldtop = sq_gettop(vm); + try { + if(SQ_FAILED(sq_compilebuffer(vm, command.c_str(), command.length(), + "", SQTrue))) + throw SquirrelError(vm, "Couldn't compile command"); + + sq_pushroottable(vm); + if(SQ_FAILED(sq_call(vm, 1, SQTrue, SQTrue))) + throw SquirrelError(vm, "Problem while executing command"); + + if(sq_gettype(vm, -1) != OT_NULL) + addLines(squirrel2string(vm, -1)); + } catch(std::exception& e) { + addLines(e.what()); + } + SQInteger newtop = sq_gettop(vm); + if(newtop < oldtop) { + log_fatal << "Script destroyed squirrel stack..." << std::endl; + } else { + sq_settop(vm, oldtop); + } +} + +void +Console::input(char c) +{ + inputBuffer.insert(inputBufferPosition, 1, c); + inputBufferPosition++; +} + +void +Console::backspace() +{ + if ((inputBufferPosition > 0) && (inputBuffer.length() > 0)) { + inputBuffer.erase(inputBufferPosition-1, 1); + inputBufferPosition--; + } +} + +void +Console::eraseChar() +{ + if (inputBufferPosition < (int)inputBuffer.length()) { + inputBuffer.erase(inputBufferPosition, 1); + } +} + +void +Console::enter() +{ + addLines("> "+inputBuffer); + parse(inputBuffer); + inputBuffer = ""; + inputBufferPosition = 0; +} + +void +Console::scroll(int numLines) +{ + offset += numLines; + if (offset > 0) offset = 0; +} + +void +Console::show_history(int offset) +{ + while ((offset > 0) && (history_position != history.end())) { + history_position++; + offset--; + } + while ((offset < 0) && (history_position != history.begin())) { + history_position--; + offset++; + } + if (history_position == history.end()) { + inputBuffer = ""; + inputBufferPosition = 0; + } else { + inputBuffer = *history_position; + inputBufferPosition = inputBuffer.length(); + } +} + +void +Console::move_cursor(int offset) +{ + if (offset == -65535) inputBufferPosition = 0; + if (offset == +65535) inputBufferPosition = inputBuffer.length(); + inputBufferPosition+=offset; + if (inputBufferPosition < 0) inputBufferPosition = 0; + if (inputBufferPosition > (int)inputBuffer.length()) inputBufferPosition = inputBuffer.length(); +} + +// Helper functions for Console::autocomplete +// TODO: Fix rough documentation +namespace { + +void sq_insert_commands(std::list& cmds, HSQUIRRELVM vm, std::string table_prefix, std::string search_prefix); + +/** + * Acts upon key,value on top of stack: + * Appends key (plus type-dependent suffix) to cmds if table_prefix+key starts with search_prefix; + * Calls sq_insert_commands if search_prefix starts with table_prefix+key (and value is a table/class/instance); + */ +void +sq_insert_command(std::list& cmds, HSQUIRRELVM vm, std::string table_prefix, std::string search_prefix) +{ + const SQChar* key_chars; + if (SQ_FAILED(sq_getstring(vm, -2, &key_chars))) return; + std::string key_string = table_prefix + key_chars; + + switch (sq_gettype(vm, -1)) { + case OT_INSTANCE: + key_string+="."; + if (search_prefix.substr(0, key_string.length()) == key_string) { + sq_getclass(vm, -1); + sq_insert_commands(cmds, vm, key_string, search_prefix); + sq_pop(vm, 1); + } + break; + case OT_TABLE: + case OT_CLASS: + key_string+="."; + if (search_prefix.substr(0, key_string.length()) == key_string) { + sq_insert_commands(cmds, vm, key_string, search_prefix); + } + break; + case OT_CLOSURE: + case OT_NATIVECLOSURE: + key_string+="()"; + break; + default: + break; + } + + if (key_string.substr(0, search_prefix.length()) == search_prefix) { + cmds.push_back(key_string); + } + +} + +/** + * calls sq_insert_command for all entries of table/class on top of stack + */ +void +sq_insert_commands(std::list& cmds, HSQUIRRELVM vm, std::string table_prefix, std::string search_prefix) +{ + sq_pushnull(vm); // push iterator + while (SQ_SUCCEEDED(sq_next(vm,-2))) { + sq_insert_command(cmds, vm, table_prefix, search_prefix); + sq_pop(vm, 2); // pop key, val + } + sq_pop(vm, 1); // pop iterator +} + + +} +// End of Console::autocomplete helper functions + +void +Console::autocomplete() +{ + //int autocompleteFrom = inputBuffer.find_last_of(" ();+", inputBufferPosition); + int autocompleteFrom = inputBuffer.find_last_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_->.", inputBufferPosition); + if (autocompleteFrom != (int)std::string::npos) { + autocompleteFrom += 1; + } else { + autocompleteFrom = 0; + } + std::string prefix = inputBuffer.substr(autocompleteFrom, inputBufferPosition - autocompleteFrom); + addLines("> "+prefix); + + std::list cmds; + + ready_vm(); + + // append all keys of the current root table to list + sq_pushroottable(vm); // push root table + while(true) { + // check all keys (and their children) for matches + sq_insert_commands(cmds, vm, "", prefix); + + // cycle through parent(delegate) table + SQInteger oldtop = sq_gettop(vm); + if(SQ_FAILED(sq_getdelegate(vm, -1)) || oldtop == sq_gettop(vm)) { + break; + } + sq_remove(vm, -2); // remove old table + } + sq_pop(vm, 1); // remove table + + // depending on number of hits, show matches or autocomplete + if (cmds.size() == 0) addLines("No known command starts with \""+prefix+"\""); + if (cmds.size() == 1) { + // one match: just replace input buffer with full command + std::string replaceWith = cmds.front(); + inputBuffer.replace(autocompleteFrom, prefix.length(), replaceWith); + inputBufferPosition += (replaceWith.length() - prefix.length()); + } + if (cmds.size() > 1) { + // multiple matches: show all matches and set input buffer to longest common prefix + std::string commonPrefix = cmds.front(); + while (cmds.begin() != cmds.end()) { + std::string cmd = cmds.front(); + cmds.pop_front(); + addLines(cmd); + for (int n = commonPrefix.length(); n >= 1; n--) { + if (cmd.compare(0, n, commonPrefix) != 0) commonPrefix.resize(n-1); else break; + } + } + std::string replaceWith = commonPrefix; + inputBuffer.replace(autocompleteFrom, prefix.length(), replaceWith); + inputBufferPosition += (replaceWith.length() - prefix.length()); + } +} + +void +Console::addLines(std::string s) +{ + std::istringstream iss(s); + std::string line; + while (std::getline(iss, line, '\n')) addLine(line); +} + +void +Console::addLine(std::string s) +{ + // output line to stderr + std::cerr << s << std::endl; + + // wrap long lines + std::string overflow; + unsigned int line_count = 0; + do { + lines.push_front(Font::wrap_to_chars(s, 99, &overflow)); + line_count++; + s = overflow; + } while (s.length() > 0); + + // trim scrollback buffer + while (lines.size() >= 1000) + lines.pop_back(); + + // increase console height if necessary + if (height < 64) { + if(height < 4) + height = 4; + height += fontheight * line_count; + } + + // reset console to full opacity + alpha = 1.0; + + // increase time that console stays open + if(stayOpen < 6) + stayOpen += 1.5; +} + +void +Console::parse(std::string s) +{ + // make sure we actually have something to parse + if (s.length() == 0) return; + + // add line to history + history.push_back(s); + history_position = history.end(); + + // split line into list of args + std::vector args; + size_t start = 0; + size_t end = 0; + while (1) { + start = s.find_first_not_of(" ,", end); + end = s.find_first_of(" ,", start); + if (start == s.npos) break; + args.push_back(s.substr(start, end-start)); + } + + // command is args[0] + if (args.size() == 0) return; + std::string command = args.front(); + args.erase(args.begin()); + + // ignore if it's an internal command + if (consoleCommand(command,args)) return; + + try { + execute_script(s); + } catch(std::exception& e) { + addLines(e.what()); + } + +} + +bool +Console::consoleCommand(std::string /*command*/, std::vector /*arguments*/) +{ + return false; +} + +bool +Console::hasFocus() +{ + return focused; +} + +void +Console::show() +{ + if(!config->console_enabled) + return; + + focused = true; + height = 256; + alpha = 1.0; + SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); +} + +void +Console::hide() +{ + focused = false; + height = 0; + stayOpen = 0; + + // clear input buffer + inputBuffer = ""; + inputBufferPosition = 0; + SDL_EnableKeyRepeat(0, SDL_DEFAULT_REPEAT_INTERVAL); +} + +void +Console::toggle() +{ + if (Console::hasFocus()) { + Console::hide(); + } + else { + Console::show(); + } +} + +void +Console::update(float elapsed_time) +{ + if(stayOpen > 0) { + stayOpen -= elapsed_time; + if(stayOpen < 0) + stayOpen = 0; + } else if(!focused && height > 0) { + alpha -= elapsed_time * FADE_SPEED; + if(alpha < 0) { + alpha = 0; + height = 0; + } + } +} + +void +Console::draw(DrawingContext& context) +{ + if (height == 0) + return; + + int layer = LAYER_GUI + 1; + + context.push_transform(); + context.set_alpha(alpha); + context.draw_surface(background2.get(), Vector(SCREEN_WIDTH/2 - background->get_width()/2 - background->get_width() + backgroundOffset, height - background->get_height()), layer); + context.draw_surface(background2.get(), Vector(SCREEN_WIDTH/2 - background->get_width()/2 + backgroundOffset, height - background->get_height()), layer); + for (int x = (SCREEN_WIDTH/2 - background->get_width()/2 - (static_cast(ceilf((float)SCREEN_WIDTH / (float)background->get_width()) - 1) * background->get_width())); x < SCREEN_WIDTH; x+=background->get_width()) { + context.draw_surface(background.get(), Vector(x, height - background->get_height()), layer); + } + backgroundOffset+=10; + if (backgroundOffset > (int)background->get_width()) backgroundOffset -= (int)background->get_width(); + + int lineNo = 0; + + if (focused) { + lineNo++; + float py = height-4-1 * font->get_height(); + context.draw_text(font.get(), "> "+inputBuffer, Vector(4, py), ALIGN_LEFT, layer); + if (SDL_GetTicks() % 1000 < 750) { + int cursor_px = 2 + inputBufferPosition; + context.draw_text(font.get(), "_", Vector(4 + (cursor_px * font->get_text_width("X")), py), ALIGN_LEFT, layer); + } + } + + int skipLines = -offset; + for (std::list::iterator i = lines.begin(); i != lines.end(); i++) { + if (skipLines-- > 0) continue; + lineNo++; + float py = height - 4 - lineNo*font->get_height(); + if (py < -font->get_height()) break; + context.draw_text(font.get(), *i, Vector(4, py), ALIGN_LEFT, layer); + } + context.pop_transform(); +} + +Console* Console::instance = NULL; +int Console::inputBufferPosition = 0; +std::string Console::inputBuffer; +ConsoleStreamBuffer Console::outputBuffer; +std::ostream Console::output(&Console::outputBuffer); + diff --git a/src/src/console.hpp b/src/src/console.hpp new file mode 100644 index 000000000..59d393486 --- /dev/null +++ b/src/src/console.hpp @@ -0,0 +1,141 @@ +// $Id$ +// +// SuperTux - Console +// Copyright (C) 2006 Christoph Sommer +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef SUPERTUX_CONSOLE_H +#define SUPERTUX_CONSOLE_H + +#include +#include +#include +#include +#include +#include +#include + +class Console; +class ConsoleStreamBuffer; +class ConsoleCommandReceiver; +class DrawingContext; +class Surface; +class Font; + +class Console +{ +public: + Console(); + ~Console(); + + static Console* instance; + + static std::ostream output; /**< stream of characters to output to the console. Do not forget to send std::endl or to flush the stream. */ + + void init_graphics(); + + void input(char c); /**< add character to inputBuffer */ + void backspace(); /**< delete character left of inputBufferPosition */ + void eraseChar(); /**< delete character at inputBufferPosition */ + void enter(); /**< process and clear input stream */ + void scroll(int offset); /**< scroll console text up or down by @c offset lines */ + void autocomplete(); /**< autocomplete current command */ + void show_history(int offset); /**< move @c offset lines forward through history; Negative offset moves backward */ + void move_cursor(int offset); /**< move the cursor @c offset chars to the right; Negative offset moves backward; 0xFFFF moves to the end */ + + void draw(DrawingContext& context); /**< draw the console in a DrawingContext */ + void update(float elapsed_time); + + void show(); /**< display the console */ + void hide(); /**< hide the console */ + void toggle(); /**< display the console if hidden, hide otherwise */ + + bool hasFocus(); /**< true if characters should be sent to the console instead of their normal target */ + + template static bool string_is(std::string s) { + std::istringstream iss(s); + T i; + if ((iss >> i) && iss.eof()) { + return true; + } else { + return false; + } + } + + template static T string_to(std::string s) { + std::istringstream iss(s); + T i; + if ((iss >> i) && iss.eof()) { + return i; + } else { + return T(); + } + } + +private: + std::list history; /**< command history. New lines get added to back. */ + std::list::iterator history_position; /**< item of command history that is currently displayed */ + std::list lines; /**< backbuffer of lines sent to the console. New lines get added to front. */ + + std::auto_ptr background; /**< console background image */ + std::auto_ptr background2; /**< second, moving console background image */ + + HSQUIRRELVM vm; /**< squirrel thread for the console (with custom roottable) */ + HSQOBJECT vm_object; + + int backgroundOffset; /**< current offset of scrolling background image */ + float height; /**< height of the console in px */ + float alpha; + int offset; /**< decrease to scroll text up */ + bool focused; /**< true if console has input focus */ + std::auto_ptr font; + float fontheight; /**< height of the font (this is a separate var, because the font could not be initialized yet but is needed in the addLine message */ + + float stayOpen; + + static int inputBufferPosition; /**< position in inputBuffer before which to append new characters */ + static std::string inputBuffer; /**< string used for keyboard input */ + static ConsoleStreamBuffer outputBuffer; /**< stream buffer used by output stream */ + + void addLines(std::string s); /**< display a string of (potentially) multiple lines in the console */ + void addLine(std::string s); /**< display a line in the console */ + void parse(std::string s); /**< react to a given command */ + + /** ready a virtual machine instance, creating a new thread and loading default .nut files if needed */ + void ready_vm(); + + /** execute squirrel script and output result */ + void execute_script(const std::string& s); + + bool consoleCommand(std::string command, std::vector arguments); /**< process internal command; return false if command was unknown, true otherwise */ + + friend class ConsoleStreamBuffer; + void flush(ConsoleStreamBuffer* buffer); /**< act upon changes in a ConsoleStreamBuffer */ +}; + +class ConsoleStreamBuffer : public std::stringbuf +{ + public: + int sync() + { + int result = std::stringbuf::sync(); + if(Console::instance != NULL) + Console::instance->flush(this); + return result; + } +}; + +#endif diff --git a/src/src/control/codecontroller.cpp b/src/src/control/codecontroller.cpp new file mode 100644 index 000000000..81bbfeb83 --- /dev/null +++ b/src/src/control/codecontroller.cpp @@ -0,0 +1,43 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "codecontroller.hpp" + +CodeController::CodeController() +{} + +CodeController::~CodeController() +{} + +void +CodeController::press(Control c, bool pressed) +{ + controls[c] = pressed; +} + +void +CodeController::update() +{ + Controller::update(); + + for(int i = 0; i < CONTROLCOUNT; ++i) + controls[i] = false; +} diff --git a/src/src/control/codecontroller.hpp b/src/src/control/codecontroller.hpp new file mode 100644 index 000000000..fdc089323 --- /dev/null +++ b/src/src/control/codecontroller.hpp @@ -0,0 +1,39 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __CODECONTROLLER_H__ +#define __CODECONTROLLER_H__ + +#include "controller.hpp" + +/** + * This is a dummy controler that doesn't react to any user input but should + * be controlled by code + */ +class CodeController : public Controller +{ +public: + CodeController(); + virtual ~CodeController(); + + void press(Control c, bool pressed = true); + void update(); +}; + +#endif diff --git a/src/src/control/controller.cpp b/src/src/control/controller.cpp new file mode 100644 index 000000000..ee7a3e2f0 --- /dev/null +++ b/src/src/control/controller.cpp @@ -0,0 +1,79 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "controller.hpp" + +const char* Controller::controlNames[] = { + "left", + "right", + "up", + "down", + "jump", + "action", + "pause-menu", + "menu-select", + "console", + "peek-left", + "peek-right", + 0 +}; + +Controller::Controller() +{ + reset(); +} + +Controller::~Controller() +{} + +void +Controller::reset() +{ + for(int i = 0; i < CONTROLCOUNT; ++i) { + controls[i] = false; + oldControls[i] = false; + } +} + +bool +Controller::hold(Control control) +{ + return controls[control]; +} + +bool +Controller::pressed(Control control) +{ + return !oldControls[control] && controls[control]; +} + +bool +Controller::released(Control control) +{ + return oldControls[control] && !controls[control]; +} + +void +Controller::update() +{ + for(int i = 0; i < CONTROLCOUNT; ++i) + oldControls[i] = controls[i]; +} diff --git a/src/src/control/controller.hpp b/src/src/control/controller.hpp new file mode 100644 index 000000000..c8b45e82f --- /dev/null +++ b/src/src/control/controller.hpp @@ -0,0 +1,68 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __CONTROLLER_H__ +#define __CONTROLLER_H__ + +class Controller +{ +public: + static const char* controlNames[]; + + enum Control { + LEFT = 0, + RIGHT, + UP, + DOWN, + + JUMP, + ACTION, + + PAUSE_MENU, + MENU_SELECT, + + CONSOLE, + + PEEK_LEFT, + PEEK_RIGHT, + + CONTROLCOUNT + }; + + Controller(); + virtual ~Controller(); + + /** returns true if the control is pressed down */ + bool hold(Control control); + /** returns true if the control has just been pressed down this frame */ + bool pressed(Control control); + /** returns true if the control has just been released this frame */ + bool released(Control control); + + virtual void reset(); + virtual void update(); + +protected: + /** current control status */ + bool controls[CONTROLCOUNT]; + /** control status at last frame */ + bool oldControls[CONTROLCOUNT]; +}; + +#endif diff --git a/src/src/control/joystickkeyboardcontroller.cpp b/src/src/control/joystickkeyboardcontroller.cpp new file mode 100644 index 000000000..2bc549d6f --- /dev/null +++ b/src/src/control/joystickkeyboardcontroller.cpp @@ -0,0 +1,1000 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun , +// 2007 Ingo Ruhnke +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include +#include "joystickkeyboardcontroller.hpp" +#include "log.hpp" +#include "gui/menu.hpp" +#include "gettext.hpp" +#include "lisp/lisp.hpp" +#include "lisp/list_iterator.hpp" +#include "game_session.hpp" +#include "console.hpp" +#include "gameconfig.hpp" + +class JoystickKeyboardController::JoystickMenu : public Menu +{ +public: + JoystickMenu(JoystickKeyboardController* controller); + virtual ~JoystickMenu(); + + void update(); + std::string get_button_name(int button); + void update_menu_item(Control id); + virtual void menu_action(MenuItem* item); + JoystickKeyboardController* controller; +}; + +class JoystickKeyboardController::KeyboardMenu : public Menu +{ +public: + KeyboardMenu(JoystickKeyboardController* controller); + ~KeyboardMenu(); + + void update(); + std::string get_key_name(SDLKey key); + virtual void menu_action(MenuItem* item); + JoystickKeyboardController* controller; +}; + +JoystickKeyboardController::JoystickKeyboardController() + : hat_state(0), + wait_for_key(-1), wait_for_joystick(-1), + key_options_menu(0), joystick_options_menu(0) +{ + // initialize default keyboard map + keymap[SDLK_LEFT] = LEFT; + keymap[SDLK_RIGHT] = RIGHT; + keymap[SDLK_UP] = UP; + keymap[SDLK_DOWN] = DOWN; + keymap[SDLK_SPACE] = JUMP; + keymap[SDLK_LCTRL] = ACTION; + keymap[SDLK_LALT] = ACTION; + keymap[SDLK_ESCAPE] = PAUSE_MENU; + keymap[SDLK_p] = PAUSE_MENU; + keymap[SDLK_PAUSE] = PAUSE_MENU; + keymap[SDLK_RETURN] = MENU_SELECT; + keymap[SDLK_KP_ENTER] = MENU_SELECT; + keymap[SDLK_CARET] = CONSOLE; + keymap[SDLK_DELETE] = PEEK_LEFT; + keymap[SDLK_END] = PEEK_RIGHT; + + jump_with_up = false; + + int joystick_count = SDL_NumJoysticks(); + min_joybuttons = -1; + max_joybuttons = -1; + max_joyaxis = -1; + max_joyhats = -1; + + for(int i = 0; i < joystick_count; ++i) { + SDL_Joystick* joystick = SDL_JoystickOpen(i); + bool good = true; + if(SDL_JoystickNumButtons(joystick) < 2) { + log_info << "Joystick " << i << " has less than 2 buttons" << std::endl; + good = false; + } + if(SDL_JoystickNumAxes(joystick) < 2 + && SDL_JoystickNumHats(joystick) == 0) { + log_info << "Joystick " << i << " has less than 2 axes and no hat" << std::endl; + good = false; + } + if(!good) { + SDL_JoystickClose(joystick); + continue; + } + + if(min_joybuttons < 0 || SDL_JoystickNumButtons(joystick) < min_joybuttons) + min_joybuttons = SDL_JoystickNumButtons(joystick); + + if(SDL_JoystickNumButtons(joystick) > max_joybuttons) + max_joybuttons = SDL_JoystickNumButtons(joystick); + + if(SDL_JoystickNumAxes(joystick) > max_joyaxis) + max_joyaxis = SDL_JoystickNumAxes(joystick); + + if(SDL_JoystickNumHats(joystick) > max_joyhats) + max_joyhats = SDL_JoystickNumHats(joystick); + + joysticks.push_back(joystick); + } + + dead_zone = 1000; + + // Default joystick button configuration + joy_button_map[0] = JUMP; + joy_button_map[1] = ACTION; + // 6 or more Buttons + if( min_joybuttons > 5 ){ + joy_button_map[4] = PEEK_LEFT; + joy_button_map[5] = PEEK_RIGHT; + // 8 or more + if(min_joybuttons > 7) + joy_button_map[min_joybuttons-1] = PAUSE_MENU; + } else { + // map the last 2 buttons to menu and pause + if(min_joybuttons > 2) + joy_button_map[min_joybuttons-1] = PAUSE_MENU; + // map all remaining joystick buttons to MENU_SELECT + for(int i = 2; i < max_joybuttons; ++i) { + if(i != min_joybuttons-1) + joy_button_map[i] = MENU_SELECT; + } + } + + // Default joystick axis configuration + joy_axis_map[-1] = LEFT; + joy_axis_map[ 1] = RIGHT; + joy_axis_map[-2] = UP; + joy_axis_map[ 2] = DOWN; + + // some joysticks or SDL seem to produce some bogus events after being opened + Uint32 ticks = SDL_GetTicks(); + while(SDL_GetTicks() - ticks < 200) { + SDL_Event event; + SDL_PollEvent(&event); + } +} + +JoystickKeyboardController::~JoystickKeyboardController() +{ + for(std::vector::iterator i = joysticks.begin(); + i != joysticks.end(); ++i) { + if(*i != 0) + SDL_JoystickClose(*i); + } + + delete key_options_menu; + delete joystick_options_menu; +} + +void +JoystickKeyboardController::read(const lisp::Lisp& lisp) +{ + const lisp::Lisp* keymap_lisp = lisp.get_lisp("keymap"); + if(keymap_lisp) { + keymap.clear(); + lisp::ListIterator iter(keymap_lisp); + while(iter.next()) { + if(iter.item() == "map") { + int key = -1; + std::string control; + const lisp::Lisp* map = iter.lisp(); + map->get("key", key); + map->get("control", control); + if(key < SDLK_FIRST || key >= SDLK_LAST) { + log_info << "Invalid key '" << key << "' in keymap" << std::endl; + continue; + } + + int i = 0; + for(i = 0; controlNames[i] != 0; ++i) { + if(control == controlNames[i]) + break; + } + if(controlNames[i] == 0) { + log_info << "Invalid control '" << control << "' in keymap" << std::endl; + continue; + } + keymap[(SDLKey) key] = (Control)i; + } else { + log_info << "Invalid lisp element '" << iter.item() << "' in keymap" << std::endl; + } + } + } + + const lisp::Lisp* joystick_lisp = lisp.get_lisp("joystick"); + if(joystick_lisp) { + joystick_lisp->get("dead-zone", dead_zone); + joystick_lisp->get("jump-with-up", jump_with_up); + lisp::ListIterator iter(joystick_lisp); + while(iter.next()) { + if(iter.item() == "map") { + int button = -1; + int axis = 0; + int hat = -1; + std::string control; + const lisp::Lisp* map = iter.lisp(); + + map->get("control", control); + int i = 0; + for(i = 0; controlNames[i] != 0; ++i) { + if(control == controlNames[i]) + break; + } + if(controlNames[i] == 0) { + log_info << "Invalid control '" << control << "' in buttonmap" << std::endl; + continue; + } + + if (map->get("button", button)) { + if(button < 0 || button >= max_joybuttons) { + log_info << "Invalid button '" << button << "' in buttonmap" << std::endl; + continue; + } + bind_joybutton(button, (Control) i); + } + + if (map->get("axis", axis)) { + if (axis == 0 || abs(axis) > max_joyaxis) { + log_info << "Invalid axis '" << axis << "' in axismap" << std::endl; + continue; + } + bind_joyaxis(axis, (Control) i); + } + + if (map->get("hat", hat)) { + if (hat != SDL_HAT_UP && + hat != SDL_HAT_DOWN && + hat != SDL_HAT_LEFT && + hat != SDL_HAT_RIGHT) { + log_info << "Invalid axis '" << axis << "' in axismap" << std::endl; + continue; + } else { + bind_joyhat(hat, (Control) i); + } + } + } + } + } +} + +void +JoystickKeyboardController::write(lisp::Writer& writer) +{ + writer.start_list("keymap"); + for(KeyMap::iterator i = keymap.begin(); i != keymap.end(); ++i) { + writer.start_list("map"); + writer.write_int("key", (int) i->first); + writer.write_string("control", controlNames[i->second]); + writer.end_list("map"); + } + writer.end_list("keymap"); + + writer.start_list("joystick"); + writer.write_int("dead-zone", dead_zone); + writer.write_bool("jump-with-up", jump_with_up); + + for(ButtonMap::iterator i = joy_button_map.begin(); i != joy_button_map.end(); + ++i) { + writer.start_list("map"); + writer.write_int("button", i->first); + writer.write_string("control", controlNames[i->second]); + writer.end_list("map"); + } + + for(HatMap::iterator i = joy_hat_map.begin(); i != joy_hat_map.end(); ++i) { + writer.start_list("map"); + writer.write_int("hat", i->first); + writer.write_string("control", controlNames[i->second]); + writer.end_list("map"); + } + + for(AxisMap::iterator i = joy_axis_map.begin(); i != joy_axis_map.end(); ++i) { + writer.start_list("map"); + writer.write_int("axis", i->first); + writer.write_string("control", controlNames[i->second]); + writer.end_list("map"); + } + + writer.end_list("joystick"); +} + +void +JoystickKeyboardController::reset() +{ + Controller::reset(); +} + +void +JoystickKeyboardController::set_joy_controls(Control id, bool value) +{ + if (jump_with_up && id == Controller::UP) + controls[Controller::JUMP] = value; + + controls[(Control)id] = value; +} + +void +JoystickKeyboardController::process_event(const SDL_Event& event) +{ + switch(event.type) { + case SDL_KEYUP: + case SDL_KEYDOWN: + process_key_event(event); + break; + + case SDL_JOYAXISMOTION: + process_axis_event(event.jaxis); + break; + + case SDL_JOYHATMOTION: + process_hat_event(event.jhat); + break; + + case SDL_JOYBUTTONDOWN: + case SDL_JOYBUTTONUP: + process_button_event(event.jbutton); + break; + + default: + break; + } +} + +void +JoystickKeyboardController::process_button_event(const SDL_JoyButtonEvent& jbutton) +{ + if(wait_for_joystick >= 0) + { + if(jbutton.state == SDL_PRESSED) + { + bind_joybutton(jbutton.button, (Control)wait_for_joystick); + joystick_options_menu->update(); + reset(); + wait_for_joystick = -1; + } + } + else + { + ButtonMap::iterator i = joy_button_map.find(jbutton.button); + if(i == joy_button_map.end()) { + log_debug << "Unmapped joybutton " << (int)jbutton.button << " pressed" << std::endl; + } else { + set_joy_controls(i->second, (jbutton.state == SDL_PRESSED)); + } + } +} + +void +JoystickKeyboardController::process_axis_event(const SDL_JoyAxisEvent& jaxis) +{ + if (wait_for_joystick >= 0) + { + if (abs(jaxis.value) > dead_zone) { + if (jaxis.value < 0) + bind_joyaxis(-(jaxis.axis + 1), Control(wait_for_joystick)); + else + bind_joyaxis(jaxis.axis + 1, Control(wait_for_joystick)); + + joystick_options_menu->update(); + wait_for_joystick = -1; + } + } + else + { + // Split the axis into left and right, so that both can be + // mapped seperatly (needed for jump/down vs up/down) + int axis = jaxis.axis + 1; + + AxisMap::iterator left = joy_axis_map.find(-axis); + AxisMap::iterator right = joy_axis_map.find(axis); + + if(left == joy_axis_map.end()) { + std::cout << "Unmapped joyaxis " << (int)jaxis.axis << " moved" << std::endl; + } else { + if (jaxis.value < -dead_zone) + set_joy_controls(left->second, true); + else if (jaxis.value > dead_zone) + set_joy_controls(left->second, false); + else + set_joy_controls(left->second, false); + } + + if(right == joy_axis_map.end()) { + std::cout << "Unmapped joyaxis " << (int)jaxis.axis << " moved" << std::endl; + } else { + if (jaxis.value < -dead_zone) + set_joy_controls(right->second, false); + else if (jaxis.value > dead_zone) + set_joy_controls(right->second, true); + else + set_joy_controls(right->second, false); + } + } +} + +void +JoystickKeyboardController::process_hat_event(const SDL_JoyHatEvent& jhat) +{ + Uint8 changed = hat_state ^ jhat.value; + + if (wait_for_joystick >= 0) + { + if (changed & SDL_HAT_UP && jhat.value & SDL_HAT_UP) + bind_joyhat(SDL_HAT_UP, (Control)wait_for_joystick); + + if (changed & SDL_HAT_DOWN && jhat.value & SDL_HAT_DOWN) + bind_joyhat(SDL_HAT_DOWN, (Control)wait_for_joystick); + + if (changed & SDL_HAT_LEFT && jhat.value & SDL_HAT_LEFT) + bind_joyhat(SDL_HAT_LEFT, (Control)wait_for_joystick); + + if (changed & SDL_HAT_RIGHT && jhat.value & SDL_HAT_RIGHT) + bind_joyhat(SDL_HAT_RIGHT, (Control)wait_for_joystick); + + joystick_options_menu->update(); + wait_for_joystick = -1; + } + else + { + if (changed & SDL_HAT_UP) + { + HatMap::iterator it = joy_hat_map.find(SDL_HAT_UP); + if (it != joy_hat_map.end()) + set_joy_controls(it->second, jhat.value & SDL_HAT_UP); + } + + if (changed & SDL_HAT_DOWN) + { + HatMap::iterator it = joy_hat_map.find(SDL_HAT_DOWN); + if (it != joy_hat_map.end()) + set_joy_controls(it->second, jhat.value & SDL_HAT_DOWN); + } + + if (changed & SDL_HAT_LEFT) + { + HatMap::iterator it = joy_hat_map.find(SDL_HAT_LEFT); + if (it != joy_hat_map.end()) + set_joy_controls(it->second, jhat.value & SDL_HAT_LEFT); + } + + if (changed & SDL_HAT_RIGHT) + { + HatMap::iterator it = joy_hat_map.find(SDL_HAT_RIGHT); + if (it != joy_hat_map.end()) + set_joy_controls(it->second, jhat.value & SDL_HAT_RIGHT); + } + } + + hat_state = jhat.value; +} + +void +JoystickKeyboardController::process_key_event(const SDL_Event& event) +{ + KeyMap::iterator key_mapping = keymap.find(event.key.keysym.sym); + + // if console key was pressed: toggle console + if ((key_mapping != keymap.end()) && (key_mapping->second == CONSOLE)) { + if (event.type == SDL_KEYDOWN) + Console::instance->toggle(); + } else { + if (Console::instance->hasFocus()) { + // if console is open: send key there + process_console_key_event(event); + } else if (Menu::current()) { + // if menu mode: send key there + process_menu_key_event(event); + } else if(key_mapping == keymap.end()) { + // default action: update controls + log_debug << "Key " << event.key.keysym.sym << " is unbound" << std::endl; + } else { + Control control = key_mapping->second; + controls[control] = (event.type == SDL_KEYDOWN); + } + } +} + +void +JoystickKeyboardController::process_console_key_event(const SDL_Event& event) +{ + if (event.type != SDL_KEYDOWN) return; + + switch (event.key.keysym.sym) { + case SDLK_RETURN: + Console::instance->enter(); + break; + case SDLK_BACKSPACE: + Console::instance->backspace(); + break; + case SDLK_TAB: + Console::instance->autocomplete(); + break; + case SDLK_PAGEUP: + Console::instance->scroll(-1); + break; + case SDLK_PAGEDOWN: + Console::instance->scroll(+1); + break; + case SDLK_HOME: + Console::instance->move_cursor(-65535); + break; + case SDLK_END: + Console::instance->move_cursor(+65535); + break; + case SDLK_UP: + Console::instance->show_history(-1); + break; + case SDLK_DOWN: + Console::instance->show_history(+1); + break; + case SDLK_LEFT: + Console::instance->move_cursor(-1); + break; + case SDLK_RIGHT: + Console::instance->move_cursor(+1); + break; + default: + int c = event.key.keysym.unicode; + if ((c >= 32) && (c <= 126)) { + Console::instance->input((char)c); + } + break; + } +} + +void +JoystickKeyboardController::process_menu_key_event(const SDL_Event& event) +{ + // wait for key mode? + if(wait_for_key >= 0) { + if(event.type == SDL_KEYUP) + return; + + if(event.key.keysym.sym != SDLK_ESCAPE + && event.key.keysym.sym != SDLK_PAUSE) { + bind_key(event.key.keysym.sym, (Control) wait_for_key); + } + reset(); + key_options_menu->update(); + wait_for_key = -1; + return; + } + if(wait_for_joystick >= 0) { + if(event.key.keysym.sym == SDLK_ESCAPE) { + reset(); + joystick_options_menu->update(); + wait_for_joystick = -1; + } + return; + } + + Control control; + /* we use default keys when the menu is open (to avoid problems when + * redefining keys to invalid settings + */ + switch(event.key.keysym.sym) { + case SDLK_UP: + control = UP; + break; + case SDLK_DOWN: + control = DOWN; + break; + case SDLK_LEFT: + control = LEFT; + break; + case SDLK_RIGHT: + control = RIGHT; + break; + case SDLK_SPACE: + case SDLK_RETURN: + case SDLK_KP_ENTER: + control = MENU_SELECT; + break; + case SDLK_ESCAPE: + case SDLK_PAUSE: + control = PAUSE_MENU; + break; + default: + return; + break; + } + + controls[control] = (event.type == SDL_KEYDOWN); +} + +void +JoystickKeyboardController::unbind_joystick_control(Control control) +{ + // remove all previous mappings for that control + for(AxisMap::iterator i = joy_axis_map.begin(); i != joy_axis_map.end(); /* no ++i */) { + if(i->second == control) + joy_axis_map.erase(i++); + else + ++i; + } + + for(ButtonMap::iterator i = joy_button_map.begin(); i != joy_button_map.end(); /* no ++i */) { + if(i->second == control) + joy_button_map.erase(i++); + else + ++i; + } + + for(HatMap::iterator i = joy_hat_map.begin(); i != joy_hat_map.end(); /* no ++i */) { + if(i->second == control) + joy_hat_map.erase(i++); + else + ++i; + } +} + +void +JoystickKeyboardController::bind_joyaxis(int axis, Control control) +{ + // axis isn't the SDL axis number, but axisnumber + 1 with sign + // changed depending on if the positive or negative end is to be + // used (negative axis 0 becomes -1, positive axis 2 becomes +3, + // etc.) + + unbind_joystick_control(control); + + // add new mapping + joy_axis_map[axis] = control; +} + +void +JoystickKeyboardController::bind_joyhat(int dir, Control c) +{ + unbind_joystick_control(c); + + // add new mapping + joy_hat_map[dir] = c; +} + +void +JoystickKeyboardController::bind_joybutton(int button, Control control) +{ + unbind_joystick_control(control); + + // add new mapping + joy_button_map[button] = control; +} + +void +JoystickKeyboardController::bind_key(SDLKey key, Control control) +{ + // remove all previous mappings for that control and for that key + for(KeyMap::iterator i = keymap.begin(); + i != keymap.end(); /* no ++i */) { + if(i->second == control) { + KeyMap::iterator e = i; + ++i; + keymap.erase(e); + } else { + ++i; + } + } + + KeyMap::iterator i = keymap.find(key); + if(i != keymap.end()) + keymap.erase(i); + + // add new mapping + keymap[key]= control; +} + +void +JoystickKeyboardController::print_joystick_mappings() +{ + std::cout << "Joystick Mappings" << std::endl; + std::cout << "-----------------" << std::endl; + for(AxisMap::iterator i = joy_axis_map.begin(); i != joy_axis_map.end(); ++i) { + std::cout << "Axis: " << i->first << " -> " << i->second << std::endl; + } + + for(ButtonMap::iterator i = joy_button_map.begin(); i != joy_button_map.end(); ++i) { + std::cout << "Button: " << i->first << " -> " << i->second << std::endl; + } + + for(HatMap::iterator i = joy_hat_map.begin(); i != joy_hat_map.end(); ++i) { + std::cout << "Hat: " << i->first << " -> " << i->second << std::endl; + } + std::cout << std::endl; +} + +SDLKey +JoystickKeyboardController::reversemap_key(Control c) +{ + for(KeyMap::iterator i = keymap.begin(); i != keymap.end(); ++i) { + if(i->second == c) + return i->first; + } + + return SDLK_UNKNOWN; +} + +int +JoystickKeyboardController::reversemap_joyaxis(Control c) +{ + for(AxisMap::iterator i = joy_axis_map.begin(); i != joy_axis_map.end(); ++i) { + if(i->second == c) + return i->first; + } + + return 0; +} + +int +JoystickKeyboardController::reversemap_joybutton(Control c) +{ + for(ButtonMap::iterator i = joy_button_map.begin(); i != joy_button_map.end(); ++i) { + if(i->second == c) + return i->first; + } + + return -1; +} + +int +JoystickKeyboardController::reversemap_joyhat(Control c) +{ + for(HatMap::iterator i = joy_hat_map.begin(); i != joy_hat_map.end(); ++i) { + if(i->second == c) + return i->first; + } + + return -1; +} + +Menu* +JoystickKeyboardController::get_key_options_menu() +{ + if(key_options_menu == 0) { + key_options_menu = new KeyboardMenu(this); + } + + return key_options_menu; +} + +Menu* +JoystickKeyboardController::get_joystick_options_menu() +{ + if(joystick_options_menu == 0) { + joystick_options_menu = new JoystickMenu(this); + } + + return joystick_options_menu; +} + +//---------------------------------------------------------------------------- + +JoystickKeyboardController::KeyboardMenu::KeyboardMenu( + JoystickKeyboardController* _controller) + : controller(_controller) +{ + add_label(_("Setup Keyboard")); + add_hl(); + add_controlfield(Controller::UP, _("Up")); + add_controlfield(Controller::DOWN, _("Down")); + add_controlfield(Controller::LEFT, _("Left")); + add_controlfield(Controller::RIGHT, _("Right")); + add_controlfield(Controller::JUMP, _("Jump")); + add_controlfield(Controller::ACTION, _("Action")); + add_controlfield(Controller::PEEK_LEFT, _("Peek Left")); + add_controlfield(Controller::PEEK_RIGHT, _("Peek Right")); + if (config->console_enabled) { + add_controlfield(Controller::CONSOLE, _("Console")); + } + add_hl(); + add_back(_("Back")); + update(); +} + +JoystickKeyboardController::KeyboardMenu::~KeyboardMenu() +{} + +std::string +JoystickKeyboardController::KeyboardMenu::get_key_name(SDLKey key) +{ + switch(key) { + case SDLK_UNKNOWN: + return _("None"); + case SDLK_UP: + return _("Up cursor"); + case SDLK_DOWN: + return _("Down cursor"); + case SDLK_LEFT: + return _("Left cursor"); + case SDLK_RIGHT: + return _("Right cursor"); + case SDLK_RETURN: + return _("Return"); + case SDLK_SPACE: + return _("Space"); + case SDLK_RSHIFT: + return _("Right Shift"); + case SDLK_LSHIFT: + return _("Left Shift"); + case SDLK_RCTRL: + return _("Right Control"); + case SDLK_LCTRL: + return _("Left Control"); + case SDLK_RALT: + return _("Right Alt"); + case SDLK_LALT: + return _("Left Alt"); + default: + return SDL_GetKeyName((SDLKey) key); + } +} + +void +JoystickKeyboardController::KeyboardMenu::menu_action(MenuItem* item) +{ + assert(item->id >= 0 && item->id < Controller::CONTROLCOUNT); + item->change_input(_("Press Key")); + controller->wait_for_key = item->id; +} + +void +JoystickKeyboardController::KeyboardMenu::update() +{ + // update menu + get_item_by_id((int) Controller::UP).change_input(get_key_name( + controller->reversemap_key(Controller::UP))); + get_item_by_id((int) Controller::DOWN).change_input(get_key_name( + controller->reversemap_key(Controller::DOWN))); + get_item_by_id((int) Controller::LEFT).change_input(get_key_name( + controller->reversemap_key(Controller::LEFT))); + get_item_by_id((int) Controller::RIGHT).change_input(get_key_name( + controller->reversemap_key(Controller::RIGHT))); + get_item_by_id((int) Controller::JUMP).change_input(get_key_name( + controller->reversemap_key(Controller::JUMP))); + get_item_by_id((int) Controller::ACTION).change_input(get_key_name( + controller->reversemap_key(Controller::ACTION))); + get_item_by_id((int) Controller::PEEK_LEFT).change_input(get_key_name( + controller->reversemap_key(Controller::PEEK_LEFT))); + get_item_by_id((int) Controller::PEEK_RIGHT).change_input(get_key_name( + controller->reversemap_key(Controller::PEEK_RIGHT))); + if (config->console_enabled) { + get_item_by_id((int) Controller::CONSOLE).change_input(get_key_name( + controller->reversemap_key(Controller::CONSOLE))); + } +} + +//--------------------------------------------------------------------------- + +JoystickKeyboardController::JoystickMenu::JoystickMenu( + JoystickKeyboardController* _controller) + : controller(_controller) +{ + add_label(_("Setup Joystick")); + add_hl(); + if(controller->joysticks.size() > 0) { + add_controlfield(Controller::UP, _("Up")); + add_controlfield(Controller::DOWN, _("Down")); + add_controlfield(Controller::LEFT, _("Left")); + add_controlfield(Controller::RIGHT, _("Right")); + add_controlfield(Controller::JUMP, _("Jump")); + add_controlfield(Controller::ACTION, _("Action")); + add_controlfield(Controller::PAUSE_MENU, _("Pause/Menu")); + add_controlfield(Controller::PEEK_LEFT, _("Peek Left")); + add_controlfield(Controller::PEEK_RIGHT, _("Peek Right")); + + add_toggle(Controller::CONTROLCOUNT, _("Jump with Up"), controller->jump_with_up); + } else { + add_deactive(-1, _("No Joysticks found")); + } + add_hl(); + add_back(_("Back")); + update(); +} + +JoystickKeyboardController::JoystickMenu::~JoystickMenu() +{} + +std::string +JoystickKeyboardController::JoystickMenu::get_button_name(int button) +{ + if(button < 0) + return _("None"); + + std::ostringstream name; + name << "Button " << button; + return name.str(); +} + +void +JoystickKeyboardController::JoystickMenu::menu_action(MenuItem* item) +{ + if (item->id >= 0 && item->id < Controller::CONTROLCOUNT) { + item->change_input(_("Press Button")); + controller->wait_for_joystick = item->id; + } else if (item->id == Controller::CONTROLCOUNT) { + controller->jump_with_up = item->toggled; + } +} + +void +JoystickKeyboardController::JoystickMenu::update_menu_item(Control id) +{ + int button = controller->reversemap_joybutton(id); + int axis = controller->reversemap_joyaxis(id); + int hat_dir = controller->reversemap_joyhat(id); + + if (button != -1) { + get_item_by_id((int)id).change_input(get_button_name(button)); + } else if (axis != 0) { + std::ostringstream name; + + name << "Axis "; + + if (axis < 0) + name << "-"; + else + name << "+"; + + if (abs(axis) == 1) + name << "X"; + else if (abs(axis) == 2) + name << "Y"; + else if (abs(axis) == 2) + name << "X2"; + else if (abs(axis) == 3) + name << "Y2"; + else + name << abs(axis); + + get_item_by_id((int)id).change_input(name.str()); + } else if (hat_dir != -1) { + std::string name; + + switch (hat_dir) + { + case SDL_HAT_UP: + name = "Hat Up"; + break; + + case SDL_HAT_DOWN: + name = "Hat Down"; + break; + + case SDL_HAT_LEFT: + name = "Hat Left"; + break; + + case SDL_HAT_RIGHT: + name = "Hat Right"; + break; + + default: + name = "Unknown hat_dir"; + break; + } + + get_item_by_id((int)id).change_input(name); + } else { + get_item_by_id((int)id).change_input("None"); + } +} + +void +JoystickKeyboardController::JoystickMenu::update() +{ + if(controller->joysticks.size() == 0) + return; + + update_menu_item(Controller::UP); + update_menu_item(Controller::DOWN); + update_menu_item(Controller::LEFT); + update_menu_item(Controller::RIGHT); + + update_menu_item(Controller::JUMP); + update_menu_item(Controller::ACTION); + update_menu_item(Controller::PAUSE_MENU); + update_menu_item(Controller::PEEK_LEFT); + update_menu_item(Controller::PEEK_RIGHT); + + get_item_by_id(Controller::CONTROLCOUNT).toggled = controller->jump_with_up; +} diff --git a/src/src/control/joystickkeyboardcontroller.hpp b/src/src/control/joystickkeyboardcontroller.hpp new file mode 100644 index 000000000..50225dc2d --- /dev/null +++ b/src/src/control/joystickkeyboardcontroller.hpp @@ -0,0 +1,115 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __JOYSTICKKEYBOARDCONTROLLER_H__ +#define __JOYSTICKKEYBOARDCONTROLLER_H__ + +#include "controller.hpp" +#include "lisp/lisp.hpp" +#include "lisp/writer.hpp" +#include +#include +#include + +class Menu; + +class JoystickKeyboardController : public Controller +{ +public: + JoystickKeyboardController(); + virtual ~JoystickKeyboardController(); + + /** Process an SDL Event and return true if the event has been used + */ + void process_event(const SDL_Event& event); + + void write(lisp::Writer& writer); + void read(const lisp::Lisp& lisp); + void reset(); + + Menu* get_key_options_menu(); + Menu* get_joystick_options_menu(); + +private: + void process_key_event(const SDL_Event& event); + void process_hat_event(const SDL_JoyHatEvent& jhat); + void process_axis_event(const SDL_JoyAxisEvent& jaxis); + void process_button_event(const SDL_JoyButtonEvent& jbutton); + void process_console_key_event(const SDL_Event& event); + void process_menu_key_event(const SDL_Event& event); + + void print_joystick_mappings(); + + typedef std::map KeyMap; + KeyMap keymap; + + typedef std::map ButtonMap; + ButtonMap joy_button_map; + + typedef std::map AxisMap; + AxisMap joy_axis_map; + + typedef std::map HatMap; + HatMap joy_hat_map; + + std::vector joysticks; + + std::string name; + + int dead_zone; + /// the number of buttons all joysticks have + int min_joybuttons; + /// the max number of buttons a joystick has + int max_joybuttons; + + int max_joyaxis; + + int max_joyhats; + + Uint8 hat_state; + + bool jump_with_up; + + SDLKey reversemap_key(Control c); + int reversemap_joybutton(Control c); + int reversemap_joyaxis(Control c); + int reversemap_joyhat(Control c); + + void unbind_joystick_control(Control c); + + void bind_joybutton(int button, Control c); + void bind_joyaxis(int axis, Control c); + void bind_joyhat(int dir, Control c); + void bind_key(SDLKey key, Control c); + + void set_joy_controls(Control id, bool value); + + int wait_for_key; + int wait_for_joystick; + + class KeyboardMenu; + class JoystickMenu; + + KeyboardMenu* key_options_menu; + JoystickMenu* joystick_options_menu; + friend class KeyboardMenu; + friend class JoystickMenu; +}; + +#endif diff --git a/src/src/direction.hpp b/src/src/direction.hpp new file mode 100644 index 000000000..e665d2073 --- /dev/null +++ b/src/src/direction.hpp @@ -0,0 +1,25 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +#ifndef SUPERTUX_DIRECTION_H +#define SUPERTUX_DIRECTION_H + +enum Direction { AUTO, LEFT, RIGHT, UP, DOWN }; + +#endif diff --git a/src/src/fadeout.cpp b/src/src/fadeout.cpp new file mode 100644 index 000000000..283771ea1 --- /dev/null +++ b/src/src/fadeout.cpp @@ -0,0 +1,56 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#include + +#include "fadeout.hpp" +#include "main.hpp" +#include "video/drawing_context.hpp" + +FadeOut::FadeOut(float fade_time, Color color) + : color(color), fade_time(fade_time), accum_time(0) +{ +} + +FadeOut::~FadeOut() +{ +} + +void +FadeOut::update(float elapsed_time) +{ + accum_time += elapsed_time; + if(accum_time > fade_time) + accum_time = fade_time; +} + +void +FadeOut::draw(DrawingContext& context) +{ + Color col = color; + col.alpha = accum_time / fade_time; + context.draw_filled_rect(Vector(0, 0), + Vector(SCREEN_WIDTH, SCREEN_HEIGHT), + col, LAYER_GUI+1); +} + +bool +FadeOut::done() +{ + return accum_time >= fade_time; +} diff --git a/src/src/fadeout.hpp b/src/src/fadeout.hpp new file mode 100644 index 000000000..b005b8c28 --- /dev/null +++ b/src/src/fadeout.hpp @@ -0,0 +1,46 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#ifndef __FADEOUT_HPP__ +#define __FADEOUT_HPP__ + +#include "video/color.hpp" +#include "screen_fade.hpp" + +/** + * Fades a screen towards a specific color + */ +class FadeOut : public ScreenFade +{ +public: + FadeOut(float fade_time, Color dest_color = Color(0, 0, 0)); + virtual ~FadeOut(); + + virtual void update(float elapsed_time); + virtual void draw(DrawingContext& context); + + /// returns true if the effect is completed + virtual bool done(); + +private: + Color color; + float fade_time; + float accum_time; +}; + +#endif diff --git a/src/src/file_system.cpp b/src/src/file_system.cpp new file mode 100644 index 000000000..266da812f --- /dev/null +++ b/src/src/file_system.cpp @@ -0,0 +1,110 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include "log.hpp" +#include "file_system.hpp" + +#include +#include +#include + +namespace FileSystem +{ + +std::string dirname(const std::string& filename) +{ + std::string::size_type p = filename.find_last_of('/'); + if(p == std::string::npos) + return ""; + + return filename.substr(0, p+1); +} + +std::string basename(const std::string& filename) +{ + std::string::size_type p = filename.find_last_of('/'); + if(p == std::string::npos) + return filename; + + return filename.substr(p+1, filename.size()-p-1); +} + +std::string strip_extension(const std::string& filename) +{ + std::string::size_type p = filename.find_last_of('.'); + if(p == std::string::npos) + return filename; + + return filename.substr(0, p); +} + +std::string normalize(const std::string& filename) +{ + std::vector path_stack; + + const char* p = filename.c_str(); + + while(true) { + while(*p == '/') { + p++; + continue; + } + + const char* pstart = p; + while(*p != '/' && *p != 0) { + ++p; + } + + size_t len = p - pstart; + if(len == 0) + break; + + std::string pathelem(pstart, p-pstart); + if(pathelem == ".") + continue; + + if(pathelem == "..") { + if(path_stack.empty()) { + + log_warning << "Invalid '..' in path '" << filename << "'" << std::endl; + // push it into the result path so that the users sees his error... + path_stack.push_back(pathelem); + } else { + path_stack.pop_back(); + } + } else { + path_stack.push_back(pathelem); + } + } + + // construct path + std::ostringstream result; + for(std::vector::iterator i = path_stack.begin(); + i != path_stack.end(); ++i) { + result << '/' << *i; + } + if(path_stack.empty()) + result << '/'; + + return result.str(); +} + +} diff --git a/src/src/file_system.hpp b/src/src/file_system.hpp new file mode 100644 index 000000000..b8211884c --- /dev/null +++ b/src/src/file_system.hpp @@ -0,0 +1,43 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef __FILESYSTEM_H__ +#define __FILESYSTEM_H__ + +#include +#include + +namespace FileSystem +{ + std::string dirname(const std::string& filename); + std::string basename(const std::string& filename); + + /** + * remove everything starting from and including the last dot + */ + std::string strip_extension(const std::string& filename); + + /** + * normalize filename so that "blup/bla/blo/../../bar" will become + * "blup/bar" + */ + std::string normalize(const std::string& filename); +} + +#endif diff --git a/src/src/flip_level_transformer.cpp b/src/src/flip_level_transformer.cpp new file mode 100644 index 000000000..83b8c4dfe --- /dev/null +++ b/src/src/flip_level_transformer.cpp @@ -0,0 +1,136 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +#include + +#include "flip_level_transformer.hpp" +#include "object/tilemap.hpp" +#include "object/camera.hpp" +#include "badguy/badguy.hpp" +#include "sector.hpp" +#include "tile_manager.hpp" +#include "spawn_point.hpp" +#include "object/platform.hpp" +#include "object/block.hpp" + +void +FlipLevelTransformer::transform_sector(Sector* sector) +{ + float height = sector->get_height(); + + for(Sector::GameObjects::iterator i = sector->gameobjects.begin(); + i != sector->gameobjects.end(); ++i) { + GameObject* object = *i; + + TileMap* tilemap = dynamic_cast (object); + if(tilemap) { + transform_tilemap(tilemap); + } + Player* player = dynamic_cast (object); + if(player) { + Vector pos = player->get_pos(); + pos.y = height - pos.y - player->get_bbox().get_height(); + player->move(pos); + continue; + } + BadGuy* badguy = dynamic_cast (object); + if(badguy) { + transform_badguy(height, badguy); + } + Platform* platform = dynamic_cast (object); + if(platform) { + transform_platform(height, *platform); + } + Block* block = dynamic_cast (object); + if(block) { + transform_block(height, *block); + } + MovingObject* mobject = dynamic_cast (object); + if(mobject) { + transform_moving_object(height, mobject); + } + } + for(Sector::SpawnPoints::iterator i = sector->spawnpoints.begin(); + i != sector->spawnpoints.end(); ++i) { + transform_spawnpoint(height, *i); + } + + if(sector->camera != 0 && sector->player != 0) + sector->camera->reset(sector->player->get_pos()); +} + +void +FlipLevelTransformer::transform_tilemap(TileMap* tilemap) +{ + for(size_t x = 0; x < tilemap->get_width(); ++x) { + for(size_t y = 0; y < tilemap->get_height()/2; ++y) { + // swap tiles + int y2 = tilemap->get_height()-1-y; + const Tile* t1 = tilemap->get_tile(x, y); + const Tile* t2 = tilemap->get_tile(x, y2); + tilemap->change(x, y, t2->getID()); + tilemap->change(x, y2, t1->getID()); + } + } + if(tilemap->get_drawing_effect() != 0) { + tilemap->set_drawing_effect(NO_EFFECT); + } else { + tilemap->set_drawing_effect(VERTICAL_FLIP); + } +} + +void +FlipLevelTransformer::transform_badguy(float height, BadGuy* badguy) +{ + Vector pos = badguy->get_start_position(); + pos.y = height - pos.y; + badguy->set_start_position(pos); +} + +void +FlipLevelTransformer::transform_spawnpoint(float height, SpawnPoint* spawn) +{ + Vector pos = spawn->pos; + pos.y = height - pos.y; + spawn->pos = pos; +} + +void +FlipLevelTransformer::transform_moving_object(float height, MovingObject*object) +{ + Vector pos = object->get_pos(); + pos.y = height - pos.y - object->get_bbox().get_height(); + object->set_pos(pos); +} + +void +FlipLevelTransformer::transform_platform(float height, Platform& platform) +{ + Path& path = platform.get_path(); + for (std::vector::iterator i = path.nodes.begin(); i != path.nodes.end(); i++) { + Vector& pos = i->position; + pos.y = height - pos.y - platform.get_bbox().get_height(); + } +} + +void +FlipLevelTransformer::transform_block(float height, Block& block) +{ + block.original_y = height - block.original_y - block.get_bbox().get_height(); +} diff --git a/src/src/flip_level_transformer.hpp b/src/src/flip_level_transformer.hpp new file mode 100644 index 000000000..832fe08ab --- /dev/null +++ b/src/src/flip_level_transformer.hpp @@ -0,0 +1,47 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +#ifndef __FLIP_LEVEL_TRANSFORMER_H__ +#define __FLIP_LEVEL_TRANSFORMER_H__ + +#include "level_transformer.hpp" + +class TileMap; +class BadGuy; +class SpawnPoint; +class MovingObject; +class Platform; +class Block; + +/** Vertically or horizontally flip a level */ +class FlipLevelTransformer : public LevelTransformer +{ +public: + virtual void transform_sector(Sector* sector); + +private: + void transform_tilemap(TileMap* tilemap); + void transform_moving_object(float height, MovingObject* object); + void transform_badguy(float height, BadGuy* badguy); + void transform_spawnpoint(float height, SpawnPoint* spawnpoint); + void transform_platform(float height, Platform& platform); + void transform_block(float height, Block& block); +}; + +#endif diff --git a/src/src/game_object.cpp b/src/src/game_object.cpp new file mode 100644 index 000000000..273367f6e --- /dev/null +++ b/src/src/game_object.cpp @@ -0,0 +1,72 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include +#include "log.hpp" +#include "game_object.hpp" +#include "object_remove_listener.hpp" + +GameObject::GameObject() + : wants_to_die(false), remove_listeners(NULL) +{ +} + +GameObject::~GameObject() +{ + // call remove listeners (and remove them from the list) + RemoveListenerListEntry* entry = remove_listeners; + while(entry != NULL) { + RemoveListenerListEntry* next = entry->next; + entry->listener->object_removed(this); + delete entry; + entry = next; + } +} + + +void +GameObject::add_remove_listener(ObjectRemoveListener* listener) +{ + RemoveListenerListEntry* entry = new RemoveListenerListEntry(); + entry->next = remove_listeners; + entry->listener = listener; + remove_listeners = entry; +} + +void +GameObject::del_remove_listener(ObjectRemoveListener* listener) +{ + RemoveListenerListEntry* entry = remove_listeners; + if (entry->listener == listener) { + remove_listeners = entry->next; + delete entry; + return; + } + RemoveListenerListEntry* next = entry->next; + while(next != NULL) { + if (next->listener == listener) { + entry->next = next->next; + delete next; + break; + } + entry = next; + next = next->next; + } +} + diff --git a/src/src/game_object.hpp b/src/src/game_object.hpp new file mode 100644 index 000000000..5acc83338 --- /dev/null +++ b/src/src/game_object.hpp @@ -0,0 +1,105 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#ifndef SUPERTUX_GAMEOBJECT_H +#define SUPERTUX_GAMEOBJECT_H + +#include +#include "refcounter.hpp" + +class DrawingContext; +class ObjectRemoveListener; + +/** + * This is a base class for all game objects. Each sector of a level will hold a + * list of active GameObject while the game is played. + * + * This class is responsible for: + * - Updating and Drawing the object. This should happen in the update() and + * draw() functions. Both are called once per frame. + * - Providing a safe way to remove the object by calling the remove_me + * functions. + */ +class GameObject : public RefCounter +{ +public: + GameObject(); + virtual ~GameObject(); + + /** This function is called once per frame and allows the object to update + * it's state. The elapsed_time is the time since the last frame in + * seconds and should be the base for all timed calculations (don't use + * SDL_GetTicks directly as this will fail in pause mode) + */ + virtual void update(float elapsed_time) = 0; + + /** The GameObject should draw itself onto the provided DrawingContext if this + * function is called. + */ + virtual void draw(DrawingContext& context) = 0; + + /** returns true if the object is not scheduled to be removed yet */ + bool is_valid() const + { + return !wants_to_die; + } + + /** schedules this object to be removed at the end of the frame */ + void remove_me() + { + wants_to_die = true; + } + + /** registers a remove listener which will be called if the object + * gets removed/destroyed + */ + void add_remove_listener(ObjectRemoveListener* listener); + + /** + * unregisters a remove listener, so it will no longer be called if the object + * gets removed/destroyed + */ + void del_remove_listener(ObjectRemoveListener* listener); + + const std::string& get_name() const + { + return name; + } + +private: + /** this flag indicates if the object should be removed at the end of the + * frame + */ + bool wants_to_die; + + struct RemoveListenerListEntry + { + RemoveListenerListEntry* next; + ObjectRemoveListener* listener; + }; + RemoveListenerListEntry* remove_listeners; + +protected: + /** + * a name for the gameobject, this is mostly a hint for scripts and for + * debugging, don't rely on names being set or being unique + */ + std::string name; +}; + +#endif /*SUPERTUX_GAMEOBJECT_H*/ diff --git a/src/src/game_session.cpp b/src/src/game_session.cpp new file mode 100644 index 000000000..0a61cd336 --- /dev/null +++ b/src/src/game_session.cpp @@ -0,0 +1,647 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "game_session.hpp" +#include "log.hpp" +#include "console.hpp" +#include "worldmap/worldmap.hpp" +#include "mainloop.hpp" +#include "audio/sound_manager.hpp" +#include "gui/menu.hpp" +#include "sector.hpp" +#include "level.hpp" +#include "tile.hpp" +#include "player_status.hpp" +#include "object/particlesystem.hpp" +#include "object/background.hpp" +#include "object/gradient.hpp" +#include "object/tilemap.hpp" +#include "object/camera.hpp" +#include "object/player.hpp" +#include "object/level_time.hpp" +#include "lisp/lisp.hpp" +#include "lisp/parser.hpp" +#include "resources.hpp" +#include "statistics.hpp" +#include "timer.hpp" +#include "options_menu.hpp" +#include "textscroller.hpp" +#include "control/codecontroller.hpp" +#include "control/joystickkeyboardcontroller.hpp" +#include "main.hpp" +#include "file_system.hpp" +#include "gameconfig.hpp" +#include "gettext.hpp" +#include "console.hpp" +#include "flip_level_transformer.hpp" +#include "trigger/secretarea_trigger.hpp" +#include "trigger/sequence_trigger.hpp" +#include "random_generator.hpp" +#include "scripting/squirrel_util.hpp" +#include "object/endsequence_walkright.hpp" +#include "object/endsequence_walkleft.hpp" +#include "object/endsequence_fireworks.hpp" +#include "direction.hpp" +#include "scripting/time_scheduler.hpp" + +// the engine will be run with a logical framerate of 64fps. +// We chose 64fps here because it is a power of 2, so 1/64 gives an "even" +// binary fraction... +static const float LOGICAL_FPS = 64.0; + +enum GameMenuIDs { + MNID_CONTINUE, + MNID_ABORTLEVEL +}; + +GameSession* GameSession::current_ = NULL; + +GameSession::GameSession(const std::string& levelfile_, Statistics* statistics) + : level(0), currentsector(0), + end_sequence(0), + levelfile(levelfile_), best_level_statistics(statistics), + capture_demo_stream(0), playback_demo_stream(0), demo_controller(0), + play_time(0) +{ + current_ = this; + currentsector = NULL; + + game_pause = false; + speed_before_pause = main_loop->get_speed(); + + statistics_backdrop.reset(new Surface("images/engine/menu/score-backdrop.png")); + + restart_level(); + + game_menu.reset(new Menu()); + game_menu->add_label(_("Pause")); + game_menu->add_hl(); + game_menu->add_entry(MNID_CONTINUE, _("Continue")); + game_menu->add_submenu(_("Options"), get_options_menu()); + game_menu->add_hl(); + game_menu->add_entry(MNID_ABORTLEVEL, _("Abort Level")); +} + +void +GameSession::restart_level() +{ + game_pause = false; + end_sequence = 0; + + main_controller->reset(); + + currentsector = 0; + + level.reset(new Level); + level->load(levelfile); + level->stats.total_coins = level->get_total_coins(); + level->stats.total_badguys = level->get_total_badguys(); + level->stats.total_secrets = level->get_total_count(); + level->stats.reset(); + if(reset_sector != "")level->stats.declare_invalid(); + + if(reset_sector != "") { + currentsector = level->get_sector(reset_sector); + if(!currentsector) { + std::stringstream msg; + msg << "Couldn't find sector '" << reset_sector << "' for resetting tux."; + throw std::runtime_error(msg.str()); + } + currentsector->activate(reset_pos); + } else { + currentsector = level->get_sector("main"); + if(!currentsector) + throw std::runtime_error("Couldn't find main sector"); + currentsector->activate("main"); + } + + //levelintro(); + + sound_manager->stop_music(); + currentsector->play_music(LEVEL_MUSIC); + + if(capture_file != "") { + int newSeed=0; // next run uses a new seed + while (newSeed == 0) // which is the next non-zero random num. + newSeed = systemRandom.rand(); + config->random_seed = systemRandom.srand(newSeed); + log_info << "Next run uses random seed " <random_seed <good()) { + std::stringstream msg; + msg << "Couldn't open demo file '" << filename << "' for writing."; + throw std::runtime_error(msg.str()); + } + capture_file = filename; + + char buf[30]; // save the seed in the demo file + snprintf(buf, sizeof(buf), "random_seed=%10d", config->random_seed); + for (int i=0; i==0 || buf[i-1]; i++) + capture_demo_stream->put(buf[i]); +} + +int +GameSession::get_demo_random_seed(const std::string& filename) +{ + std::istream* test_stream = new std::ifstream(filename.c_str()); + if(test_stream->good()) { + char buf[30]; // recall the seed from the demo file + int seed; + for (int i=0; i<30 && (i==0 || buf[i-1]); i++) + test_stream->get(buf[i]); + if (sscanf(buf, "random_seed=%10d", &seed) == 1) { + log_info << "Random seed " << seed << " from demo file" << std::endl; + return seed; + } + else + log_info << "Demo file contains no random number" << std::endl; + } + return 0; +} + +void +GameSession::play_demo(const std::string& filename) +{ + delete playback_demo_stream; + delete demo_controller; + + playback_demo_stream = new std::ifstream(filename.c_str()); + if(!playback_demo_stream->good()) { + std::stringstream msg; + msg << "Couldn't open demo file '" << filename << "' for reading."; + throw std::runtime_error(msg.str()); + } + + Player& tux = *currentsector->player; + demo_controller = new CodeController(); + tux.set_controller(demo_controller); + + // skip over random seed, if it exists in the file + char buf[30]; // ascii decimal seed + int seed; + for (int i=0; i<30 && (i==0 || buf[i-1]); i++) + playback_demo_stream->get(buf[i]); + if (sscanf(buf, "random_seed=%010d", &seed) != 1) + playback_demo_stream->seekg(0); // old style w/o seed, restart at beg +} + +void +GameSession::levelintro() +{ + sound_manager->stop_music(); + + DrawingContext context; + for(Sector::GameObjects::iterator i = currentsector->gameobjects.begin(); + i != currentsector->gameobjects.end(); ++i) { + Background* background = dynamic_cast (*i); + if(background) { + background->draw(context); + } + Gradient* gradient = dynamic_cast (*i); + if(gradient) { + gradient->draw(context); + } + } + +// context.draw_text(gold_text, level->get_name(), Vector(SCREEN_WIDTH/2, 160), +// ALIGN_CENTER, LAYER_FOREGROUND1); + context.draw_center_text(gold_text, level->get_name(), Vector(0, 160), + LAYER_FOREGROUND1); + + std::stringstream ss_coins; + ss_coins << _("Coins") << ": " << player_status->coins; + context.draw_text(white_text, ss_coins.str(), Vector(SCREEN_WIDTH/2, 210), + ALIGN_CENTER, LAYER_FOREGROUND1); + + if((level->get_author().size()) && (level->get_author() != "SuperTux Team")) + context.draw_text(white_small_text, + std::string(_("contributed by ")) + level->get_author(), + Vector(SCREEN_WIDTH/2, 350), ALIGN_CENTER, LAYER_FOREGROUND1); + + if(best_level_statistics != NULL) + best_level_statistics->draw_message_info(context, _("Best Level Statistics")); + + wait_for_event(1.0, 3.0); +} + +void +GameSession::on_escape_press() +{ + if(currentsector->player->is_dying() || end_sequence) + { + // Let the timers run out, we fast-forward them to force past a sequence + if (end_sequence) + end_sequence->stop(); + + currentsector->player->dying_timer.start(FLT_EPSILON); + return; // don't let the player open the menu, when he is dying + } + + if(level->on_menukey_script != "") { + std::istringstream in(level->on_menukey_script); + run_script(in, "OnMenuKeyScript"); + } else { + toggle_pause(); + } +} + +void +GameSession::toggle_pause() +{ + if(!game_pause) { + speed_before_pause = main_loop->get_speed(); + main_loop->set_speed(0); + Menu::set_current(game_menu.get()); + game_menu->set_active_item(MNID_CONTINUE); + game_pause = true; + } else { + main_loop->set_speed(speed_before_pause); + Menu::set_current(NULL); + game_pause = false; + } +} + +HSQUIRRELVM +GameSession::run_script(std::istream& in, const std::string& sourcename) +{ + using namespace Scripting; + + // garbage collect thread list + for(ScriptList::iterator i = scripts.begin(); + i != scripts.end(); ) { + HSQOBJECT& object = *i; + HSQUIRRELVM vm = object_to_vm(object); + + if(sq_getvmstate(vm) != SQ_VMSTATE_SUSPENDED) { + sq_release(global_vm, &object); + i = scripts.erase(i); + continue; + } + + ++i; + } + + HSQOBJECT object = create_thread(global_vm); + scripts.push_back(object); + + HSQUIRRELVM vm = object_to_vm(object); + + compile_and_run(vm, in, sourcename); + + return vm; +} + +void +GameSession::process_events() +{ + // end of pause mode? + if(!Menu::current() && game_pause) { + game_pause = false; + } + + // playback a demo? + if(playback_demo_stream != 0) { + demo_controller->update(); + char left = false; + char right = false; + char up = false; + char down = false; + char jump = false; + char action = false; + playback_demo_stream->get(left); + playback_demo_stream->get(right); + playback_demo_stream->get(up); + playback_demo_stream->get(down); + playback_demo_stream->get(jump); + playback_demo_stream->get(action); + demo_controller->press(Controller::LEFT, left); + demo_controller->press(Controller::RIGHT, right); + demo_controller->press(Controller::UP, up); + demo_controller->press(Controller::DOWN, down); + demo_controller->press(Controller::JUMP, jump); + demo_controller->press(Controller::ACTION, action); + } + + // save input for demo? + if(capture_demo_stream != 0) { + capture_demo_stream ->put(main_controller->hold(Controller::LEFT)); + capture_demo_stream ->put(main_controller->hold(Controller::RIGHT)); + capture_demo_stream ->put(main_controller->hold(Controller::UP)); + capture_demo_stream ->put(main_controller->hold(Controller::DOWN)); + capture_demo_stream ->put(main_controller->hold(Controller::JUMP)); + capture_demo_stream ->put(main_controller->hold(Controller::ACTION)); + } +} + +void +GameSession::check_end_conditions() +{ + Player* tux = currentsector->player; + + /* End of level? */ + if(end_sequence && end_sequence->is_done()) { + finish(true); + } else if (!end_sequence && tux->is_dead()) { + restart_level(); + } +} + +void +GameSession::draw(DrawingContext& context) +{ + currentsector->draw(context); + drawstatus(context); + + if(game_pause) + draw_pause(context); +} + +void +GameSession::draw_pause(DrawingContext& context) +{ + context.draw_filled_rect( + Vector(0,0), Vector(SCREEN_WIDTH, SCREEN_HEIGHT), + Color(.2f, .2f, .2f, .5f), LAYER_FOREGROUND1); +} + +void +GameSession::process_menu() +{ + Menu* menu = Menu::current(); + if(menu) { + menu->update(); + + if(menu == game_menu.get()) { + switch (game_menu->check()) { + case MNID_CONTINUE: + Menu::set_current(0); + toggle_pause(); + break; + case MNID_ABORTLEVEL: + Menu::set_current(0); + main_loop->exit_screen(); + break; + } + } + } +} + +void +GameSession::setup() +{ + Menu::set_current(NULL); + current_ = this; + + if(currentsector != Sector::current()) { + currentsector->activate(currentsector->player->get_pos()); + } + currentsector->play_music(LEVEL_MUSIC); + + // Eat unneeded events + SDL_Event event; + while(SDL_PollEvent(&event)) + {} +} + +void +GameSession::update(float elapsed_time) +{ + // handle controller + if(main_controller->pressed(Controller::PAUSE_MENU)) + on_escape_press(); + + process_events(); + process_menu(); + + check_end_conditions(); + + // respawning in new sector? + if(newsector != "" && newspawnpoint != "") { + Sector* sector = level->get_sector(newsector); + if(sector == 0) { + log_warning << "Sector '" << newsector << "' not found" << std::endl; + } + sector->activate(newspawnpoint); + sector->play_music(LEVEL_MUSIC); + currentsector = sector; + newsector = ""; + newspawnpoint = ""; + } + + // Update the world state and all objects in the world + if(!game_pause) { + // Update the world + if (!end_sequence) { + play_time += elapsed_time; //TODO: make sure we don't count cutscene time + level->stats.time = play_time; + currentsector->update(elapsed_time); + } else { + if (!end_sequence->is_tux_stopped()) { + currentsector->update(elapsed_time); + } else { + end_sequence->update(elapsed_time); + } + } + } + + // update sounds + sound_manager->set_listener_position(currentsector->player->get_pos()); + + /* Handle music: */ + if (end_sequence) + return; + + if(currentsector->player->invincible_timer.started()) { + if(currentsector->player->invincible_timer.get_timeleft() <= + TUX_INVINCIBLE_TIME_WARNING) { + currentsector->play_music(HERRING_WARNING_MUSIC); + } else { + currentsector->play_music(HERRING_MUSIC); + } + } else if(currentsector->get_music_type() != LEVEL_MUSIC) { + currentsector->play_music(LEVEL_MUSIC); + } +} + +void +GameSession::finish(bool win) +{ + using namespace WorldMapNS; + + if(win) { + if(WorldMap::current()) + WorldMap::current()->finished_level(level.get()); + } + + main_loop->exit_screen(); +} + +void +GameSession::respawn(const std::string& sector, const std::string& spawnpoint) +{ + newsector = sector; + newspawnpoint = spawnpoint; +} + +void +GameSession::set_reset_point(const std::string& sector, const Vector& pos) +{ + reset_sector = sector; + reset_pos = pos; +} + +std::string +GameSession::get_working_directory() +{ + return FileSystem::dirname(levelfile); +} + +void +GameSession::display_info_box(const std::string& text) +{ + InfoBox* box = new InfoBox(text); + + bool running = true; + DrawingContext context; + + while(running) { + + // TODO make a screen out of this, another mainloop is ugly + main_controller->update(); + SDL_Event event; + while (SDL_PollEvent(&event)) { + main_controller->process_event(event); + if(event.type == SDL_QUIT) + main_loop->quit(); + } + + if(main_controller->pressed(Controller::JUMP) + || main_controller->pressed(Controller::ACTION) + || main_controller->pressed(Controller::PAUSE_MENU) + || main_controller->pressed(Controller::MENU_SELECT)) + running = false; + else if(main_controller->pressed(Controller::DOWN)) + box->scrolldown(); + else if(main_controller->pressed(Controller::UP)) + box->scrollup(); + box->draw(context); + draw(context); + context.do_drawing(); + sound_manager->update(); + } + + delete box; +} + +void +GameSession::start_sequence(const std::string& sequencename) +{ + // handle special "stoptux" sequence + if (sequencename == "stoptux") { + if (!end_sequence) { + log_warning << "Final target reached without an active end sequence" << std::endl; + this->start_sequence("endsequence"); + } + if (end_sequence) end_sequence->stop_tux(); + return; + } + + // abort if a sequence is already playing + if (end_sequence) + return; + + if (sequencename == "endsequence") { + if (currentsector->get_players()[0]->physic.get_velocity_x() < 0) { + end_sequence = new EndSequenceWalkLeft(); + } else { + end_sequence = new EndSequenceWalkRight(); + } + } else if (sequencename == "fireworks") { + end_sequence = new EndSequenceFireworks(); + } else { + log_warning << "Unknown sequence '" << sequencename << "'. Ignoring." << std::endl; + return; + } + + /* slow down the game for end-sequence */ + main_loop->set_speed(0.5f); + + currentsector->add_object(end_sequence); + end_sequence->start(); + + sound_manager->play_music("music/leveldone.ogg", false); + currentsector->player->invincible_timer.start(10000.0f); + + // Stop all clocks. + for(std::vector::iterator i = currentsector->gameobjects.begin(); + i != currentsector->gameobjects.end(); ++i) + { + GameObject* obj = *i; + + LevelTime* lt = dynamic_cast (obj); + if(lt) + lt->stop(); + } +} + +/* (Status): */ +void +GameSession::drawstatus(DrawingContext& context) +{ + player_status->draw(context); + + // draw level stats while end_sequence is running + if (end_sequence) { + level->stats.draw_endseq_panel(context, best_level_statistics, statistics_backdrop.get()); + } +} diff --git a/src/src/game_session.hpp b/src/src/game_session.hpp new file mode 100644 index 000000000..fdb8f4f96 --- /dev/null +++ b/src/src/game_session.hpp @@ -0,0 +1,144 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#ifndef SUPERTUX_GAMELOOP_H +#define SUPERTUX_GAMELOOP_H + +#include +#include +#include +#include "screen.hpp" +#include "math/vector.hpp" +#include "video/surface.hpp" +#include "object/endsequence.hpp" + +class Level; +class Sector; +class Statistics; +class DrawingContext; +class CodeController; +class Menu; + +/** + * The GameSession class controlls the controll flow of the Game (the part + * where you actually play a level) + */ +class GameSession : public Screen +{ +public: + GameSession(const std::string& levelfile, Statistics* statistics = NULL); + ~GameSession(); + + void record_demo(const std::string& filename); + int get_demo_random_seed(const std::string& filename); + void play_demo(const std::string& filename); + + void draw(DrawingContext& context); + void update(float frame_ratio); + void setup(); + + void set_current() + { current_ = this; } + static GameSession* current() + { return current_; } + + /// ends the current level + void finish(bool win = true); + void respawn(const std::string& sectorname, const std::string& spawnpointname); + void set_reset_point(const std::string& sectorname, const Vector& pos); + std::string get_reset_point_sectorname() + { return reset_sector; } + + Vector get_reset_point_pos() + { return reset_pos; } + + void display_info_box(const std::string& text); + + Sector* get_current_sector() + { return currentsector; } + + Level* get_current_level() + { return level.get(); } + + void start_sequence(const std::string& sequencename); + + /** + * returns the "working directory" usually this is the directory where the + * currently played level resides. This is used when locating additional + * resources for the current level/world + */ + std::string get_working_directory(); + void restart_level(); + + void toggle_pause(); + +private: + void check_end_conditions(); + void process_events(); + void capture_demo_step(); + + void levelintro(); + void drawstatus(DrawingContext& context); + void draw_pause(DrawingContext& context); + + HSQUIRRELVM run_script(std::istream& in, const std::string& sourcename); + void on_escape_press(); + void process_menu(); + + std::auto_ptr level; + std::auto_ptr statistics_backdrop; + + // scripts + typedef std::vector ScriptList; + ScriptList scripts; + + Sector* currentsector; + + int levelnb; + int pause_menu_frame; + + EndSequence* end_sequence; + + bool game_pause; + float speed_before_pause; + + std::string levelfile; + + // reset point (the point where tux respawns if he dies) + std::string reset_sector; + Vector reset_pos; + + // the sector and spawnpoint we should spawn after this frame + std::string newsector; + std::string newspawnpoint; + + static GameSession* current_; + + Statistics* best_level_statistics; + + std::ostream* capture_demo_stream; + std::string capture_file; + std::istream* playback_demo_stream; + CodeController* demo_controller; + + std::auto_ptr game_menu; + + float play_time; /**< total time in seconds that this session ran interactively */ +}; + +#endif /*SUPERTUX_GAMELOOP_H*/ diff --git a/src/src/gameconfig.cpp b/src/src/gameconfig.cpp new file mode 100644 index 000000000..060eed074 --- /dev/null +++ b/src/src/gameconfig.cpp @@ -0,0 +1,130 @@ +// $Id$ +// +// SuperTux - A Jump'n Run +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#include + +#include "gameconfig.hpp" + +#include +#include +#include + +#include "lisp/parser.hpp" +#include "lisp/lisp.hpp" +#include "lisp/writer.hpp" +#include "control/joystickkeyboardcontroller.hpp" +#include "resources.hpp" +#include "main.hpp" + +Config* config = 0; + +Config::Config() +{ + use_fullscreen = true; + video = AUTO_VIDEO; + try_vsync = true; + show_fps = false; + sound_enabled = true; + music_enabled = true; + console_enabled = false; + random_seed = 0; // set by time(), by default (unless in config) + + screenwidth = 800; + screenheight = 600; + aspect_ratio = -1; // autodetect + + enable_script_debugger = false; + + locale = ""; // autodetect +} + +Config::~Config() +{} + +void +Config::load() +{ + lisp::Parser parser; + const lisp::Lisp* root = parser.parse("config"); + + const lisp::Lisp* config_lisp = root->get_lisp("supertux-config"); + if(!config_lisp) + throw std::runtime_error("File is not a supertux-config file"); + + config_lisp->get("show_fps", show_fps); + config_lisp->get("console", console_enabled); + config_lisp->get("locale", locale); + config_lisp->get("random_seed", random_seed); + + const lisp::Lisp* config_video_lisp = config_lisp->get_lisp("video"); + if(config_video_lisp) { + config_video_lisp->get("fullscreen", use_fullscreen); + std::string video_string; + config_video_lisp->get("video", video_string); + video = get_video_system(video_string); + config_video_lisp->get("vsync", try_vsync); + config_video_lisp->get("width", screenwidth); + config_video_lisp->get("height", screenheight); + config_video_lisp->get("aspect_ratio", aspect_ratio); + } + + const lisp::Lisp* config_audio_lisp = config_lisp->get_lisp("audio"); + if(config_audio_lisp) { + config_audio_lisp->get("sound_enabled", sound_enabled); + config_audio_lisp->get("music_enabled", music_enabled); + } + + const lisp::Lisp* config_control_lisp = config_lisp->get_lisp("control"); + if(config_control_lisp && main_controller) { + main_controller->read(*config_control_lisp); + } +} + +void +Config::save() +{ + lisp::Writer writer("config"); + + writer.start_list("supertux-config"); + + writer.write_bool("show_fps", show_fps); + writer.write_bool("console", console_enabled); + writer.write_string("locale", locale); + + writer.start_list("video"); + writer.write_bool("fullscreen", use_fullscreen); + writer.write_string("video", get_video_string(video)); + writer.write_bool("vsync", try_vsync); + writer.write_int("width", screenwidth); + writer.write_int("height", screenheight); + writer.write_float("aspect_ratio", aspect_ratio); + writer.end_list("video"); + + writer.start_list("audio"); + writer.write_bool("sound_enabled", sound_enabled); + writer.write_bool("music_enabled", music_enabled); + writer.end_list("audio"); + + if(main_controller) { + writer.start_list("control"); + main_controller->write(writer); + writer.end_list("control"); + } + + writer.end_list("supertux-config"); +} diff --git a/src/src/gameconfig.hpp b/src/src/gameconfig.hpp new file mode 100644 index 000000000..ac45730c7 --- /dev/null +++ b/src/src/gameconfig.hpp @@ -0,0 +1,66 @@ +// $Id$ +// +// SuperTux= +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#ifndef SUPERTUX_CONFIG_H +#define SUPERTUX_CONFIG_H + +#include + +#include + +#include "video/video_systems.hpp" + +class Config +{ +public: + Config(); + ~Config(); + + void load(); + void save(); + + /** screen width in pixel (warning: this is the real screen width+height, + * you should use the logical SCREEN_WIDTH and SCREEN_HEIGHT for your + * rendering code.) + */ + int screenwidth; + int screenheight; + float aspect_ratio; + + bool use_fullscreen; + VideoSystem video; + bool try_vsync; + bool show_fps; + bool sound_enabled; + bool music_enabled; + bool console_enabled; + + int random_seed; // initial random seed. 0 ==> set from time() + + /** this variable is set if supertux should start in a specific level */ + std::string start_level; + bool enable_script_debugger; + std::string start_demo; + std::string record_demo; + + std::string locale; /**< force SuperTux language to this locale, e.g. "de". A file "data/locale/xx.po" must exist for this to work. An empty string means autodetect. */ +}; + +extern Config* config; + +#endif diff --git a/src/src/gettext.hpp b/src/src/gettext.hpp new file mode 100644 index 000000000..a2bd00349 --- /dev/null +++ b/src/src/gettext.hpp @@ -0,0 +1,38 @@ +/* + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published + by the Free Software Foundation; either 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; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. */ +#ifndef _LIBGETTEXT_H +#define _LIBGETTEXT_H + +#include "tinygettext/tinygettext.hpp" + +extern TinyGetText::DictionaryManager dictionary_manager; + +static inline const char* _(const char* message) +{ + return dictionary_manager.get_dictionary().translate(message); +} + +static inline std::string _(const std::string& message) +{ + return dictionary_manager.get_dictionary().translate(message); +} + +static inline const char* N_(const char* id, const char* id2, int num) +{ + return dictionary_manager.get_dictionary().translate(id, id2, num).c_str(); +} + +#endif /* _LIBGETTEXT_H */ diff --git a/src/src/gui/button.cpp b/src/src/gui/button.cpp new file mode 100644 index 000000000..6b16a130d --- /dev/null +++ b/src/src/gui/button.cpp @@ -0,0 +1,257 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include + +#include +#include + +#include "main.hpp" +#include "button.hpp" +#include "mousecursor.hpp" +#include "video/font.hpp" +#include "video/surface.hpp" + +Font* Button::info_font = 0; +extern SDL_Surface* screen; + +/* Buttons */ + +Button::Button(Surface* image_, std::string info_, SDLKey binding_) + : binding(binding_) +{ + image = image_; + size = Vector(image->get_width(), image->get_height()); + id = 0; + info = info_; +} + +Button::~Button() +{ +} + +void Button::draw(DrawingContext &context, bool selected) +{ +if(selected) + context.draw_filled_rect(pos, size, Color (200,240,220), LAYER_GUI); +else + context.draw_filled_rect(pos, size, Color (200,200,220), LAYER_GUI); + +Vector tanslation = -context.get_translation(); +if(state == BT_SHOW_INFO) + { + Vector offset; + if(pos.x + tanslation.x < 100 && pos.y + tanslation.y > SCREEN_HEIGHT - 20) + offset = Vector(size.x, - 10); + else if(pos.x + tanslation.x < 100) + offset = Vector(size.x, 0); + else + offset = Vector(-30, -size.y/2); + context.draw_text(info_font, info, pos + offset, ALIGN_LEFT, LAYER_GUI+2); + if(binding != 0) + context.draw_text(info_font, "(" + std::string(SDL_GetKeyName(binding)) + + ")", pos + offset + Vector(0,12), + ALIGN_LEFT, LAYER_GUI+2); + } + +context.draw_surface_part(image, Vector(0,0), size, pos, LAYER_GUI+1); +} + +int Button::event(SDL_Event &event, int x_offset, int y_offset) +{ +state = BT_NONE; +switch(event.type) + { + case SDL_MOUSEBUTTONDOWN: + if(event.button.x > pos.x + x_offset && event.button.x < pos.x + x_offset + size.x && + event.button.y > pos.y + y_offset && event.button.y < pos.y + y_offset + size.y) + { + if(event.button.button == SDL_BUTTON_RIGHT) + state = BT_SHOW_INFO; + } + break; + case SDL_MOUSEBUTTONUP: + if(event.button.x > pos.x + x_offset && event.button.x < pos.x + x_offset + size.x && + event.button.y > pos.y + y_offset && event.button.y < pos.y + y_offset + size.y) + { + if(event.button.button == SDL_BUTTON_LEFT) + state = BT_SELECTED; + } + break; + case SDL_KEYDOWN: // key pressed + if(event.key.keysym.sym == binding) + state = BT_SELECTED; + break; + default: + break; + } +return state; +} + +/* Group of buttons */ + +ButtonGroup::ButtonGroup(Vector pos_, Vector buttons_size_, Vector buttons_box_) + : pos(pos_), buttons_size(buttons_size_), buttons_box(buttons_box_) +{ +buttons.clear(); +row = 0; +button_selected = -1; +mouse_hover = false; +mouse_left_button = false; +buttons_pair_nb = 0; +} + +ButtonGroup::~ButtonGroup() +{ +} + +void ButtonGroup::add_button(Button button, int id, bool select) +{ +button.pos.x = ((buttons.size()-buttons_pair_nb) % (int)buttons_box.x) * buttons_size.x; +button.pos.y = ((int)((buttons.size()-buttons_pair_nb) / buttons_box.x)) * buttons_size.y; +button.size = buttons_size; +button.id = id; +if(select) + button_selected = id; + +buttons.push_back(button); +} + +void ButtonGroup::add_pair_of_buttons(Button button1, int id1, Button button2, int id2) +{ +button1.pos.x = button2.pos.x = ((buttons.size()-buttons_pair_nb) % (int)buttons_box.x) * buttons_size.x; +button1.pos.y = button2.pos.y = ((int)((buttons.size()-buttons_pair_nb) / buttons_box.x)) * buttons_size.y; +button1.size.x = button2.size.x = buttons_size.x; +button1.size.y = button2.size.y = buttons_size.y / 2; +button2.pos.y += buttons_size.y / 2; +button1.id = id1; +button2.id = id2; + +buttons_pair_nb++; +buttons.push_back(button1); +buttons.push_back(button2); +} + +void ButtonGroup::draw(DrawingContext &context) +{ +context.draw_filled_rect(pos - Vector(12,4), + Vector(buttons_size.x*buttons_box.x + 16, buttons_size.y*buttons_box.y + 8), + Color (0,0,0, 128), LAYER_GUI-1); + +context.push_transform(); +context.set_translation(Vector(-pos.x, -pos.y + buttons_size.y*row)); +for(Buttons::iterator i = buttons.begin(); i != buttons.end(); ++i) + { + if(i->pos.y < row*buttons_size.y || + i->pos.y + i->size.y > (row + buttons_box.y) * buttons_size.y) + continue; + + i->draw(context, i->id == button_selected); + } +context.pop_transform(); +} + +bool ButtonGroup::event(SDL_Event &event) +{ +bool caught_event = false; + +switch(event.type) + { + case SDL_MOUSEMOTION: + mouse_hover = false; + + if(mouse_left_button) + { + pos.x += int(event.motion.xrel * float(SCREEN_WIDTH)/screen->w); + pos.y += int(event.motion.yrel * float(SCREEN_HEIGHT)/screen->h); + caught_event = true; + } + if(event.button.x > pos.x-12 && event.button.x < pos.x+16 + buttons_box.x*buttons_size.x && + event.button.y > pos.y-4 && event.button.y < pos.y+8 + buttons_box.y*buttons_size.y) + mouse_hover = true; + break; + case SDL_MOUSEBUTTONDOWN: + if(event.button.x < pos.x-12 || event.button.x > pos.x+16 + + buttons_box.x*buttons_size.x || event.button.y < pos.y-4 || + event.button.y > pos.y+8 + buttons_box.y*buttons_size.y) + break; + + caught_event = true; + + if(event.button.button == SDL_BUTTON_WHEELUP) + { + row--; + if(row < 0) + row = 0; + } + else if(event.button.button == SDL_BUTTON_WHEELDOWN) + { + row++; + if(row > (int)((buttons.size()-buttons_pair_nb)/buttons_box.x) - (int)buttons_box.y + + ((int)(buttons.size()-buttons_pair_nb)%(int)buttons_box.x != 0 ? 1 : 0)) + row = (int)((buttons.size()-buttons_pair_nb)/buttons_box.x) - (int)buttons_box.y + + ((int)(buttons.size()-buttons_pair_nb)%(int)buttons_box.x != 0 ? 1 : 0); + } + else if(event.button.button == SDL_BUTTON_LEFT) + mouse_left_button = true; + else + caught_event = false; + break; + case SDL_MOUSEBUTTONUP: + mouse_left_button = false; + break; + default: + break; + } + +if(caught_event) + return true; + +for(Buttons::iterator i = buttons.begin(); i != buttons.end(); ++i) + { + if(i->pos.y < row*buttons_size.y || + i->pos.y + i->size.y > (row + buttons_box.y) * buttons_size.y) + continue; + + if(i->event(event, (int)pos.x, + (int)pos.y - row*(int)buttons_size.y) == BT_SELECTED) + { + button_selected = i->id; + caught_event = true; + break; + } + } + +return caught_event; +} + +int ButtonGroup::selected_id() +{ +return button_selected; +} + +void ButtonGroup::set_unselected() +{ +button_selected = -1; +} + +bool ButtonGroup::is_hover() +{ +return mouse_hover; +} diff --git a/src/src/gui/button.hpp b/src/src/gui/button.hpp new file mode 100644 index 000000000..b81f51dbf --- /dev/null +++ b/src/src/gui/button.hpp @@ -0,0 +1,91 @@ +// $Id$ +// +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef SUPERTUX_BUTTON_H +#define SUPERTUX_BUTTON_H + +#include +#include + +#include "math/vector.hpp" +#include "video/drawing_context.hpp" + +class Surface; + +class ButtonGroup; + +enum { + BT_NONE, + BT_HOVER, + BT_SELECTED, + BT_SHOW_INFO + }; + +class Button +{ +public: + Button(Surface* image_, std::string info_, SDLKey binding_); + ~Button(); + + void draw(DrawingContext& context, bool selected); + int event(SDL_Event& event, int x_offset = 0, int y_offset = 0); + + static Font* info_font; + +private: + friend class ButtonGroup; + + Vector pos, size; + + Surface* image; + SDLKey binding; + + int id; + int state; + std::string info; +}; + +class ButtonGroup +{ +public: + ButtonGroup(Vector pos_, Vector size_, Vector button_box_); + ~ButtonGroup(); + + void draw(DrawingContext& context); + bool event(SDL_Event& event); + + void add_button(Button button, int id, bool select = false); + void add_pair_of_buttons(Button button1, int id1, Button button2, int id2); + + int selected_id(); + void set_unselected(); + bool is_hover(); + +private: + Vector pos, buttons_size, buttons_box; + typedef std::vector