1 /*
2 * Copyright (C) 2006-2019 Christopho, Solarus - http://www.solarus-games.org
3 *
4 * Solarus 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 * Solarus 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 along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17 #include "solarus/audio/Sound.h"
18 #include "solarus/entities/Hero.h"
19 #include "solarus/entities/Teletransporter.h"
20 #include "solarus/core/Debug.h"
21 #include "solarus/core/Game.h"
22 #include "solarus/core/Map.h"
23 #include "solarus/core/QuestFiles.h"
24 #include "solarus/graphics/Sprite.h"
25 #include "solarus/lua/LuaContext.h"
26
27 namespace Solarus {
28
29 /**
30 * \brief Constructor.
31 * \param name Name of the teletransporter.
32 * \param layer Layer of the teletransporter.
33 * \param xy Coordinates where to create the entity.
34 * \param size Size of the teletransporter's rectangle.
35 * \param sprite_name Sprite animation set id to use, or an empty string.
36 * \param sound_id Sound to play when using the teletransporter,
37 * or an empty string.
38 * \param transition_style Style of transition between the two maps.
39 * \param destination_map_id Id of the destination map.
40 * \param destination_name Location on the destination map,
41 * or "_same" to keep the hero's coordinates,
42 * or "_side" to place the hero on the appropriate side of the map.
43 * An empty string means the default destination entity of the map.
44 */
Teletransporter(const std::string & name,int layer,const Point & xy,const Size & size,const std::string & sprite_name,const std::string & sound_id,Transition::Style transition_style,const std::string & destination_map_id,const std::string & destination_name)45 Teletransporter::Teletransporter(
46 const std::string& name,
47 int layer,
48 const Point& xy,
49 const Size& size,
50 const std::string& sprite_name,
51 const std::string& sound_id,
52 Transition::Style transition_style,
53 const std::string& destination_map_id,
54 const std::string& destination_name):
55
56 Entity(name, 0, layer, xy, size),
57 sound_id(sound_id),
58 transition_style(transition_style),
59 destination_map_id(destination_map_id),
60 destination_name(destination_name),
61 destination_side(-1),
62 transition_direction(0),
63 transporting_hero(false) {
64
65 set_collision_modes(CollisionMode::COLLISION_CUSTOM);
66
67 if (!sprite_name.empty()) {
68 create_sprite(sprite_name);
69 }
70 }
71
72 /**
73 * \copydoc Entity::notify_creating
74 */
notify_creating()75 void Teletransporter::notify_creating() {
76
77 Entity::notify_creating();
78
79 int x = get_x();
80 int y = get_y();
81
82 // Compute the destination side in case the destination name is "_side"
83 // or becomes it later.
84 if (get_height() >= get_width()) {
85 if (x + get_width() == 0) {
86 destination_side = 0;
87 }
88 else if (x == get_map().get_width()) {
89 destination_side = 2;
90 }
91 }
92
93 if (destination_side == -1) {
94 if (get_width() >= get_height()) {
95 if (y + get_height() == 0) {
96 destination_side = 3;
97 }
98 else if (y == get_map().get_height()) {
99 destination_side = 1;
100 }
101 }
102 }
103
104 if (destination_side != -1) {
105 set_layer_independent_collisions(true);
106 transition_direction = (destination_side + 2) % 4;
107 }
108 }
109
110 /**
111 * \brief Returns the type of entity.
112 * \return the type of entity
113 */
get_type() const114 EntityType Teletransporter::get_type() const {
115 return ThisType;
116 }
117
118 /**
119 * \brief Returns the sound to play when using this teletransporter.
120 * \return Id of the teletransporter's sound, or an empty string if no sound
121 * is played.
122 */
get_sound_id() const123 const std::string& Teletransporter::get_sound_id() const {
124 return sound_id;
125 }
126
127 /**
128 * \brief Sets the sound to play when using this teletransporter.
129 * \param sound_id Id of the teletransporter's sound, or an empty string to
130 * play no sound.
131 */
set_sound_id(const std::string & sound_id)132 void Teletransporter::set_sound_id(const std::string& sound_id) {
133 this->sound_id = sound_id;
134 }
135
136 /**
137 * \brief Returns the style of transition between both maps.
138 * \return Style of transition of this teletransporter.
139 */
get_transition_style() const140 Transition::Style Teletransporter::get_transition_style() const {
141 return transition_style;
142 }
143
144 /**
145 * \brief Sets the style of transition between both maps.
146 * \param transition_style Style of transition of this teletransporter.
147 */
set_transition_style(Transition::Style transition_style)148 void Teletransporter::set_transition_style(Transition::Style transition_style) {
149 this->transition_style = transition_style;
150 }
151
152 /**
153 * \brief Returns the id of the destination map.
154 *
155 * This might be the same map.
156 *
157 * \return The id of the destination map.
158 */
get_destination_map_id() const159 const std::string& Teletransporter::get_destination_map_id() const {
160 return destination_map_id;
161 }
162
163 /**
164 * \brief Sets the id of the destination map.
165 *
166 * This might be the same map.
167 *
168 * \param map_id The id of the destination map.
169 */
set_destination_map_id(const std::string & map_id)170 void Teletransporter::set_destination_map_id(const std::string& map_id) {
171 this->destination_map_id = map_id;
172 }
173
174 /**
175 * \brief Returns the destination description of this teletransporter.
176 * \return Name of a destination entity of the destination map,
177 * or "_same" to keep the hero's coordinates,
178 * or "_side" to place the hero on the appropriate side of the map.
179 * An empty string means the default destination entity of the map.
180 */
get_destination_name() const181 const std::string& Teletransporter::get_destination_name() const {
182 return destination_name;
183 }
184
185 /**
186 * \brief Sets the destination description of this teletransporter.
187 * \param destination_name Name of a destination entity of the destination map,
188 * or "_same" to keep the hero's coordinates,
189 * or "_side" to place the hero on the appropriate side of the map.
190 * An empty string means the default destination entity of the map.
191 */
set_destination_name(const std::string & destination_name)192 void Teletransporter::set_destination_name(const std::string& destination_name) {
193 this->destination_name = destination_name;
194 }
195
196 /**
197 * \brief Returns whether this teletransporter is on the side of the map.
198 *
199 * When true is returned, this means that the teletransporter can make the hero
200 * scroll towards an adjacent map.
201 *
202 * \return true if this teletransporter is on the side of the map
203 */
is_on_map_side() const204 bool Teletransporter::is_on_map_side() const {
205 return destination_name == "_side" && destination_side != -1;
206 }
207
208 /**
209 * \brief Returns whether this entity is an obstacle for another one.
210 * \param other another entity
211 * \return true if this entity is an obstacle for the other one
212 */
is_obstacle_for(Entity & other)213 bool Teletransporter::is_obstacle_for(Entity& other) {
214
215 return other.is_teletransporter_obstacle(*this);
216 }
217
218 /**
219 * \brief Returns whether an entity's collides with this entity.
220 * \param entity an entity
221 * \return true if the entity's collides with this entity
222 */
test_collision_custom(Entity & entity)223 bool Teletransporter::test_collision_custom(Entity& entity) {
224
225 bool collision = false;
226 bool normal_case = true;
227
228 // specific collision tests for some situations
229 if (entity.is_hero()) {
230
231 Hero& hero = static_cast<Hero&>(entity);
232 if (is_on_map_side()) {
233 // scrolling towards an adjacent map
234 const Point& touching_point = hero.get_touching_point(transition_direction);
235 collision = hero.is_moving_towards(transition_direction)
236 && overlaps(touching_point);
237 normal_case = false;
238 }
239
240 else if (!get_map().test_collision_with_border(get_center_point()) &&
241 hero.get_ground_below() == Ground::HOLE) {
242 // falling into a hole
243 collision = overlaps(hero.get_ground_point());
244 normal_case = false;
245 }
246 }
247
248 // usual collision test
249 if (normal_case) {
250 const Rectangle& entity_rectangle = entity.get_bounding_box();
251 int x1 = entity_rectangle.get_x() + 4;
252 int x2 = x1 + entity_rectangle.get_width() - 9;
253 int y1 = entity_rectangle.get_y() + 4;
254 int y2 = y1 + entity_rectangle.get_height() - 9;
255
256 collision = overlaps(x1, y1) && overlaps(x2, y1) &&
257 overlaps(x1, y2) && overlaps(x2, y2);
258 }
259
260 if (!collision && !is_on_map_side()) {
261 transporting_hero = false;
262 }
263
264 return collision;
265 }
266
267 /**
268 * \brief This function is called by the engine when an entity overlaps the teletransporter.
269 * \param entity_overlapping the entity overlapping the detector
270 * \param collision_mode the collision mode that detected the collision
271 */
notify_collision(Entity & entity_overlapping,CollisionMode collision_mode)272 void Teletransporter::notify_collision(Entity& entity_overlapping, CollisionMode collision_mode) {
273
274 entity_overlapping.notify_collision_with_teletransporter(*this, collision_mode);
275 }
276
277 /**
278 * \brief Makes the teletransporter move the hero to the destination.
279 * \param hero the hero
280 */
transport_hero(Hero & hero)281 void Teletransporter::transport_hero(Hero& hero) {
282
283 if (!is_enabled() || is_being_removed()) {
284 return;
285 }
286
287 if (transporting_hero) {
288 // already done
289 return;
290 }
291
292 std::string name = destination_name;
293 int hero_x = hero.get_x();
294 int hero_y = hero.get_y();
295
296 if (is_on_map_side()) {
297
298 // special destination point: side of the map
299 // we determine the appropriate side based on the teletransporter's position;
300 // we also place the hero on the old map so that its position corresponds to the new map
301
302 switch (destination_side) {
303
304 case 0:
305 name += '0'; // scroll to the west
306 hero_x = 0;
307 break;
308
309 case 1:
310 name += '1'; // scroll to the south
311 hero_y = get_map().get_height() + 5;
312 break;
313
314 case 2:
315 name += '2'; // scroll to the east
316 hero_x = get_map().get_width();
317 break;
318
319 case 3:
320 name += '3'; // scroll to the north
321 hero_y = 5;
322 break;
323
324 default:
325 Debug::die(std::string("Bad destination side for teletransporter '")
326 + get_name() + "'");
327 }
328 }
329
330 transporting_hero = true;
331
332 get_lua_context()->teletransporter_on_activated(*this);
333
334 if (!is_enabled() || is_being_removed()) {
335 // The teletransporter was just disabled: abort the teletransportation.
336 transporting_hero = false;
337 return;
338 }
339
340 if (!sound_id.empty()) {
341 Sound::play(sound_id);
342 }
343
344 get_game().set_current_map(destination_map_id, name, transition_style);
345 transporting_hero = false;
346 hero.set_xy(hero_x, hero_y);
347 }
348
349 }
350
351