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