From 5b3d87d24b4c2c750d34d1e970254358cba087a4 Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Fri, 4 May 2018 11:16:02 -0400 Subject: Fixed behavior of uncollidable bodies The collidable flag, previously unused, now correctly disables collision detection when unset. This has the side effect of platforms being able to move through a dying player. It is undecided whether this behavior is wanted. --- src/components/ponderable.h | 3 +- src/systems/pondering.cpp | 880 ++++++++++++++++++++++---------------------- 2 files changed, 445 insertions(+), 438 deletions(-) diff --git a/src/components/ponderable.h b/src/components/ponderable.h index 6a01400..eff20e9 100644 --- a/src/components/ponderable.h +++ b/src/components/ponderable.h @@ -109,7 +109,8 @@ public: /** * If disabled, collision detection for this body will not be performed and - * other bodies will ignore it. + * other bodies will ignore it. Disabling this will cause applicable bodies to + * become ungrounded and unferried. */ bool collidable = true; diff --git a/src/systems/pondering.cpp b/src/systems/pondering.cpp index 0be3add..f5d3df2 100644 --- a/src/systems/pondering.cpp +++ b/src/systems/pondering.cpp @@ -148,272 +148,275 @@ void PonderingSystem::tickBody( bool oldGrounded = ponderable.grounded; ponderable.grounded = false; - // Find horizontal collisions. - if (result.newX < oldX) + if (ponderable.collidable) { - bool boundaryCollision = false; - auto it = mappable.leftBoundaries.lower_bound(oldX); - - // Find the axis distance of the closest environmental boundary. - for (; - (it != std::end(mappable.leftBoundaries)) && - (it->first >= result.newX); - it++) + // Find horizontal collisions. + if (result.newX < oldX) { - // Check that the boundary is in range for the other axis. - if ((oldBottom > it->second.lower) && (oldY < it->second.upper)) + bool boundaryCollision = false; + auto it = mappable.leftBoundaries.lower_bound(oldX); + + // Find the axis distance of the closest environmental boundary. + for (; + (it != std::end(mappable.leftBoundaries)) && + (it->first >= result.newX); + it++) { - // We have a collision! - boundaryCollision = true; + // Check that the boundary is in range for the other axis. + if ((oldBottom > it->second.lower) && (oldY < it->second.upper)) + { + // We have a collision! + boundaryCollision = true; - break; + break; + } } - } - // Find a list of potential colliders, sorted so that the closest is - // first. - std::vector colliders; + // Find a list of potential colliders, sorted so that the closest is + // first. + std::vector colliders; - for (id_type collider : entities) - { - // Can't collide with self. - if (collider == entity) + for (id_type collider : entities) { - continue; - } + // Can't collide with self. + if (collider == entity) + { + continue; + } - auto& colliderPonder = game_.getEntityManager(). - getComponent(collider); + auto& colliderPonder = game_.getEntityManager(). + getComponent(collider); - // Only check objects that are active. - if (!colliderPonder.active) - { - continue; - } + // Only check objects that are active and collidable. + if (!colliderPonder.active || !colliderPonder.collidable) + { + continue; + } - auto& colliderTrans = game_.getEntityManager(). - getComponent(collider); - - // Check if the entity would move into the potential collider, - if ((colliderTrans.x + colliderTrans.w > result.newX) && - // that it wasn't already colliding, - (colliderTrans.x + colliderTrans.w <= oldX) && - // that the position on the other axis is in range, - (colliderTrans.y + colliderTrans.h > oldY) && - (colliderTrans.y < oldBottom) && - // and that the collider is not farther away than the environmental - // boundary. - (!boundaryCollision || - (colliderTrans.x + colliderTrans.w >= it->first))) - { - colliders.push_back(collider); + auto& colliderTrans = game_.getEntityManager(). + getComponent(collider); + + // Check if the entity would move into the potential collider, + if ((colliderTrans.x + colliderTrans.w > result.newX) && + // that it wasn't already colliding, + (colliderTrans.x + colliderTrans.w <= oldX) && + // that the position on the other axis is in range, + (colliderTrans.y + colliderTrans.h > oldY) && + (colliderTrans.y < oldBottom) && + // and that the collider is not farther away than the environmental + // boundary. + (!boundaryCollision || + (colliderTrans.x + colliderTrans.w >= it->first))) + { + colliders.push_back(collider); + } } - } - std::sort( - std::begin(colliders), - std::end(colliders), - [&] (id_type left, id_type right) { - auto& leftTrans = game_.getEntityManager(). - getComponent(left); + std::sort( + std::begin(colliders), + std::end(colliders), + [&] (id_type left, id_type right) { + auto& leftTrans = game_.getEntityManager(). + getComponent(left); - auto& rightTrans = game_.getEntityManager(). - getComponent(right); + auto& rightTrans = game_.getEntityManager(). + getComponent(right); - return (rightTrans.x < leftTrans.x); - }); + return (rightTrans.x < leftTrans.x); + }); - for (id_type collider : colliders) - { - auto& colliderTrans = game_.getEntityManager(). - getComponent(collider); - - // Check if the entity would still move into the potential collider. - if (colliderTrans.x + colliderTrans.w <= result.newX) + for (id_type collider : colliders) { - break; - } + auto& colliderTrans = game_.getEntityManager(). + getComponent(collider); - auto& colliderPonder = game_.getEntityManager(). - getComponent(collider); + // Check if the entity would still move into the potential collider. + if (colliderTrans.x + colliderTrans.w <= result.newX) + { + break; + } - processCollision( - entity, - collider, - Direction::left, - colliderPonder.colliderType, - colliderTrans.x + colliderTrans.w, - colliderTrans.y, - colliderTrans.y + colliderTrans.h, - result); - - if (result.stopProcessing) - { - break; - } - } + auto& colliderPonder = game_.getEntityManager(). + getComponent(collider); - // If movement hasn't been stopped by an intermediary object, and - // collision checking hasn't been stopped, process the environmental - // boundaries closest to the entity. - if (!result.stopProcessing && !result.touchedWall && boundaryCollision) - { - double boundaryAxis = it->first; + processCollision( + entity, + collider, + Direction::left, + colliderPonder.colliderType, + colliderTrans.x + colliderTrans.w, + colliderTrans.y, + colliderTrans.y + colliderTrans.h, + result); - for (; - (it != std::end(mappable.leftBoundaries)) && - (it->first == boundaryAxis); - it++) + if (result.stopProcessing) + { + break; + } + } + + // If movement hasn't been stopped by an intermediary object, and + // collision checking hasn't been stopped, process the environmental + // boundaries closest to the entity. + if (!result.stopProcessing && !result.touchedWall && boundaryCollision) { - if ((oldBottom > it->second.lower) && (oldY < it->second.upper)) + double boundaryAxis = it->first; + + for (; + (it != std::end(mappable.leftBoundaries)) && + (it->first == boundaryAxis); + it++) { - processCollision( - entity, - mapEntity, - Direction::left, - it->second.type, - it->first, - it->second.lower, - it->second.upper, - result); - - if (result.stopProcessing) + if ((oldBottom > it->second.lower) && (oldY < it->second.upper)) { - break; + processCollision( + entity, + mapEntity, + Direction::left, + it->second.type, + it->first, + it->second.lower, + it->second.upper, + result); + + if (result.stopProcessing) + { + break; + } } } } - } - } else if (result.newX > oldX) - { - bool boundaryCollision = false; - auto it = mappable.rightBoundaries.lower_bound(oldRight); - - // Find the axis distance of the closest environmental boundary. - for (; - (it != std::end(mappable.rightBoundaries)) - && (it->first <= (result.newX + transformable.w)); - it++) + } else if (result.newX > oldX) { - // Check that the boundary is in range for the other axis. - if ((oldBottom > it->second.lower) && (oldY < it->second.upper)) + bool boundaryCollision = false; + auto it = mappable.rightBoundaries.lower_bound(oldRight); + + // Find the axis distance of the closest environmental boundary. + for (; + (it != std::end(mappable.rightBoundaries)) + && (it->first <= (result.newX + transformable.w)); + it++) { - // We have a collision! - boundaryCollision = true; + // Check that the boundary is in range for the other axis. + if ((oldBottom > it->second.lower) && (oldY < it->second.upper)) + { + // We have a collision! + boundaryCollision = true; - break; + break; + } } - } - // Find a list of potential colliders, sorted so that the closest is - // first. - std::vector colliders; + // Find a list of potential colliders, sorted so that the closest is + // first. + std::vector colliders; - for (id_type collider : entities) - { - // Can't collide with self. - if (collider == entity) + for (id_type collider : entities) { - continue; - } + // Can't collide with self. + if (collider == entity) + { + continue; + } - auto& colliderPonder = game_.getEntityManager(). - getComponent(collider); + auto& colliderPonder = game_.getEntityManager(). + getComponent(collider); - // Only check objects that are active. - if (!colliderPonder.active) - { - continue; - } + // Only check objects that are active and collidable. + if (!colliderPonder.active || !colliderPonder.collidable) + { + continue; + } - auto& colliderTrans = game_.getEntityManager(). - getComponent(collider); - - // Check if the entity would move into the potential collider, - if ((colliderTrans.x < result.newX + transformable.w) && - // that it wasn't already colliding, - (colliderTrans.x >= oldRight) && - // that the position on the other axis is in range, - (colliderTrans.y + colliderTrans.h > oldY) && - (colliderTrans.y < oldBottom) && - // and that the collider is not farther away than the environmental - // boundary. - (!boundaryCollision || (colliderTrans.x <= it->first))) - { - colliders.push_back(collider); + auto& colliderTrans = game_.getEntityManager(). + getComponent(collider); + + // Check if the entity would move into the potential collider, + if ((colliderTrans.x < result.newX + transformable.w) && + // that it wasn't already colliding, + (colliderTrans.x >= oldRight) && + // that the position on the other axis is in range, + (colliderTrans.y + colliderTrans.h > oldY) && + (colliderTrans.y < oldBottom) && + // and that the collider is not farther away than the environmental + // boundary. + (!boundaryCollision || (colliderTrans.x <= it->first))) + { + colliders.push_back(collider); + } } - } - - std::sort( - std::begin(colliders), - std::end(colliders), - [&] (id_type left, id_type right) { - auto& leftTrans = game_.getEntityManager(). - getComponent(left); - auto& rightTrans = game_.getEntityManager(). - getComponent(right); + std::sort( + std::begin(colliders), + std::end(colliders), + [&] (id_type left, id_type right) { + auto& leftTrans = game_.getEntityManager(). + getComponent(left); - return (leftTrans.x < rightTrans.x); - }); + auto& rightTrans = game_.getEntityManager(). + getComponent(right); - for (id_type collider : colliders) - { - auto& colliderTrans = game_.getEntityManager(). - getComponent(collider); + return (leftTrans.x < rightTrans.x); + }); - // Check if the entity would still move into the potential collider. - if (colliderTrans.x >= result.newX + transformable.w) + for (id_type collider : colliders) { - break; - } + auto& colliderTrans = game_.getEntityManager(). + getComponent(collider); - auto& colliderPonder = game_.getEntityManager(). - getComponent(collider); + // Check if the entity would still move into the potential collider. + if (colliderTrans.x >= result.newX + transformable.w) + { + break; + } - processCollision( - entity, - collider, - Direction::right, - colliderPonder.colliderType, - colliderTrans.x, - colliderTrans.y, - colliderTrans.y + colliderTrans.h, - result); - - if (result.stopProcessing) - { - break; - } - } + auto& colliderPonder = game_.getEntityManager(). + getComponent(collider); - // If movement hasn't been stopped by an intermediary object, and - // collision checking hasn't been stopped, process the environmental - // boundaries closest to the entity. - if (!result.stopProcessing && !result.touchedWall && boundaryCollision) - { - double boundaryAxis = it->first; + processCollision( + entity, + collider, + Direction::right, + colliderPonder.colliderType, + colliderTrans.x, + colliderTrans.y, + colliderTrans.y + colliderTrans.h, + result); - for (; - (it != std::end(mappable.rightBoundaries)) && - (it->first == boundaryAxis); - it++) + if (result.stopProcessing) + { + break; + } + } + + // If movement hasn't been stopped by an intermediary object, and + // collision checking hasn't been stopped, process the environmental + // boundaries closest to the entity. + if (!result.stopProcessing && !result.touchedWall && boundaryCollision) { - if ((oldBottom > it->second.lower) && (oldY < it->second.upper)) + double boundaryAxis = it->first; + + for (; + (it != std::end(mappable.rightBoundaries)) && + (it->first == boundaryAxis); + it++) { - processCollision( - entity, - mapEntity, - Direction::right, - it->second.type, - it->first, - it->second.lower, - it->second.upper, - result); - - if (result.stopProcessing) + if ((oldBottom > it->second.lower) && (oldY < it->second.upper)) { - break; + processCollision( + entity, + mapEntity, + Direction::right, + it->second.type, + it->first, + it->second.lower, + it->second.upper, + result); + + if (result.stopProcessing) + { + break; + } } } } @@ -421,277 +424,280 @@ void PonderingSystem::tickBody( } // Find vertical collisions - result.touchedWall = false; - - if ((!result.stopProcessing) && (result.newY < oldY)) + if (ponderable.collidable && !result.stopProcessing) { - bool boundaryCollision = false; - auto it = mappable.upBoundaries.lower_bound(oldY); - - // Find the axis distance of the closest environmental boundary. - for (; - (it != std::end(mappable.upBoundaries)) && - (it->first >= result.newY); - it++) + result.touchedWall = false; + + if (result.newY < oldY) { - // Check that the boundary is in range for the other axis. - if ((result.newX + transformable.h > it->second.lower) && - (result.newX < it->second.upper)) + bool boundaryCollision = false; + auto it = mappable.upBoundaries.lower_bound(oldY); + + // Find the axis distance of the closest environmental boundary. + for (; + (it != std::end(mappable.upBoundaries)) && + (it->first >= result.newY); + it++) { - // We have a collision! - boundaryCollision = true; + // Check that the boundary is in range for the other axis. + if ((result.newX + transformable.h > it->second.lower) && + (result.newX < it->second.upper)) + { + // We have a collision! + boundaryCollision = true; - break; + break; + } } - } - // Find a list of potential colliders, sorted so that the closest is - // first. - std::vector colliders; + // Find a list of potential colliders, sorted so that the closest is + // first. + std::vector colliders; - for (id_type collider : entities) - { - // Can't collide with self. - if (collider == entity) + for (id_type collider : entities) { - continue; - } + // Can't collide with self. + if (collider == entity) + { + continue; + } - auto& colliderPonder = game_.getEntityManager(). - getComponent(collider); + auto& colliderPonder = game_.getEntityManager(). + getComponent(collider); - // Only check objects that are active. - if (!colliderPonder.active) - { - continue; - } + // Only check objects that are active and collidable. + if (!colliderPonder.active || !colliderPonder.collidable) + { + continue; + } - auto& colliderTrans = game_.getEntityManager(). - getComponent(collider); - - // Check if the entity would move into the potential collider, - if ((colliderTrans.y + colliderTrans.h > result.newY) && - // that it wasn't already colliding, - (colliderTrans.y + colliderTrans.h <= oldY) && - // that the position on the other axis is in range, - (colliderTrans.x + colliderTrans.w > result.newX) && - (colliderTrans.x < result.newX + transformable.w) && - // and that the collider is not farther away than the environmental - // boundary. - (!boundaryCollision || - (colliderTrans.y + colliderTrans.h >= it->first))) - { - colliders.push_back(collider); + auto& colliderTrans = game_.getEntityManager(). + getComponent(collider); + + // Check if the entity would move into the potential collider, + if ((colliderTrans.y + colliderTrans.h > result.newY) && + // that it wasn't already colliding, + (colliderTrans.y + colliderTrans.h <= oldY) && + // that the position on the other axis is in range, + (colliderTrans.x + colliderTrans.w > result.newX) && + (colliderTrans.x < result.newX + transformable.w) && + // and that the collider is not farther away than the environmental + // boundary. + (!boundaryCollision || + (colliderTrans.y + colliderTrans.h >= it->first))) + { + colliders.push_back(collider); + } } - } - - std::sort( - std::begin(colliders), - std::end(colliders), - [&] (id_type left, id_type right) { - auto& leftTrans = game_.getEntityManager(). - getComponent(left); - auto& rightTrans = game_.getEntityManager(). - getComponent(right); + std::sort( + std::begin(colliders), + std::end(colliders), + [&] (id_type left, id_type right) { + auto& leftTrans = game_.getEntityManager(). + getComponent(left); - return (rightTrans.y < leftTrans.y); - }); + auto& rightTrans = game_.getEntityManager(). + getComponent(right); - for (id_type collider : colliders) - { - auto& colliderTrans = game_.getEntityManager(). - getComponent(collider); + return (rightTrans.y < leftTrans.y); + }); - // Check if the entity would still move into the potential collider. - if (colliderTrans.y + colliderTrans.h <= result.newY) + for (id_type collider : colliders) { - break; - } + auto& colliderTrans = game_.getEntityManager(). + getComponent(collider); - auto& colliderPonder = game_.getEntityManager(). - getComponent(collider); + // Check if the entity would still move into the potential collider. + if (colliderTrans.y + colliderTrans.h <= result.newY) + { + break; + } - processCollision( - entity, - collider, - Direction::up, - colliderPonder.colliderType, - colliderTrans.y + colliderTrans.h, - colliderTrans.x, - colliderTrans.x + colliderTrans.w, - result); - - if (result.stopProcessing) - { - break; - } - } + auto& colliderPonder = game_.getEntityManager(). + getComponent(collider); - // If movement hasn't been stopped by an intermediary object, and - // collision checking hasn't been stopped, process the environmental - // boundaries closest to the entity. - if (!result.stopProcessing && !result.touchedWall && boundaryCollision) - { - double boundaryAxis = it->first; + processCollision( + entity, + collider, + Direction::up, + colliderPonder.colliderType, + colliderTrans.y + colliderTrans.h, + colliderTrans.x, + colliderTrans.x + colliderTrans.w, + result); - for (; - (it != std::end(mappable.upBoundaries)) && - (it->first == boundaryAxis); - it++) + if (result.stopProcessing) + { + break; + } + } + + // If movement hasn't been stopped by an intermediary object, and + // collision checking hasn't been stopped, process the environmental + // boundaries closest to the entity. + if (!result.stopProcessing && !result.touchedWall && boundaryCollision) { - if ((result.newX + transformable.w > it->second.lower) && - (result.newX < it->second.upper)) + double boundaryAxis = it->first; + + for (; + (it != std::end(mappable.upBoundaries)) && + (it->first == boundaryAxis); + it++) { - processCollision( - entity, - mapEntity, - Direction::up, - it->second.type, - it->first, - it->second.lower, - it->second.upper, - result); - - if (result.stopProcessing) + if ((result.newX + transformable.w > it->second.lower) && + (result.newX < it->second.upper)) { - break; + processCollision( + entity, + mapEntity, + Direction::up, + it->second.type, + it->first, + it->second.lower, + it->second.upper, + result); + + if (result.stopProcessing) + { + break; + } } } } - } - } else if ((!result.stopProcessing) && (result.newY > oldY)) - { - bool boundaryCollision = false; - auto it = mappable.downBoundaries.lower_bound(oldBottom); - - // Find the axis distance of the closest environmental boundary. - for (; - (it != std::end(mappable.downBoundaries)) - && (it->first <= (result.newY + transformable.h)); - it++) + } else if (result.newY > oldY) { - // Check that the boundary is in range for the other axis. - if ((result.newX + transformable.w > it->second.lower) && - (result.newX < it->second.upper)) + bool boundaryCollision = false; + auto it = mappable.downBoundaries.lower_bound(oldBottom); + + // Find the axis distance of the closest environmental boundary. + for (; + (it != std::end(mappable.downBoundaries)) + && (it->first <= (result.newY + transformable.h)); + it++) { - // We have a collision! - boundaryCollision = true; + // Check that the boundary is in range for the other axis. + if ((result.newX + transformable.w > it->second.lower) && + (result.newX < it->second.upper)) + { + // We have a collision! + boundaryCollision = true; - break; + break; + } } - } - // Find a list of potential colliders, sorted so that the closest is - // first. - std::vector colliders; + // Find a list of potential colliders, sorted so that the closest is + // first. + std::vector colliders; - for (id_type collider : entities) - { - // Can't collide with self. - if (collider == entity) + for (id_type collider : entities) { - continue; - } + // Can't collide with self. + if (collider == entity) + { + continue; + } - auto& colliderPonder = game_.getEntityManager(). - getComponent(collider); + auto& colliderPonder = game_.getEntityManager(). + getComponent(collider); - // Only check objects that are active. - if (!colliderPonder.active) - { - continue; - } + // Only check objects that are active and collidable. + if (!colliderPonder.active || !colliderPonder.collidable) + { + continue; + } - auto& colliderTrans = game_.getEntityManager(). - getComponent(collider); - - // Check if the entity would move into the potential collider, - if ((colliderTrans.y < result.newY + transformable.h) && - // that it wasn't already colliding, - (colliderTrans.y >= oldBottom) && - // that the position on the other axis is in range, - (colliderTrans.x + colliderTrans.w > result.newX) && - (colliderTrans.x < result.newX + transformable.w) && - // and that the collider is not farther away than the environmental - // boundary. - (!boundaryCollision || (colliderTrans.y <= it->first))) - { - colliders.push_back(collider); + auto& colliderTrans = game_.getEntityManager(). + getComponent(collider); + + // Check if the entity would move into the potential collider, + if ((colliderTrans.y < result.newY + transformable.h) && + // that it wasn't already colliding, + (colliderTrans.y >= oldBottom) && + // that the position on the other axis is in range, + (colliderTrans.x + colliderTrans.w > result.newX) && + (colliderTrans.x < result.newX + transformable.w) && + // and that the collider is not farther away than the environmental + // boundary. + (!boundaryCollision || (colliderTrans.y <= it->first))) + { + colliders.push_back(collider); + } } - } - - std::sort( - std::begin(colliders), - std::end(colliders), - [&] (id_type left, id_type right) { - auto& leftTrans = game_.getEntityManager(). - getComponent(left); - auto& rightTrans = game_.getEntityManager(). - getComponent(right); + std::sort( + std::begin(colliders), + std::end(colliders), + [&] (id_type left, id_type right) { + auto& leftTrans = game_.getEntityManager(). + getComponent(left); - return (leftTrans.y < rightTrans.y); - }); + auto& rightTrans = game_.getEntityManager(). + getComponent(right); - for (id_type collider : colliders) - { - auto& colliderTrans = game_.getEntityManager(). - getComponent(collider); + return (leftTrans.y < rightTrans.y); + }); - // Check if the entity would still move into the potential collider. - if (colliderTrans.y >= result.newY + transformable.h) + for (id_type collider : colliders) { - break; - } + auto& colliderTrans = game_.getEntityManager(). + getComponent(collider); - auto& colliderPonder = game_.getEntityManager(). - getComponent(collider); + // Check if the entity would still move into the potential collider. + if (colliderTrans.y >= result.newY + transformable.h) + { + break; + } - processCollision( - entity, - collider, - Direction::down, - colliderPonder.colliderType, - colliderTrans.y, - colliderTrans.x, - colliderTrans.x + colliderTrans.w, - result); - - if (result.stopProcessing) - { - break; - } - } + auto& colliderPonder = game_.getEntityManager(). + getComponent(collider); - // If movement hasn't been stopped by an intermediary object, and - // collision checking hasn't been stopped, process the environmental - // boundaries closest to the entity. - if (!result.stopProcessing && !result.touchedWall && boundaryCollision) - { - double boundaryAxis = it->first; + processCollision( + entity, + collider, + Direction::down, + colliderPonder.colliderType, + colliderTrans.y, + colliderTrans.x, + colliderTrans.x + colliderTrans.w, + result); - for (; - (it != std::end(mappable.downBoundaries)) && - (it->first == boundaryAxis); - it++) + if (result.stopProcessing) + { + break; + } + } + + // If movement hasn't been stopped by an intermediary object, and + // collision checking hasn't been stopped, process the environmental + // boundaries closest to the entity. + if (!result.stopProcessing && !result.touchedWall && boundaryCollision) { - if ((result.newX + transformable.w > it->second.lower) && - (result.newX < it->second.upper)) + double boundaryAxis = it->first; + + for (; + (it != std::end(mappable.downBoundaries)) && + (it->first == boundaryAxis); + it++) { - processCollision( - entity, - mapEntity, - Direction::down, - it->second.type, - it->first, - it->second.lower, - it->second.upper, - result); - - if (result.stopProcessing) + if ((result.newX + transformable.w > it->second.lower) && + (result.newX < it->second.upper)) { - break; + processCollision( + entity, + mapEntity, + Direction::down, + it->second.type, + it->first, + it->second.lower, + it->second.upper, + result); + + if (result.stopProcessing) + { + break; + } } } } -- cgit 1.4.1