#include "character_system.h" #include "consts.h" #include "mixer.h" #include "sprite.h" #include "game.h" #include "transform_system.h" #include "animation_system.h" #include "script_system.h" void CharacterSystem::initSprite(int spriteId, int movementSpeed) { Sprite& sprite = game_.getSprite(spriteId); sprite.orientable = true; sprite.movementSpeed = movementSpeed; sprite.trailsAreHalved = false; } void CharacterSystem::addSpriteToParty(int leaderId, int followerId) { Sprite& leader = game_.getSprite(leaderId); Sprite& follower = game_.getSprite(followerId); follower.orientable = false; follower.leaderId = leaderId; vec2i targetPos = leader.loc; if (!leader.followers.empty()) { Sprite& backFollower = game_.getSprite(leader.followers.back()); follower.trail = backFollower.trail; targetPos = backFollower.loc; } Direction toFace = leader.dir; if (targetPos != follower.loc) { toFace = directionFacingPoint(targetPos - follower.loc); } int truePartyDelay = PARTY_FRAME_DELAY / leader.movementSpeed; for (int i=0; i(truePartyDelay) + targetPos; follower.trail.push_front({.pos = tween, .dir = toFace, .medium = CharacterMedium::Normal}); } leader.followers.push_back(followerId); game_.getSystem().setSpriteAnimation(followerId, "still"); } void CharacterSystem::transplantParty(int leaderId, vec2i pos, Direction dir) { Sprite& leader = game_.getSprite(leaderId); CharacterState oldState = leader.characterState; CharacterMedium newMedium = game_.getSystem().getMediumAtPosition(leaderId, pos); std::vector followers = leader.followers; leader.followers.clear(); leader.trailsAreHalved = false; leader.characterMedium = newMedium; game_.getSystem().moveSprite(leaderId, pos); game_.getSystem().setSpriteDirection(leaderId, dir); for (int followerId : followers) { Sprite& follower = game_.getSprite(followerId); follower.trail.clear(); game_.getSystem().moveSprite(followerId, pos); game_.getSystem().setSpriteDirection(followerId, dir); follower.characterMedium = newMedium; addSpriteToParty(leaderId, followerId); } if (oldState == CharacterState::Running) { startRunning(leaderId); } else { setPartyState(leaderId, oldState); } } void CharacterSystem::breakUpParty(int leaderId) { Sprite& leader = game_.getSprite(leaderId); std::vector followers = leader.followers; leader.followers.clear(); leader.trailsAreHalved = false; for (int followerId : followers) { Sprite& follower = game_.getSprite(followerId); follower.trail.clear(); follower.leaderId = -1; } } void CharacterSystem::moveInDirection(int spriteId, Direction dir) { Sprite& sprite = game_.getSprite(spriteId); sprite.movementDir = dir; switch (sprite.characterMedium) { case CharacterMedium::Normal: case CharacterMedium::Water: { game_.getSystem().setSpriteDirection(spriteId, dir); break; } case CharacterMedium::Ladder: { if (dirHasDir(dir, Direction::up)) { game_.getSystem().setSpriteDirection(spriteId, Direction::up); } else if (dirHasDir(dir, Direction::down)) { game_.getSystem().setSpriteDirection(spriteId, Direction::down); } break; } } if (sprite.characterState == CharacterState::Still) { setPartyState(spriteId, CharacterState::Walking); } else if (sprite.characterState == CharacterState::Crouching) { for (int followerId : sprite.followers) { Sprite& follower = game_.getSprite(followerId); if (follower.characterMedium == CharacterMedium::Normal) { game_.getSystem().setSpriteDirection(followerId, dir); } } } } void CharacterSystem::stopDirecting(int spriteId) { Sprite& sprite = game_.getSprite(spriteId); if (sprite.characterState == CharacterState::Walking) { setPartyState(spriteId, CharacterState::Still); } } void CharacterSystem::tick(double dt) { if (game_.isGameplayPaused()) return; inputTimer_.accumulate(dt); while (inputTimer_.step()) { for (int spriteId : game_.getSprites()) { Sprite& sprite = game_.getSprite(spriteId); if (sprite.orientable && !sprite.paused) { vec2i pLoc = sprite.loc; if (sprite.characterState == CharacterState::Still || sprite.characterState == CharacterState::Crouching) { continue; } // Make sure this matches up with the code in adjustPartyTrails(). int speed = sprite.movementSpeed; if (sprite.characterState == CharacterState::Running && sprite.characterMedium == CharacterMedium::Normal) { speed *= 2; } pLoc += (unitVecInDirection(sprite.movementDir) * speed); // Check collision. CollisionResult collision = game_.getSystem().checkCollision(spriteId, sprite.loc, pLoc, sprite.movementDir, CheckCollisionOptions::AllowSliding); if (!(collision.blocked && sprite.clipping)) { pLoc = collision.adjustedLoc; } for (int colliderSpriteId : collision.colliders) { Sprite& collider = game_.getSprite(colliderSpriteId); if (!collider.walkthroughScript.empty()) { game_.getSystem().runScript(game_.getMap().getName(), collider.walkthroughScript); } if (!sprite.bumpPlayerScript.empty()) { bool bumpedPlayer = collider.player; if (!bumpedPlayer && collider.leaderId != -1) { Sprite& colliderLeader = game_.getSprite(collider.leaderId); bumpedPlayer = colliderLeader.player; } if (bumpedPlayer) { game_.getSystem().runScript(game_.getMap().getName(), sprite.bumpPlayerScript); } } } // Move everything if (pLoc != sprite.loc) { game_.getSystem().moveSprite(spriteId, pLoc); CharacterMedium newMedium = game_.getSystem().getMediumAtPosition(spriteId, pLoc); if (newMedium != sprite.characterMedium) { sprite.characterMedium = newMedium; setAnimationFor(spriteId, sprite.characterState); adjustPartyTrails(spriteId); // Stop running if you go into water. if (newMedium == CharacterMedium::Water && sprite.characterState == CharacterState::Running) { stopRunning(spriteId); } } if (sprite.characterState == CharacterState::Running) { const Map& map = game_.getMap(); vec2i newMapTileLoc = pLoc / map.getTileSize(); StepType newTileStep = map.getStepType(newMapTileLoc.x(), newMapTileLoc.y()); if (sprite.stepType != newTileStep) { stopRunningSound(sprite); sprite.stepType = newTileStep; if (newTileStep != StepType::none) { sprite.runningSfxChannel = game_.getMixer().loopSound(runningSfxForStepType(newTileStep)); } } } for (int followerId : sprite.followers) { Sprite& pNext = game_.getSprite(followerId); const Movement& posdir = pNext.trail.front(); game_.getSystem().moveSprite(followerId, posdir.pos); game_.getSystem().setSpriteDirection(followerId, posdir.dir); if (posdir.medium != pNext.characterMedium) { pNext.characterMedium = posdir.medium; setAnimationFor(followerId, sprite.characterState); } pNext.trail.pop_front(); pNext.trail.push_back({.pos = pLoc, .dir = sprite.dir, .medium = sprite.characterMedium}); } } else { // We haven't moved at all. if (sprite.characterState == CharacterState::Running) { stopRunning(spriteId); game_.getMixer().playSound("../res/sfx/bump.wav"); } } } } } } void CharacterSystem::beginCrouch(int spriteId) { Sprite& sprite = game_.getSprite(spriteId); if (sprite.characterState == CharacterState::Running) { stopRunning(spriteId); } else { if (sprite.characterMedium == CharacterMedium::Ladder || sprite.characterMedium == CharacterMedium::Water || sprite.cantCrouch) { return; } setPartyState(spriteId, CharacterState::Crouching); } } void CharacterSystem::endCrouch(int spriteId) { Sprite& sprite = game_.getSprite(spriteId); if (sprite.characterState == CharacterState::Crouching) { startRunning(spriteId); } } void CharacterSystem::startRunning(int spriteId) { setPartyState(spriteId, CharacterState::Running); adjustPartyTrails(spriteId); } void CharacterSystem::stopRunning(int spriteId) { Sprite& sprite = game_.getSprite(spriteId); setPartyState(spriteId, CharacterState::Still); stopRunningSound(sprite); adjustPartyTrails(spriteId); } void CharacterSystem::setPartyState(int spriteId, CharacterState state) { Sprite& sprite = game_.getSprite(spriteId); sprite.characterState = state; setAnimationFor(spriteId, state); for (int followerId : sprite.followers) { setAnimationFor(followerId, state); } } void CharacterSystem::stopRunningSound(Sprite& sprite) { sprite.stepType = StepType::none; if (sprite.runningSfxChannel != -1) { game_.getMixer().stopChannel(sprite.runningSfxChannel); sprite.runningSfxChannel = -1; } } void CharacterSystem::halt(int spriteId) { // Because special stuff happens when we stop running, we have to handle the // running case here. Sprite& sprite = game_.getSprite(spriteId); if (sprite.characterState == CharacterState::Running) { stopRunning(spriteId); } else { // Other than that, it is simple to go to Still from Walking or Crouching. setPartyState(spriteId, CharacterState::Still); } } void CharacterSystem::destroySprite(int spriteId) { Sprite& sprite = game_.getSprite(spriteId); if (sprite.runningSfxChannel != -1) { stopRunningSound(sprite); } } void CharacterSystem::setAnimationFor(int spriteId, CharacterState state) { Sprite& sprite = game_.getSprite(spriteId); std::string animName; switch (sprite.characterMedium) { case CharacterMedium::Normal: { std::string animName; switch (state) { case CharacterState::Still: { animName = "still"; break; } case CharacterState::Walking: { animName = "walk"; break; } case CharacterState::Crouching: { animName = "crouch"; break; } case CharacterState::Running: { animName = "run"; break; } } game_.getSystem().setSpriteAnimation(spriteId, animName); sprite.hasShadow = sprite.normallyHasShadow; sprite.bobbing = sprite.bobsWhenNormal; break; } case CharacterMedium::Ladder: { game_.getSystem().setSpriteAnimation(spriteId, "climb"); sprite.hasShadow = false; sprite.bobbing = false; if (state == CharacterState::Still || state == CharacterState::Crouching) { sprite.animPaused = true; } else { sprite.animPaused = false; } break; } case CharacterMedium::Water: { std::string animName = "swim_still"; if (state == CharacterState::Walking) { animName = "swim_walk"; } game_.getSystem().setSpriteAnimation(spriteId, animName); sprite.hasShadow = false; sprite.bobbing = false; break; } } } void CharacterSystem::adjustPartyTrails(int spriteId) { Sprite& sprite = game_.getSprite(spriteId); // Trails should be halved if the effective moving speed is twice the normal // speed, which right now is whenever you are running and not on a ladder. bool shouldBeHalved = false; if (sprite.characterState == CharacterState::Running && sprite.characterMedium == CharacterMedium::Normal) { shouldBeHalved = true; } if (sprite.trailsAreHalved != shouldBeHalved) { sprite.trailsAreHalved = shouldBeHalved; if (shouldBeHalved) { // Halve the movement buffer for the followers. for (int followerId : sprite.followers) { Sprite& follower = game_.getSprite(followerId); std::deque newMove; while (!follower.trail.empty()) { newMove.push_back(follower.trail.front()); follower.trail.pop_front(); follower.trail.pop_front(); } follower.trail = std::move(newMove); } } else { // Double the movement buffer for the followers. for (int followerId : sprite.followers) { Sprite& follower = game_.getSprite(followerId); std::deque newMove; vec2i lastPos = follower.loc; while (!follower.trail.empty()) { Movement m1 = follower.trail.front(); Movement m2 = m1; m1.pos = (m1.pos + lastPos) / 2; lastPos = m2.pos; newMove.push_back(m1); newMove.push_back(m2); follower.trail.pop_front(); } follower.trail = std::move(newMove); } } } }