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/core/Map.h"
18 #include "solarus/core/System.h"
19 #include "solarus/entities/StreamAction.h"
20 #include "solarus/entities/Stream.h"
21 #include "solarus/graphics/Sprite.h"
22 #include "solarus/movements/Movement.h"
23 #include <cmath>
24 
25 namespace Solarus {
26 
27 /**
28  * \brief Creates a stream action.
29  * \param stream The stream applied.
30  * \param entity_moved Entity the stream is applied to.
31  */
StreamAction(Stream & stream,Entity & entity_moved)32 StreamAction::StreamAction(Stream& stream, Entity& entity_moved):
33   stream(std::static_pointer_cast<Stream>(stream.shared_from_this())),
34   entity_moved(std::static_pointer_cast<Entity>(entity_moved.shared_from_this())),
35   active(true),
36   suspended(false),
37   when_suspended(0),
38   target(0, 0),
39   next_move_date(0),
40   delay(0) {
41 
42   recompute_movement();
43 
44   next_move_date = System::now();
45 }
46 
47 /**
48  * \brief Returns the stream responsible from this action.
49  * \return The stream.
50  */
get_stream() const51 const Stream& StreamAction::get_stream() const {
52   return *stream;
53 }
54 
55 /**
56  * \overload Non-const version.
57  */
get_stream()58 Stream& StreamAction::get_stream() {
59   return *stream;
60 }
61 
62 /**
63  * \brief Returns the entity this action is applied to.
64  * \return The entity moved by the stream.
65  */
get_entity_moved() const66 const Entity& StreamAction::get_entity_moved() const {
67   return *entity_moved;
68 }
69 
70 /**
71  * \brief Returns whether the action is currently active.
72  * The action is inactive if the stream effect was completely applied,
73  * or if the stream of the entity are destroyed or disabled.
74  */
is_active() const75 bool StreamAction::is_active() const {
76   return active;
77 }
78 
79 /**
80  * \brief Updates the direction of the movement to the target.
81  *
82  * This function should be called periodically.
83  */
recompute_movement()84 void StreamAction::recompute_movement() {
85 
86   if (!is_active()) {
87     return;
88   }
89 
90   // Compute the direction of the movement and its target point.
91   const int direction8 = stream->get_direction();
92   const Point& xy = Entity::direction_to_xy_move(direction8);
93   const int dx = xy.x;
94   const int dy = xy.y;
95   const int speed = stream->get_speed();
96 
97   if (speed > 0) {
98     delay = (uint32_t) (1000 / speed);
99   }
100   else {
101     // Stream of speed 0: inactive.
102     delay = 0;
103   }
104 
105   if (stream->get_allow_movement()) {
106     // Don't center the entity on non-blocking streams.
107     target = entity_moved->get_xy();
108   }
109   else {
110     target = stream->get_xy();
111   }
112 
113   // Stop after the stream.
114   if (dx != 0) {
115     // Horizontal stream.
116     target.x += stream->get_width() * (dx > 0 ? 1 : -1);
117   }
118 
119   if (dy != 0) {
120     // Vertical stream.
121     target.y += stream->get_height() * (dy > 0 ? 1 : -1);
122   }
123 
124   if (stream->get_direction() % 2 != 0) {
125     if (target != entity_moved->get_xy()) {
126       // Adjust the speed to the diagonal movement.
127       delay = (uint32_t) (delay * std::sqrt(2));
128     }
129   }
130 
131   const SpritePtr& sprite = stream->get_sprite();
132   if (sprite != nullptr &&
133       sprite->get_nb_frames() > 1 &&
134       delay == sprite->get_frame_delay() &&
135       sprite->get_next_frame_date() >= System::now() &&
136       next_move_date < sprite->get_next_frame_date()) {
137     // The stream and its sprite happen to have exactly the same speed:
138     // adjust the delay to synchronize them perfectly
139     // to avoid flickering.
140     next_move_date = sprite->get_next_frame_date();
141   }
142 }
143 
144 /**
145  * \brief Called repeatedly by the main loop.
146  * Updates the effect of the stream on the entity.
147  */
update()148 void StreamAction::update() {
149 
150   // If the action is already disabled, do nothing.
151   if (!is_active()) {
152     return;
153   }
154 
155   // First check that the stream and the entity still exist
156   // and are enabled.
157   if (stream->is_being_removed()) {
158     stream = nullptr;
159     active = false;
160     return;
161   }
162 
163   if (!stream->is_enabled()) {
164     active = false;
165     return;
166   }
167 
168   if (entity_moved->is_being_removed()) {
169     entity_moved = nullptr;
170     active = false;
171     return;
172   }
173 
174   if (!entity_moved->is_enabled()) {
175     active = false;
176     return;
177   }
178 
179   if (entity_moved->get_layer() != stream->get_layer()) {
180     // The entity's layer has changed: stop influencing its position.
181     active = false;
182     return;
183   }
184 
185   // Stop the stream action if the entity escapes a non-blocking stream.
186   const Point& ground_point = entity_moved->get_ground_point();
187   if (
188       stream->get_allow_movement() &&
189       !stream->overlaps(ground_point)  // We are no longer on the stream.
190   ) {
191     // Blocking streams are more special.
192     // The hero cannot escape them so we don't need this.
193     // Also, diagonal blocking streams continue
194     // to move the entity even when it does not overlap anymore.
195     // This is needed to have precise
196     // exact diagonal movements of 16 pixels in stream mazes.
197 
198     if (entity_moved->get_distance(target) > 12) {
199       // This last test is to avoid stopping a stream when being close to the target.
200       // Indeed, in the last pixels before the target, the entity's ground
201       // point is no longer on the stream. We continue anyway until the target.
202       active = false;
203       return;
204     }
205   }
206 
207   if (is_suspended()) {
208     return;
209   }
210 
211   if (stream->get_speed() <= 0) {
212     return;
213   }
214 
215   // Update the position.
216   recompute_movement();
217   while (
218       System::now() >= next_move_date &&
219       is_active()
220   ) {
221     next_move_date += delay;
222 
223     int dx = 0;
224     int dy = 0;
225     if (target.x > entity_moved->get_x()) {
226       dx = 1;
227     }
228     else if (target.x < entity_moved->get_x()) {
229       dx = -1;
230     }
231     if (target.y > entity_moved->get_y()) {
232       dy = 1;
233     }
234     else if (target.y < entity_moved->get_y()) {
235       dy = -1;
236     }
237 
238     if (test_obstacles(dx, dy)) {
239       bool collision = true;
240       // Collision with an obstacle: try only X or only Y.
241       if (dx != 0 && dy != 0) {
242         if (!test_obstacles(dx, 0)) {
243           dy = 0;
244           collision = false;
245         }
246         else if (!test_obstacles(0, dy)) {
247           dx = 0;
248           collision = false;
249         }
250       }
251 
252       if (collision) {
253         if (!stream->get_allow_movement()) {
254           // Stop the stream if it was a blocking one.
255           active = false;
256         }
257         break;
258       }
259     }
260 
261     Point new_xy = entity_moved->get_xy() + Point(dx, dy);
262 
263     // See if the entity has come outside the stream,
264     // in other words, if the movement is finished.
265     if (new_xy == target) {
266       // The target point is reached: stop the stream.
267       active = false;
268     }
269     entity_moved->set_xy(new_xy);
270     entity_moved->notify_position_changed();
271     recompute_movement();
272   }
273 }
274 
275 /**
276  * \brief Returns whether the entity moved has finished to follow the stream.
277  * \return \c true if the target point is reached.
278  */
has_reached_target() const279 bool StreamAction::has_reached_target() const {
280 
281   return entity_moved->get_xy() == target;
282 }
283 
284 /**
285  * \brief Returns whether this stream action is currently suspended.
286  * \return \c true if the stream action is suspended.
287  */
is_suspended() const288 bool StreamAction::is_suspended() const {
289   return suspended;
290 }
291 
292 /**
293  * \brief Suspends or resumes this stream action.
294  * \param suspended \c true to suspend, \c false to resume.
295  */
set_suspended(bool suspended)296 void StreamAction::set_suspended(bool suspended) {
297 
298   this->suspended = suspended;
299   if (suspended) {
300     when_suspended = System::now();
301   }
302   else {
303     if (when_suspended != 0) {
304       next_move_date += System::now() - when_suspended;
305     }
306   }
307 }
308 
309 /**
310  * \brief Returns whether an obstacle blocks a candidate move.
311  * \param dx X component of the translation to test.
312  * \param dy Y component of the translation to test.
313  * \return \c true if the entity cannot currently move of dx,dy because of an
314  * obstacle.
315  */
test_obstacles(int dx,int dy)316 bool StreamAction::test_obstacles(int dx, int dy) {
317 
318   if (entity_moved->get_movement() != nullptr &&
319       entity_moved->get_movement()->are_obstacles_ignored()) {
320     // This entity currently ignores obstacles.
321     return false;
322   }
323 
324   Rectangle collision_box = entity_moved->get_bounding_box();
325   collision_box.add_xy(dx, dy);
326 
327   Map& map = entity_moved->get_map();
328   return map.test_collision_with_obstacles(
329       entity_moved->get_layer(),
330       collision_box,
331       *entity_moved
332   );
333 
334 }
335 
336 }
337 
338