1// SuperTux - A Jump'n Run
2// Copyright (C) 2004 Ingo Ruhnke <grumbel@gmail.com>
3// Copyright (C) 2006 Christoph Sommer <christoph.sommer@2006.expires.deltadevelopment.de>
4//
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18#include "worldmap/tux.hpp"
19
20#include <sstream>
21
22#include "control/input_manager.hpp"
23#include "editor/editor.hpp"
24#include "sprite/sprite.hpp"
25#include "sprite/sprite_manager.hpp"
26#include "supertux/savegame.hpp"
27#include "supertux/tile.hpp"
28#include "util/log.hpp"
29#include "worldmap/camera.hpp"
30#include "worldmap/level_tile.hpp"
31#include "worldmap/special_tile.hpp"
32#include "worldmap/sprite_change.hpp"
33
34namespace worldmap {
35
36static const float TUXSPEED = 200;
37static const float map_message_TIME = 2.8f;
38
39Tux::Tux(WorldMap* worldmap) :
40 m_back_direction(),
41 m_worldmap(worldmap),
42 m_sprite(SpriteManager::current()->create(m_worldmap->get_savegame().get_player_status().worldmap_sprite)),
43 m_controller(InputManager::current()->get_controller()),
44 m_input_direction(Direction::NONE),
45 m_direction(Direction::NONE),
46 m_tile_pos(),
47 m_offset(0),
48 m_moving(false),
49 m_ghost_mode(false)
50{
51}
52
53void
54Tux::draw(DrawingContext& context)
55{
56 if (m_worldmap->get_camera().is_panning()) return;
57
58 std::string action = get_action_prefix_for_bonus(m_worldmap->get_savegame().get_player_status().bonus);
59 if (!action.empty())
60 {
61 m_sprite->set_action(m_moving ? action + "-walking" : action + "-stop");
62 }
63 else
64 {
65 log_debug << "Bonus type not handled in worldmap." << std::endl;
66 m_sprite->set_action("large-stop");
67 }
68 m_sprite->draw(context.color(), get_pos(), LAYER_OBJECTS);
69}
70
71std::string
72Tux::get_action_prefix_for_bonus(const BonusType& bonus) const
73{
74 if (bonus == GROWUP_BONUS)
75 return "large";
76 if (bonus == FIRE_BONUS)
77 return "fire";
78 if (bonus == ICE_BONUS)
79 return "ice";
80 if (bonus == AIR_BONUS)
81 return "air";
82 if (bonus == EARTH_BONUS)
83 return "earth";
84 if (bonus == NO_BONUS)
85 return "small";
86
87 return "";
88}
89
90Vector
91Tux::get_pos() const
92{
93 float x = m_tile_pos.x * 32;
94 float y = m_tile_pos.y * 32;
95
96 switch (m_direction)
97 {
98 case Direction::WEST:
99 x -= m_offset - 32;
100 break;
101 case Direction::EAST:
102 x += m_offset - 32;
103 break;
104 case Direction::NORTH:
105 y -= m_offset - 32;
106 break;
107 case Direction::SOUTH:
108 y += m_offset - 32;
109 break;
110 case Direction::NONE:
111 break;
112 }
113
114 return Vector(x, y);
115}
116
117void
118Tux::stop()
119{
120 m_offset = 0;
121 m_direction = Direction::NONE;
122 m_input_direction = Direction::NONE;
123 m_moving = false;
124}
125
126void
127Tux::set_direction(Direction dir)
128{
129 m_input_direction = dir;
130}
131
132void
133Tux::set_ghost_mode(bool enabled)
134{
135 m_ghost_mode = enabled;
136}
137
138bool
139Tux::get_ghost_mode() const
140{
141 return m_ghost_mode;
142}
143
144void
145Tux::try_start_walking()
146{
147 if (m_moving)
148 return;
149 if (m_input_direction == Direction::NONE)
150 return;
151
152 auto level = m_worldmap->at_level();
153
154 // We got a new direction, so lets start walking when possible
155 Vector next_tile;
156 if ((!level || level->is_solved() || level->is_perfect()
157 || (Editor::current() && Editor::current()->is_testing_level()))
158 && m_worldmap->path_ok(m_input_direction, m_tile_pos, &next_tile)) {
159 m_tile_pos = next_tile;
160 m_moving = true;
161 m_direction = m_input_direction;
162 m_back_direction = reverse_dir(m_direction);
163 } else if (m_ghost_mode || (m_input_direction == m_back_direction)) {
164 m_moving = true;
165 m_direction = m_input_direction;
166 m_tile_pos = m_worldmap->get_next_tile(m_tile_pos, m_direction);
167 m_back_direction = reverse_dir(m_direction);
168 }
169}
170
171bool
172Tux::can_walk(int tile_data, Direction dir) const
173{
174 return m_ghost_mode ||
175 ((tile_data & Tile::WORLDMAP_NORTH && dir == Direction::NORTH) ||
176 (tile_data & Tile::WORLDMAP_SOUTH && dir == Direction::SOUTH) ||
177 (tile_data & Tile::WORLDMAP_EAST && dir == Direction::EAST) ||
178 (tile_data & Tile::WORLDMAP_WEST && dir == Direction::WEST));
179}
180
181void
182Tux::change_sprite(SpriteChange* sprite_change)
183{
184 //SpriteChange* sprite_change = m_worldmap->at_sprite_change(tile_pos);
185 if (sprite_change != nullptr) {
186 m_sprite = sprite_change->m_sprite->clone();
187 sprite_change->clear_stay_action();
188 m_worldmap->get_savegame().get_player_status().worldmap_sprite = sprite_change->m_sprite_name;
189 }
190}
191
192void
193Tux::try_continue_walking(float dt_sec)
194{
195 if (!m_moving)
196 return;
197
198 // Let tux walk
199 m_offset += TUXSPEED * dt_sec;
200
201 // Do nothing if we have not yet reached the next tile
202 if (m_offset <= 32)
203 return;
204
205 m_offset -= 32;
206
207 auto sprite_change = m_worldmap->at_sprite_change(m_tile_pos);
208 change_sprite(sprite_change);
209
210 // if this is a special_tile with passive_message, display it
211 auto special_tile = m_worldmap->at_special_tile();
212 if (special_tile)
213 {
214 // direction and the apply_action_ are opposites, since they "see"
215 // directions in a different way
216 if ((m_direction == Direction::NORTH && special_tile->get_apply_action_south()) ||
217 (m_direction == Direction::SOUTH && special_tile->get_apply_action_north()) ||
218 (m_direction == Direction::WEST && special_tile->get_apply_action_east()) ||
219 (m_direction == Direction::EAST && special_tile->get_apply_action_west()))
220 {
221 process_special_tile(special_tile);
222 }
223 }
224
225 // check if we are at a Teleporter
226 auto teleporter = m_worldmap->at_teleporter(m_tile_pos);
227
228 // stop if we reached a level, a WORLDMAP_STOP tile, a teleporter or a special tile without a passive_message
229 if ((m_worldmap->at_level()) ||
230 (m_worldmap->tile_data_at(m_tile_pos) & Tile::WORLDMAP_STOP) ||
231 (special_tile && !special_tile->is_passive_message() && special_tile->get_script().empty()) ||
232 (teleporter) ||
233 m_ghost_mode)
234 {
235 if (special_tile && !special_tile->get_map_message().empty() && !special_tile->is_passive_message()) {
236 m_worldmap->set_passive_message({}, 0.0f);
237 }
238 stop();
239 return;
240 }
241
242 // if user wants to change direction, try changing, else guess the direction in which to walk next
243 const int tile_data = m_worldmap->tile_data_at(m_tile_pos);
244 if ((m_direction != m_input_direction) && can_walk(tile_data, m_input_direction)) {
245 m_direction = m_input_direction;
246 m_back_direction = reverse_dir(m_direction);
247 } else {
248 Direction dir = Direction::NONE;
249 if (tile_data & Tile::WORLDMAP_NORTH && m_back_direction != Direction::NORTH)
250 dir = Direction::NORTH;
251 else if (tile_data & Tile::WORLDMAP_SOUTH && m_back_direction != Direction::SOUTH)
252 dir = Direction::SOUTH;
253 else if (tile_data & Tile::WORLDMAP_EAST && m_back_direction != Direction::EAST)
254 dir = Direction::EAST;
255 else if (tile_data & Tile::WORLDMAP_WEST && m_back_direction != Direction::WEST)
256 dir = Direction::WEST;
257
258 if (dir == Direction::NONE) {
259 // Should never be reached if tiledata is good
260 log_warning << "Could not determine where to walk next" << std::endl;
261 stop();
262 return;
263 }
264
265 m_direction = dir;
266 m_input_direction = m_direction;
267 m_back_direction = reverse_dir(m_direction);
268 }
269
270 // Walk automatically to the next tile
271 if (m_direction == Direction::NONE)
272 return;
273
274 Vector next_tile;
275 if (!m_ghost_mode && !m_worldmap->path_ok(m_direction, m_tile_pos, &next_tile)) {
276 log_debug << "Tilemap data is buggy" << std::endl;
277 stop();
278 return;
279 }
280
281 auto next_sprite = m_worldmap->at_sprite_change(next_tile);
282 if (next_sprite != nullptr && next_sprite->m_change_on_touch) {
283 change_sprite(next_sprite);
284 }
285 //SpriteChange* last_sprite = m_worldmap->at_sprite_change(tile_pos);
286 if (sprite_change != nullptr && next_sprite != nullptr) {
287 log_debug << "Old: " << m_tile_pos << " New: " << next_tile << std::endl;
288 sprite_change->set_stay_action();
289 }
290
291 m_tile_pos = next_tile;
292}
293
294void
295Tux::update_input_direction()
296{
297 if (m_controller.hold(Control::UP))
298 m_input_direction = Direction::NORTH;
299 else if (m_controller.hold(Control::DOWN))
300 m_input_direction = Direction::SOUTH;
301 else if (m_controller.hold(Control::LEFT))
302 m_input_direction = Direction::WEST;
303 else if (m_controller.hold(Control::RIGHT))
304 m_input_direction = Direction::EAST;
305}
306
307void
308Tux::update(float dt_sec)
309{
310 if (m_worldmap->get_camera().is_panning()) return;
311
312 update_input_direction();
313 if (m_moving)
314 try_continue_walking(dt_sec);
315 else
316 try_start_walking();
317}
318
319void
320Tux::setup()
321{
322 // check if we already touch a SpriteChange object
323 auto sprite_change = m_worldmap->at_sprite_change(m_tile_pos);
324 change_sprite(sprite_change);
325}
326
327void
328Tux::process_special_tile(SpecialTile* special_tile)
329{
330 if (!special_tile) {
331 return;
332 }
333
334 if (special_tile->is_passive_message()) {
335 m_worldmap->set_passive_message(special_tile->get_map_message(), map_message_TIME);
336 } else if (!special_tile->get_script().empty()) {
337 try {
338 m_worldmap->run_script(special_tile->get_script(), "specialtile");
339 } catch(std::exception& e) {
340 log_warning << "Couldn't execute special tile script: " << e.what()
341 << std::endl;
342 }
343 }
344}
345
346} // namespace worldmap
347
348/* EOF */
349