1// SuperTux - "Totem" Badguy
2// Copyright (C) 2006 Christoph Sommer <christoph.sommer@2006.expires.deltadevelopment.de>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17#include "badguy/totem.hpp"
18
19#include <math.h>
20
21#include "audio/sound_manager.hpp"
22#include "object/player.hpp"
23#include "sprite/sprite.hpp"
24#include "supertux/sector.hpp"
25
26static const float JUMP_ON_SPEED_Y = -400;
27static const float JUMP_OFF_SPEED_Y = -500;
28static const std::string LAND_ON_TOTEM_SOUND = "sounds/totem.ogg";
29
30Totem::Totem(const ReaderMapping& reader) :
31 BadGuy(reader, "images/creatures/totem/totem.sprite"),
32 carrying(nullptr),
33 carried_by(nullptr)
34{
35 SoundManager::current()->preload( LAND_ON_TOTEM_SOUND );
36}
37
38Totem::~Totem()
39{
40 if (carrying) carrying->jump_off();
41 if (carried_by) jump_off();
42}
43
44bool
45Totem::updatePointers(const GameObject* from_object, GameObject* to_object)
46{
47 if (from_object == carrying) {
48 carrying = dynamic_cast<Totem*>(to_object);
49 return true;
50 }
51 if (from_object == carried_by) {
52 carried_by = dynamic_cast<Totem*>(to_object);
53 return true;
54 }
55 return false;
56}
57
58void
59Totem::initialize()
60{
61 if (!carried_by) {
62static const float WALKSPEED = 100;
63 m_physic.set_velocity_x(m_dir == Direction::LEFT ? -WALKSPEED : WALKSPEED);
64 m_sprite->set_action(m_dir == Direction::LEFT ? "walking-left" : "walking-right");
65 return;
66 } else {
67 synchronize_with(carried_by);
68 m_sprite->set_action(m_dir == Direction::LEFT ? "stacked-left" : "stacked-right");
69 return;
70 }
71}
72
73void
74Totem::active_update(float dt_sec)
75{
76 BadGuy::active_update(dt_sec);
77
78 if (!carried_by) {
79 if (on_ground() && might_fall())
80 {
81 m_dir = (m_dir == Direction::LEFT ? Direction::RIGHT : Direction::LEFT);
82 initialize();
83 }
84
85 // jump a bit if we find a suitable totem
86 for (auto& obj : Sector::get().get_objects_by_type<MovingObject>()) {
87 auto t = dynamic_cast<Totem*>(&obj);
88 if (!t) continue;
89
90 // skip if we are not approaching each other
91 if (!((m_dir == Direction::LEFT) && (t->m_dir == Direction::RIGHT))) continue;
92
93 Vector p1 = m_col.m_bbox.p1();
94 Vector p2 = t->get_pos();
95
96 // skip if not on same height
97 float dy = (p1.y - p2.y);
98 if (fabsf(dy - 0) > 2) continue;
99
100 // skip if too far away
101 float dx = (p1.x - p2.x);
102 if (fabsf(dx - 128) > 2) continue;
103
104 m_physic.set_velocity_y(JUMP_ON_SPEED_Y);
105 p1.y -= 1;
106 set_pos(p1);
107 break;
108 }
109 }
110
111 if (carried_by) {
112 synchronize_with(carried_by);
113 }
114
115 if (carrying) {
116 carrying->synchronize_with(this);
117 }
118
119}
120
121bool
122Totem::collision_squished(GameObject& object)
123{
124 /// Tux shouldn't be able to bisect totem stack by sacrificing his powerup.
125 /// --Hume2
126 if (carrying) {
127 return false;
128 }
129
130 if (carried_by) {
131 auto player = dynamic_cast<Player*>(&object);
132 if (player) player->bounce(*this);
133 jump_off();
134 }
135
136 m_sprite->set_action(m_dir == Direction::LEFT ? "squished-left" : "squished-right");
137 m_col.m_bbox.set_size(m_sprite->get_current_hitbox_width(), m_sprite->get_current_hitbox_height());
138
139 kill_squished(object);
140 return true;
141}
142
143void
144Totem::collision_solid(const CollisionHit& hit)
145{
146 update_on_ground_flag(hit);
147
148 // if we are being carried around, pass event to bottom of stack and ignore it
149 if (carried_by) {
150 carried_by->collision_solid(hit);
151 return;
152 }
153
154 // If we hit something from above or below: stop moving in this direction
155 if (hit.top || hit.bottom) {
156 m_physic.set_velocity_y(0);
157 }
158
159 // If we are hit from the direction we are facing: turn around
160 if (hit.left && (m_dir == Direction::LEFT)) {
161 m_dir = Direction::RIGHT;
162 initialize();
163 }
164 if (hit.right && (m_dir == Direction::RIGHT)) {
165 m_dir = Direction::LEFT;
166 initialize();
167 }
168}
169
170HitResponse
171Totem::collision_badguy(BadGuy& badguy, const CollisionHit& hit)
172{
173 // if we are being carried around, pass event to bottom of stack and ignore it
174 if (carried_by) {
175 carried_by->collision_badguy(badguy, hit);
176 return CONTINUE;
177 }
178
179 // if we hit a Totem that is not from our stack: have our base jump on its top
180 auto totem = dynamic_cast<Totem*>(&badguy);
181 if (totem) {
182 auto thisBase = this; while (thisBase->carried_by) thisBase=thisBase->carried_by;
183 auto srcBase = totem; while (srcBase->carried_by) srcBase=srcBase->carried_by;
184 auto thisTop = this; while (thisTop->carrying) thisTop=thisTop->carrying;
185 if (srcBase != thisBase) {
186 srcBase->jump_on(thisTop);
187 }
188 }
189
190 // If we are hit from the direction we are facing: turn around
191 if (hit.left && (m_dir == Direction::LEFT)) {
192 m_dir = Direction::RIGHT;
193 initialize();
194 }
195 if (hit.right && (m_dir == Direction::RIGHT)) {
196 m_dir = Direction::LEFT;
197 initialize();
198 }
199
200 return CONTINUE;
201}
202
203void
204Totem::kill_fall()
205{
206 if (carrying) carrying->jump_off();
207 if (carried_by) jump_off();
208
209 BadGuy::kill_fall();
210}
211
212void
213Totem::jump_on(Totem* target)
214{
215 if (target->carrying) {
216 log_warning << "target is already carrying someone" << std::endl;
217 return;
218 }
219
220 target->carrying = this;
221
222 carried_by = target;
223 initialize();
224 m_col.m_bbox.set_size(m_sprite->get_current_hitbox_width(), m_sprite->get_current_hitbox_height());
225
226 SoundManager::current()->play( LAND_ON_TOTEM_SOUND , get_pos());
227
228 synchronize_with(target);
229}
230
231void
232Totem::jump_off() {
233 if (!carried_by) {
234 log_warning << "not carried by anyone" << std::endl;
235 return;
236 }
237
238 carried_by->carrying = nullptr;
239
240 carried_by = nullptr;
241
242 initialize();
243 m_col.m_bbox.set_size(m_sprite->get_current_hitbox_width(), m_sprite->get_current_hitbox_height());
244
245 m_physic.set_velocity_y(JUMP_OFF_SPEED_Y);
246}
247
248void
249Totem::synchronize_with(Totem* base)
250{
251
252 if (m_dir != base->m_dir) {
253 m_dir = base->m_dir;
254 m_sprite->set_action(m_dir == Direction::LEFT ? "stacked-left" : "stacked-right");
255 }
256
257 Vector pos = base->get_pos();
258 pos.y -= m_sprite->get_current_hitbox_height();
259 set_pos(pos);
260
261 m_physic.set_velocity_x(base->m_physic.get_velocity_x());
262 m_physic.set_velocity_y(base->m_physic.get_velocity_y());
263}
264
265/* EOF */
266