X-Git-Url: https://git.verplant.org/?a=blobdiff_plain;f=src%2Fworld.cpp;h=8b68b0847ce063df3a84c0b914601c2061a34c20;hb=d84d73b701cc7fa2bd74f3490b9be1bf8b6f705a;hp=3f63a2ed170b1c2ba135c73a5ffb988a840fcb65;hpb=03fe5c560a616e7d38a8b1d5d11bfe4675fa8896;p=supertux.git diff --git a/src/world.cpp b/src/world.cpp index 3f63a2ed1..8b68b0847 100644 --- a/src/world.cpp +++ b/src/world.cpp @@ -1,9 +1,7 @@ // $Id$ -// +// // SuperTux -// Copyright (C) 2000 Bill Kendrick -// Copyright (C) 2004 Tobias Glaesser -// Copyright (C) 2004 Ingo Ruhnke +// 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 @@ -14,629 +12,242 @@ // 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 "globals.h" -#include "scene.h" -#include "screen/screen.h" -#include "defines.h" -#include "world.h" -#include "level.h" -#include "tile.h" -#include "resources.h" -#include "gameobjs.h" -#include "camera.h" -#include "background.h" -#include "tilemap.h" - -Surface* img_distro[4]; - -World* World::current_ = 0; - -World::World(const std::string& filename, int level_nr) - : level(0), tux(0), background(0), camera(0) -{ - // FIXME: Move this to action and draw and everywhere else where the - // world calls child functions - current_ = this; - - tux = new Player; - add_object(tux); - - level = new Level; - camera = new Camera(tux, level); - add_object(camera); - - if(level_nr >= 0) { - level->load(filename, level_nr, this); - } else { - level->load(filename, this); - } - tux->move(level->start_pos); - - set_defaults(); - - // add background - activate_particle_systems(); - - // add tilemap - add_object(new TileMap(level)); - level->load_song(); - - apply_bonuses(); -} - -void -World::apply_bonuses() -{ - // Apply bonuses from former levels - switch (player_status.bonus) - { - case PlayerStatus::NO_BONUS: - break; - - case PlayerStatus::FLOWER_BONUS: - tux->got_power = Player::FIRE_POWER; // FIXME: add ice power to here - // fall through - - case PlayerStatus::GROWUP_BONUS: - tux->grow(false); - break; - } -} - -World::~World() -{ - for (std::vector::iterator i = gameobjects.begin(); - i != gameobjects.end(); ++i) { - delete *i; - } - - delete level; - - current_ = 0; -} - -void -World::set_defaults() -{ - player_status.score_multiplier = 1; - - counting_distros = false; - distro_counter = 0; - - /* set current song/music */ - currentmusic = LEVEL_MUSIC; -} - -void -World::add_object(GameObject* object) -{ - // XXX hack for now until new collision code is ready - BadGuy* badguy = dynamic_cast (object); - if(badguy) - bad_guys.push_back(badguy); - Bullet* bullet = dynamic_cast (object); - if(bullet) - bullets.push_back(bullet); - Upgrade* upgrade = dynamic_cast (object); - if(upgrade) - upgrades.push_back(upgrade); - Trampoline* trampoline = dynamic_cast (object); - if(trampoline) - trampolines.push_back(trampoline); - FlyingPlatform* flying_platform = dynamic_cast (object); - if(flying_platform) - flying_platforms.push_back(flying_platform); - Background* background = dynamic_cast (object); - if(background) - this->background = background; - - gameobjects.push_back(object); +#include + +#include +#include +#include + +#include "world.hpp" +#include "file_system.hpp" +#include "lisp/parser.hpp" +#include "lisp/lisp.hpp" +#include "physfs/physfs_stream.hpp" +#include "scripting/squirrel_util.hpp" +#include "scripting/serialize.hpp" +#include "log.hpp" +#include "worldmap/worldmap.hpp" +#include "mainloop.hpp" + +static bool has_suffix(const std::string& data, const std::string& suffix) +{ + if (data.length() >= suffix.length()) + return data.compare(data.length() - suffix.length(), suffix.length(), suffix) == 0; + else + return false; } -void -World::parse_objects(lisp_object_t* cur) -{ - while(!lisp_nil_p(cur)) { - lisp_object_t* data = lisp_car(cur); - std::string object_type = lisp_symbol(lisp_car(data)); - - LispReader reader(lisp_cdr(data)); +World* World::current_ = NULL; - if(object_type == "trampoline") { - add_object(new Trampoline(reader)); - } - else if(object_type == "flying-platform") { - add_object(new FlyingPlatform(reader)); - } - else { - BadGuyKind kind = badguykind_from_string(object_type); - add_object(new BadGuy(kind, reader)); - } - - cur = lisp_cdr(cur); - } -} - -void -World::activate_particle_systems() +World::World() { - if (level->particle_system == "clouds") - { - add_object(new CloudParticleSystem); - } - else if (level->particle_system == "snow") - { - add_object(new SnowParticleSystem); - } - else if (level->particle_system != "") - { - st_abort("unknown particle system specified in level", ""); - } + is_levelset = true; + hide_from_contribs = false; + sq_resetobject(&world_thread); } -void -World::draw() +World::~World() { - /* Draw objects */ - for(std::vector::iterator i = gameobjects.begin(); - i != gameobjects.end(); ++i) - if((*i)->is_valid()) - (*i)->draw(context); + sq_release(Scripting::global_vm, &world_thread); + if(current_ == this) + current_ = NULL; } void -World::action(float elapsed_time) +World::set_savegame_filename(const std::string& filename) { - tux->check_bounds(context); - - /* update objects (don't use iterators here, because the list might change - * during the iteration) - */ - for(size_t i = 0; i < gameobjects.size(); ++i) - if(gameobjects[i]->is_valid()) - gameobjects[i]->action(elapsed_time); - - /* Handle all possible collisions. */ - collision_handler(); - - /** cleanup marked objects */ - for(std::vector::iterator i = gameobjects.begin(); - i != gameobjects.end(); /* nothing */) { - if((*i)->is_valid() == false) { - BadGuy* badguy = dynamic_cast (*i); - if(badguy) { - bad_guys.erase(std::remove(bad_guys.begin(), bad_guys.end(), badguy), - bad_guys.end()); - } - Bullet* bullet = dynamic_cast (*i); - if(bullet) { - bullets.erase( - std::remove(bullets.begin(), bullets.end(), bullet), - bullets.end()); - } - Upgrade* upgrade = dynamic_cast (*i); - if(upgrade) { - upgrades.erase( - std::remove(upgrades.begin(), upgrades.end(), upgrade), - upgrades.end()); + this->savegame_filename = filename; + // make sure the savegame directory exists + std::string dirname = FileSystem::dirname(filename); + if(!PHYSFS_exists(dirname.c_str())) { + if(PHYSFS_mkdir(dirname.c_str())) { + std::ostringstream msg; + msg << "Couldn't create directory for savegames '" + << dirname << "': " < (*i); - if(trampoline) { - trampolines.erase( - std::remove(trampolines.begin(), trampolines.end(), trampoline), - trampolines.end()); - } - FlyingPlatform* flying_platform= dynamic_cast (*i); - if(flying_platform) { - flying_platforms.erase( - std::remove(flying_platforms.begin(), flying_platforms.end(), flying_platform), - flying_platforms.end()); - } - - delete *i; - i = gameobjects.erase(i); - } else { - ++i; - } + } + + if(!PHYSFS_isDirectory(dirname.c_str())) { + std::ostringstream msg; + msg << "Savegame path '" << dirname << "' is not a directory"; + throw std::runtime_error(msg.str()); } } void -World::collision_handler() +World::load(const std::string& filename) { - // CO_BULLET & CO_BADGUY check - for(unsigned int i = 0; i < bullets.size(); ++i) - { - for (BadGuys::iterator j = bad_guys.begin(); j != bad_guys.end(); ++j) - { - if((*j)->dying != DYING_NOT) - continue; - - if(rectcollision(bullets[i]->base, (*j)->base)) - { - // We have detected a collision and now call the - // collision functions of the collided objects. - (*j)->collision(bullets[i], CO_BULLET, COLLISION_NORMAL); - bullets[i]->collision(CO_BADGUY); - break; // bullet is invalid now, so break - } - } - } - - /* CO_BADGUY & CO_BADGUY check */ - for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i) - { - if((*i)->dying != DYING_NOT) - continue; - - BadGuys::iterator j = i; - ++j; - for (; j != bad_guys.end(); ++j) - { - if(j == i || (*j)->dying != DYING_NOT) - continue; - - if(rectcollision((*i)->base, (*j)->base)) - { - // We have detected a collision and now call the - // collision functions of the collided objects. - (*j)->collision(*i, CO_BADGUY); - (*i)->collision(*j, CO_BADGUY); - } - } - } - - if(tux->dying != DYING_NOT) return; - - // CO_BADGUY & CO_PLAYER check - for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i) - { - if((*i)->dying != DYING_NOT) - continue; + basedir = FileSystem::dirname(filename); + + lisp::Parser parser; + std::auto_ptr root (parser.parse(filename)); + + const lisp::Lisp* info = root->get_lisp("supertux-world"); + if(info == NULL) + info = root->get_lisp("supertux-level-subset"); + if(info == NULL) + throw std::runtime_error("File is not a world or levelsubset file"); + + hide_from_contribs = false; + is_levelset = true; + + info->get("title", title); + info->get("description", description); + info->get("levelset", is_levelset); + info->get_vector("levels", levels); + info->get("hide-from-contribs", hide_from_contribs); + + // Level info file doesn't define any levels, so read the + // directory to see what we can find - if(rectcollision_offset((*i)->base, tux->base, 0, 0)) - { - // We have detected a collision and now call the collision - // functions of the collided objects. - if (tux->previous_base.y < tux->base.y && - tux->previous_base.y + tux->previous_base.height - < (*i)->base.y + (*i)->base.height/2 - && !tux->invincible_timer.started()) - { - (*i)->collision(tux, CO_PLAYER, COLLISION_SQUISH); - } - else - { - tux->collision(*i, CO_BADGUY); - (*i)->collision(tux, CO_PLAYER, COLLISION_NORMAL); - } - } - } - - // CO_UPGRADE & CO_PLAYER check - for(unsigned int i = 0; i < upgrades.size(); ++i) - { - if(rectcollision(upgrades[i]->base, tux->base)) - { - // We have detected a collision and now call the collision - // functions of the collided objects. - upgrades[i]->collision(tux, CO_PLAYER, COLLISION_NORMAL); - } - } - - // CO_TRAMPOLINE & (CO_PLAYER or CO_BADGUY) - for (Trampolines::iterator i = trampolines.begin(); i != trampolines.end(); ++i) - { - if (rectcollision((*i)->base, tux->base)) - { - if (tux->previous_base.y < tux->base.y && - tux->previous_base.y + tux->previous_base.height - < (*i)->base.y + (*i)->base.height/2) - { - (*i)->collision(tux, CO_PLAYER, COLLISION_SQUISH); - } - else if (tux->previous_base.y <= tux->base.y) - { - tux->collision(*i, CO_TRAMPOLINE); - (*i)->collision(tux, CO_PLAYER, COLLISION_NORMAL); - } - } + std::string path = basedir + "/"; + char** files = PHYSFS_enumerateFiles(path.c_str()); + if(!files) { + log_warning << "Couldn't read subset dir '" << path << "'" << std::endl; + return; } - // CO_FLYING_PLATFORM & (CO_PLAYER or CO_BADGUY) - for (FlyingPlatforms::iterator i = flying_platforms.begin(); i != flying_platforms.end(); ++i) - { - if (rectcollision((*i)->base, tux->base)) - { - if (tux->previous_base.y < tux->base.y && - tux->previous_base.y + tux->previous_base.height - < (*i)->base.y + (*i)->base.height/2) - { - (*i)->collision(tux, CO_PLAYER, COLLISION_SQUISH); - tux->collision(*i, CO_FLYING_PLATFORM); - } -/* else if (tux->previous_base.y <= tux->base.y) - { - }*/ + for(const char* const* filename = files; *filename != 0; ++filename) { + if(has_suffix(*filename, ".stl")) { + levels.push_back(path + *filename); } } + PHYSFS_freeList(files); } void -World::add_score(const Vector& pos, int s) +World::run() { - player_status.score += s; + using namespace Scripting; - add_object(new FloatingScore(pos, s)); -} + current_ = this; + + // create new squirrel table for persisten game state + HSQUIRRELVM vm = Scripting::global_vm; -void -World::add_bouncy_distro(const Vector& pos) -{ - add_object(new BouncyDistro(pos)); -} + sq_pushroottable(vm); + sq_pushstring(vm, "state", -1); + sq_newtable(vm); + if(SQ_FAILED(sq_createslot(vm, -3))) + throw Scripting::SquirrelError(vm, "Couldn't create state table"); + sq_pop(vm, 1); -void -World::add_broken_brick(const Vector& pos, Tile* tile) -{ - add_broken_brick_piece(pos, Vector(-1, -4), tile); - add_broken_brick_piece(pos + Vector(0, 16), Vector(-1.5, -3), tile); - - add_broken_brick_piece(pos + Vector(16, 0), Vector(1, -4), tile); - add_broken_brick_piece(pos + Vector(16, 16), Vector(1.5, -3), tile); + load_state(); + + std::string filename = basedir + "/world.nut"; + try { + IFileStream in(filename); + + sq_release(global_vm, &world_thread); + world_thread = create_thread(global_vm); + compile_and_run(object_to_vm(world_thread), in, filename); + } catch(std::exception& e) { + // fallback: try to load worldmap worldmap.stwm + using namespace WorldMapNS; + main_loop->push_screen(new WorldMap(basedir + "worldmap.stwm")); + } } void -World::add_broken_brick_piece(const Vector& pos, const Vector& movement, - Tile* tile) +World::save_state() { - add_object(new BrokenBrick(tile, pos, movement)); -} + using namespace Scripting; -void -World::add_bouncy_brick(const Vector& pos) -{ - add_object(new BouncyBrick(pos)); -} + lisp::Writer writer(savegame_filename); -BadGuy* -World::add_bad_guy(float x, float y, BadGuyKind kind) -{ - BadGuy* badguy = new BadGuy(kind, x, y); - add_object(badguy); - return badguy; -} + writer.start_list("supertux-savegame"); + writer.write_int("version", 1); + + using namespace WorldMapNS; + if(WorldMap::current() != NULL) { + std::ostringstream title; + title << WorldMap::current()->get_title(); + title << " (" << WorldMap::current()->solved_level_count() + << "/" << WorldMap::current()->level_count() << ")"; + writer.write_string("title", title.str()); + } -void -World::add_upgrade(const Vector& pos, Direction dir, UpgradeKind kind) -{ - add_object(new Upgrade(pos, dir, kind)); -} + writer.start_list("tux"); + player_status->write(writer); + writer.end_list("tux"); -bool -World::add_bullet(const Vector& pos, float xm, Direction dir) -{ - if(tux->got_power == Player::FIRE_POWER) - { - if(bullets.size() > MAX_FIRE_BULLETS-1) - return false; - } - else if(tux->got_power == Player::ICE_POWER) - { - if(bullets.size() > MAX_ICE_BULLETS-1) - return false; - } - - Bullet* new_bullet = 0; - if(tux->got_power == Player::FIRE_POWER) - new_bullet = new Bullet(pos, xm, dir, FIRE_BULLET); - else if(tux->got_power == Player::ICE_POWER) - new_bullet = new Bullet(pos, xm, dir, ICE_BULLET); - else - st_abort("wrong bullet type.", ""); - add_object(new_bullet); + writer.start_list("state"); - play_sound(sounds[SND_SHOOT], SOUND_CENTER_SPEAKER); - - return true; + sq_pushroottable(global_vm); + sq_pushstring(global_vm, "state", -1); + if(SQ_SUCCEEDED(sq_get(global_vm, -2))) { + Scripting::save_squirrel_table(global_vm, -1, writer); + sq_pop(global_vm, 1); + } + sq_pop(global_vm, 1); + writer.end_list("state"); + + writer.end_list("supertux-savegame"); } void -World::play_music(int musictype) +World::load_state() { - currentmusic = musictype; - switch(currentmusic) { - case HURRYUP_MUSIC: - music_manager->play_music(get_level()->get_level_music_fast()); - break; - case LEVEL_MUSIC: - music_manager->play_music(get_level()->get_level_music()); - break; - case HERRING_MUSIC: - music_manager->play_music(herring_song); - break; - default: - music_manager->halt_music(); - break; - } -} + using namespace Scripting; -int -World::get_music_type() -{ - return currentmusic; -} + try { + lisp::Parser parser; + std::auto_ptr root (parser.parse(savegame_filename)); -/* Break a brick: */ -bool -World::trybreakbrick(float x, float y, bool small) -{ - Level* plevel = get_level(); - - Tile* tile = gettile(x, y); - if (tile->attributes & Tile::BRICK) - { - if (tile->data > 0) - { - /* Get a distro from it: */ - add_bouncy_distro( - Vector(((int)(x + 1) / 32) * 32, (int)(y / 32) * 32)); - - // TODO: don't handle this in a global way but per-tile... - if (!counting_distros) - { - counting_distros = true; - distro_counter = 5; - } - else - { - distro_counter--; - } - - if (distro_counter <= 0) - { - counting_distros = false; - plevel->change(x, y, TM_IA, tile->next_tile); - } - - play_sound(sounds[SND_DISTRO], SOUND_CENTER_SPEAKER); - player_status.score = player_status.score + SCORE_DISTRO; - player_status.distros++; - return true; - } - else if (!small) - { - /* Get rid of it: */ - plevel->change(x, y, TM_IA, tile->next_tile); - - /* Replace it with broken bits: */ - add_broken_brick(Vector( - ((int)(x + 1) / 32) * 32, - (int)(y / 32) * 32), tile); - - /* Get some score: */ - play_sound(sounds[SND_BRICK], SOUND_CENTER_SPEAKER); - player_status.score = player_status.score + SCORE_BRICK; - - return true; - } - } + const lisp::Lisp* lisp = root->get_lisp("supertux-savegame"); + if(lisp == NULL) + throw std::runtime_error("file is not a supertux-savegame file"); - return false; -} + int version = 1; + lisp->get("version", version); + if(version != 1) + throw std::runtime_error("incompatible savegame version"); -/* Empty a box: */ -void -World::tryemptybox(float x, float y, Direction col_side) -{ - Tile* tile = gettile(x,y); - if (!(tile->attributes & Tile::FULLBOX)) - return; + const lisp::Lisp* tux = lisp->get_lisp("tux"); + if(tux == NULL) + throw std::runtime_error("No tux section in savegame"); + player_status->read(*tux); - // according to the collision side, set the upgrade direction - if(col_side == LEFT) - col_side = RIGHT; - else - col_side = LEFT; - - int posx = ((int)(x+1) / 32) * 32; - int posy = (int)(y/32) * 32 - 32; - switch(tile->data) - { - case 1: // Box with a distro! - add_bouncy_distro(Vector(posx, posy)); - play_sound(sounds[SND_DISTRO], SOUND_CENTER_SPEAKER); - player_status.score = player_status.score + SCORE_DISTRO; - player_status.distros++; - break; - - case 2: // Add a fire flower upgrade! - if (tux->size == SMALL) /* Tux is small, add mints! */ - add_upgrade(Vector(posx, posy), col_side, UPGRADE_GROWUP); - else /* Tux is big, add a fireflower: */ - add_upgrade(Vector(posx, posy), col_side, UPGRADE_FIREFLOWER); - play_sound(sounds[SND_UPGRADE], SOUND_CENTER_SPEAKER); - break; + const lisp::Lisp* state = lisp->get_lisp("state"); + if(state == NULL) + throw std::runtime_error("No state section in savegame"); - case 5: // Add an ice flower upgrade! - if (tux->size == SMALL) /* Tux is small, add mints! */ - add_upgrade(Vector(posx, posy), col_side, UPGRADE_GROWUP); - else /* Tux is big, add an iceflower: */ - add_upgrade(Vector(posx, posy), col_side, UPGRADE_ICEFLOWER); - play_sound(sounds[SND_UPGRADE], SOUND_CENTER_SPEAKER); - break; - - case 3: // Add a golden herring - add_upgrade(Vector(posx, posy), col_side, UPGRADE_HERRING); - break; - - case 4: // Add a 1up extra - add_upgrade(Vector(posx, posy), col_side, UPGRADE_1UP); - break; - default: - break; - } - - /* Empty the box: */ - level->change(x, y, TM_IA, tile->next_tile); + sq_pushroottable(global_vm); + sq_pushstring(global_vm, "state", -1); + if(SQ_FAILED(sq_deleteslot(global_vm, -2, SQFalse))) + sq_pop(global_vm, 1); + + sq_pushstring(global_vm, "state", -1); + sq_newtable(global_vm); + load_squirrel_table(global_vm, -1, state); + if(SQ_FAILED(sq_createslot(global_vm, -3))) + throw std::runtime_error("Couldn't create state table"); + sq_pop(global_vm, 1); + } catch(std::exception& e) { + log_debug << "Couldn't load savegame: " << e.what() << std::endl; + } } -/* Try to grab a distro: */ -void -World::trygrabdistro(float x, float y, int bounciness) +const std::string& +World::get_level_filename(unsigned int i) const { - Tile* tile = gettile(x, y); - if (tile && (tile->attributes & Tile::COIN)) - { - level->change(x, y, TM_IA, tile->next_tile); - play_sound(sounds[SND_DISTRO], SOUND_CENTER_SPEAKER); - - if (bounciness == BOUNCE) - { - add_bouncy_distro(Vector(((int)(x + 1) / 32) * 32, - (int)(y / 32) * 32)); - } - - player_status.score = player_status.score + SCORE_DISTRO; - player_status.distros++; - } + return levels[i]; } -/* Try to bump a bad guy from below: */ -void -World::trybumpbadguy(float x, float y) +unsigned int +World::get_num_levels() const { - // Bad guys: - for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i) - { - if ((*i)->base.x >= x - 32 && (*i)->base.x <= x + 32 && - (*i)->base.y >= y - 16 && (*i)->base.y <= y + 16) - { - (*i)->collision(tux, CO_PLAYER, COLLISION_BUMP); - } - } - - // Upgrades: - for (unsigned int i = 0; i < upgrades.size(); i++) - { - if (upgrades[i]->base.height == 32 && - upgrades[i]->base.x >= x - 32 && upgrades[i]->base.x <= x + 32 && - upgrades[i]->base.y >= y - 16 && upgrades[i]->base.y <= y + 16) - { - upgrades[i]->collision(tux, CO_PLAYER, COLLISION_BUMP); - } - } + return levels.size(); } -/* EOF */ - +const std::string& +World::get_basedir() const +{ + return basedir; +}