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