#include "character_modelview.hpp"

#include "character.hpp"

#include "playstate.hpp"
#include "stage.hpp"
#include "spaces/space.hpp"
#include "tiles/tile.hpp"
#include "character_controller.hpp"
//#include "sequences.hpp"
//#include "sequencelibrary.hpp"
//#include "controllers/playercontroller.hpp"
#include "kungflowutils.hpp"

#include "main.hpp"

//unsigned int CharacterModelView::_moveID = 1;

CharacterModelView::CharacterModelView(const KungFlowGame *game, const PlayState *playState, Character *character, const Vector2 &position) : game(game), playState(playState), character(character) {

  processedMoveIDList.set_capacity(8);

  //maxChangeMPS = 10.0f;
  //if (id == 0) maxChangeMPS = 40.0f;
  //maxChangeAirMultiplier = 0.5f;

  unsigned int beatDuration_ms = playState->GetCurrentMusic()->GetBeatDuration_ms();

  // guideline: never go over 0.95 times the beatduration (for 1 beat, so 1.95 for 2, etc)

  if (character->GetTeam() == e_Team_GoodGuys) {

    this->healthPoints_max = 10.0f;

    stats.balanceFactor = 1.0f;
    //stats.agilityFactor = 0.5f;
    stats.hurtThreshold = 0.1f;

    stats.walkStats.agilityFactor = 1.0f;//stats.agilityFactor;
    stats.walkStats.walkDuration_ms = 140;//beatDuration_ms;//40;//100;

    stats.jabStats.distanceFactor = _ATTACKDISTANCEFACTOR * 1.2f;//1.2f;//1.6f;//1.4f;//1.2f;
    stats.jabStats.onsetDuration_ms = beatDuration_ms * 0.0f;//100;
    stats.jabStats.executeDuration_ms = beatDuration_ms * 0.3f;//0.4f;//250;
    stats.jabStats.successfulSustainDuration_ms = beatDuration_ms * 0.2f;//100;
    stats.jabStats.failureSustainDuration_ms = beatDuration_ms * 1.1f;//600;
    stats.jabStats.cooldownDuration_ms = 0;

    stats.dashStats.damageFactor = 0.8f;
    stats.dashStats.onsetDuration_ms = 50;
    //stats.dashStats.executeDuration_ms = 300;
    stats.dashStats.sustainDuration_ms = 400;//150;
    stats.dashStats.cooldownDuration_ms = 100;

    stats.blockStats.distanceFactor = 1.0f;
    stats.blockStats.onsetDuration_ms = beatDuration_ms * 0.0f;
    stats.blockStats.executeDuration_ms = beatDuration_ms * 0.7f;
    stats.blockStats.sustainDuration_ms = beatDuration_ms * 0.1f;
    stats.blockStats.cooldownDuration_ms = beatDuration_ms * 0.1f;

    stats.dodgeStats.distanceFactor = 1.0f;
    stats.dodgeStats.onsetDuration_ms = beatDuration_ms * 0.0f;
    stats.dodgeStats.executeDuration_ms = beatDuration_ms * 0.4f;
    stats.dodgeStats.sustainDuration_ms = beatDuration_ms * 0.25f;
    stats.dodgeStats.cooldownDuration_ms = beatDuration_ms * 1.0f;

    stats.throwStats.maxDistance = 10.0f * _PHYSICSSCALE;
    stats.throwStats.onsetDuration_ms = beatDuration_ms * 0.1f;
    stats.throwStats.executeDuration_ms = beatDuration_ms * 0.2f;
    stats.throwStats.sustainDuration_ms = beatDuration_ms * 0.5f;
    stats.throwStats.cooldownDuration_ms = beatDuration_ms * 0.1f;

    stats.jumpStats.distanceFactor = 0.5f;
    stats.jumpStats.onsetDuration_ms = beatDuration_ms * 0.05f;//50;
    stats.jumpStats.executeDuration_ms = beatDuration_ms * 1.5f;//900;
    stats.jumpStats.sustainDuration_ms = beatDuration_ms * 0.2f; //100;
    stats.jumpStats.cooldownDuration_ms = beatDuration_ms * 0.0f;//200;

    stats.landingStats.executeDuration_ms = beatDuration_ms * 1.0f;

    stats.hitStats.executeDuration_ms = 300;
    stats.hitStats.sustainDuration_ms = 300;

  } else { // non-good guys

    this->healthPoints_max = 0.5f;//0.3f;

    stats.balanceFactor = 0.5f;
    //stats.agilityFactor = 0.3f;
    stats.hurtThreshold = 0.1f;

    stats.walkStats.agilityFactor = 0.7f;
    stats.walkStats.walkDuration_ms = 160;//250;

/*
    stats.jabStats.distanceFactor = 1.6f;//1.2f;//1.0f;
    stats.jabStats.onsetDuration_ms = int(1000.0f / (playState->GetCurrentMusic()->GetBPM() / 60.0f)) + 150; //500;//450;
    stats.jabStats.executeDuration_ms = 300;
    stats.jabStats.successfulSustainDuration_ms = 400;
    stats.jabStats.failureSustainDuration_ms = 1000;
    stats.jabStats.cooldownDuration_ms = 600;
*/

    stats.jabStats.distanceFactor = _ATTACKDISTANCEFACTOR * 1.0f;//1.0f;//1.6f;
    stats.jabStats.onsetDuration_ms = beatDuration_ms * 1.0;
    stats.jabStats.executeDuration_ms = beatDuration_ms * 0.4f;//0.5f;
    stats.jabStats.successfulSustainDuration_ms = beatDuration_ms * 0.3f;
    stats.jabStats.failureSustainDuration_ms = beatDuration_ms;
    stats.jabStats.cooldownDuration_ms = beatDuration_ms;

    stats.dashStats.damageFactor = 0.8f;
    stats.dashStats.onsetDuration_ms = 50;
    //stats.dashStats.executeDuration_ms = 300;
    stats.dashStats.sustainDuration_ms = 150;
    stats.dashStats.cooldownDuration_ms = 100;

    stats.blockStats.distanceFactor = 1.0f;
    stats.blockStats.onsetDuration_ms = beatDuration_ms * 0.2f;
    stats.blockStats.executeDuration_ms = beatDuration_ms * 0.5f;
    stats.blockStats.sustainDuration_ms = beatDuration_ms * 0.1f;
    stats.blockStats.cooldownDuration_ms = beatDuration_ms * 0.2f;

    stats.dodgeStats.distanceFactor = 1.0f;
    stats.dodgeStats.onsetDuration_ms = beatDuration_ms * 0.0f;
    stats.dodgeStats.executeDuration_ms = beatDuration_ms * 0.5f;
    stats.dodgeStats.sustainDuration_ms = beatDuration_ms * 0.25f;
    stats.dodgeStats.cooldownDuration_ms = beatDuration_ms * 1.0f;

    stats.throwStats.maxDistance = 10.0f;
    stats.throwStats.onsetDuration_ms = beatDuration_ms * 0.2f;
    stats.throwStats.executeDuration_ms = beatDuration_ms * 0.4f;
    stats.throwStats.sustainDuration_ms = beatDuration_ms * 0.3f;
    stats.throwStats.cooldownDuration_ms = beatDuration_ms * 2.0f;

    stats.jumpStats.distanceFactor = 0.5f;
    stats.jumpStats.onsetDuration_ms = beatDuration_ms * 0.1f;//100;
    stats.jumpStats.executeDuration_ms = beatDuration_ms * 1.5f;//900;
    stats.jumpStats.sustainDuration_ms = beatDuration_ms * 0.3f; //200;
    stats.jumpStats.cooldownDuration_ms = beatDuration_ms * 1.0f;//1000;

    stats.landingStats.executeDuration_ms = beatDuration_ms * 1.0f;

    stats.hitStats.executeDuration_ms = 500;
    stats.hitStats.sustainDuration_ms = 800;
  }

  this->healthPoints = healthPoints_max;
  this->invincibilityTimer_ms = 0;

/*
  // make sure initial values be sane
  moveStateData.jabData.duration_ms = stats.defaultJabDuration_ms;
  //moveStateData.jabData.cooldownDuration_ms = stats.defaultJabCooldownDuration_ms;
  moveStateData.jabSustainData.duration_ms = stats.defaultJabSustainDuration_ms;
  moveStateData.kickData.duration_ms = stats.defaultKickDuration_ms;
  moveStateData.kickData.cooldownDuration_ms = stats.defaultKickCooldownDuration_ms;
  moveStateData.kickSustainData.duration_ms = stats.defaultKickSustainDuration_ms;
  moveStateData.hitData.duration_ms = stats.defaultHitDuration_ms;
  moveStateData.blockData.duration_ms = stats.defaultBlockDuration_ms;
  moveStateData.blockData.remainingTime_ms = stats.defaultBlockDuration_ms;
  moveStateData.dodgeData.duration_ms = stats.defaultDodgeDuration_ms;
  //moveStateData.blockData.cooldownDuration_ms = stats.defaultBlockCooldownDuration_ms;
*/

  //xx dashData.distanceFactor = 1.0f;
  //defaultDashCooldownTime_ms = 4000;
  //if (id == 0) defaultDashCooldownTime_ms = 2000;
  //dashBias = 1.0f; // physics vs. dash 'anim' bias
  //xx dashData.duration_ms = 1000;

  //xx hitData.distanceFactor = 1.0f;
  //hitBias = 0.5f; // physics vs. hit 'anim' bias
  //xx hitData.duration_ms = 1200;

  //xx KickImpactFactor = 0.4f;

  desiredVelocity = 0;
  desiredBodyAngle = 0;

  //printf("creating player named %s\n", player->GetLastName().c_str());

/*
  playerSprite = new AnimatedSprite();

  std::string graphicFile;
  std::string neonFile;
  if (id == 0) { graphicFile = "res/gfx/player2_SSL.png"; neonFile = "res/gfx/playerbackgroundhint_cyan.png"; } else
  if (id == 1) { graphicFile = "res/gfx/player1_SSL.png"; neonFile = "res/gfx/playerbackgroundhint_red.png"; }  else
               { graphicFile = "res/gfx/player.png"; neonFile = "res/gfx/playerbackgroundhint_cyan.png"; } // failsafe

  playerSprite->LoadFile(graphicFile, e_FilterMode_Linear, 1, 1);
  AnimatedSpriteSequence sequence;
  sequence.push_back(AnimatedSpriteFrame(0, 0, 1000));
  playerSprite->AddSequence(sequence);
  playerSprite->SelectSequence(0);
*/

  std::string spritesheetFile;
  std::string neonFile;
  if (character->GetTeam() == e_Team_GoodGuys) { spritesheetFile = "res/gfx/spritesheet_character_short_red.png"; neonFile = "res/gfx/playerbackgroundhint_green.png"; } else
  if (character->GetTeam() == e_Team_BadGuys)  { spritesheetFile = "res/gfx/spritesheet_character_short_cyan.png"; neonFile = "res/gfx/playerbackgroundhint_red.png"; }  else
                                               { spritesheetFile = "res/gfx/spritesheet_character1.png"; neonFile = "res/gfx/playerbackgroundhint_green.png"; } // failsafe

  characterSprite = new AnimatedSprite();
  characterSprite->LoadFile(spritesheetFile, e_FilterMode_Nearest, 16, 8);


  // init moves

  //moves.reserve(e_MoveType_SIZE);
  for (unsigned int i = 0; i < e_MoveType_SIZE; i++) {
    moves[i] = nullptr;
  }
  moves[e_MoveType_Walk] = new CharacterWalkMove(playState, characterSprite, stats.walkStats);
  moves[e_MoveType_Landing] = new CharacterLandingMove(playState, characterSprite, stats.landingStats);
  moves[e_MoveType_Hit] = new CharacterHitMove(playState, characterSprite, stats.hitStats);
  /*
  moves[e_MoveType_Jab] = new CharacterJabMove(playState, characterSprite, stats.jabStats);
  moves[e_MoveType_Dash] = new CharacterDashMove(playState, characterSprite, stats.dashStats);
  moves[e_MoveType_Block] = nullptr;
  moves[e_MoveType_Dodge] = new CharacterDodgeMove(playState, characterSprite, stats.dodgeStats);
  moves[e_MoveType_Jump] = new CharacterJumpMove(playState, characterSprite, stats.jumpStats);
  */

  // physics state

  SetActiveMoveType(this->physicsState, e_MoveType_Walk);
  this->previousPhysicsState = this->physicsState;


  //playerObject = new Object(&playState->GetWorldNode(), playerSprite);
  playerObject = new Object(&playState->GetWorldNode());//, characterSprite);
  playerObject->spatial.z = 0.0f;
  playerObject->spatial.scale = Vector2(1.0f, 1.0f);
//  playerObject->spatial.scale = Vector2(0.707f, 0.707f);
//  playerObject->spatial.scale *= 1.414f;
  //playerObject->spatial.position -= playerObject->spatial.scale.coords[1]; // underside of sprite == 0, 0
  playerObject->spatial.orientation = playState->GetViewOrientation();

/*
  shadowSprite = new Sprite();
  shadowSprite->LoadFile("res/gfx/shadow32x16.png", e_FilterMode_Anisotropic);
  shadowSprite->SetAlpha(0.4f);//0.3f);
  shadowObject = new Object(&playState->GetWorldNode(), shadowSprite);
  //shadowObject->spatial.position.Set(-0.2f, 0.0f);
  shadowObject->spatial.orientation = playState->GetViewOrientation();
  shadowObject->spatial.scale.Set(1.6f, 0.8f);
  //shadowObject->spatial.scale.Set(2.0f, 1.0f);
*/

  graphicObject = new Object(playerObject, characterSprite);
  //graphicObject->spatial.position = Vector2(0.0f, -1.0f);
  graphicObject->spatial.scale = Vector2(2.0f, 2.0f);
  graphicObject->spatial.position = Vector2(0.0f, -0.5f * graphicObject->spatial.scale.coords[1]);
  //graphicObject->spatial.scale = Vector2(1.414f, 1.414f);
  //graphicObject->spatial.scale *= 1.414f;
  //graphicObject->spatial.position -= graphicObject->spatial.scale.coords[1]; // underside of sprite == 0, 0

  neonSprite = new Sprite();
  neonSprite->LoadFile(neonFile, e_FilterMode_Nearest);
  neonSprite->SetAlpha(0.0f);
  neonObject = new Object(&playState->GetWorldNode(), neonSprite);
  neonObject->spatial.z = 0.0f;
  //neonObject->spatial.scale = Vector2(4.0f, 2.0f);
  //neonObject->spatial.scale = Vector2(1.4f, 0.7f);// * playerObject->spatial.scale;
  neonObject->spatial.scale = Vector2(1.5f, 0.75f);// * playerObject->spatial.scale;
  neonObject->spatial.orientation = playState->GetViewOrientation();

/*
  for (int i = 0; i < e_MoveState_SIZE; i++) {
    modeSprite[i] = new Sprite();
  }
  modeSprite[e_MoveState_Physics]->LoadFile("res/gfx/player_mode_neutral.png", e_FilterMode_Linear);
  modeSprite[e_MoveState_KickBuffer]->LoadFile("res/gfx/player_mode_kickbuffer.png", e_FilterMode_Linear);
  modeSprite[e_MoveState_Jab]->LoadFile("res/gfx/player_mode_kick.png", e_FilterMode_Linear);
  modeSprite[e_MoveState_JabSustain]->LoadFile("res/gfx/player_mode_neutral.png", e_FilterMode_Linear);
  modeSprite[e_MoveState_Kick]->LoadFile("res/gfx/player_mode_kick.png", e_FilterMode_Linear);
  modeSprite[e_MoveState_KickSustain]->LoadFile("res/gfx/player_mode_neutral.png", e_FilterMode_Linear);
  modeSprite[e_MoveState_Hit]->LoadFile("res/gfx/player_mode_hit.png", e_FilterMode_Linear);
  modeSprite[e_MoveState_HitStun]->LoadFile("res/gfx/player_mode_hit.png", e_FilterMode_Linear);
  modeSprite[e_MoveState_Block]->LoadFile("res/gfx/player_mode_defend.png", e_FilterMode_Linear);
  modeSprite[e_MoveState_Dodge]->LoadFile("res/gfx/player_mode_defend.png", e_FilterMode_Linear);
  modeObject = new Object(&playState->GetWorldNode(), modeSprite[e_MoveState_Physics]);
  modeObject->spatial.z = 0.0f;
  modeObject->spatial.scale = Vector2(0.5f, 0.5f);
*/

  if (character->GetTeam() == e_Team_GoodGuys) name = "bruce"; else
  if (character->GetTeam() == e_Team_BadGuys)  name = "bad guy"; else
                                               name = "unnamed warrior"; // failsafe

  nameSprite = new Sprite();
  SDL_Color color = { 0, 200, 200 };
  //SDL_Color color = { 250, 250, 100 };
  if (character->GetTeam() == e_Team_GoodGuys) {
    color.r = 250; color.g = 250; color.b = 100;
  }
  nameSprite->CreateText(game->GetFont(e_FontID_Debug), name, color, e_FilterMode_Nearest);
  nameObject = new Object(0, nameSprite);
  nameObject->spatial.z = 0.0f;
  nameObject->spatial.scale = Vector2(nameSprite->GetWidth(), nameSprite->GetHeight());
  nameSprite->SetAlpha(0.5f);
  //nameObject->spatial.position = Vector2(0.0f, -2.0f);

  SetBasePosition(position);
  const Tile *tile = playState->GetStage()->GetStadium()->GetTile(position);
  if (tile) physicsState.zPosition = tile->GetZPosition();// else if (character->GetTeamID() == 0) printf("NO TILE!\n");
//  if (character->GetTeamID() == 0) position.Print();
//  if (character->GetTeamID() == 0) printf("%f\n", physicsState.zPosition);

  //if (matchTeam->GetSide() == -1) bodyAngle = 0; else bodyAngle = pi;

  //xx dashData.Reset();
  //xx dashCooldownTime_ms = 0;

  //xx requestKick = false;
  //UpdateSituation();

//  currentSequence.sequence = 0;
}

CharacterModelView::~CharacterModelView() {
  delete playerObject;
  //delete playerSprite;
  delete graphicObject;
  delete characterSprite;
  delete neonSprite;
  delete neonObject;
  /*
  delete modeObject;
  for (unsigned int i = 0; i < e_MoveType_SIZE; i++) {
    delete modeSprite[i];
  }
  */
  delete nameSprite;
  delete nameObject;

  for (unsigned int i = 0; i < e_MoveType_SIZE; i++) {
    if (moves[i] != nullptr) delete moves[i];
  }

}

void CharacterModelView::SetBasePosition(const Vector2 &newPos) {
  basePosition = newPos;
  physicsState.position = newPos;
  playerObject->spatial.position = newPos;
}

const Vector2 &CharacterModelView::GetBasePosition() const {
  return basePosition;
}

void CharacterModelView::OnStepPhysicsTime() {

  // we can use physicsState here both to read from and to write to.


  // regen health

  if (physicsState.activeMove->GetMoveType() == e_MoveType_Walk && GetHealthPoints() > 0.0f)
    this->healthPoints = Clamp(this->healthPoints + playState->GetPhysicsTimeStep_ms() * 0.0001f, 0.0f, this->healthPoints_max);

  invincibilityTimer_ms = std::max(0, (signed int)invincibilityTimer_ms - (signed int)playState->GetPhysicsTimeStep_ms());

/*
  // teleport?
  if (character->GetController()->GetControllerType() == e_ControllerType_Human) {
    const std::vector<Enemy*> &enemiesRef = playState->GetStage()->GetAllEnemiesRef();
    for (unsigned int i = 0; i < enemiesRef.size(); i++) {
      if (enemiesRef.at(i)->GetEnemyType() == e_EnemyType_Grenade && enemiesRef.at(i)->GetTeam() == e_Team_GoodGuys) {
        if (enemiesRef.at(i)->GetPosition().GetDistance(GetPosition()) > 7.0f) {
          // teleport!
          SetPosition(physicsState, enemiesRef.at(i)->GetPosition());
          enemiesRef.at(i)->Expire();
        }
      }
    }
  }
*/

  if (physicsState.activeMove->GetMoveType() == e_MoveType_Walk) {

    WalkStats adaptedWalkStats = stats.walkStats;

    adaptedWalkStats.agilityFactor *= 4.0f;

    // move independent of beat
    adaptedWalkStats.agilityFactor *= 0.25f;

/*
    // move on the beat
    // pow( (cos(x * 4 * pi) * 0.5 + 0.5) * (0.5 + 0.5 * (cos(x * 2 * pi) * 0.5 + 0.5)) , 0.8 ) | from x = 0 to 2
    float musicPowerFactor1Step = cos(playState->GetCurrentMusic()->GetBeatOffset() * 2 * pi) * 0.5f + 0.5f;
    float musicPowerFactor2Step = cos(playState->GetCurrentMusic()->GetBeatOffset() * 4 * pi) * 0.5f + 0.5f;
    //float musicPowerFactor = musicPowerFactor1Step * (0.5f + 0.5f * musicPowerFactor2Step);
    float musicPowerFactor = musicPowerFactor2Step * (0.5f + 0.5f * musicPowerFactor1Step);
    musicPowerFactor = pow(musicPowerFactor, 1.5f);
    //musicPowerFactor = pow(musicPowerFactor, 2.5f); // mostly the main beats
    //musicPowerFactor = 1.0f - musicPowerFactor; // move offbeat
    //musicPowerFactor = Curve(musicPowerFactor, 1.0f);
    //musicPowerFactor = 0.5f * musicPowerFactor + 0.5f * (1.0f - playState->GetCurrentMusic()->GetBeatOffset());
    adaptedWalkStats.agilityFactor *= 0.0f + 1.0f * musicPowerFactor;
*/

/*
    // binary
    if (playState->GetCurrentMusic()->GetBeatOffset() < 0.05f || playState->GetCurrentMusic()->GetBeatOffset() > 0.95f) {
      adaptedWalkStats.agilityFactor *= 1.0f;
    } else if (playState->GetCurrentMusic()->GetBeatOffset() > 0.45f && playState->GetCurrentMusic()->GetBeatOffset() < 0.55f) {
      adaptedWalkStats.agilityFactor *= 0.5f;
    } else {
      adaptedWalkStats.agilityFactor *= 0.0f;
    }
*/

    // less agile'y on speed
    adaptedWalkStats.agilityFactor *= 1.8f;
    adaptedWalkStats.agilityFactor *= 1.0f - 0.8f * pow(Clamp(GetVelocity() / playState->GetMediumVelocity(), 0.0f, 1.0f), 1.0f);
    //if (desiredVelocity > playState->GetMediumVelocity() && GetVelocity() > playState->GetSlowVelocity()) adaptedWalkStats.agilityFactor *= 0.1f;//1.0f - 0.99f * pow(NormalizedClamp(desiredVelocity, playState->GetMediumVelocity(), playState->GetFullVelocity()), 0.2f);
    //if (id == 0) printf("%f\n", physicsState.movement.GetLength());

    // sprint
    /*
    if (desiredVelocity > playState->GetMediumVelocity() && GetVelocity() > playState->GetMediumVelocity()) {
      //if (moves[e_MoveType_Walk]->GetCurrentTimeFactor() >= 1.0f || walkDuration_ms = playState->GetCurrentMusic()->GetBeatDuration_ms() * 2) {
      adaptedWalkStats.walkDuration_ms = playState->GetCurrentMusic()->GetBeatDuration_ms() * 1;
      //}
      //adaptedWalkStats.agilityFactor *= 0.1f;
    }
    */

    static_cast<CharacterWalkMove*>(moves[e_MoveType_Walk])->SetStats(adaptedWalkStats);
  }


  // advance move times and such

  UpdateMoves();


  // physics, this is where the magic happens


  // apply active move

  Vector2 resultingMovement;
  float resultingZMovement;
  radian resultingBodyAngle;
  physicsState.activeMove->Calculate(physicsState.movement, physicsState.zMovement, physicsState.bodyAngle, desiredDirection * desiredVelocity, this->desiredBodyAngle, playState->GetPhysicsTimeStep_ms(), resultingMovement, resultingZMovement, resultingBodyAngle);
  physicsState.movement = resultingMovement;//.GetNormalizedMax(100);
  physicsState.bodyAngle = resultingBodyAngle;
  physicsState.zMovement = resultingZMovement;

/* now in walk move
  // body rotation takes time
  radian maxRadiansPerSecond = 0.5f * pi;
  radian angleToNew = Vector2(1, 0).GetRotated(resultingBodyAngle).GetAngle(Vector2(1, 0).GetRotated(physicsState.bodyAngle));
  radian maxAngle = playState->GetPhysicsTimeStep_ms() * 0.001f * maxRadiansPerSecond;
  angleToNew = Clamp(angleToNew, -maxAngle, maxAngle);
  physicsState.bodyAngle = Vector2(1, 0).GetRotated(physicsState.bodyAngle + angleToNew).GetAngle();
  //if (id == 0) printf("%f\n", physicsState.bodyAngle);
*/

  // gravity (now inside the moves)
  //ApplyGravity(physicsState.zMovement, 20.0f, playState->GetPhysicsTimeStep_ms());


  // do not fall through ground

  bool hitFloor = ApplyFloor(physicsState.position, physicsState.zPosition, physicsState.zMovement, playState->GetStage()->GetStadium(), 0.0f);
  if (GetActiveMoveType() == e_MoveType_Walk || GetActiveMoveType() == e_MoveType_Jump) {
    float landingMoveThreshold = 3.0f;
    if (hitFloor && previousPhysicsState.zMovement < -landingMoveThreshold) {
      RequestLanding(NormalizedClamp(abs(previousPhysicsState.zMovement) - landingMoveThreshold, 0.0f, 5.0f) * 0.5f + 0.5f);
    }
  }


  // air resistance

  // http://hyperphysics.phy-astr.gsu.edu/hbase/airfri.html
  float crossSectionalArea = 1.0f;
  ApplyFriction(physicsState.movement, crossSectionalArea, playState->GetPhysicsTimeStep_ms());
/*
  // exponential
  // the problem with this code is that a character can come to a sudden standstill when being accelerated too fast all of a sudden
  //float friction = 0.2f + 0.1f * velocity; // higher == more
  float friction = 0.0f + 0.005f * GetVelocity() * GetVelocity(); // higher == more
  friction *= frictionCoefficient;
  physicsState.movement -= (physicsState.movement * friction * playState->GetPhysicsTimeStep_ms() * 0.001f).GetNormalizedMax(physicsState.movement.GetLength()); // normalize so we don't get a pingpong around 0 velo
*/
/*
  // linear
  float frictionCoeff = 0.9f;
  float frictionInv = pow(1.0f - frictionCoeff, playState->GetPhysicsTimeStep_ms() * 0.001f);
  if (id == 0) printf("%f\n", frictionInv);
  physicsState.movement *= frictionInv;
*/


  // move!

  //physicsState.position += physicsState.movement * playState->GetPhysicsTimeStep_ms() * 0.001f;
  //Vector2 plannedPosition = physicsState.position + (physicsState.movement + forcedMovementAddition) * playState->GetPhysicsTimeStep_ms() * 0.001f;
  Vector2 plannedPosition = physicsState.position + physicsState.movement * playState->GetPhysicsTimeStep_ms() * 0.001f;
  physicsState.zPosition = physicsState.zPosition + physicsState.zMovement * playState->GetPhysicsTimeStep_ms() * 0.001f;
  //playState->GetStage()->MoveStageEntity(_character, plannedPosition);


  // flow

  if (character->GetController()->GetControllerType() == e_ControllerType_Human && !_DISABLEFLOW) {// && GetActiveMoveType() == e_MoveType_Walk) {

    //Vector2 augmentedPhysicsStateMovement = physicsState.movement;
    int flowAxis = playState->GetStage()->GetStadium()->GetFlowAxis();
    float flowVelocity = playState->GetStage()->GetStadium()->GetFlowVelocity();

    plannedPosition.coords[flowAxis] += flowVelocity * playState->GetPhysicsTimeStep_ms() * 0.001f * GetActiveMove()->GetFlowInfluenceFactor();

    // auto-magnet towards the flow line
    bool flowLineMagnet = true;
    //if (id != 0) flowLineMagnet = false;
    float flowLineIntensityMultiplier;
    if (GetActiveMoveType() == e_MoveType_Walk) flowLineIntensityMultiplier = 1.0f;
                                           else flowLineIntensityMultiplier = 0.5f;
    if (flowLineMagnet) {
      int flowAxis = playState->GetStage()->GetStadium()->GetFlowAxis();
      float playerToFlowLine = playState->GetStage()->GetStadium()->GetFlowPosition().coords[flowAxis] - character->GetPosition().coords[flowAxis];
      signed int playerToFlowLineSignSide = SignSide(playerToFlowLine);
      float toFlowLineMagnet = NormalizedClamp(playerToFlowLine, -_AXISMOVEMENT_BANDWIDTH * 0.5f, _AXISMOVEMENT_BANDWIDTH * 0.5f) * 2.0f - 1.0f; // -1 to 1;
      float toFlowLineIntensity = pow(fabs(toFlowLineMagnet), _AXISMOVEMENT_CENTERPULL_EXP); // 0 to 1
      toFlowLineIntensity *= 5.0f * flowLineIntensityMultiplier;
      plannedPosition.coords[flowAxis] += playerToFlowLineSignSide * toFlowLineIntensity * playState->GetPhysicsTimeStep_ms() * 0.001f * GetActiveMove()->GetFlowInfluenceFactor();
      //plannedPosition.coords[flowAxis] += playerToFlowLineSignSide * toFlowLineIntensity * playState->GetPhysicsTimeStep_ms() * 0.001f;
    }

  }

  physicsState.position = playState->GetStage()->CollideNewPosition(this->character, physicsState.position, plannedPosition, physicsState.zPosition);

  // tmp test: looparound map y
  /*
  if (id == 0) {
    if (physicsState.position.coords[1] >= playState->GetStage()->GetStadium()->GetDimensions()[1] - 3) {
      physicsState.position.coords[1] = 3;
      //physicsState.zPosition = ...;
    }
  }
  */

}

void CharacterModelView::InitPhysicsStateQueue() {
  // use current situation as starting point for queued one
  queuedPhysicsState = physicsState;
}

void CharacterModelView::ApplyPhysicsStateQueue() {
  // apply the queued state to the real situation

  Vector2 currentPosition = physicsState.position;
  //float currentZPosition = physicsState.zPosition;
  physicsState = queuedPhysicsState;
  // maybe the queued position is not traversable; check with the stage first
  physicsState.position = playState->GetStage()->CollideNewPosition(this->character, currentPosition, queuedPhysicsState.position, queuedPhysicsState.zPosition);

}

void CharacterModelView::PostStepPhysicsTime() {

  // stuff that we need to do now we know the new physics sitation
  // may NOT update physicsState or stateData

  // fancy neon graphics to warn other players of upcoming violence
  /*
  if (physicsState.activeMoveState == e_MoveState_KickBuffer) {
    neonSprite->SetAlpha(pow(moveStateData.kickBufferData.powerFactor, 3.0f));
  } else if (previousPhysicsState.activeMoveState == e_MoveState_KickBuffer) {
    neonSprite->SetAlpha(0.0f);
  }
  */

  /*
  if (id == 0) {

    //if (physicsState.activeMove->GetMoveType() == e_MoveType_Walk) {
    if (moves[e_MoveType_Jab] && moves[e_MoveType_Jab]->IsLegal()) {
      //float intensity = Clamp(1.0f - (moveStateData.jabData.timeSinceLastActivation_ms * 0.002f), 0.0f, 1.0f);
      float intensity = 0.2f;
      neonSprite->SetAlpha(intensity);
    //} else if (previousPhysicsState.activeMove->GetMoveType() == e_MoveType_Jab) {
    } else {
      neonSprite->SetAlpha(0.0f);
    }

  }
  */

  if (character->GetTeam() == e_Team_BadGuys) {
    if (physicsState.activeMove->GetMoveType() == e_MoveType_Jab) {
      float intensity;// = Clamp(1.0f - (physicsState.activeMove->GetTimeSinceLastActivation_ms() * 0.002f), 0.0f, 1.0f);
      //neonSprite->SetAlpha(pow(intensity, 0.6f));
      if (physicsState.activeMove->GetTimeSinceLastActivation_ms() < stats.jabStats.onsetDuration_ms) intensity = 0.5f; else intensity = 0.0f;
      neonSprite->SetAlpha(intensity);
    } else if (previousPhysicsState.activeMove->GetMoveType() == e_MoveType_Jab) {
      neonSprite->SetAlpha(0.0f);
    }
  }

  if (previousPhysicsState.activeMove->GetMoveType() != physicsState.activeMove->GetMoveType()) {
    OnChangeMoveType(previousPhysicsState.activeMove->GetMoveType(), physicsState.activeMove->GetMoveType());
  }

  //if (id == 0) printf("%i, %i\n", previousPhysicsState.activeMoveState, physicsState.activeMoveState);
  previousPhysicsState = physicsState;


  // sprites

  playerObject->spatial.position = physicsState.position;
  neonObject->spatial.position = physicsState.position;//playerObject->spatial.position;

  // height (for jumps and such)
  playerObject->spatial.position.coords[0] -= physicsState.zPosition;// * 1.414f; // pow(2.0f, 0.5f);
  playerObject->spatial.position.coords[1] -= physicsState.zPosition;// * 1.414f; // pow(2.0f, 0.5f);
  //playerObject->spatial.position -= playerObject->spatial.scale.coords[1] * 0.25f; // underside of sprite == 0.0
  //playerObject->spatial.position -= 0.5f; // underside of sprite == 0.0
  //shadowObject->spatial.position += 0.1f; // a lil' lower on screen
  //shadowObject->spatial.position.coords[1] = 1.41f * physicsState.zPosition; // todo: maybe detach shadow from player; this is silly (moving it down twice to correct for jumping player sprite pos)

  const Tile *tile = playState->GetStage()->GetStadium()->GetTile(physicsState.position);
  if (tile) {
    neonObject->spatial.position.coords[0] -= tile->GetZPosition();// * 1.414f; // pow(2.0f, 0.5f); (used to be: "0.707f; // 1/sqrt(2)" )
    neonObject->spatial.position.coords[1] -= tile->GetZPosition();// * 1.414f; // pow(2.0f, 0.5f);
    //shadowObject->spatial.position.coords[0] -= tile->zPosition;// * 1.414f; // pow(2.0f, 0.5f); (used to be: "0.707f; // 1/sqrt(2)" )
    //shadowObject->spatial.position.coords[1] -= tile->zPosition;// * 1.414f; // pow(2.0f, 0.5f);
  }

  // choose what direction we are looking in, considering there's 8 different possible directions (when it comes to sprites)
  int directionID = std::max(0, std::min(7, int(floor(ModuloFloat((GetBodyAngle() - 0.125f * pi) / (2.0f * pi), 1.0f) * 8.0f)) ));
  // left/right only mode if (directionID < 4) directionID = 2; else directionID = 6;
  physicsState.activeMove->SelectSpriteSequence(directionID);

  // step animated sequence
  characterSprite->OnStepTime(playState->GetPhysicsTimeStep_ms());

}

void CharacterModelView::Draw(const Viewport &viewport, const Camera &camera) const {

  playState->GetStage()->DrawShadow(physicsState.position, physicsState.zPosition, 1.0f, 1.0f);


  // health bar

  if (character->GetTeam() == e_Team_GoodGuys) {
    SDL_SetRenderDrawColor(GetSDLRenderer(), 0, 250, 0, 180);
  } else if (character->GetTeam() == e_Team_BadGuys) {
    SDL_SetRenderDrawColor(GetSDLRenderer(), 250, 0, 0, 180);
    SDL_SetRenderDrawBlendMode(GetSDLRenderer(), SDL_BLENDMODE_BLEND);
  }
  Vector2 hpBarPos = WorldToScreenVector(playerObject->GetDerivedSpatial().position + Vector2(-1.05f, -1.05f), game->GetMainViewport(), playState->GetCamera());
  float hpBarWidth = std::max(0.0f, this->healthPoints) / this->healthPoints_max;
  hpBarWidth *= 60.0f;
  SDL_Rect rect;
  rect.x = hpBarPos.coords[0] - hpBarWidth * 0.5f;
  rect.y = hpBarPos.coords[1] - 2;
  rect.w = hpBarWidth * 1.0f;
  rect.h = 2;
  SDL_RenderDrawRect(GetSDLRenderer(), &rect);
  rect.y += 2;
  SDL_RenderDrawRect(GetSDLRenderer(), &rect);
  SDL_SetRenderDrawColor(GetSDLRenderer(), 255, 255, 255, 255);
  SDL_SetRenderDrawBlendMode(GetSDLRenderer(), SDL_BLENDMODE_NONE);

  if (character->GetTeam() == e_Team_GoodGuys && character->GetController()->GetControllerType() == e_ControllerType_Human) {
    neonSprite->SetAlpha(0.5f);
  } else {
    neonSprite->SetAlpha(0.0f);
  }
  neonObject->Draw(viewport, camera);

//  const Tile *tile = playState->GetStage()->GetStadium()->GetTile(physicsState.position);
//  shadowObject->spatial.position.coords[0] -= tile->zPosition * 0.707f; // 1/sqrt(2)
//  shadowObject->spatial.position.coords[1] -= tile->zPosition * 0.707f; // 1/sqrt(2)
  //shadowObject->Draw(viewport, camera);

  graphicObject->Draw(viewport, camera);
  //modeObject->spatial.position = playerObject->spatial.position;
  //modeObject->spatial.orientation = playerObject->spatial.orientation;
//  modeObject->Draw(viewport, camera);
  nameObject->spatial.position = WorldToScreenVector(playerObject->GetDerivedSpatial().position + Vector2(-1.25, -1.25), game->GetMainViewport(), playState->GetCamera());
  nameObject->Draw();

/* too.. intense, maybe?
  float musicPowerFactor = 1.0f - playState->GetCurrentMusic()->GetBeatOffset();
  float intensity = 0.6f * (0.7f + 0.3f * musicPowerFactor);
*/

  float charLightScale = 1.0;
  float charLightintensity = 0.5f;

  if (character->GetTeam() == e_Team_GoodGuys) {
    //playState->GetStage()->DrawLight(GetPosition(), 0, 6.0f, 0.15f, 0.25f, 0.2f);
    //playState->GetStage()->DrawLight(GetPosition(), 0, 6.0f, 0.35f, 0.25f, 0.05f);
    playState->GetStage()->DrawLight(GetPosition() - GetZPosition(), 0, 4.0f * charLightScale, 0.8f * charLightintensity, 0.4f * charLightintensity, 0.3f * charLightintensity);
  } else {
    playState->GetStage()->DrawLight(GetPosition() - GetZPosition(), 0, 2.5f * charLightScale, 0.1f * charLightintensity, 0.3f * charLightintensity, 0.7f * charLightintensity);
  }

}

/*
void CharacterModelView::GetSpriteBounds(Vector2 &center, Vector2 &radius) const {
  center = physicsState.position - physicsState.zPosition;
  //radius = graphicObject->spatial.scale * 0.5f;
  radius = Vector2(1.0f, 2.0f); // todo: how to make this nice
  //radius = tmpSpatial.scale * 0.5f;// * GetRadius();

  //playState->DrawDebugLine(center - radius.GetRotated(playState->GetViewOrientation()), center + radius.GetRotated(playState->GetViewOrientation()), e_CoordinateSystem_World, 0.2f, 0.6f, 0.2f, 0.1f);
  //playState->DrawDebugText("center", center, e_CoordinateSystem_World, 1.0f, 0.3f, 0.3f, 0.1f);
}
*/

void CharacterModelView::OnCollide(const StageEntity *entity) {

  // read from physicsState, write to queuedPhysicsState

  const e_MoveType &moveType = physicsState.activeMove->GetMoveType();
  bool lethal = physicsState.activeMove->IsLethal();

  if (entity->GetType() == e_StageEntityType_Character) {

    const Character *oppCharacter = static_cast<const Character*>(entity);
    const CharacterModelView *oppMV = oppCharacter->GetModelView_const();

    if (IsOnProcessedMoveIDList(oppMV->GetActiveMoveID())) return; // !! breakout !!

    const e_MoveType &oppMoveType = oppMV->GetActiveMoveType();
    bool oppLethal = oppMV->GetActiveMove()->IsLethal();


    // OMG they be hittin you

//    if (oppMoveType == e_MoveType_Jab) {


    if (oppLethal && invincibilityTimer_ms == 0) {

      float hurtFactor = oppMV->GetActiveMove()->GetDamagePotentialFactor();

      Vector2 movementDelta = GetMovement() - oppCharacter->GetMovement();
      float movementDeltaFactor = NormalizedClamp(movementDelta.GetLength(), 0.0f, 20.0f);
      movementDeltaFactor = 0.5f;//0.5f + 0.5f * movementDeltaFactor; // it's just a bonus; hitting while standing still should still yield some results
      //if (id == 0) printf("movdelfac: %f\n", movementDeltaFactor);

      float impactAngleFactor = movementDelta.GetNormalized(0).GetDotProduct((GetPosition() - oppCharacter->GetPosition()).GetNormalized(0)) * -0.5f + 0.5f;

      hurtFactor *= movementDeltaFactor * impactAngleFactor;
      //if (id == 0) printf("impactfac: %f\n", impactAngleFactor);

      // add this interaction to a list. if another interaction of this move with this character occurs, it won't do anything.
      processedMoveIDList.push_back(oppMV->GetActiveMoveID());

      // calculate 'window of hurt'
      //if (id == 0) printf("timeFactor: %f\n", timeFactor);
      //float hurtFactor = (timeFactor > 0.1f && timeFactor < 0.9f) ? 1.0f : 0.0f;
      //float hurtFactor = 1.0f;//(timeFactor > 0.1f && timeFactor < 0.85f) ? 1.0f : 0.0f;
      //if (id == 0) printf("hurtFactor: %f\n", hurtFactor);

      if (moveType != e_MoveType_Block && !lethal) {

        //RequestHit(queuedPhysicsState, oppMV->GetMovement());
        // hax if (oppMV->GetStateData().jabData.timeSinceLastActivation_ms > 300)
        if (hurtFactor > stats.hurtThreshold) {

          Vector2 oppBodyDirVector = _DEFAULTVECTOR.GetRotated(oppMV->GetBodyAngle());
          Vector2 oppToMeVector = (GetPosition() - oppMV->GetPosition()).GetNormalized(0);
          if (oppMoveType == e_MoveType_Jab) {
            RequestHit(queuedPhysicsState, (oppBodyDirVector * 0.8f + oppToMeVector * 0.2f).GetNormalized(0) * hurtFactor * 3.0f);
          } else if (oppMoveType == e_MoveType_Dash) {
            RequestHit(queuedPhysicsState, (oppBodyDirVector * 0.3f + oppToMeVector * 0.7f).GetNormalized(0) * hurtFactor * 5.0f);
          }
          AddHealthPoints(hurtFactor * -1.0f);

          //printf("OUCH\n");
        }

      }

      else if (lethal) {

        if (hurtFactor > stats.hurtThreshold) {
          //SetActiveMoveState(e_MoveState_Physics);
          //queuedPhysicsState.movement = queuedPhysicsState.movement * 0.2f; // 'stun'
          queuedPhysicsState.movement = (-Vector2(1, 0)).GetRotated(queuedPhysicsState.bodyAngle) * 2.8f; // bounce back velocity

          static_cast<CharacterWalkMove*>(moves[e_MoveType_Walk])->SetVariables(physicsState.movement, desiredDirection * desiredVelocity);
          SetActiveMoveType(queuedPhysicsState, e_MoveType_Walk);

          //RequestJabSustain(queuedPhysicsState);

          Vector2 oppBodyDirVector = _DEFAULTVECTOR.GetRotated(oppMV->GetBodyAngle());
          Vector2 oppToMeVector = (GetPosition() - oppMV->GetPosition()).GetNormalized(0);
          RequestHit(queuedPhysicsState, (oppBodyDirVector * 0.4f + oppToMeVector * 0.6f).GetNormalized(0) * hurtFactor * 1.5f);
          AddHealthPoints(hurtFactor * -0.5f);

          //printf("ouch\n");
        }

      }

    }


    // WTF you be hittin them

    if (lethal) {

      //if (oppMoveState != e_MoveState_Block) {
      if (moveType == e_MoveType_Jab) {
        if (static_cast<CharacterJabMove*>(physicsState.activeMove)->GetHitSomething() == false) {
          static_cast<CharacterJabMove*>(physicsState.activeMove)->SetHitSomething(true);

          if (oppMoveType != e_MoveType_Block) {

            playState->SoundPlay(e_GlobalSound_Hit01, physicsState.activeMove->GetDamagePotentialFactor() * 0.5f + 0.5f);
            if (character->GetController()->GetControllerType() == e_ControllerType_Human) game->JoyRumble(0, GetActiveMove()->GetDamagePotentialFactor() * _RUMBLEFACTOR, 250);

          } else { // if (oppMoveType == e_MoveType_Block) {

            playState->SoundPlay(e_GlobalSound_Hit01, physicsState.activeMove->GetDamagePotentialFactor() * 0.5f);
            if (character->GetController()->GetControllerType() == e_ControllerType_Human) game->JoyRumble(0, GetActiveMove()->GetDamagePotentialFactor() * 0.6f * _RUMBLEFACTOR, 400);

          }

          //printf("piw\n");
        }
        //game->JoyRumble(0, 0.5f, playState->GetPhysicsTimeStep_ms());
      }

      if (moveType == e_MoveType_Dash) {
        if (static_cast<CharacterDashMove*>(physicsState.activeMove)->GetHitSomething() == false) {
          static_cast<CharacterDashMove*>(physicsState.activeMove)->SetHitSomething(true);
          playState->SoundPlay(e_GlobalSound_Hit01, physicsState.activeMove->GetDamagePotentialFactor());
          //printf("piw\n");
        }
      }

    }

  } else if (entity->GetType() == e_StageEntityType_Enemy) {
    if (character->GetTeam() != entity->GetTeam() || entity->GetTeam() == e_Team_Evil) {
      if (static_cast<const Enemy*>(entity)->GetEnemyType() == e_EnemyType_Grenade) {
        if (moveType != e_MoveType_Block && moveType != e_MoveType_Dodge && invincibilityTimer_ms == 0) {
          Vector2 hitDirection = entity->GetMovement().GetNormalized(_DEFAULTVECTOR);
          //RequestHit(hitDirection, 1.0f);
          float severity = static_cast<const Enemy*>(entity)->GetDamagePotentialFactor();//NormalizedClamp(entity->GetVelocity(), 0.0f, 20.0f);
          //if (entity->GetTeam() == e_Team_GoodGuys) printf("severity: %f\n", severity);
          RequestHit(hitDirection * (severity * 0.6f + 0.4f) * 2.0f * (1.0f - stats.balanceFactor * 0.5f), 0.2f + 0.8f * severity);
          //AddHealthPoints(-0.8f * severity);
          AddHealthPoints(severity * -1.0f);
          //playState->SoundPlay(e_GlobalSound_Hit01, 0.3f);
        }
      }
    }
  }

}

void CharacterModelView::SetDesiredVelocity(float desiredVelocity) {
  this->desiredVelocity = Clamp(desiredVelocity, 0, playState->GetFullVelocity());
  //if (id == 0) printf("%f\n", this->desiredVelocity);
}

bool CharacterModelView::RequestJab(const Vector2 &direction, float distanceFactor) {

  assert(moves[e_MoveType_Jab] != nullptr);

  if (_JAB_ONBEAT) {
    //float musicPowerFactor = cos(playState->GetCurrentMusic()->GetBeatOffset() * 2 * pi) * 0.5f + 0.5f;
    float musicPowerFactor = playState->GetCurrentMusic()->GetBeatOffset();
    if (musicPowerFactor < 0.95f && musicPowerFactor > 0.05f) return false;
  }

  if ( (physicsState.activeMove->GetMoveType() == e_MoveType_Walk && fabs(physicsState.zMovement) < 1.5f) ||
       //(physicsState.activeMove->GetMoveType() == e_MoveType_Jump) ||// && fabs(physicsState.zMovement) < 2.0f) ||
       (physicsState.activeMove->GetMoveType() == e_MoveType_Landing && physicsState.activeMove->GetCurrentTimeFactor() > 0.5f) ||
//     previousPhysicsState.activeMove->GetMoveType() == e_MoveType_Jab ||
       (physicsState.activeMove->GetMoveType() == e_MoveType_Dodge && physicsState.activeMove->GetCurrentTimeFactor() > 0.5f) ||
       (physicsState.activeMove->GetMoveType() == e_MoveType_Block && physicsState.activeMove->GetCurrentTimeFactor() > 0.5f) ) { // todo: this is a hax, needs to be like: getcurrenttimefactor == in sustain

    //if (this->_character->GetTeamID() == 0) printf("%i, %i\n", previousPhysicsState.activeMove->GetMoveType(), physicsState.activeMove->GetMoveType());

//    if (previousPhysicsState.activeMove->GetMoveType() != e_MoveType_Jab) { // first jab

    if (moves[e_MoveType_Jab]->IsLegal()) {

      static_cast<CharacterJabMove*>(moves[e_MoveType_Jab])->SetVariables(physicsState.movement, direction, distanceFactor);
      SetActiveMoveType(this->physicsState, e_MoveType_Jab);

      return true;
    }
/*
    } else {//if (physicsState.activeMove->GetMoveType() != e_MoveType_Jab) { // follow up jab

      printf("GVD!\n");
      //RequestDodge(targetPhysicsState, -direction);
todo: hier was ik, dit is een breinbreker :p hoe maak ik deze hele if constructie werkende?
      // make some super epic hit combo move thing here
      //if (moves[e_MoveType_Jab]->IsLegal()) {
      //if (physicsState.activeMove->GetMoveType() != e_MoveType_Jab) { // follow up jab
        RequestDodge(targetPhysicsState, -direction);
        //static_cast<CharacterJabMove*>(moves[e_MoveType_Jab])->SetVariables(physicsState.movement, direction, 1.0f);
        //SetActiveMoveType(targetPhysicsState, e_MoveType_Jab);
      //}

      return true;
    }
*/

  }

  return false;
}

bool CharacterModelView::RequestDash(const Vector2 &target) {

  assert(moves[e_MoveType_Dash] != nullptr);

  if (physicsState.activeMove->GetMoveType() == e_MoveType_Walk) {

    if (moves[e_MoveType_Dash]->IsLegal()) {

      //Vector2 bodyDirection = _DEFAULTVECTOR.GetRotated(physicsState.bodyAngle);
      Vector2 toTarget = target - GetPosition();
      radian desiredBodyAngle = toTarget.GetNormalized(0).GetAngle();
      //printf("desiredBodyAngle (may be nullvector.getangle, fun test): %f\n", desiredBodyAngle);

      static_cast<CharacterDashMove*>(moves[e_MoveType_Dash])->SetVariables(physicsState.movement, toTarget, desiredBodyAngle);
      SetActiveMoveType(this->physicsState, e_MoveType_Dash);

      return true;
    }
  }

  return false;
}

bool CharacterModelView::RequestBlock(const Vector2 &direction) {

  assert(moves[e_MoveType_Block] != nullptr);

  if (_BLOCK_ONBEAT) {
    float musicPowerFactor = playState->GetCurrentMusic()->GetBeatOffset();
    if (musicPowerFactor < 0.95f && musicPowerFactor > 0.15f) return false;
    //if ((musicPowerFactor < 0.95f && musicPowerFactor > 0.15f) && (musicPowerFactor < 0.45f || musicPowerFactor > 0.65f)) return false; // every half beat
    //if ((musicPowerFactor < 0.95f && musicPowerFactor > 0.0f) && (musicPowerFactor < 0.45f || musicPowerFactor > 0.5f)) return false;
  }

  if ( (physicsState.activeMove->GetMoveType() == e_MoveType_Walk && fabs(physicsState.zMovement) < 1.5f) ||
       //(physicsState.activeMove->GetMoveType() == e_MoveType_Jump && fabs(physicsState.zMovement) < 2.0f) ||
       (physicsState.activeMove->GetMoveType() == e_MoveType_Hit && physicsState.activeMove->GetCurrentTimeFactor() > 0.5f) ||
       (physicsState.activeMove->GetMoveType() == e_MoveType_Jab && !physicsState.activeMove->IsLethal()) ) {

    if (moves[e_MoveType_Block]->IsLegal()) {

      static_cast<CharacterBlockMove*>(moves[e_MoveType_Block])->SetVariables(physicsState.movement, direction, direction.GetAngle());
      SetActiveMoveType(this->physicsState, e_MoveType_Block);

      return true;
    }
  }

  return false;
}

bool CharacterModelView::RequestDodge(const Vector2 &direction) {

  assert(moves[e_MoveType_Dodge] != nullptr);

  if (_DODGE_ONBEAT) {
    float musicPowerFactor = playState->GetCurrentMusic()->GetBeatOffset();
    if (musicPowerFactor < 0.95f && musicPowerFactor > 0.15f) return false;
    //if ((musicPowerFactor < 0.95f && musicPowerFactor > 0.15f) && (musicPowerFactor < 0.45f || musicPowerFactor > 0.65f)) return false; // every half beat
    //if ((musicPowerFactor < 0.95f && musicPowerFactor > 0.0f) && (musicPowerFactor < 0.45f || musicPowerFactor > 0.5f)) return false;
  }

  if ( (physicsState.activeMove->GetMoveType() == e_MoveType_Walk && fabs(physicsState.zMovement) < 1.5f) ||
       //(physicsState.activeMove->GetMoveType() == e_MoveType_Jump && fabs(physicsState.zMovement) < 2.0f) ||
       (physicsState.activeMove->GetMoveType() == e_MoveType_Hit && physicsState.activeMove->GetCurrentTimeFactor() > 0.8f) ||
       (physicsState.activeMove->GetMoveType() == e_MoveType_Jab && !physicsState.activeMove->IsLethal()) ) {

    if (moves[e_MoveType_Dodge]->IsLegal()) {

      // check what side of the body direction is closest
      Vector2 quantizedDodgeDirection = direction;
      /*
      radian angle = direction.GetAngle(bodyDirection);
      // pick a side to dodge to
      if (angle > 0) quantizedDodgeDirection = bodyDirection.GetRotated( 0.5f * pi); else
                     quantizedDodgeDirection = bodyDirection.GetRotated(-0.5f * pi);
      quantizedDodgeDirection = (direction * 0.4f + quantizedDodgeDirection * 0.6f).GetNormalized(0); // also some manual influence
      // keep looking 45 deg at opponent, to stay in the action
      Vector2 desiredBodyDirection = bodyDirection + -quantizedDodgeDirection;
      desiredBodyDirection.Normalize(0);
      radian desiredBodyAngle = desiredBodyDirection.GetAngle();
      */

      Vector2 bodyDirection = _DEFAULTVECTOR.GetRotated(physicsState.bodyAngle);
      Vector2 desiredBodyDirection = direction * 0.3f + bodyDirection;//bodyDirection + -quantizedDodgeDirection;
      desiredBodyDirection.Normalize(0);
      radian desiredBodyAngle = desiredBodyDirection.GetAngle();

      static_cast<CharacterDodgeMove*>(moves[e_MoveType_Dodge])->SetVariables(physicsState.movement, quantizedDodgeDirection, desiredBodyAngle);
      SetActiveMoveType(this->physicsState, e_MoveType_Dodge);

      return true;
    }
  }

  return false;
}

bool CharacterModelView::RequestThrow(const Vector2 &target) {

  assert(moves[e_MoveType_Throw] != nullptr);

  if (_THROW_ONBEAT) {
    float musicPowerFactor = playState->GetCurrentMusic()->GetBeatOffset();
    if (musicPowerFactor < 0.85f && musicPowerFactor > 0.15f) return false;
  }

  if (physicsState.activeMove->GetMoveType() == e_MoveType_Walk ||
      (physicsState.activeMove->GetMoveType() == e_MoveType_Jump) ||//&& fabs(physicsState.zMovement) < 1.5f) ||
      (physicsState.activeMove->GetMoveType() == e_MoveType_Landing)) {// && physicsState.activeMove->GetCurrentTimeFactor() > 0.1f)) {

    if (moves[e_MoveType_Throw]->IsLegal()) {

      Vector2 toTargetTranslation = (target - physicsState.position);
      static_cast<CharacterThrowMove*>(moves[e_MoveType_Throw])->SetVariables(physicsState.movement, toTargetTranslation);
      SetActiveMoveType(this->physicsState, e_MoveType_Throw);

      return true;
    }
  }

  return false;

}

bool CharacterModelView::RequestJump(const Vector2 &direction, float powerFactor) {

  assert(moves[e_MoveType_Jump] != nullptr);

  if (_JUMP_ONBEAT) {
    float musicPowerFactor = playState->GetCurrentMusic()->GetBeatOffset();
    if (musicPowerFactor < 0.75f && musicPowerFactor > 0.05f) return false;
  }

/* wip double jump
  if ((physicsState.activeMove->GetMoveType() == e_MoveType_Walk && fabs(physicsState.zMovement) < 0.1f) ||
      (physicsState.activeMove->GetMoveType() == e_MoveType_Jump && fabs(physicsState.zMovement) < 0.1f && physicsState.activeMove->GetCurrentTimeFactor() > 0.3f)) {
*/

  if (physicsState.activeMove->GetMoveType() == e_MoveType_Walk && fabs(physicsState.zMovement) < 1.5f) {// && fabs(physicsState.zMovement) < 0.1f) {

    if (moves[e_MoveType_Jump]->IsLegal()) {// || physicsState.activeMove->GetMoveType() == e_MoveType_Jump) { // don't check legality on jump to ignore cooldown time on double jumps

      //int allowedJumps = 0; if (physicsState.activeMove->GetMoveType() == e_MoveType_Jump) allowedJumps = 1; // double jump?

      static_cast<CharacterJumpMove*>(moves[e_MoveType_Jump])->SetVariables(physicsState.movement, direction, powerFactor);//nthTime);
      SetActiveMoveType(this->physicsState, e_MoveType_Jump);

      /*
      if (id == 0) {
        //playState->GetStage()->SpawnEnemy(e_EnemyType_Grenade, physicsState.position, direction * 10.0f);
        radian spreadAngle = 0.3f * pi;
        float step = spreadAngle / 7;
        for (radian rot = -spreadAngle * 0.5f; rot <= spreadAngle * 0.5f; rot += step) {
          Vector2 shotDirection = direction.GetRotated(rot);
          //StageEntity *entity =
          playState->GetStage()->SpawnEnemy(e_EnemyType_Grenade, physicsState.position + shotDirection * 0.5f, physicsState.zPosition + GetHeight() * 0.5f, shotDirection * 12.0f, 0.0f);
          //entity->SetPosition(physicsState.position + shotDirection * (GetRadius() + entity->GetRadius())); // make sure there's no collision with body
        }
      }
      */

      return true;
    }
  }

  return false;
}

bool CharacterModelView::RequestLanding(float durationMultiplier) {

  if (moves[e_MoveType_Landing]->IsLegal()) {

    if (_DISABLEFLOW) {
      static_cast<CharacterLandingMove*>(moves[e_MoveType_Landing])->SetVariables(physicsState.movement, (desiredDirection * 0.3f + GetDirection() + 0.4f).GetNormalized(_DEFAULTVECTOR.GetRotated(GetBodyAngle())), durationMultiplier);
    } else {
      static_cast<CharacterLandingMove*>(moves[e_MoveType_Landing])->SetVariables(physicsState.movement, (desiredDirection * 0.3f + GetDirection() * 0.4f + playState->GetStage()->GetStadium()->GetFlowDirection() * 0.5f).GetNormalized(_DEFAULTVECTOR.GetRotated(GetBodyAngle())), durationMultiplier);
    }
    //static_cast<CharacterLandingMove*>(moves[e_MoveType_Landing])->SetVariables(physicsState.movement, desiredDirection.GetNormalized(_DEFAULTVECTOR.GetRotated(GetBodyAngle())), durationMultiplier);
    SetActiveMoveType(this->physicsState, e_MoveType_Landing);

    return true;
  }

  return false;

}

bool CharacterModelView::RequestHit(PhysicsState &targetPhysicsState, const Vector2 &translation, float sustainMultiplier) {//, float powerFactor) {

  assert(moves[e_MoveType_Hit] != nullptr);

  if (moves[e_MoveType_Hit]->IsLegal() && GetHealthPoints() > 0.0f) {

    static_cast<CharacterHitMove*>(moves[e_MoveType_Hit])->SetVariables(translation, sustainMultiplier);
    SetActiveMoveType(targetPhysicsState, e_MoveType_Hit);

    /* this doesn't work, since we're probably in the collision code, during which the slave entity may have its positional stuff queued (== will be overwritten afterwards)
    // if we hold anything, lose it
    StageEntity *slave = playState->GetStage()->GetSlave(this->character);
    if (slave) {
      playState->GetStage()->RemoveMasterSlaveConnection(this->character, slave);
      slave->SetMovement(Vector2(Random(-5, 5), Random(-5, 5)));
      slave->SetZPosition(GetZPosition() + GetHeight() + 0.1f);
      slave->SetZMovement(GetZMovement() + 4.0f);
    }
    */

    return true;
  }

  return false;
}

void CharacterModelView::SetActiveMoveType(PhysicsState &targetPhysicsState, const e_MoveType &newType) {

  if (moves[newType] != nullptr) {
    targetPhysicsState.activeMove = moves[newType];
    targetPhysicsState.activeMove->ResetAndActivate();
  }

}

void CharacterModelView::OnChangeMoveType(const e_MoveType &oldType, const e_MoveType &newType) {
  //modeObject->SetSprite(modeSprite[newType]);
  //if (id == 0) printf("%i\n", physicsState.activeMoveState);

  // reset double jump counter
  //if (oldType == e_MoveType_Jump) static_cast<CharacterJumpMove*>(moves[e_MoveType_Jump])->Reset2ndTime();

}

void CharacterModelView::UpdateMoves() {

  for (unsigned int i = 0; i < e_MoveType_SIZE; i++) {
    if (moves[i] != nullptr) {
      moves[i]->StepTime(playState->GetPhysicsTimeStep_ms());
    }
  }

  if (physicsState.activeMove->IsActive() == false) { // done, apparently
    //unsigned int desiredWalkDuration_ms = stats.walkStats.walkDuration_ms;//140;
    int walkStepsPerBeat = 2.0;
    unsigned int desiredWalkDuration_ms = std::max(10, (int)round(playState->GetCurrentMusic()->GetBeatDuration_ms() * (1.0f - playState->GetCurrentMusic()->GetBeatOffset()))) / walkStepsPerBeat;
    float maxVelocity = playState->GetFullVelocity();
    if (playState->GetStage()->GetSlave(character) != nullptr) {
      // carrying something heavy! now we can somehow only move on the beat :p
      //desiredWalkDuration_ms = std::max(10, (int)round(playState->GetCurrentMusic()->GetBeatDuration_ms() * (1.0f - playState->GetCurrentMusic()->GetBeatOffset())));
      maxVelocity = playState->GetSlowVelocity();
    }
    static_cast<CharacterWalkMove*>(moves[e_MoveType_Walk])->SetVariables(physicsState.movement, desiredDirection * Clamp(desiredVelocity, 0, maxVelocity), desiredWalkDuration_ms);
    SetActiveMoveType(physicsState, e_MoveType_Walk);
  }

}

bool CharacterModelView::IsOnProcessedMoveIDList(unsigned int moveID) const {
  //bool found = processedMoveIDList.find(moveID);
  if (std::find(processedMoveIDList.begin(), processedMoveIDList.end(), moveID) != processedMoveIDList.end()) {
    return true;
  } else {
    return false;
  }
}
