From c38211432b49e261d1f164d1b04b8f8f1ffcf9d4 Mon Sep 17 00:00:00 2001 From: Christoph Sommer Date: Wed, 31 Jan 2007 20:19:42 +0000 Subject: [PATCH] Tux can now climb stuff. Still has some rough edges and no graphics, but it's a start... SVN-Revision: 4762 --- data/images/tiles.strf | 21 ++++- data/images/tiles/forest/ladder.png | Bin 0 -> 1520 bytes data/levels/test/laddertest.stl | 173 ++++++++++++++++++++++++++++++++++++ src/object/player.cpp | 106 +++++++++++++++++++++- src/object/player.hpp | 14 +++ src/trigger/climbable.cpp | 135 ++++++++++++++++++++++++++++ src/trigger/climbable.hpp | 52 +++++++++++ src/trigger/trigger_base.cpp | 20 ++++- src/trigger/trigger_base.hpp | 15 +++- 9 files changed, 526 insertions(+), 10 deletions(-) create mode 100644 data/images/tiles/forest/ladder.png create mode 100644 data/levels/test/laddertest.stl create mode 100644 src/trigger/climbable.cpp create mode 100644 src/trigger/climbable.hpp diff --git a/data/images/tiles.strf b/data/images/tiles.strf index 133ef8b4d..5b3d40c2e 100644 --- a/data/images/tiles.strf +++ b/data/images/tiles.strf @@ -256,9 +256,9 @@ 76 201 174 130 75 200 173 129 295 296 297 298 - 81 126 127 0 - 1355 1809 1812 0 - 1356 1810 1813 0 + 81 126 127 2047 + 1355 1809 1812 2048 + 1356 1810 1813 2049 1411 1811 1814 0 1988 1989 0 0 1990 1991 0 0 @@ -6925,4 +6925,19 @@ (solid #t) (unisolid #t) ) + (tiles + (width 1) + (height 3) + (ids + 2047 + 2048 + 2049 + ) + (attributes + 0 + 0 + 0 + ) + (image "tiles/forest/ladder.png") + ) ) diff --git a/data/images/tiles/forest/ladder.png b/data/images/tiles/forest/ladder.png new file mode 100644 index 0000000000000000000000000000000000000000..c71cceb7bbbfac582e72528abdef6db82baadded GIT binary patch literal 1520 zcmVWFU8GbZ8({Xk{QrNlj4iWF>9@00mS@L_t(&-tC$@k7P#` zfWLF9>UQ@`?@oK!#q45WErA5ybzr~_93bI{N8%KbAQ2!!{sKRM-#~&T5CIVhk`WRC z36B7Sg@Aoq<9Y0C_x9sHs_GEj@pxA=-O+8F-0Gl{Zhhz0sXBG)(-ls~_r7`4o|ch2 zAIy30-VA8E zU$dY7;Hxw_^0Qz46BDtA2Mh79-rTbve)o$UJeuOoxcQkeF+$y$)-7@rys_{xn5DP_}P;n)evOXC5vc}-9c0wUJg(+`63+O2C`nGE^M`^ShVk;Hfq zP=|1M@|4S$0!}>6!7wOJw6g#T6&@bUxG~wmiD$gyP=QgQXb~J^5sr>4%9QX(j*S{=Vy#z;V6RQo1B=I24)oAwNvH=5#jr9y=(w4Tq@YxEjXH%q-;19QZ`aH zhJ_<%qmBs`MFmne>Xa$#1i-<`ipD(Cd2_}X1|@jK7lj}WswR=F{ezi+2tR)N%j^ss zWt~aM`1F+#3XBE;_5x21mpnOHQC5*AXU2u2iLk1St9uRv{&8o@JHP)gRm=>6oMZS= z;qe++tP*?UArB8q%-RL`+dn;EzKj5@s>EpE&ZgBn#i=sh4XnyYl@b<>EJhPESH=VO zb^|Ai3g?8$Zoo3<7+ytKR5gP_7!{u3K=4jc;NJe?xm+%*cs{|$?N`bfrysAI$Fqu* zTZmKPXtv^&&ri-Vycz7DlvGWosWTpjVTc!g@$Q{Y>cF>Ozixl~&y+9Sn4DuMKyb>k zj9?k(1VL~emtm(uRi|?dRS~>8k{R;s^GbJ>SVb5He<4Ffph*d@%Hg!asUQMDy9+=B ziXay<)Xb=w$h>Menb#noDs*`gZDJ@Q#FSaCA~_p58yKAiu!*5L8LK*RFsm4kJjc_D zvTm>r`I{JOK+cAmurml?Lh5)2Y+@*Y7z~2L%<$fwRjE$1u!f-kP0EBI)G?zW)9n&$ z$2!Jo?Ub`}s-}3ijh66Yh62R&Y{hDSDjmnLj^P1RAti9C$GJFw~e0n@=%MbkgcmH0? z@PpuRBE)E0^8=e0-l<@;wRxL(B)R81yj+o~T}$54RXp}+9%-sM=w&`o#u|C=9J zcX#joK<@{7KhXPu-VgMCp!Wm4AL#u+?+1E6(EEYj5A=SZ_XE8j=>0(N2YNry`+?pM z^nPHQexOOlvQD3zA6VA0T^pZ-A9(zl0000name = name; controller = main_controller; @@ -126,12 +129,12 @@ Player::Player(PlayerStatus* _player_status, const std::string& name) sound_manager->preload("sounds/invincible.wav"); sound_manager->preload("sounds/splash.ogg"); - init(); } Player::~Player() { + if (climbing) stop_climbing(*climbing); delete smalltux_gameover; delete smalltux_star; delete bigtux_star; @@ -167,6 +170,8 @@ Player::init() on_ground_flag = false; grabbed_object = NULL; + climbing = 0; + physic.reset(); } @@ -230,6 +235,7 @@ Player::adjust_height(float new_height) void Player::trigger_sequence(std::string sequence_name) { + if (climbing) stop_climbing(*climbing); GameSession::current()->start_sequence(sequence_name); } @@ -596,6 +602,10 @@ Player::handle_input() handle_input_ghost(); return; } + if (climbing) { + handle_input_climbing(); + return; + } /* Peeking */ if( controller->released( Controller::PEEK_LEFT ) ) { @@ -684,6 +694,7 @@ Player::try_grab() continue; if(moving_object->get_bbox().contains(pos)) { + if (climbing) stop_climbing(*climbing); grabbed_object = portable; grabbed_object->grab(*this, get_pos(), dir); break; @@ -783,6 +794,7 @@ Player::set_bonus(BonusType type, bool animate) } if(animate) growing_timer.start(GROWING_TIME); + if (climbing) stop_climbing(*climbing); } if ((type == NO_BONUS) || (type == GROWUP_BONUS)) { @@ -793,6 +805,7 @@ Player::set_bonus(BonusType type, bool animate) Vector paccel = Vector(0, 1000); std::string action = (dir==LEFT)?"left":"right"; Sector::current()->add_object(new SpriteParticle("images/objects/particles/firetux-helmet.sprite", action, ppos, ANCHOR_TOP, pspeed, paccel, LAYER_OBJECTS-1)); + if (climbing) stop_climbing(*climbing); } if ((player_status->bonus == ICE_BONUS) && (animate)) { // visually lose cap @@ -801,6 +814,7 @@ Player::set_bonus(BonusType type, bool animate) Vector paccel = Vector(0, 1000); std::string action = (dir==LEFT)?"left":"right"; Sector::current()->add_object(new SpriteParticle("images/objects/particles/icetux-cap.sprite", action, ppos, ANCHOR_TOP, pspeed, paccel, LAYER_OBJECTS-1)); + if (climbing) stop_climbing(*climbing); } player_status->max_fire_bullets = 0; player_status->max_ice_bullets = 0; @@ -862,7 +876,11 @@ Player::draw(DrawingContext& context) int layer = LAYER_OBJECTS + 1; /* Set Tux sprite action */ - if (backflipping) + if (climbing) + { + tux_body->set_action("skid-left"); + } + else if (backflipping) { if(dir == LEFT) tux_body->set_action("backflip-left"); @@ -1078,6 +1096,7 @@ Player::kill(bool completely) return; sound_manager->play("sounds/hurt.wav"); + if (climbing) stop_climbing(*climbing); physic.set_velocity_x(0); @@ -1141,6 +1160,7 @@ Player::move(const Vector& vector) set_size(31.8f, 31.8f); duck = false; last_ground_y = vector.y; + if (climbing) stop_climbing(*climbing); physic.reset(); } @@ -1213,6 +1233,7 @@ Player::deactivate() physic.set_velocity_y(0); physic.set_acceleration_x(0); physic.set_acceleration_y(0); + if (climbing) stop_climbing(*climbing); } void @@ -1234,6 +1255,8 @@ Player::set_ghost_mode(bool enable) if (ghost_mode == enable) return; + if (climbing) stop_climbing(*climbing); + if (enable) { ghost_mode = true; set_group(COLGROUP_DISABLED); @@ -1246,3 +1269,80 @@ Player::set_ghost_mode(bool enable) log_debug << "You feel solid again." << std::endl; } } + + +void +Player::start_climbing(Climbable& climbable) +{ + if (climbing == &climbable) return; + + climbing = &climbable; + physic.enable_gravity(false); + physic.set_velocity(0, 0); + physic.set_acceleration(0, 0); +} + +void +Player::stop_climbing(Climbable& /*climbable*/) +{ + if (!climbing) return; + + climbing = 0; + + if (grabbed_object) { + grabbed_object->ungrab(*this, dir); + grabbed_object = NULL; + } + + physic.enable_gravity(true); + physic.set_velocity(0, 0); + physic.set_acceleration(0, 0); + + if ((controller->hold(Controller::JUMP)) || (controller->hold(Controller::UP))) { + on_ground_flag = true; + // TODO: This won't help. Why? + do_jump(-300); + } +} + +void +Player::handle_input_climbing() +{ + if (!climbing) { + log_warning << "handle_input_climbing called with climbing set to 0. Input handling skipped" << std::endl; + return; + } + + float vx = 0; + float vy = 0; + if (controller->hold(Controller::LEFT)) { + dir = LEFT; + vx -= MAX_CLIMB_XM; + } + if (controller->hold(Controller::RIGHT)) { + dir = RIGHT; + vx += MAX_CLIMB_XM; + } + if (controller->hold(Controller::UP)) { + vy -= MAX_CLIMB_YM; + } + if (controller->hold(Controller::DOWN)) { + vy += MAX_CLIMB_YM; + } + if (controller->hold(Controller::JUMP)) { + if (can_jump) { + stop_climbing(*climbing); + return; + } + } else { + can_jump = true; + } + if (controller->hold(Controller::ACTION)) { + stop_climbing(*climbing); + return; + } + physic.set_velocity(vx, vy); + physic.set_acceleration(0, 0); +} + + diff --git a/src/object/player.hpp b/src/object/player.hpp index dc23f2ffb..32d824b3b 100644 --- a/src/object/player.hpp +++ b/src/object/player.hpp @@ -39,6 +39,7 @@ class BadGuy; class Portable; +class Climbable; /* Times: */ static const float TUX_SAFE_TIME = 1.8f; @@ -259,10 +260,21 @@ public: * Orders the current GameSession to start a sequence */ void trigger_sequence(std::string sequence_name); + + /** + * Requests that the player start climbing the given Climbable + */ + void start_climbing(Climbable& climbable); + + /** + * Requests that the player stop climbing the given Climbable + */ + void stop_climbing(Climbable& climbable); private: void handle_input(); void handle_input_ghost(); /**< input handling while in ghost mode */ + void handle_input_climbing(); /**< input handling while climbing */ bool deactivated; void init(); @@ -295,6 +307,8 @@ private: bool ghost_mode; /**< indicates if Tux should float around and through solid objects */ Timer unduck_hurt_timer; /**< if Tux wants to stand up again after ducking and cannot, this timer is started */ + + Climbable* climbing; /**< Climbable object we are currently climbing, null if none */ }; #endif /*SUPERTUX_PLAYER_H*/ diff --git a/src/trigger/climbable.cpp b/src/trigger/climbable.cpp new file mode 100644 index 000000000..b2a4e4923 --- /dev/null +++ b/src/trigger/climbable.cpp @@ -0,0 +1,135 @@ +// $Id$ +// +// SuperTux - Climbable area +// 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 "climbable.hpp" +#include "game_session.hpp" +#include "lisp/lisp.hpp" +#include "lisp/writer.hpp" +#include "object_factory.hpp" +#include "main.hpp" +#include "sector.hpp" +#include "level.hpp" +#include "gettext.hpp" +#include "object/tilemap.hpp" + +namespace { + const float GRACE_DX = 8; // how far off may the player's bounding-box be x-wise + const float GRACE_DY = 8; // how far off may the player's bounding-box be y-wise + const float ACTIVATE_TRY_FOR = 1; // how long to try correcting mis-alignment of player and climbable before giving up + const float POSITION_FIX_AX = 50; // x-wise acceleration applied to player when trying to align player and Climbable + const float POSITION_FIX_AY = 50; // y-wise acceleration applied to player when trying to align player and Climbable +} + +Climbable::Climbable(const lisp::Lisp& reader) + : climbed_by(0) +{ + reader.get("x", bbox.p1.x); + reader.get("y", bbox.p1.y); + float w = 32, h = 32; + reader.get("width", w); + reader.get("height", h); + bbox.set_size(w, h); +} + +Climbable::Climbable(const Rect& area) + : climbed_by(0) +{ + bbox = area; +} + +Climbable::~Climbable() +{ + if (climbed_by) { + climbed_by->stop_climbing(*this); + climbed_by = 0; + } +} + +void +Climbable::write(lisp::Writer& writer) +{ + writer.start_list("climbable"); + + writer.write_float("x", bbox.p1.x); + writer.write_float("y", bbox.p1.y); + writer.write_float("width", bbox.get_width()); + writer.write_float("height", bbox.get_height()); + + writer.end_list("climbable"); +} + +void +Climbable::update(float /*elapsed_time*/) +{ + if (!climbed_by) return; + + if (!may_climb(*climbed_by)) { + climbed_by->stop_climbing(*this); + climbed_by = 0; + } +} + +void +Climbable::draw(DrawingContext& context) +{ + if (climbed_by) { + context.push_transform(); + context.set_translation(Vector(0, 0)); + Vector pos = Vector(0, SCREEN_HEIGHT/2 - gold_text->get_height()/2); + context.draw_center_text(gold_text, "Up we go...", pos, LAYER_GUI); + context.pop_transform(); + } +} + +void +Climbable::event(Player& player, EventType type) +{ + if ((type == EVENT_ACTIVATE) || (activate_try_timer.started())) { + if (may_climb(player)) { + climbed_by = &player; + player.start_climbing(*this); + activate_try_timer.stop(); + } else { + if (type == EVENT_ACTIVATE) activate_try_timer.start(ACTIVATE_TRY_FOR); + if (player.get_bbox().p1.x < get_bbox().p1.x - GRACE_DX) player.add_velocity(Vector(POSITION_FIX_AX,0)); + if (player.get_bbox().p2.x > get_bbox().p2.x + GRACE_DX) player.add_velocity(Vector(-POSITION_FIX_AX,0)); + if (player.get_bbox().p1.y < get_bbox().p1.y - GRACE_DY) player.add_velocity(Vector(0,POSITION_FIX_AY)); + if (player.get_bbox().p2.y > get_bbox().p2.y + GRACE_DY) player.add_velocity(Vector(0,-POSITION_FIX_AY)); + } + } + if(type == EVENT_LOSETOUCH) { + player.stop_climbing(*this); + climbed_by = 0; + } +} + +bool +Climbable::may_climb(Player& player) +{ + if (player.get_bbox().p1.x < get_bbox().p1.x - GRACE_DX) return false; + if (player.get_bbox().p2.x > get_bbox().p2.x + GRACE_DX) return false; + if (player.get_bbox().p1.y < get_bbox().p1.y - GRACE_DY) return false; + if (player.get_bbox().p2.y > get_bbox().p2.y + GRACE_DY) return false; + return true; +} + +IMPLEMENT_FACTORY(Climbable, "climbable"); + diff --git a/src/trigger/climbable.hpp b/src/trigger/climbable.hpp new file mode 100644 index 000000000..170483d8c --- /dev/null +++ b/src/trigger/climbable.hpp @@ -0,0 +1,52 @@ +// $Id$ +// +// SuperTux - Climbable area +// 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 __CLIMBABLE_H__ +#define __CLIMBABLE_H__ + +#include "trigger_base.hpp" +#include "serializable.hpp" +#include "resources.hpp" +#include "video/drawing_context.hpp" +#include "timer.hpp" +#include "object/player.hpp" + +class Climbable : public TriggerBase, public Serializable +{ +public: + Climbable(const lisp::Lisp& reader); + Climbable(const Rect& area); + ~Climbable(); + + void write(lisp::Writer& writer); + void event(Player& player, EventType type); + void update(float elapsed_time); + void draw(DrawingContext& context); + + /** + * returns true if the player is within bounds of the Climbable + */ + bool may_climb(Player& player); + +protected: + Player* climbed_by; /**< set to player who's currently climbing us, null if nobody is */ + Timer activate_try_timer; /**< try to correct mis-alignment while this timer runs */ +}; + +#endif diff --git a/src/trigger/trigger_base.cpp b/src/trigger/trigger_base.cpp index 480cd90eb..47e09c532 100644 --- a/src/trigger/trigger_base.cpp +++ b/src/trigger/trigger_base.cpp @@ -24,7 +24,7 @@ #include "object/player.hpp" TriggerBase::TriggerBase() - : sprite(0) + : sprite(0), lasthit(false), hit(false), losetouch_listener(0) { set_group(COLGROUP_TOUCHABLE); } @@ -36,6 +36,12 @@ TriggerBase::~TriggerBase() void TriggerBase::update(float ) { + if (lasthit && !hit) { + if (losetouch_listener) { + event(*losetouch_listener, EVENT_LOSETOUCH); + losetouch_listener = 0; + } + } lasthit = hit; hit = false; } @@ -55,9 +61,19 @@ TriggerBase::collision(GameObject& other, const CollisionHit& ) Player* player = dynamic_cast (&other); if(player) { hit = true; - if(!lasthit) + if(!lasthit) { + losetouch_listener = player; + player->add_remove_listener(this); event(*player, EVENT_TOUCH); + } } return ABORT_MOVE; } + +void +TriggerBase::object_removed(GameObject* object) +{ + if (losetouch_listener == object) losetouch_listener = 0; +} + diff --git a/src/trigger/trigger_base.hpp b/src/trigger/trigger_base.hpp index 887f413a5..d03d85b08 100644 --- a/src/trigger/trigger_base.hpp +++ b/src/trigger/trigger_base.hpp @@ -20,20 +20,24 @@ #ifndef SUPERTUX_TRIGGER_BASE_H #define SUPERTUX_TRIGGER_BASE_H +#include #include "moving_object.hpp" #include "math/rect.hpp" #include "sprite/sprite.hpp" +#include "object_remove_listener.hpp" class Player; /** This class is the base class for all objects you can interact with in some * way. There are several interaction types defined like touch and activate */ -class TriggerBase : public MovingObject +class TriggerBase : public MovingObject, public ObjectRemoveListener { public: enum EventType { - EVENT_TOUCH, EVENT_ACTIVATE + EVENT_TOUCH, /**< Object came into contact */ + EVENT_LOSETOUCH, /**< Lost contact with object */ + EVENT_ACTIVATE /**< Action button pressed */ }; TriggerBase(); @@ -47,11 +51,18 @@ public: * Receive trigger events */ virtual void event(Player& player, EventType type) = 0; + + /** + * Called by GameObject destructor of an object in losetouch_listeners + */ + virtual void object_removed(GameObject* object); private: Sprite* sprite; bool lasthit; bool hit; + + Player* losetouch_listener; /**< Player that will be informed when we lose touch with him */ }; #endif /*SUPERTUX_INTERACTIVE_OBJECT_H*/ -- 2.11.0