diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/systems/pondering.cpp | 1088 | ||||
-rw-r--r-- | src/systems/pondering.h | 40 |
2 files changed, 533 insertions, 595 deletions
diff --git a/src/systems/pondering.cpp b/src/systems/pondering.cpp index ed14772..7b3ab2d 100644 --- a/src/systems/pondering.cpp +++ b/src/systems/pondering.cpp | |||
@@ -96,15 +96,6 @@ void PonderingSystem::tickBody( | |||
96 | return; | 96 | return; |
97 | } | 97 | } |
98 | 98 | ||
99 | auto& realizable = game_.getEntityManager(). | ||
100 | getComponent<RealizableComponent>( | ||
101 | game_.getSystemManager().getSystem<RealizingSystem>().getSingleton()); | ||
102 | |||
103 | id_type mapEntity = realizable.activeMap; | ||
104 | |||
105 | auto& mappable = game_.getEntityManager(). | ||
106 | getComponent<MappableComponent>(mapEntity); | ||
107 | |||
108 | auto& transformable = game_.getEntityManager(). | 99 | auto& transformable = game_.getEntityManager(). |
109 | getComponent<TransformableComponent>(entity); | 100 | getComponent<TransformableComponent>(entity); |
110 | 101 | ||
@@ -121,14 +112,9 @@ void PonderingSystem::tickBody( | |||
121 | } | 112 | } |
122 | } | 113 | } |
123 | 114 | ||
124 | const double oldX = transformable.x; | 115 | // Move |
125 | const double oldY = transformable.y; | 116 | double newX = transformable.x; |
126 | const double oldRight = oldX + transformable.w; | 117 | double newY = transformable.y; |
127 | const double oldBottom = oldY + transformable.h; | ||
128 | |||
129 | CollisionResult result; | ||
130 | result.newX = transformable.x; | ||
131 | result.newY = transformable.y; | ||
132 | 118 | ||
133 | if (!ponderable.frozen) | 119 | if (!ponderable.frozen) |
134 | { | 120 | { |
@@ -137,692 +123,626 @@ void PonderingSystem::tickBody( | |||
137 | auto& ferryTrans = game_.getEntityManager(). | 123 | auto& ferryTrans = game_.getEntityManager(). |
138 | getComponent<TransformableComponent>(ponderable.ferry); | 124 | getComponent<TransformableComponent>(ponderable.ferry); |
139 | 125 | ||
140 | result.newX = ferryTrans.x + ponderable.relX; | 126 | newX = ferryTrans.x + ponderable.relX; |
141 | result.newY = ferryTrans.y + ponderable.relY; | 127 | newY = ferryTrans.y + ponderable.relY; |
142 | } | 128 | } |
143 | 129 | ||
144 | result.newX += ponderable.velX * dt; | 130 | newX += ponderable.velX * dt; |
145 | result.newY += ponderable.velY * dt; | 131 | newY += ponderable.velY * dt; |
146 | } | 132 | } |
147 | 133 | ||
148 | bool oldGrounded = ponderable.grounded; | 134 | CollisionResult result = |
149 | ponderable.grounded = false; | 135 | moveBody( |
136 | entity, | ||
137 | newX, | ||
138 | newY); | ||
150 | 139 | ||
151 | if (ponderable.collidable) | 140 | // Perform cleanup for orientable entites |
141 | bool groundedChanged = (ponderable.grounded != result.grounded); | ||
142 | ponderable.grounded = result.grounded; | ||
143 | |||
144 | if (game_.getEntityManager().hasComponent<OrientableComponent>(entity)) | ||
152 | { | 145 | { |
153 | // Find horizontal collisions. | 146 | auto& orientable = game_.getEntityManager(). |
154 | if (result.newX < oldX) | 147 | getComponent<OrientableComponent>(entity); |
155 | { | ||
156 | bool boundaryCollision = false; | ||
157 | auto it = mappable.leftBoundaries.lower_bound(oldX); | ||
158 | 148 | ||
159 | // Find the axis distance of the closest environmental boundary. | 149 | // Handle changes in groundedness |
160 | for (; | 150 | if (groundedChanged) |
161 | (it != std::end(mappable.leftBoundaries)) && | 151 | { |
162 | (it->first >= result.newX); | 152 | if (ponderable.grounded) |
163 | it++) | ||
164 | { | 153 | { |
165 | // Check that the boundary is in range for the other axis. | 154 | game_.getSystemManager().getSystem<OrientingSystem>().land(entity); |
166 | if ((oldBottom > it->second.lower) && (oldY < it->second.upper)) | 155 | } else { |
167 | { | 156 | game_.getSystemManager(). |
168 | // We have a collision! | 157 | getSystem<OrientingSystem>().startFalling(entity); |
169 | boundaryCollision = true; | ||
170 | |||
171 | break; | ||
172 | } | ||
173 | } | 158 | } |
159 | } | ||
174 | 160 | ||
175 | // Find a list of potential colliders, sorted so that the closest is | 161 | // Complete dropping, if necessary |
176 | // first. | 162 | if (orientable.getDropState() == OrientableComponent::DropState::active) |
177 | std::vector<id_type> colliders; | 163 | { |
164 | orientable.setDropState(OrientableComponent::DropState::none); | ||
165 | } | ||
166 | } | ||
178 | 167 | ||
179 | for (id_type collider : entities) | 168 | // Ferry or unferry as necessary |
180 | { | 169 | if ((ponderable.type == PonderableComponent::Type::freefalling) && |
181 | // Can't collide with self. | 170 | groundedChanged) |
182 | if (collider == entity) | 171 | { |
183 | { | 172 | if (ponderable.grounded && |
184 | continue; | 173 | game_.getEntityManager(). |
185 | } | 174 | hasComponent<PonderableComponent>(result.groundEntity)) |
175 | { | ||
176 | // The body is now being ferried | ||
177 | auto& ferryPonder = game_.getEntityManager(). | ||
178 | getComponent<PonderableComponent>(result.groundEntity); | ||
179 | |||
180 | ponderable.ferried = true; | ||
181 | ponderable.ferry = result.groundEntity; | ||
186 | 182 | ||
187 | auto& colliderPonder = game_.getEntityManager(). | 183 | ferryPonder.passengers.insert(entity); |
188 | getComponent<PonderableComponent>(collider); | 184 | } else if (ponderable.ferried) |
185 | { | ||
186 | // The body is no longer being ferried | ||
187 | unferry(entity); | ||
188 | } | ||
189 | } | ||
189 | 190 | ||
190 | // Only check objects that are active and collidable. | 191 | // Update a ferry passenger's relative position |
191 | if (!colliderPonder.active || !colliderPonder.collidable) | 192 | if (ponderable.ferried) |
192 | { | 193 | { |
193 | continue; | 194 | auto& ferryTrans = game_.getEntityManager(). |
194 | } | 195 | getComponent<TransformableComponent>(ponderable.ferry); |
195 | 196 | ||
196 | auto& colliderTrans = game_.getEntityManager(). | 197 | ponderable.relX = transformable.x - ferryTrans.x; |
197 | getComponent<TransformableComponent>(collider); | 198 | ponderable.relY = transformable.y - ferryTrans.y; |
198 | 199 | } | |
199 | // Check if the entity would move into the potential collider, | ||
200 | if ((colliderTrans.x + colliderTrans.w > result.newX) && | ||
201 | // that it wasn't already colliding, | ||
202 | (colliderTrans.x + colliderTrans.w <= oldX) && | ||
203 | // that the position on the other axis is in range, | ||
204 | (colliderTrans.y + colliderTrans.h > oldY) && | ||
205 | (colliderTrans.y < oldBottom) && | ||
206 | // and that the collider is not farther away than the environmental | ||
207 | // boundary. | ||
208 | (!boundaryCollision || | ||
209 | (colliderTrans.x + colliderTrans.w >= it->first))) | ||
210 | { | ||
211 | colliders.push_back(collider); | ||
212 | } | ||
213 | } | ||
214 | 200 | ||
215 | std::sort( | 201 | // Handle ferry passengers |
216 | std::begin(colliders), | 202 | std::set<id_type> passengers = ponderable.passengers; |
217 | std::end(colliders), | ||
218 | [&] (id_type left, id_type right) { | ||
219 | auto& leftTrans = game_.getEntityManager(). | ||
220 | getComponent<TransformableComponent>(left); | ||
221 | 203 | ||
222 | auto& rightTrans = game_.getEntityManager(). | 204 | for (id_type passenger : passengers) |
223 | getComponent<TransformableComponent>(right); | 205 | { |
206 | tickBody( | ||
207 | passenger, | ||
208 | dt, | ||
209 | entities); | ||
210 | } | ||
224 | 211 | ||
225 | return (rightTrans.x < leftTrans.x); | 212 | // Move to an adjacent map, if necessary |
226 | }); | 213 | if (result.adjacentlyWarping) |
214 | { | ||
215 | double warpX = result.newX; | ||
216 | double warpY = result.newY; | ||
227 | 217 | ||
228 | for (id_type collider : colliders) | 218 | switch (result.adjWarpDir) |
219 | { | ||
220 | case Direction::left: | ||
229 | { | 221 | { |
230 | auto& colliderTrans = game_.getEntityManager(). | 222 | warpX = GAME_WIDTH + WALL_GAP - transformable.w; |
231 | getComponent<TransformableComponent>(collider); | ||
232 | |||
233 | // Check if the entity would still move into the potential collider. | ||
234 | if (colliderTrans.x + colliderTrans.w <= result.newX) | ||
235 | { | ||
236 | break; | ||
237 | } | ||
238 | 223 | ||
239 | auto& colliderPonder = game_.getEntityManager(). | 224 | break; |
240 | getComponent<PonderableComponent>(collider); | 225 | } |
241 | 226 | ||
242 | processCollision( | 227 | case Direction::right: |
243 | entity, | 228 | { |
244 | collider, | 229 | warpX = -WALL_GAP; |
245 | Direction::left, | ||
246 | colliderPonder.colliderType, | ||
247 | colliderTrans.x + colliderTrans.w, | ||
248 | colliderTrans.y, | ||
249 | colliderTrans.y + colliderTrans.h, | ||
250 | result); | ||
251 | 230 | ||
252 | if (result.stopProcessing) | 231 | break; |
253 | { | ||
254 | break; | ||
255 | } | ||
256 | } | 232 | } |
257 | 233 | ||
258 | // If movement hasn't been stopped by an intermediary object, and | 234 | case Direction::up: |
259 | // collision checking hasn't been stopped, process the environmental | ||
260 | // boundaries closest to the entity. | ||
261 | if (!result.stopProcessing && !result.touchedWall && boundaryCollision) | ||
262 | { | 235 | { |
263 | double boundaryAxis = it->first; | 236 | warpY = MAP_HEIGHT * TILE_HEIGHT - transformable.h; |
264 | 237 | ||
265 | for (; | 238 | break; |
266 | (it != std::end(mappable.leftBoundaries)) && | ||
267 | (it->first == boundaryAxis); | ||
268 | it++) | ||
269 | { | ||
270 | if ((oldBottom > it->second.lower) && (oldY < it->second.upper)) | ||
271 | { | ||
272 | processCollision( | ||
273 | entity, | ||
274 | mapEntity, | ||
275 | Direction::left, | ||
276 | it->second.type, | ||
277 | it->first, | ||
278 | it->second.lower, | ||
279 | it->second.upper, | ||
280 | result); | ||
281 | |||
282 | if (result.stopProcessing) | ||
283 | { | ||
284 | break; | ||
285 | } | ||
286 | } | ||
287 | } | ||
288 | } | 239 | } |
289 | } else if (result.newX > oldX) | ||
290 | { | ||
291 | bool boundaryCollision = false; | ||
292 | auto it = mappable.rightBoundaries.lower_bound(oldRight); | ||
293 | 240 | ||
294 | // Find the axis distance of the closest environmental boundary. | 241 | case Direction::down: |
295 | for (; | ||
296 | (it != std::end(mappable.rightBoundaries)) | ||
297 | && (it->first <= (result.newX + transformable.w)); | ||
298 | it++) | ||
299 | { | 242 | { |
300 | // Check that the boundary is in range for the other axis. | 243 | warpY = -WALL_GAP; |
301 | if ((oldBottom > it->second.lower) && (oldY < it->second.upper)) | ||
302 | { | ||
303 | // We have a collision! | ||
304 | boundaryCollision = true; | ||
305 | 244 | ||
306 | break; | 245 | break; |
307 | } | ||
308 | } | 246 | } |
247 | } | ||
309 | 248 | ||
310 | // Find a list of potential colliders, sorted so that the closest is | 249 | game_.getSystemManager().getSystem<PlayingSystem>(). |
311 | // first. | 250 | changeMap( |
312 | std::vector<id_type> colliders; | 251 | entity, |
313 | 252 | result.adjWarpMapId, | |
314 | for (id_type collider : entities) | 253 | warpX, |
315 | { | 254 | warpY); |
316 | // Can't collide with self. | 255 | } |
317 | if (collider == entity) | 256 | } |
318 | { | ||
319 | continue; | ||
320 | } | ||
321 | 257 | ||
322 | auto& colliderPonder = game_.getEntityManager(). | 258 | CollisionResult PonderingSystem::moveBody( |
323 | getComponent<PonderableComponent>(collider); | 259 | id_type entity, |
260 | double x, | ||
261 | double y) | ||
262 | { | ||
263 | auto& ponderable = game_.getEntityManager(). | ||
264 | getComponent<PonderableComponent>(entity); | ||
324 | 265 | ||
325 | // Only check objects that are active and collidable. | 266 | auto& transformable = game_.getEntityManager(). |
326 | if (!colliderPonder.active || !colliderPonder.collidable) | 267 | getComponent<TransformableComponent>(entity); |
327 | { | ||
328 | continue; | ||
329 | } | ||
330 | 268 | ||
331 | auto& colliderTrans = game_.getEntityManager(). | 269 | const double oldX = transformable.x; |
332 | getComponent<TransformableComponent>(collider); | 270 | const double oldY = transformable.y; |
333 | 271 | const double oldRight = oldX + transformable.w; | |
334 | // Check if the entity would move into the potential collider, | 272 | const double oldBottom = oldY + transformable.h; |
335 | if ((colliderTrans.x < result.newX + transformable.w) && | ||
336 | // that it wasn't already colliding, | ||
337 | (colliderTrans.x >= oldRight) && | ||
338 | // that the position on the other axis is in range, | ||
339 | (colliderTrans.y + colliderTrans.h > oldY) && | ||
340 | (colliderTrans.y < oldBottom) && | ||
341 | // and that the collider is not farther away than the environmental | ||
342 | // boundary. | ||
343 | (!boundaryCollision || (colliderTrans.x <= it->first))) | ||
344 | { | ||
345 | colliders.push_back(collider); | ||
346 | } | ||
347 | } | ||
348 | 273 | ||
349 | std::sort( | 274 | CollisionResult result; |
350 | std::begin(colliders), | ||
351 | std::end(colliders), | ||
352 | [&] (id_type left, id_type right) { | ||
353 | auto& leftTrans = game_.getEntityManager(). | ||
354 | getComponent<TransformableComponent>(left); | ||
355 | 275 | ||
356 | auto& rightTrans = game_.getEntityManager(). | 276 | if (ponderable.collidable) |
357 | getComponent<TransformableComponent>(right); | 277 | { |
278 | result = detectCollisions(entity, x, y); | ||
279 | } else { | ||
280 | result.newX = x; | ||
281 | result.newY = y; | ||
282 | } | ||
358 | 283 | ||
359 | return (leftTrans.x < rightTrans.x); | 284 | // Move |
360 | }); | 285 | if (!ponderable.frozen) |
286 | { | ||
287 | transformable.x = result.newX; | ||
288 | transformable.y = result.newY; | ||
289 | } | ||
361 | 290 | ||
362 | for (id_type collider : colliders) | 291 | return result; |
363 | { | 292 | } |
364 | auto& colliderTrans = game_.getEntityManager(). | ||
365 | getComponent<TransformableComponent>(collider); | ||
366 | 293 | ||
367 | // Check if the entity would still move into the potential collider. | 294 | namespace CollisionParams { |
368 | if (colliderTrans.x >= result.newX + transformable.w) | ||
369 | { | ||
370 | break; | ||
371 | } | ||
372 | 295 | ||
373 | auto& colliderPonder = game_.getEntityManager(). | 296 | template <typename HorizVert> |
374 | getComponent<PonderableComponent>(collider); | 297 | class Desc : public HorizVert { |
298 | public: | ||
375 | 299 | ||
376 | processCollision( | 300 | inline static bool AtLeastInAxisSweep( |
377 | entity, | 301 | double boundaryAxis, |
378 | collider, | 302 | double entityAxis) |
379 | Direction::right, | 303 | { |
380 | colliderPonder.colliderType, | 304 | return (boundaryAxis >= entityAxis); |
381 | colliderTrans.x, | 305 | } |
382 | colliderTrans.y, | ||
383 | colliderTrans.y + colliderTrans.h, | ||
384 | result); | ||
385 | 306 | ||
386 | if (result.stopProcessing) | 307 | inline static bool IsPastAxis( |
387 | { | 308 | double colliderAxis, |
388 | break; | 309 | double entityAxis) |
389 | } | 310 | { |
390 | } | 311 | return (colliderAxis > entityAxis); |
312 | } | ||
391 | 313 | ||
392 | // If movement hasn't been stopped by an intermediary object, and | 314 | inline static double OldAxis(const TransformableComponent& transformable) |
393 | // collision checking hasn't been stopped, process the environmental | 315 | { |
394 | // boundaries closest to the entity. | 316 | return HorizVert::AxisOldLower(transformable); |
395 | if (!result.stopProcessing && !result.touchedWall && boundaryCollision) | 317 | } |
396 | { | ||
397 | double boundaryAxis = it->first; | ||
398 | 318 | ||
399 | for (; | 319 | inline static double NewAxis( |
400 | (it != std::end(mappable.rightBoundaries)) && | 320 | const CollisionResult& result, |
401 | (it->first == boundaryAxis); | 321 | const TransformableComponent&) |
402 | it++) | 322 | { |
403 | { | 323 | return HorizVert::AxisNewLower(result); |
404 | if ((oldBottom > it->second.lower) && (oldY < it->second.upper)) | ||
405 | { | ||
406 | processCollision( | ||
407 | entity, | ||
408 | mapEntity, | ||
409 | Direction::right, | ||
410 | it->second.type, | ||
411 | it->first, | ||
412 | it->second.lower, | ||
413 | it->second.upper, | ||
414 | result); | ||
415 | |||
416 | if (result.stopProcessing) | ||
417 | { | ||
418 | break; | ||
419 | } | ||
420 | } | ||
421 | } | ||
422 | } | ||
423 | } | 324 | } |
424 | } | ||
425 | 325 | ||
426 | // Find vertical collisions | 326 | inline static double ObjectAxis(const TransformableComponent& transformable) |
427 | if (ponderable.collidable && !result.stopProcessing) | 327 | { |
428 | { | 328 | return HorizVert::AxisOldUpper(transformable); |
429 | result.touchedWall = false; | 329 | } |
430 | 330 | ||
431 | if (result.newY < oldY) | 331 | inline static bool Closer(double left, double right) |
432 | { | 332 | { |
433 | bool boundaryCollision = false; | 333 | return right < left; |
434 | auto it = mappable.upBoundaries.lower_bound(oldY); | 334 | } |
335 | }; | ||
435 | 336 | ||
436 | // Find the axis distance of the closest environmental boundary. | 337 | template <typename HorizVert> |
437 | for (; | 338 | class Asc : public HorizVert { |
438 | (it != std::end(mappable.upBoundaries)) && | 339 | public: |
439 | (it->first >= result.newY); | ||
440 | it++) | ||
441 | { | ||
442 | // Check that the boundary is in range for the other axis. | ||
443 | if ((result.newX + transformable.w > it->second.lower) && | ||
444 | (result.newX < it->second.upper)) | ||
445 | { | ||
446 | // We have a collision! | ||
447 | boundaryCollision = true; | ||
448 | 340 | ||
449 | break; | 341 | inline static bool AtLeastInAxisSweep( |
450 | } | 342 | double boundaryAxis, |
451 | } | 343 | double entityAxis) |
344 | { | ||
345 | return (boundaryAxis <= entityAxis); | ||
346 | } | ||
452 | 347 | ||
453 | // Find a list of potential colliders, sorted so that the closest is | 348 | inline static bool IsPastAxis( |
454 | // first. | 349 | double colliderAxis, |
455 | std::vector<id_type> colliders; | 350 | double entityAxis) |
351 | { | ||
352 | return (colliderAxis < entityAxis); | ||
353 | } | ||
456 | 354 | ||
457 | for (id_type collider : entities) | 355 | inline static double OldAxis(const TransformableComponent& transformable) |
458 | { | 356 | { |
459 | // Can't collide with self. | 357 | return HorizVert::AxisOldUpper(transformable); |
460 | if (collider == entity) | 358 | } |
461 | { | ||
462 | continue; | ||
463 | } | ||
464 | 359 | ||
465 | auto& colliderPonder = game_.getEntityManager(). | 360 | inline static double NewAxis( |
466 | getComponent<PonderableComponent>(collider); | 361 | const CollisionResult& result, |
362 | const TransformableComponent& transformable) | ||
363 | { | ||
364 | return HorizVert::AxisNewUpper(result, transformable); | ||
365 | } | ||
467 | 366 | ||
468 | // Only check objects that are active and collidable. | 367 | inline static double ObjectAxis(const TransformableComponent& transformable) |
469 | if (!colliderPonder.active || !colliderPonder.collidable) | 368 | { |
470 | { | 369 | return HorizVert::AxisOldLower(transformable); |
471 | continue; | 370 | } |
472 | } | ||
473 | 371 | ||
474 | auto& colliderTrans = game_.getEntityManager(). | 372 | inline static bool Closer(double left, double right) |
475 | getComponent<TransformableComponent>(collider); | 373 | { |
476 | 374 | return left < right; | |
477 | // Check if the entity would move into the potential collider, | 375 | } |
478 | if ((colliderTrans.y + colliderTrans.h > result.newY) && | 376 | }; |
479 | // that it wasn't already colliding, | ||
480 | (colliderTrans.y + colliderTrans.h <= oldY) && | ||
481 | // that the position on the other axis is in range, | ||
482 | (colliderTrans.x + colliderTrans.w > result.newX) && | ||
483 | (colliderTrans.x < result.newX + transformable.w) && | ||
484 | // and that the collider is not farther away than the environmental | ||
485 | // boundary. | ||
486 | (!boundaryCollision || | ||
487 | (colliderTrans.y + colliderTrans.h >= it->first))) | ||
488 | { | ||
489 | colliders.push_back(collider); | ||
490 | } | ||
491 | } | ||
492 | 377 | ||
493 | std::sort( | 378 | class Horizontal { |
494 | std::begin(colliders), | 379 | public: |
495 | std::end(colliders), | ||
496 | [&] (id_type left, id_type right) { | ||
497 | auto& leftTrans = game_.getEntityManager(). | ||
498 | getComponent<TransformableComponent>(left); | ||
499 | 380 | ||
500 | auto& rightTrans = game_.getEntityManager(). | 381 | inline static double AxisOldLower( |
501 | getComponent<TransformableComponent>(right); | 382 | const TransformableComponent& transformable) |
383 | { | ||
384 | return transformable.x; | ||
385 | } | ||
502 | 386 | ||
503 | return (rightTrans.y < leftTrans.y); | 387 | inline static double AxisOldUpper( |
504 | }); | 388 | const TransformableComponent& transformable) |
389 | { | ||
390 | return transformable.x + transformable.w; | ||
391 | } | ||
505 | 392 | ||
506 | for (id_type collider : colliders) | 393 | inline static double AxisNewLower(const CollisionResult& result) |
507 | { | 394 | { |
508 | auto& colliderTrans = game_.getEntityManager(). | 395 | return result.newX; |
509 | getComponent<TransformableComponent>(collider); | 396 | } |
510 | 397 | ||
511 | // Check if the entity would still move into the potential collider. | 398 | inline static double AxisNewUpper( |
512 | if (colliderTrans.y + colliderTrans.h <= result.newY) | 399 | const CollisionResult& result, |
513 | { | 400 | const TransformableComponent& transformable) |
514 | break; | 401 | { |
515 | } | 402 | return result.newX + transformable.w; |
403 | } | ||
516 | 404 | ||
517 | auto& colliderPonder = game_.getEntityManager(). | 405 | inline static double NonAxisOldLower( |
518 | getComponent<PonderableComponent>(collider); | 406 | const TransformableComponent& transformable) |
407 | { | ||
408 | return transformable.y; | ||
409 | } | ||
519 | 410 | ||
520 | processCollision( | 411 | inline static double NonAxisOldUpper( |
521 | entity, | 412 | const TransformableComponent& transformable) |
522 | collider, | 413 | { |
523 | Direction::up, | 414 | return transformable.y + transformable.h; |
524 | colliderPonder.colliderType, | 415 | } |
525 | colliderTrans.y + colliderTrans.h, | ||
526 | colliderTrans.x, | ||
527 | colliderTrans.x + colliderTrans.w, | ||
528 | result); | ||
529 | 416 | ||
530 | if (result.stopProcessing) | 417 | inline static double NonAxisNewLower( |
531 | { | 418 | const CollisionResult& result, |
532 | break; | 419 | const TransformableComponent& transformable) |
533 | } | 420 | { |
534 | } | 421 | return result.newY; |
422 | } | ||
535 | 423 | ||
536 | // If movement hasn't been stopped by an intermediary object, and | 424 | inline static double NonAxisNewUpper( |
537 | // collision checking hasn't been stopped, process the environmental | 425 | const CollisionResult& result, |
538 | // boundaries closest to the entity. | 426 | const TransformableComponent& transformable) |
539 | if (!result.stopProcessing && !result.touchedWall && boundaryCollision) | 427 | { |
540 | { | 428 | return result.newY + transformable.h; |
541 | double boundaryAxis = it->first; | 429 | } |
430 | }; | ||
542 | 431 | ||
543 | for (; | 432 | class Vertical { |
544 | (it != std::end(mappable.upBoundaries)) && | 433 | public: |
545 | (it->first == boundaryAxis); | 434 | |
546 | it++) | 435 | inline static double AxisOldLower( |
547 | { | 436 | const TransformableComponent& transformable) |
548 | if ((result.newX + transformable.w > it->second.lower) && | ||
549 | (result.newX < it->second.upper)) | ||
550 | { | ||
551 | processCollision( | ||
552 | entity, | ||
553 | mapEntity, | ||
554 | Direction::up, | ||
555 | it->second.type, | ||
556 | it->first, | ||
557 | it->second.lower, | ||
558 | it->second.upper, | ||
559 | result); | ||
560 | |||
561 | if (result.stopProcessing) | ||
562 | { | ||
563 | break; | ||
564 | } | ||
565 | } | ||
566 | } | ||
567 | } | ||
568 | } else if (result.newY > oldY) | ||
569 | { | 437 | { |
570 | bool boundaryCollision = false; | 438 | return transformable.y; |
571 | auto it = mappable.downBoundaries.lower_bound(oldBottom); | 439 | } |
572 | 440 | ||
573 | // Find the axis distance of the closest environmental boundary. | 441 | inline static double AxisOldUpper( |
574 | for (; | 442 | const TransformableComponent& transformable) |
575 | (it != std::end(mappable.downBoundaries)) | 443 | { |
576 | && (it->first <= (result.newY + transformable.h)); | 444 | return transformable.y + transformable.h; |
577 | it++) | 445 | } |
578 | { | ||
579 | // Check that the boundary is in range for the other axis. | ||
580 | if ((result.newX + transformable.w > it->second.lower) && | ||
581 | (result.newX < it->second.upper)) | ||
582 | { | ||
583 | // We have a collision! | ||
584 | boundaryCollision = true; | ||
585 | 446 | ||
586 | break; | 447 | inline static double AxisNewLower(const CollisionResult& result) |
587 | } | 448 | { |
588 | } | 449 | return result.newY; |
450 | } | ||
589 | 451 | ||
590 | // Find a list of potential colliders, sorted so that the closest is | 452 | inline static double AxisNewUpper( |
591 | // first. | 453 | const CollisionResult& result, |
592 | std::vector<id_type> colliders; | 454 | const TransformableComponent& transformable) |
455 | { | ||
456 | return result.newY + transformable.h; | ||
457 | } | ||
593 | 458 | ||
594 | for (id_type collider : entities) | 459 | inline static double NonAxisOldLower( |
595 | { | 460 | const TransformableComponent& transformable) |
596 | // Can't collide with self. | 461 | { |
597 | if (collider == entity) | 462 | return transformable.x; |
598 | { | 463 | } |
599 | continue; | ||
600 | } | ||
601 | 464 | ||
602 | auto& colliderPonder = game_.getEntityManager(). | 465 | inline static double NonAxisOldUpper( |
603 | getComponent<PonderableComponent>(collider); | 466 | const TransformableComponent& transformable) |
467 | { | ||
468 | return transformable.x + transformable.w; | ||
469 | } | ||
604 | 470 | ||
605 | // Only check objects that are active and collidable. | 471 | inline static double NonAxisNewLower( |
606 | if (!colliderPonder.active || !colliderPonder.collidable) | 472 | const CollisionResult& result, |
607 | { | 473 | const TransformableComponent& transformable) |
608 | continue; | 474 | { |
609 | } | 475 | return result.newX; |
476 | } | ||
610 | 477 | ||
611 | auto& colliderTrans = game_.getEntityManager(). | 478 | inline static double NonAxisNewUpper( |
612 | getComponent<TransformableComponent>(collider); | 479 | const CollisionResult& result, |
613 | 480 | const TransformableComponent& transformable) | |
614 | // Check if the entity would move into the potential collider, | 481 | { |
615 | if ((colliderTrans.y < result.newY + transformable.h) && | 482 | return result.newX + transformable.w; |
616 | // that it wasn't already colliding, | 483 | } |
617 | (colliderTrans.y >= oldBottom) && | 484 | }; |
618 | // that the position on the other axis is in range, | ||
619 | (colliderTrans.x + colliderTrans.w > result.newX) && | ||
620 | (colliderTrans.x < result.newX + transformable.w) && | ||
621 | // and that the collider is not farther away than the environmental | ||
622 | // boundary. | ||
623 | (!boundaryCollision || (colliderTrans.y <= it->first))) | ||
624 | { | ||
625 | colliders.push_back(collider); | ||
626 | } | ||
627 | } | ||
628 | 485 | ||
629 | std::sort( | 486 | template <Direction dir, typename AscDesc> |
630 | std::begin(colliders), | 487 | class DetectCollisions : public AscDesc { |
631 | std::end(colliders), | 488 | public: |
632 | [&] (id_type left, id_type right) { | ||
633 | auto& leftTrans = game_.getEntityManager(). | ||
634 | getComponent<TransformableComponent>(left); | ||
635 | 489 | ||
636 | auto& rightTrans = game_.getEntityManager(). | 490 | static const Direction Dir = dir; |
637 | getComponent<TransformableComponent>(right); | 491 | }; |
638 | 492 | ||
639 | return (leftTrans.y < rightTrans.y); | 493 | class Left : public DetectCollisions<Direction::left, Desc<Horizontal>> { |
640 | }); | 494 | public: |
641 | 495 | ||
642 | for (id_type collider : colliders) | 496 | inline static const MappableComponent::desc_boundaries_type& MapBoundaries( |
643 | { | 497 | const MappableComponent& mappable) |
644 | auto& colliderTrans = game_.getEntityManager(). | 498 | { |
645 | getComponent<TransformableComponent>(collider); | 499 | return mappable.leftBoundaries; |
500 | } | ||
501 | }; | ||
646 | 502 | ||
647 | // Check if the entity would still move into the potential collider. | 503 | class Right : public DetectCollisions<Direction::right, Asc<Horizontal>> { |
648 | if (colliderTrans.y >= result.newY + transformable.h) | 504 | public: |
649 | { | ||
650 | break; | ||
651 | } | ||
652 | 505 | ||
653 | auto& colliderPonder = game_.getEntityManager(). | 506 | inline static const MappableComponent::asc_boundaries_type& MapBoundaries( |
654 | getComponent<PonderableComponent>(collider); | 507 | const MappableComponent& mappable) |
508 | { | ||
509 | return mappable.rightBoundaries; | ||
510 | } | ||
511 | }; | ||
655 | 512 | ||
656 | processCollision( | 513 | class Up : public DetectCollisions<Direction::up, Desc<Vertical>> { |
657 | entity, | 514 | public: |
658 | collider, | ||
659 | Direction::down, | ||
660 | colliderPonder.colliderType, | ||
661 | colliderTrans.y, | ||
662 | colliderTrans.x, | ||
663 | colliderTrans.x + colliderTrans.w, | ||
664 | result); | ||
665 | 515 | ||
666 | if (result.stopProcessing) | 516 | inline static const MappableComponent::desc_boundaries_type& MapBoundaries( |
667 | { | 517 | const MappableComponent& mappable) |
668 | break; | 518 | { |
669 | } | 519 | return mappable.upBoundaries; |
670 | } | 520 | } |
521 | }; | ||
671 | 522 | ||
672 | // If movement hasn't been stopped by an intermediary object, and | 523 | class Down : public DetectCollisions<Direction::down, Asc<Vertical>> { |
673 | // collision checking hasn't been stopped, process the environmental | 524 | public: |
674 | // boundaries closest to the entity. | ||
675 | if (!result.stopProcessing && !result.touchedWall && boundaryCollision) | ||
676 | { | ||
677 | double boundaryAxis = it->first; | ||
678 | 525 | ||
679 | for (; | 526 | inline static const MappableComponent::asc_boundaries_type& MapBoundaries( |
680 | (it != std::end(mappable.downBoundaries)) && | 527 | const MappableComponent& mappable) |
681 | (it->first == boundaryAxis); | 528 | { |
682 | it++) | 529 | return mappable.downBoundaries; |
683 | { | ||
684 | if ((result.newX + transformable.w > it->second.lower) && | ||
685 | (result.newX < it->second.upper)) | ||
686 | { | ||
687 | processCollision( | ||
688 | entity, | ||
689 | mapEntity, | ||
690 | Direction::down, | ||
691 | it->second.type, | ||
692 | it->first, | ||
693 | it->second.lower, | ||
694 | it->second.upper, | ||
695 | result); | ||
696 | |||
697 | if (result.stopProcessing) | ||
698 | { | ||
699 | break; | ||
700 | } | ||
701 | } | ||
702 | } | ||
703 | } | ||
704 | } | 530 | } |
705 | } | 531 | }; |
532 | }; | ||
706 | 533 | ||
707 | // Move | 534 | CollisionResult PonderingSystem::detectCollisions( |
708 | if (!ponderable.frozen) | 535 | id_type entity, |
536 | double x, | ||
537 | double y) | ||
538 | { | ||
539 | auto& transformable = game_.getEntityManager(). | ||
540 | getComponent<TransformableComponent>(entity); | ||
541 | |||
542 | CollisionResult result; | ||
543 | result.newX = x; | ||
544 | result.newY = transformable.y; | ||
545 | |||
546 | // Find horizontal collisions. | ||
547 | if (result.newX < transformable.x) | ||
709 | { | 548 | { |
710 | transformable.x = result.newX; | 549 | detectCollisionsInDirection<CollisionParams::Left>(entity, result); |
711 | transformable.y = result.newY; | 550 | } else if (result.newX > transformable.x) |
551 | { | ||
552 | detectCollisionsInDirection<CollisionParams::Right>(entity, result); | ||
712 | } | 553 | } |
713 | 554 | ||
714 | // Perform cleanup for orientable entites | 555 | // Find vertical collisions |
715 | if (game_.getEntityManager().hasComponent<OrientableComponent>(entity)) | 556 | if (!result.stopProcessing) |
716 | { | 557 | { |
717 | auto& orientable = game_.getEntityManager(). | 558 | result.newY = y; |
718 | getComponent<OrientableComponent>(entity); | 559 | result.touchedWall = false; |
719 | 560 | ||
720 | // Handle changes in groundedness | 561 | if (result.newY < transformable.y) |
721 | if (ponderable.grounded != oldGrounded) | ||
722 | { | 562 | { |
723 | if (ponderable.grounded) | 563 | detectCollisionsInDirection<CollisionParams::Up>(entity, result); |
724 | { | 564 | } else if (result.newY > transformable.y) |
725 | game_.getSystemManager().getSystem<OrientingSystem>().land(entity); | 565 | { |
726 | } else { | 566 | detectCollisionsInDirection<CollisionParams::Down>(entity, result); |
727 | game_.getSystemManager(). | ||
728 | getSystem<OrientingSystem>().startFalling(entity); | ||
729 | } | ||
730 | } | 567 | } |
568 | } | ||
731 | 569 | ||
732 | // Complete dropping, if necessary | 570 | return result; |
733 | if (orientable.getDropState() == OrientableComponent::DropState::active) | 571 | } |
572 | |||
573 | template <typename Param> | ||
574 | void PonderingSystem::detectCollisionsInDirection( | ||
575 | id_type entity, | ||
576 | CollisionResult& result) | ||
577 | { | ||
578 | // Get map data. | ||
579 | auto& realizable = game_.getEntityManager(). | ||
580 | getComponent<RealizableComponent>( | ||
581 | game_.getSystemManager().getSystem<RealizingSystem>().getSingleton()); | ||
582 | |||
583 | id_type mapEntity = realizable.activeMap; | ||
584 | |||
585 | auto& mappable = game_.getEntityManager(). | ||
586 | getComponent<MappableComponent>(mapEntity); | ||
587 | |||
588 | // Get old location. | ||
589 | auto& transformable = game_.getEntityManager(). | ||
590 | getComponent<TransformableComponent>(entity); | ||
591 | |||
592 | bool boundaryCollision = false; | ||
593 | |||
594 | auto boundaries = Param::MapBoundaries(mappable); | ||
595 | auto it = boundaries.lower_bound(Param::OldAxis(transformable)); | ||
596 | |||
597 | // Find the axis distance of the closest environmental boundary. | ||
598 | for (; | ||
599 | (it != std::end(boundaries)) && | ||
600 | Param::AtLeastInAxisSweep( | ||
601 | it->first, | ||
602 | Param::NewAxis(result, transformable)); | ||
603 | it++) | ||
604 | { | ||
605 | // Check that the boundary is in range for the other axis. | ||
606 | if ((Param::NonAxisNewUpper(result, transformable) > it->second.lower) && | ||
607 | (Param::NonAxisNewLower(result, transformable) < it->second.upper)) | ||
734 | { | 608 | { |
735 | orientable.setDropState(OrientableComponent::DropState::none); | 609 | // We have a collision! |
610 | boundaryCollision = true; | ||
611 | |||
612 | break; | ||
736 | } | 613 | } |
737 | } | 614 | } |
738 | 615 | ||
739 | // Ferry or unferry as necessary | 616 | // Find a list of potential colliders, sorted so that the closest is |
740 | if ((ponderable.type == PonderableComponent::Type::freefalling) && | 617 | // first. |
741 | (ponderable.grounded != oldGrounded)) | 618 | std::vector<id_type> colliders; |
619 | |||
620 | auto entities = game_.getEntityManager().getEntitiesWithComponents< | ||
621 | PonderableComponent, | ||
622 | TransformableComponent>(); | ||
623 | |||
624 | for (id_type collider : entities) | ||
742 | { | 625 | { |
743 | if (ponderable.grounded && | 626 | // Can't collide with self. |
744 | game_.getEntityManager(). | 627 | if (collider == entity) |
745 | hasComponent<PonderableComponent>(result.groundEntity)) | ||
746 | { | 628 | { |
747 | // The body is now being ferried | 629 | continue; |
748 | auto& ferryPonder = game_.getEntityManager(). | 630 | } |
749 | getComponent<PonderableComponent>(result.groundEntity); | ||
750 | 631 | ||
751 | ponderable.ferried = true; | 632 | auto& colliderPonder = game_.getEntityManager(). |
752 | ponderable.ferry = result.groundEntity; | 633 | getComponent<PonderableComponent>(collider); |
753 | 634 | ||
754 | ferryPonder.passengers.insert(entity); | 635 | // Only check objects that are active and collidable. |
755 | } else if (ponderable.ferried) | 636 | if (!colliderPonder.active || !colliderPonder.collidable) |
756 | { | 637 | { |
757 | // The body is no longer being ferried | 638 | continue; |
758 | unferry(entity); | ||
759 | } | 639 | } |
760 | } | ||
761 | |||
762 | // Update a ferry passenger's relative position | ||
763 | if (ponderable.ferried) | ||
764 | { | ||
765 | auto& ferryTrans = game_.getEntityManager(). | ||
766 | getComponent<TransformableComponent>(ponderable.ferry); | ||
767 | 640 | ||
768 | ponderable.relX = transformable.x - ferryTrans.x; | 641 | auto& colliderTrans = game_.getEntityManager(). |
769 | ponderable.relY = transformable.y - ferryTrans.y; | 642 | getComponent<TransformableComponent>(collider); |
643 | |||
644 | // Check if the entity would move into the potential collider, | ||
645 | if (Param::IsPastAxis( | ||
646 | Param::ObjectAxis(colliderTrans), | ||
647 | Param::NewAxis(result, transformable)) && | ||
648 | // that it wasn't already colliding, | ||
649 | !Param::IsPastAxis( | ||
650 | Param::ObjectAxis(colliderTrans), | ||
651 | Param::OldAxis(transformable)) && | ||
652 | // that the position on the non-axis is in range, | ||
653 | (Param::NonAxisOldUpper(colliderTrans) > | ||
654 | Param::NonAxisNewLower(result, transformable)) && | ||
655 | (Param::NonAxisOldLower(colliderTrans) < | ||
656 | Param::NonAxisNewUpper(result, transformable)) && | ||
657 | // and that the collider is not farther away than the environmental | ||
658 | // boundary. | ||
659 | (!boundaryCollision || | ||
660 | Param::AtLeastInAxisSweep( | ||
661 | Param::ObjectAxis(colliderTrans), | ||
662 | it->first))) | ||
663 | { | ||
664 | colliders.push_back(collider); | ||
665 | } | ||
770 | } | 666 | } |
771 | 667 | ||
772 | // Handle ferry passengers | 668 | std::sort( |
773 | std::set<id_type> passengers = ponderable.passengers; | 669 | std::begin(colliders), |
670 | std::end(colliders), | ||
671 | [&] (id_type left, id_type right) { | ||
672 | auto& leftTrans = game_.getEntityManager(). | ||
673 | getComponent<TransformableComponent>(left); | ||
774 | 674 | ||
775 | for (id_type passenger : passengers) | 675 | auto& rightTrans = game_.getEntityManager(). |
776 | { | 676 | getComponent<TransformableComponent>(right); |
777 | tickBody( | ||
778 | passenger, | ||
779 | dt, | ||
780 | entities); | ||
781 | } | ||
782 | 677 | ||
783 | // Move to an adjacent map, if necessary | 678 | return Param::Closer( |
784 | if (result.adjacentlyWarping) | 679 | Param::ObjectAxis(leftTrans), |
680 | Param::ObjectAxis(rightTrans)); | ||
681 | }); | ||
682 | |||
683 | for (id_type collider : colliders) | ||
785 | { | 684 | { |
786 | double warpX = result.newX; | 685 | auto& colliderTrans = game_.getEntityManager(). |
787 | double warpY = result.newY; | 686 | getComponent<TransformableComponent>(collider); |
788 | 687 | ||
789 | switch (result.adjWarpDir) | 688 | // Check if the entity would still move into the potential collider. |
689 | if (!Param::IsPastAxis( | ||
690 | Param::ObjectAxis(colliderTrans), | ||
691 | Param::NewAxis(result, transformable))) | ||
790 | { | 692 | { |
791 | case Direction::left: | 693 | break; |
792 | { | 694 | } |
793 | warpX = GAME_WIDTH + WALL_GAP - transformable.w; | ||
794 | |||
795 | break; | ||
796 | } | ||
797 | |||
798 | case Direction::right: | ||
799 | { | ||
800 | warpX = -WALL_GAP; | ||
801 | 695 | ||
802 | break; | 696 | auto& colliderPonder = game_.getEntityManager(). |
803 | } | 697 | getComponent<PonderableComponent>(collider); |
804 | 698 | ||
805 | case Direction::up: | 699 | processCollision( |
806 | { | 700 | entity, |
807 | warpY = MAP_HEIGHT * TILE_HEIGHT - transformable.h; | 701 | collider, |
702 | Param::Dir, | ||
703 | colliderPonder.colliderType, | ||
704 | Param::ObjectAxis(colliderTrans), | ||
705 | Param::NonAxisOldLower(colliderTrans), | ||
706 | Param::NonAxisOldUpper(colliderTrans), | ||
707 | result); | ||
708 | |||
709 | if (result.stopProcessing) | ||
710 | { | ||
711 | break; | ||
712 | } | ||
713 | } | ||
808 | 714 | ||
809 | break; | 715 | // If movement hasn't been stopped by an intermediary object, and |
810 | } | 716 | // collision checking hasn't been stopped, process the environmental |
717 | // boundaries closest to the entity. | ||
718 | if (!result.stopProcessing && !result.touchedWall && boundaryCollision) | ||
719 | { | ||
720 | double boundaryAxis = it->first; | ||
811 | 721 | ||
812 | case Direction::down: | 722 | for (; |
723 | (it != std::end(boundaries)) && | ||
724 | (it->first == boundaryAxis); | ||
725 | it++) | ||
726 | { | ||
727 | if ((Param::NonAxisNewUpper(result, transformable) > it->second.lower) && | ||
728 | (Param::NonAxisNewLower(result, transformable) < it->second.upper)) | ||
813 | { | 729 | { |
814 | warpY = -WALL_GAP; | 730 | processCollision( |
731 | entity, | ||
732 | mapEntity, | ||
733 | Param::Dir, | ||
734 | it->second.type, | ||
735 | it->first, | ||
736 | it->second.lower, | ||
737 | it->second.upper, | ||
738 | result); | ||
815 | 739 | ||
816 | break; | 740 | if (result.stopProcessing) |
741 | { | ||
742 | break; | ||
743 | } | ||
817 | } | 744 | } |
818 | } | 745 | } |
819 | |||
820 | game_.getSystemManager().getSystem<PlayingSystem>(). | ||
821 | changeMap( | ||
822 | entity, | ||
823 | result.adjWarpMapId, | ||
824 | warpX, | ||
825 | warpY); | ||
826 | } | 746 | } |
827 | } | 747 | } |
828 | 748 | ||
@@ -1012,7 +932,7 @@ void PonderingSystem::processCollision( | |||
1012 | result.newY = axis - transformable.h; | 932 | result.newY = axis - transformable.h; |
1013 | result.groundEntity = collider; | 933 | result.groundEntity = collider; |
1014 | ponderable.velY = 0.0; | 934 | ponderable.velY = 0.0; |
1015 | ponderable.grounded = true; | 935 | result.grounded = true; |
1016 | 936 | ||
1017 | break; | 937 | break; |
1018 | } | 938 | } |
diff --git a/src/systems/pondering.h b/src/systems/pondering.h index eed0d32..abc6db2 100644 --- a/src/systems/pondering.h +++ b/src/systems/pondering.h | |||
@@ -5,6 +5,19 @@ | |||
5 | #include "components/ponderable.h" | 5 | #include "components/ponderable.h" |
6 | #include "direction.h" | 6 | #include "direction.h" |
7 | 7 | ||
8 | struct CollisionResult | ||
9 | { | ||
10 | double newX; | ||
11 | double newY; | ||
12 | bool stopProcessing = false; | ||
13 | bool touchedWall = false; | ||
14 | bool adjacentlyWarping = false; | ||
15 | Direction adjWarpDir; | ||
16 | size_t adjWarpMapId; | ||
17 | bool grounded = false; | ||
18 | EntityManager::id_type groundEntity; | ||
19 | }; | ||
20 | |||
8 | class PonderingSystem : public System { | 21 | class PonderingSystem : public System { |
9 | public: | 22 | public: |
10 | 23 | ||
@@ -34,23 +47,28 @@ public: | |||
34 | 47 | ||
35 | private: | 48 | private: |
36 | 49 | ||
37 | struct CollisionResult | 50 | |
38 | { | ||
39 | double newX; | ||
40 | double newY; | ||
41 | bool stopProcessing = false; | ||
42 | bool touchedWall = false; | ||
43 | bool adjacentlyWarping = false; | ||
44 | Direction adjWarpDir; | ||
45 | size_t adjWarpMapId; | ||
46 | id_type groundEntity; | ||
47 | }; | ||
48 | 51 | ||
49 | void tickBody( | 52 | void tickBody( |
50 | id_type entity, | 53 | id_type entity, |
51 | double dt, | 54 | double dt, |
52 | const std::set<id_type>& entities); | 55 | const std::set<id_type>& entities); |
53 | 56 | ||
57 | CollisionResult moveBody( | ||
58 | id_type entity, | ||
59 | double x, | ||
60 | double y); | ||
61 | |||
62 | CollisionResult detectCollisions( | ||
63 | id_type entity, | ||
64 | double x, | ||
65 | double y); | ||
66 | |||
67 | template <typename Param> | ||
68 | void detectCollisionsInDirection( | ||
69 | id_type entity, | ||
70 | CollisionResult& result); | ||
71 | |||
54 | void processCollision( | 72 | void processCollision( |
55 | id_type entity, | 73 | id_type entity, |
56 | id_type collider, | 74 | id_type collider, |