#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; i++) {
vec2i tween = ((follower.loc - targetPos) * i) / static_cast<double>(truePartyDelay) + targetPos;
follower.trail.push_front({.pos = tween, .dir = toFace, .medium = CharacterMedium::Normal});
}
leader.followers.push_back(followerId);
game_.getSystem<AnimationSystem>().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<TransformSystem>().getMediumAtPosition(leaderId, pos);
std::vector<int> followers = leader.followers;
leader.followers.clear();
leader.trailsAreHalved = false;
leader.characterMedium = newMedium;
game_.getSystem<TransformSystem>().moveSprite(leaderId, pos);
game_.getSystem<AnimationSystem>().setSpriteDirection(leaderId, dir);
for (int followerId : followers) {
Sprite& follower = game_.getSprite(followerId);
follower.trail.clear();
game_.getSystem<TransformSystem>().moveSprite(followerId, pos);
game_.getSystem<AnimationSystem>().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<int> 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<AnimationSystem>().setSpriteDirection(spriteId, dir);
break;
}
case CharacterMedium::Ladder: {
if (dirHasDir(dir, Direction::up)) {
game_.getSystem<AnimationSystem>().setSpriteDirection(spriteId, Direction::up);
} else if (dirHasDir(dir, Direction::down)) {
game_.getSystem<AnimationSystem>().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<AnimationSystem>().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<TransformSystem>().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<ScriptSystem>().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<ScriptSystem>().runScript(game_.getMap().getName(), sprite.bumpPlayerScript);
}
}
}
if (collision.blocked && sprite.characterState == CharacterState::Running && !sprite.clipping) {
stopRunning(spriteId);
game_.getMixer().playSound("../res/sfx/bump.wav");
}
// Move everything
if (pLoc != sprite.loc) {
game_.getSystem<TransformSystem>().moveSprite(spriteId, pLoc);
CharacterMedium newMedium = game_.getSystem<TransformSystem>().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<TransformSystem>().moveSprite(followerId, posdir.pos);
game_.getSystem<AnimationSystem>().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});
}
}
}
}
}
}
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<AnimationSystem>().setSpriteAnimation(spriteId, animName);
sprite.hasShadow = sprite.normallyHasShadow;
sprite.bobbing = sprite.bobsWhenNormal;
break;
}
case CharacterMedium::Ladder: {
game_.getSystem<AnimationSystem>().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<AnimationSystem>().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<Movement> 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<Movement> 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);
}
}
}
}