| 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 | |
| 34 | namespace worldmap { |
| 35 | |
| 36 | static const float TUXSPEED = 200; |
| 37 | static const float map_message_TIME = 2.8f; |
| 38 | |
| 39 | Tux::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 | |
| 53 | void |
| 54 | Tux::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 | |
| 71 | std::string |
| 72 | Tux::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 | |
| 90 | Vector |
| 91 | Tux::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 | |
| 117 | void |
| 118 | Tux::stop() |
| 119 | { |
| 120 | m_offset = 0; |
| 121 | m_direction = Direction::NONE; |
| 122 | m_input_direction = Direction::NONE; |
| 123 | m_moving = false; |
| 124 | } |
| 125 | |
| 126 | void |
| 127 | Tux::set_direction(Direction dir) |
| 128 | { |
| 129 | m_input_direction = dir; |
| 130 | } |
| 131 | |
| 132 | void |
| 133 | Tux::set_ghost_mode(bool enabled) |
| 134 | { |
| 135 | m_ghost_mode = enabled; |
| 136 | } |
| 137 | |
| 138 | bool |
| 139 | Tux::get_ghost_mode() const |
| 140 | { |
| 141 | return m_ghost_mode; |
| 142 | } |
| 143 | |
| 144 | void |
| 145 | Tux::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 | |
| 171 | bool |
| 172 | Tux::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 | |
| 181 | void |
| 182 | Tux::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 | |
| 192 | void |
| 193 | Tux::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 | |
| 294 | void |
| 295 | Tux::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 | |
| 307 | void |
| 308 | Tux::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 | |
| 319 | void |
| 320 | Tux::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 | |
| 327 | void |
| 328 | Tux::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 | |