#include "simulation.h" #include "consts.h" #include "level.h" #include "views.h" Simulation::Simulation( const Level& level) : level_(level) { id_type trainId = emplaceEntity(); Entity& train = getEntity(trainId); train.size = TILE_SIZE; train.speed = schedule_.getBPS() * 2.0; train.colliderType = ColliderType::train; train.scheduled = true; train.colorVal = 90; train.gridPos = vec2s { 6, 1 }; train.moveDir = Direction::left; id_type playerId = emplaceEntity(); Entity& player = getEntity(playerId); player.size = TILE_SIZE; player.speed = 3.0; player.controllable = true; player.colliderType = ColliderType::player; player.colorVal = 180; player.gridPos = vec2s { 1, 5 }; id_type crateId = emplaceEntity(); Entity& crate = getEntity(crateId); crate.size = TILE_SIZE; crate.speed = schedule_.getBPS() * 2.0; crate.colliderType = ColliderType::crate; crate.canBePushedBy.insert(ColliderType::player); crate.canBePushedBy.insert(ColliderType::crate); crate.canBePushedBy.insert(ColliderType::train); crate.gridPos = vec2s { 4, 5 }; id_type crateId2 = emplaceEntity(); Entity& crate2 = getEntity(crateId2); crate2.size = TILE_SIZE; crate2.speed = schedule_.getBPS() * 2.0; crate2.colliderType = ColliderType::crate; crate2.canBePushedBy.insert(ColliderType::player); crate2.canBePushedBy.insert(ColliderType::crate); crate2.canBePushedBy.insert(ColliderType::train); crate2.gridPos = vec2s { 6, 7 }; id_type trackId = emplaceEntity(); Entity& track = getEntity(trackId); track.size = TILE_SIZE; track.layer = Layer::track; track.isTrack = true; track.trackDir1 = Direction::right; track.trackDir2 = Direction::down; track.colorVal = 130; track.gridPos = vec2s { 6, 1 }; id_type trackId2 = emplaceEntity(); Entity& track2 = getEntity(trackId2); track2.size = TILE_SIZE; track2.layer = Layer::track; track2.isTrack = true; track2.trackDir1 = Direction::right; track2.trackDir2 = Direction::up; track2.colorVal = 130; track2.gridPos = vec2s { 6, 2 }; id_type trackId3 = emplaceEntity(); Entity& track3 = getEntity(trackId3); track3.size = TILE_SIZE; track3.layer = Layer::track; track3.isTrack = true; track3.trackDir1 = Direction::up; track3.trackDir2 = Direction::left; track3.colorVal = 130; track3.gridPos = vec2s { 7, 2 }; id_type trackId4 = emplaceEntity(); Entity& track4 = getEntity(trackId4); track4.size = TILE_SIZE; track4.layer = Layer::track; track4.isTrack = true; track4.trackDir1 = Direction::left; track4.trackDir2 = Direction::down; track4.colorVal = 130; track4.gridPos = vec2s { 7, 1 }; } void Simulation::tick( double dt, const Uint8* keystate) { // Control for (Entity& entity : entityRange() | views::isControllable() | views::isNotMoving()) { if (keystate[SDL_SCANCODE_LSHIFT] || keystate[SDL_SCANCODE_RSHIFT]) { Direction lookDir = Direction::none; if (keystate[SDL_SCANCODE_LEFT]) { lookDir = Direction::left; } else if (keystate[SDL_SCANCODE_UP]) { lookDir = Direction::up; } else if (keystate[SDL_SCANCODE_RIGHT]) { lookDir = Direction::right; } else if (keystate[SDL_SCANCODE_DOWN]) { lookDir = Direction::down; } vec2s lookPos = posInDir(entity.gridPos, lookDir); for (Entity& block : entityRange() | views::atGridPos(lookPos) | views::isNotMoving() | views::canBePushedBy(entity.colliderType) | views::isOnLayer(entity.layer)) { block.shouldMoveDir.push_back(lookDir); } } else { if (keystate[SDL_SCANCODE_LEFT]) { entity.shouldMoveDir.push_back(Direction::left); } if (keystate[SDL_SCANCODE_UP]) { entity.shouldMoveDir.push_back(Direction::up); } if (keystate[SDL_SCANCODE_RIGHT]) { entity.shouldMoveDir.push_back(Direction::right); } if (keystate[SDL_SCANCODE_DOWN]) { entity.shouldMoveDir.push_back(Direction::down); } } } // Schedule schedule_.accumulate(dt); if (schedule_.step()) { for (Entity& entity : entityRange() | views::isScheduled() | views::isNotMoving()) { auto tracks = entityRange() | views::atGridPos(entity.gridPos) | views::isTrack(); if (!ranges::empty(tracks)) { Entity& track = ranges::front(tracks); Direction from = oppositeDir(entity.moveDir); if (from == track.trackDir1) { entity.shouldMoveDir.push_back(track.trackDir2); } else if (from == track.trackDir2) { entity.shouldMoveDir.push_back(track.trackDir1); } } } } // Collision // Movement for (Entity& entity : entityRange()) { while (!entity.moving && !entity.shouldMoveDir.empty()) { moveEntityOnGrid(entity, entity.shouldMoveDir.front()); entity.shouldMoveDir.pop_front(); } entity.shouldMoveDir.clear(); if (entity.moving) { entity.movementTween += entity.speed * dt; if (entity.movementTween >= 1.0) { entity.moving = false; entity.gridPos = entity.destPos; } } if (entity.moving) { entity.pos.x() = TILE_SIZE.x() * entity.destPos.x() * entity.movementTween + TILE_SIZE.x() * entity.gridPos.x() * (1.0 - entity.movementTween); entity.pos.y() = TILE_SIZE.y() * entity.destPos.y() * entity.movementTween + TILE_SIZE.y() * entity.gridPos.y() * (1.0 - entity.movementTween); } else { entity.pos = TILE_SIZE * entity.gridPos; } } } void Simulation::render(SDL_Renderer* ren) { if (!renderedMap_) { renderedMap_ = level_.render(ren, Layer::map); } SDL_RenderCopy(ren, renderedMap_.get(), nullptr, nullptr); constexpr Layer renderOrder[] = { Layer::track, Layer::object }; ranges::for_each( ranges::view::for_each( renderOrder, [&] (Layer layer) { return entityRange() | views::isOnLayer(layer); }), [&] (const Entity& entity) { SDL_SetRenderDrawColor(ren, entity.colorVal, entity.colorVal, 65, 255); SDL_Rect rect { static_cast(entity.pos.x()), static_cast(entity.pos.y()), static_cast(entity.size.w()), static_cast(entity.size.h()) }; SDL_RenderFillRect(ren, &rect); }); } id_type Simulation::emplaceEntity() { id_type nextId; if (!available_.empty()) { nextId = available_.front(); available_.pop_front(); entities_.at(nextId) = Entity(nextId); } else { nextId = entities_.size(); entities_.emplace_back(nextId); } active_.insert(nextId); return nextId; } void Simulation::deleteEntity(id_type id) { available_.push_back(id); active_.erase(id); } bool Simulation::moveEntityOnGrid( Entity& entity, Direction moveDir, bool validate) { bool actuallyMove = true; vec2s shouldMoveTo = posInDir(entity.gridPos, moveDir); switch (moveDir) { case Direction::left: { if (entity.gridPos.x() == 0) { return false; } break; } case Direction::right: { if (entity.gridPos.x() == level_.getSize().w() - 1) { return false; } break; } case Direction::up: { if (entity.gridPos.y() == 0) { return false; } break; } case Direction::down: { if (entity.gridPos.y() == level_.getSize().h() - 1) { return false; } break; } } if (!level_.getTileset(Layer::map).canEntityMoveTo( entity.colliderType, level_.at(shouldMoveTo))) { return false; } // Can't move into a space that something else is already moving into. if (!ranges::empty( entityRange() | views::isMovingTo(shouldMoveTo) | views::isOnLayer(entity.layer))) { return false; } for (Entity& block : entityRange() | views::atGridPos(shouldMoveTo) | views::isOnLayer(entity.layer)) { if (!block.moving) { if (!block.canBePushedBy.count(entity.colliderType)) { return false; } if (!moveEntityOnGrid(block, moveDir, validate)) { return false; } } else if (block.moveDir != moveDir) { // Can't move perpendicularly into a space that something else is moving // out of. return false; } double entityTimeLeft = 1.0 / entity.speed; double blockTimeLeft = (1.0 - block.movementTween) / block.speed; if (entityTimeLeft < blockTimeLeft) { actuallyMove = false; } } if (!validate && actuallyMove) { entity.moving = true; entity.destPos = shouldMoveTo; entity.movementTween = 0.0; entity.moveDir = moveDir; } return true; }