#include "character_humancontroller.hpp"

#include "defines.hpp"
#include "main.hpp"
#include "playstate.hpp"
#include "stage.hpp"
#include "entities/mixtape.hpp"
#include "enemies/grenade.hpp"
#include "tiles/tile.hpp"
#include "character.hpp"
#include "character_modelview.hpp"
#include "aicontrollers/character_aicontroller.hpp"
#include "kungflowutils.hpp"

CharacterHumanController::CharacterHumanController(const KungFlowGame *game, const PlayState *playState, Character *character, const HumanGamer *humanGamer) : CharacterController(game, playState, e_ControllerType_Human), character(character), humanGamer(humanGamer) {
  //createdSequence.sequence = 0;
  character->GetModelView()->AddMoveOwnership( e_MoveType_Jab  , new CharacterJabMove  (playState, character->GetModelView()->GetCharacterSprite(), character->GetModelView()->GetStats().jabStats) ); // todo: probably should put those default stats elsewhere
  character->GetModelView()->AddMoveOwnership( e_MoveType_Block, new CharacterBlockMove(playState, character->GetModelView()->GetCharacterSprite(), character->GetModelView()->GetStats().blockStats) );
  character->GetModelView()->AddMoveOwnership( e_MoveType_Dodge, new CharacterDodgeMove(playState, character->GetModelView()->GetCharacterSprite(), character->GetModelView()->GetStats().dodgeStats) );
  character->GetModelView()->AddMoveOwnership( e_MoveType_Jump , new CharacterJumpMove (playState, character->GetModelView()->GetCharacterSprite(), character->GetModelView()->GetStats().jumpStats) );

  CharacterThrowMove *throwMove = new CharacterThrowMove(playState, character->GetModelView()->GetCharacterSprite(), character->GetModelView()->GetStats().throwStats); // todo: probably should put those default stats elsewhere
  character->GetModelView()->AddMoveOwnership( e_MoveType_Throw, throwMove );
  throwConnection = throwMove->sig_Throw.connect(boost::bind(&CharacterHumanController::OnThrow, this, _1, _2));

  throwBuffer = 1.0f;
}

CharacterHumanController::~CharacterHumanController() {
  throwConnection.disconnect();
}

void CharacterHumanController::OnStepPhysicsTime(CharacterModelView *characterModelView) {

  float autoAimFactor = _AUTOAIMDIRECTIONFACTOR;
  e_Team ourTeam = character->GetTeam();//e_Team_GoodGuys;
  e_Team opponentTeam = (ourTeam == e_Team_GoodGuys) ? e_Team_BadGuys : e_Team_GoodGuys;

  float secondsToReloadThrowBuffer = 20.0f;//6.0f;
  throwBuffer = Clamp(throwBuffer + (playState->GetPhysicsTimeStep_ms() * 0.001f) / secondsToReloadThrowBuffer, 0.0f, 1.0f);

  unsigned long time_ms = game->GetPhysicsTime_ms();

  const KeyInput &keyInput = humanGamer->GetKeyInput();
  const JoyInput &joyInput = humanGamer->GetJoyInput();


  // input keeping

  Input input;
  input.timestamp_ms = time_ms;

  // jump
  input.buttonAction.buttonID = 0;
  if (keyInput.actionkeys[4] || joyInput.buttons[0]) {
    input.buttonAction.pressed = true;
    //input.quantizationType = keyInput.actionkeys[5] ? e_QuantizationType_Digital : e_QuantizationType_Analog;
  } else {
    input.buttonAction.pressed = false;
  }
  inputHistory.TryInsertEntry(input);

  // attack
  input.buttonAction.buttonID = 1;
  if (keyInput.actionkeys[5] || joyInput.buttons[2]) {
    input.buttonAction.pressed = true;
    //input.quantizationType = keyInput.actionkeys[4] ? e_QuantizationType_Digital : e_QuantizationType_Analog;
  } else {
    input.buttonAction.pressed = false;
  }
  inputHistory.TryInsertEntry(input);

  // defend
  input.buttonAction.buttonID = 2;
  if (keyInput.actionkeys[3] || joyInput.buttons[1]) {
    input.buttonAction.pressed = true;
    //input.quantizationType = keyInput.actionkeys[3] ? e_QuantizationType_Digital : e_QuantizationType_Analog;
  } else {
    input.buttonAction.pressed = false;
  }
  inputHistory.TryInsertEntry(input);

  // alt attack
  input.buttonAction.buttonID = 3;
  if (keyInput.actionkeys[1] || joyInput.buttons[3]) {
    input.buttonAction.pressed = true;
    //input.quantizationType = keyInput.actionkeys[1] ? e_QuantizationType_Digital : e_QuantizationType_Analog;
  } else {
    input.buttonAction.pressed = false;
  }
  inputHistory.TryInsertEntry(input);

  int jumpButton = 0;
  int throwButton = 1;
  int dodgeButton = 2;
  int jabButton = 3;

  //inputHistory.PrintStatus();
  //inputHistory.PrintHistory();
  //printf("\n");

/*
  Vector2 stick2DirectionRaw = joyInput.stick2_processed;
  //float powerFactor = fabs(playState->GetCurrentMusic()->GetBeatOffset() * 2.0f - 1.0f);
  float powerFactor = cos(playState->GetCurrentMusic()->GetBeatOffset() * 2 * pi) * 0.5f + 0.5f;
  powerFactor = 0.3f * powerFactor + 0.7f * (1.0f - playState->GetCurrentMusic()->GetBeatOffset());
  //if (game->GetPhysicsTimeStepsDone() % 70 == 0 && stick2DirectionRaw.GetLength() > 0.5f) {
  if (powerFactor > 0.994f && stick2DirectionRaw.GetLength() > 0.5f) {
    //playState->GetStage()->SpawnEnemy(e_EnemyType_Grenade, physicsState.position, direction * 10.0f);
    radian spreadAngle = 0.125f * pi;
    float step = spreadAngle / 2;
    for (radian rot = -spreadAngle * 0.5f; rot <= spreadAngle * 0.5f; rot += step) {
      Vector2 shotDirection = stick2DirectionRaw.GetNormalized(0).GetRotated(rot);
      //StageEntity *entity =
      playState->GetStage()->SpawnEnemy(e_EnemyType_Grenade, character->GetPosition() + shotDirection * 0.5f, character->GetZPosition() + character->GetHeight(), shotDirection * 7.0f, -0.2f);
      //entity->SetPosition(physicsState.position + shotDirection * (GetRadius() + entity->GetRadius())); // make sure there's no collision with body
    }
  }
*/

//  move only on the beat
//  powerFactor = cos(playState->GetCurrentMusic()->GetBeatOffset() * 2 * pi) * 0.5f + 0.5f;
//  powerFactor = 0.3f * powerFactor + 0.7f * (1.0f - playState->GetCurrentMusic()->GetBeatOffset());
//  if (powerFactor < 0.95f) return;q

  Vector2 inputDirectionRaw = joyInput.stick1_processed;
  float inputVelocity = 0;
  if (_KEYBOARD_ROTATEINPUT45DEGREES == false) {
    if (keyInput.left)  inputDirectionRaw.coords[0] -= 1.0f;
    if (keyInput.right) inputDirectionRaw.coords[0] += 1.0f;
    if (keyInput.up)    inputDirectionRaw.coords[1] -= 1.0f;
    if (keyInput.down)  inputDirectionRaw.coords[1] += 1.0f;
  } else {
    Vector2 inputDirectionKey;
    if (keyInput.left)  inputDirectionKey.coords[0] -= 1.0f;
    if (keyInput.right) inputDirectionKey.coords[0] += 1.0f;
    if (keyInput.up)    inputDirectionKey.coords[1] -= 1.0f;
    if (keyInput.down)  inputDirectionKey.coords[1] += 1.0f;
    if (inputDirectionKey.GetLength() > 0) inputDirectionRaw += inputDirectionKey.GetRotated(0.25f * pi);
  }
  if (keyInput.left || keyInput.right || keyInput.up || keyInput.down) autoAimFactor = 1.0f; // using keyboard == unable to properly aim manually
  inputDirectionRaw.Rotate(playState->GetViewOrientation());

  //bool isThrowPressed = false;
  //inputHistory.FindCurrentStatus(1, isThrowPressed);

/*
  // flow (now resides in char modelview class mostly)

  float axisMovementBandwidth = _AXISMOVEMENT_BANDWIDTH;

  bool flow = true;
  if (flow) {
    int flowAxis = playState->GetStage()->GetStadium()->GetFlowAxis();

    // forced movement towards the flow line
    float positionOnAxis = playState->GetStage()->GetStadium()->GetFlowPosition().coords[flowAxis];
    float playerToPositionOnAxis = positionOnAxis - character->GetPosition().coords[flowAxis];
    signed int playerToPositionOnAxisSignSide = SignSide(playerToPositionOnAxis);
    float toFlowLineMagnet = Curve(NormalizedClamp(playerToPositionOnAxis, -axisMovementBandwidth * 0.5f, axisMovementBandwidth * 0.5f), 0.0f) * 2.0f - 1.0f; // -1 to 1;
    float toFlowLineIntensity = pow(fabs(toFlowLineMagnet), _AXISMOVEMENT_CENTERPULL_EXP); // 0 to 1

    // user desired movement
    float inputOverAxis = inputDirectionRaw.coords[flowAxis];

    float flowAxisInput = playerToPositionOnAxisSignSide * toFlowLineIntensity + inputOverAxis * (1.0f - toFlowLineIntensity);

    inputDirectionRaw.coords[playState->GetStage()->GetStadium()->GetFlowAxis()] = flowAxisInput;
  }
*/

  //inputDirection.Normalize(0);
  //QuantizeDirection(inputDirection, 1.0f);

  //inputVelocity = (inputDirection.GetLength() > 0.5f) ? 1.0f : 0.0f;
  inputVelocity = std::min(1.0f, inputDirectionRaw.GetLength());
  //float veloMultiplier = playState->GetMediumVelocity();
  //if (keyInput.actionkeys[2]) veloMultiplier = playState->GetFullVelocity();
  //if (keyInput.actionkeys[8]) veloMultiplier = playState->GetSlowVelocity();
  //inputVelocity *= veloMultiplier;
  Vector2 inputDirection = GetQuantizedDirection(inputDirectionRaw, playState->GetQuantizationFactor());

  inputDirection.Normalize(0);
  inputDirectionRaw.Normalize(0);
  //inputDirection.Print();
  //printf("%f\n", inputVelocity);

/*
  // sprinting
  bool isPressed = false;
  inputHistory.FindCurrentStatus(1, isPressed); // alt attack
  //printf("%i\n", isPressed);
  if (isPressed) {
    characterModelView->SetDesiredVelocity(inputVelocity * playState->GetFullVelocity()); // sprinting is not fully implemented yet on the modelview side
  } else {
    characterModelView->SetDesiredVelocity(inputVelocity * playState->GetMediumVelocity());
  }
*/


  // ## auto aim ##

  float characterAimDistance = 5.0f;
  float emptySpaceAimDistance = 5.0f;
  float futurePredict_sec = 0.5f;

  Character *focussedCharacter = nullptr;
  e_Team aimTargetTeam = opponentTeam;

  // failsafe default value
  focussedCharacter = _stage->GetClosestCharacter(aimTargetTeam, character->GetPosition() + inputDirectionRaw * characterAimDistance, character);

  // focus aim on assigned character, if available. (overwrite previous focussedcharacter pointer)
  if (aimTargetTeam != ourTeam) { // only when enemy target
    std::vector<Character*> assignedOpponents;
    playState->GetStage()->GetAssignedOpponentCharacters(character, assignedOpponents);
    if (!assignedOpponents.empty()) {
      // sort assigned opponents, closest first
      std::vector<CharacterProximity> proximities;
      for (unsigned int i = 0; i < assignedOpponents.size(); i++) {
        //if (assignedOpponents.at(i)->GetTeamID() == teamID && assignedOpponents.at(i) != ignore) {
        CharacterProximity prox;
        prox.character = assignedOpponents.at(i);
        prox.distance = character->GetPosition().GetDistance(assignedOpponents.at(i)->GetPosition());
        proximities.push_back(prox);
      }
      std::sort(proximities.begin(), proximities.end());
      focussedCharacter = proximities.at(0).character;
    }
  }

  Vector2 lookDirection;
  Vector2 autoAimDirection;
  Vector2 autoAimPosition;

  if (focussedCharacter) {

    Vector2 focussedCharacterPosition = focussedCharacter->GetPosition();
    Vector2 toFocussedCharacter = focussedCharacterPosition - characterModelView->GetPosition();
    //lookDirection = toFocussedCharacter.GetNormalized(0);
    lookDirection = (toFocussedCharacter.GetNormalized(0) + inputDirectionRaw * 3.0f).GetNormalized(_DEFAULTVECTOR);

    autoAimPosition = focussedCharacterPosition + focussedCharacter->GetMovement() * futurePredict_sec;
    Vector2 toFocussedCharacterFuture = autoAimPosition - characterModelView->GetPosition();
    autoAimDirection = toFocussedCharacterFuture.GetNormalized(0);

  } else {

    // no obvious target
    lookDirection = inputDirectionRaw;
    autoAimDirection = inputDirectionRaw;
    autoAimPosition = character->GetPosition() + autoAimDirection * emptySpaceAimDistance; // default empty space target

  }

  // always autoaim, or only when already pointing roughly in the right direction?
  radian autoAimThresholdDistance = _AUTOAIMTHRESHOLDARC;//0.25f * pi;
  if (keyInput.left || keyInput.right || keyInput.up || keyInput.down) autoAimThresholdDistance = 0.125f * pi; // remember, 0.25f * pi == 45 deg == the maximum a keyboard/d-pad user can denote, so half that is the middle between to keyboard directions

  bool onlyToFlowHemisphere = false; // if the autokick dir is towards the players behind, ignore (we can't shoot there anyway)
  //if (!onlyToFlowHemisphere || (onlyToFlowHemisphere && playState->GetStage()->GetStadium()->GetFlowDirection().GetDotProduct(autoKickDirection) > 0)) {
  if ( (onlyToFlowHemisphere == true && fabs(playState->GetStage()->GetStadium()->GetFlowDirection().GetAngle(autoAimDirection)) > 0.5f * pi + autoAimThresholdDistance) ||
       fabs(inputDirectionRaw.GetAngle(autoAimDirection)) > autoAimThresholdDistance ) {
    //autoKickDirection = inputDirectionRaw;
    autoAimFactor = 0.0f;
    //autoKickTargetPosition = characterModelView->GetPosition() + inputDirectionRaw * 1.0f;
  }
  //Vector2 desiredKickDirection = autoKickDirection;//inputDirection * 0.5f + autoKickDirection * 0.5f;
  //desiredKickDirection = GetQuantizedDirection(desiredKickDirection, 0.5f);

  Vector2 desiredJabDirection = (inputDirectionRaw * (1.0f - autoAimFactor) + autoAimDirection * autoAimFactor).GetNormalized(0);
  Vector2 desiredThrowDirection = (inputDirectionRaw * (1.0f - autoAimFactor) + autoAimDirection * autoAimFactor).GetNormalized(0);
  //Vector2 desiredDefendDirection = inputDirection.GetNormalized(-_DEFAULTVECTOR.GetRotated(characterModelView->GetBodyAngle()));
//  Vector2 desiredDefendDirection = (-playState->GetStage()->GetStadium()->GetFlowDirection() * 0.3f +
//                                    inputDirectionRaw * 0.7f).GetNormalized(); // inputDirection.GetNormalized(-_DEFAULTVECTOR.GetRotated(characterModelView->GetBodyAngle()))
  Vector2 desiredDefendDirection = (inputDirectionRaw * (1.0f - autoAimFactor) + autoAimDirection * autoAimFactor).GetNormalized(0);
  Vector2 desiredJumpDirection = inputDirectionRaw;



  // ## control character's modelview (aka move yo ass!) ##

  // don't steer during throw gauge hax
  bool noSteerDuringThrowGauge = false;
  bool isThrowPressed = false;
  if (noSteerDuringThrowGauge) inputHistory.FindCurrentStatus(throwButton, isThrowPressed);
  if (!isThrowPressed) {
    characterModelView->SetDesiredDirection(inputDirection);//.EnforceMaximumDeviation(character->GetDirection(), 0.3f).GetNormalized());
  } else {
    characterModelView->SetDesiredDirection(characterModelView->GetDesiredDirection());//.EnforceMaximumDeviation(character->GetDirection(), 0.3f).GetNormalized());
  }
  //characterModelView->SetDesiredDirection(inputDirection);//.EnforceMaximumDeviation(character->GetDirection(), 0.3f).GetNormalized());
  characterModelView->SetDesiredVelocity(inputVelocity * playState->GetMediumVelocity());
  characterModelView->SetDesiredBodyAngle(lookDirection.GetNormalized(playState->GetStage()->GetStadium()->GetFlowDirection().GetNormalized(_DEFAULTVECTOR)).GetAngle());



  // ## MOVES ##


  // jab attack

  //if (keyInput.actionkeys[4] || joyInput.buttons[0]) {
  std::vector<ButtonStatus> jabCombo;
  jabCombo.push_back(ButtonStatus(jabButton, true));
  //jabCombo.push_back(ButtonStatus(0, false));
  // * test: jab power not by loading gauge, but by music timing. so disable the need for button release * jabCombo.push_back(ButtonStatus(0, false));
  bool ignoreFirstButtonTime = jabCombo.size() > 1;
  unsigned int jabComboTime_ms = playState->GetCurrentMusic()->GetBeatDuration_ms() * 1.5f;// * 0.95f;//600;
  unsigned int elapsedTime_ms = 0;
  unsigned long lastEntryID = 0;
  //printf("%f\n", float(game->GetPhysicsTime_ms() * 0.001f) * (125 / 60.0f));
  //printf("beat %i . %f\n", playState->GetCurrentMusic()->GetBeat(), playState->GetCurrentMusic()->GetBeatOffset());
  if (inputHistory.FindCombo(jabCombo, time_ms, jabComboTime_ms, ignoreFirstButtonTime, elapsedTime_ms, lastEntryID, false) == true) {
    //printf("%f\n", std::min(1.0f, (float)elapsedTime_ms / (float)jabComboTime_ms));

    //float powerFactor = cos(playState->GetCurrentMusic()->GetBeatOffset() * 2 * pi) * 0.5f + 0.5f;
    //powerFactor = pow(powerFactor, 1.5f);
    //printf("beat %i . %f (== %f power)\n", playState->GetCurrentMusic()->GetBeat(), playState->GetCurrentMusic()->GetBeatOffset(), powerFactor);
    //if (characterModelView->RequestJab(desiredJabDirection, powerFactor)) {

    //float distanceFactor = std::min(1.0f, (float)elapsedTime_ms / (float)jabComboTime_ms); // combo time power
    float distanceFactor = 1.0f;
    if (characterModelView->RequestJab(desiredJabDirection, distanceFactor)) {
      //inputHistory.MarkComboAsProcessed(jabCombo, time_ms, jabComboTime_ms, true);
      inputHistory.SetProcessedUpTo(lastEntryID);
      //printf("[[ EXECUTE ]]\n");
    }
  }


  // throw / shoot

  //if (throwBuffer > 0.125f) {
  const StageEntity *slave = _stage->GetSlave(this->character);
  if (slave) {

    // throw object


    //float bareKnuckleDistance = 0.0f;// 1.2f;

    bool isThrowPressed = false;
    // auto fire
    //inputHistory.FindCurrentStatus(throwButton, isThrowPressed);

    Vector2 stick2DirectionRaw = joyInput.stick2_processed;
    if (stick2DirectionRaw.GetLength() > 0.5f) {
      isThrowPressed = true;
      desiredThrowDirection = stick2DirectionRaw.GetNormalized(0);
    }

    std::vector<ButtonStatus> throwCombo;
    throwCombo.push_back(ButtonStatus(throwButton, true));
    throwCombo.push_back(ButtonStatus(throwButton, false));
    ignoreFirstButtonTime = throwCombo.size() > 1;
    unsigned int throwComboTime_ms = playState->GetCurrentMusic()->GetBeatDuration_ms() * 1.0f;// * 0.95f;//600;
    elapsedTime_ms = 0;
    lastEntryID = 0;

    if (inputHistory.FindCombo(throwCombo, time_ms, throwComboTime_ms, ignoreFirstButtonTime, elapsedTime_ms, lastEntryID, false) == true || isThrowPressed) {
    //if (isThrowPressed) {

      bool success = false;

      /* something about close range stuff dunno
      if (badGuy && badGuy->GetPosition().GetDistance(character->GetPosition()) < bareKnuckleDistance && fabs(badGuy->GetZPosition() - character->GetZPosition()) < 0.5f) {
        float flowDirectionBias = 0.4f;
        Vector2 convergedThrowDirection = ( inputDirectionRaw * (1.0f - flowDirectionBias) + playState->GetStage()->GetStadium()->GetFlowDirection() * flowDirectionBias ).GetNormalized(inputDirectionRaw);
      */

      bool fullAuto = false; // not to be actually used in release. handy for tests, or for saving CPU if we know for sure we want full auto
      if (fullAuto) {

        success = characterModelView->RequestThrow(autoAimPosition);

      } else {

        Vector2 throwTranslation;

        // aim
        if (_DISABLEFLOW == true) {
          throwTranslation = desiredThrowDirection;
        } else {
          // clamp angle forwards
          radian maxAngle = 0.45f * pi;
          throwTranslation = playState->GetStage()->GetStadium()->GetFlowDirection().GetRotated(Clamp(desiredThrowDirection.GetAngle(playState->GetStage()->GetStadium()->GetFlowDirection()), -maxAngle, maxAngle));
        }

        // powered
        float power = 3.0f + 5.0f * pow(NormalizedClamp(elapsedTime_ms, 0, 800), 0.5f);
        //float power = 10.0f;
        throwTranslation *= power;

        success = characterModelView->RequestThrow(character->GetPosition() + throwTranslation);
      }

      if (success) {
        throwBuffer -= 0.25f;//playState->GetPhysicsTimeStep_ms() * 0.001f;
        inputHistory.SetProcessedUpTo(lastEntryID);
        //throwBuffer -= 0.25f;//playState->GetPhysicsTimeStep_ms() * 0.001f;
      }

    }

  } else {

    // shoot grenades

    bool isThrowPressed = false;
    // auto fire
    inputHistory.FindCurrentStatus(throwButton, isThrowPressed);

    Vector2 stick2DirectionRaw = joyInput.stick2_processed;
    if (stick2DirectionRaw.GetLength() > 0.5f) {
      isThrowPressed = true;
      desiredThrowDirection = stick2DirectionRaw.GetNormalized(0);
    }

    std::vector<ButtonStatus> throwCombo;
    throwCombo.push_back(ButtonStatus(throwButton, true));
    throwCombo.push_back(ButtonStatus(throwButton, false));
    ignoreFirstButtonTime = throwCombo.size() > 1;
    unsigned int throwComboTime_ms = playState->GetCurrentMusic()->GetBeatDuration_ms() * 1.0f;// * 0.95f;//600;
    elapsedTime_ms = 0;
    lastEntryID = 0;

    if (inputHistory.FindCombo(throwCombo, time_ms, throwComboTime_ms, ignoreFirstButtonTime, elapsedTime_ms, lastEntryID, false) == true || isThrowPressed) {
    //if (isThrowPressed) {

      bool success = false;

      /* something about close range stuff dunno
      if (badGuy && badGuy->GetPosition().GetDistance(character->GetPosition()) < bareKnuckleDistance && fabs(badGuy->GetZPosition() - character->GetZPosition()) < 0.5f) {
        float flowDirectionBias = 0.4f;
        Vector2 convergedThrowDirection = ( inputDirectionRaw * (1.0f - flowDirectionBias) + playState->GetStage()->GetStadium()->GetFlowDirection() * flowDirectionBias ).GetNormalized(inputDirectionRaw);
      */

      bool fullAuto = false; // not to be actually used in release. handy for tests, or for saving CPU if we know for sure we want full auto
      if (fullAuto) {

        success = characterModelView->RequestThrow(autoAimPosition);

      } else {

        Vector2 throwTranslation;

        // aim
        if (_DISABLEFLOW == true) {
          throwTranslation = desiredThrowDirection;
        } else {
          // clamp angle forwards
          radian maxAngle = 0.45f * pi;
          throwTranslation = playState->GetStage()->GetStadium()->GetFlowDirection().GetRotated(Clamp(desiredThrowDirection.GetAngle(playState->GetStage()->GetStadium()->GetFlowDirection()), -maxAngle, maxAngle));
        }

        // powered
        //float power = 3.0f + 5.0f * pow(NormalizedClamp(elapsedTime_ms, 0, 800), 0.5f);
        float power = 12.0f;
        throwTranslation *= power;

        /*
        // front/back differentation (bi-directional)
        unsigned int flowAxis = playState->GetStage()->GetStadium()->GetFlowAxis();
        //if (inputDirectionRaw.coords[flowAxis] >= -0.25f) {
        if (desiredThrowDirection.coords[flowAxis] > -0.77f) {
          //throwTranslation = playState->GetStage()->GetStadium()->GetFlowDirection().GetRotated(Clamp(inputDirectionRaw.GetAngle(playState->GetStage()->GetStadium()->GetFlowDirection()), -maxAngle, maxAngle));
          throwTranslation = playState->GetStage()->GetStadium()->GetFlowDirection().GetRotated(Clamp(desiredThrowDirection.GetAngle(playState->GetStage()->GetStadium()->GetFlowDirection()), -maxAngle, maxAngle));
        } else { // invert backwards direction, more convenient
          // mirrored version
          //throwTranslation = playState->GetStage()->GetStadium()->GetFlowDirection().GetRotated(Clamp((-inputDirectionRaw).GetAngle(playState->GetStage()->GetStadium()->GetFlowDirection()), -maxAngle, maxAngle));
          //throwTranslation.coords[1 - flowAxis] *= -1.0f; // invert sides
          // aim straight forward version
          throwTranslation = playState->GetStage()->GetStadium()->GetFlowDirection();
        }
        */

        /* not sure what this is atm :p
        Vector2 flowDirection = playState->GetStage()->GetStadium()->GetFlowDirection();
        Vector2 throwDirection = flowDirection;
        unsigned int flowAxis = playState->GetStage()->GetStadium()->GetFlowAxis();
        throwDirection.coords[abs(1 - flowAxis)] = inputDirectionRaw.coords[abs(1.0f - flowAxis)] * inputDirectionRaw.coords[flowAxis];
        throwDirection.Normalize(flowDirection);
        */

        success = characterModelView->RequestThrow(character->GetPosition() + throwTranslation);
      }

      if (success) {
        throwBuffer -= 0.25f;//playState->GetPhysicsTimeStep_ms() * 0.001f;
        inputHistory.SetProcessedUpTo(lastEntryID);
        //throwBuffer -= 0.25f;//playState->GetPhysicsTimeStep_ms() * 0.001f;
      }

    }

    /* mixtape creation
    if (!mixtapeExists) {

      Mixtape *mixtape = new Mixtape(game, playState, character->GetPosition() + character->GetMovement() * 0.3f, character->GetZPosition() + 1.5f, character->GetMovement() * 0.3f, -1.0f);
      playState->GetStage()->AddEntityOwnership(mixtape);

    } else {

      // mixtape aready exists, but isn't our slave. check if a mate has it, and if so, request pass on throwbutton
      bool isThrowPressed = false;
      inputHistory.FindCurrentStatus(throwButton, isThrowPressed);
      if (isThrowPressed)  {

        StageEntity *flag = playState->GetStage()->GetFlag();
        if (flag != nullptr) {
          const Character *master = playState->GetStage()->GetMaster(flag);
          if (master != nullptr) {

            if (master->GetTeam() == ourTeam) {
              if (master->GetController_const()->GetControllerType() == e_ControllerType_AI) {
                static_cast<CharacterAIController*>(master->GetController())->RequestPass(character, flag);
              }
            }

          } // flag master exists
        } // flag exists

      } // throwpressed

    } // mixtapeExists
    */

  }


  // dodge / block

  std::vector<ButtonStatus> dodgeCombo;
  dodgeCombo.push_back(ButtonStatus(dodgeButton, true));
  ignoreFirstButtonTime = dodgeCombo.size() > 1;
  unsigned int dodgeComboTime_ms = playState->GetCurrentMusic()->GetBeatDuration_ms() * 1.5f;// * 0.95f;//600;
  elapsedTime_ms = 0;
  lastEntryID = 0;
  if (inputHistory.FindCombo(dodgeCombo, time_ms, dodgeComboTime_ms, ignoreFirstButtonTime, elapsedTime_ms, lastEntryID, false) == true) {

    bool success = false;

/*
    // check direction: sideways == dodge, forward == block
    float ndotl = lookDirection.GetDotProduct(desiredDefendDirection);
    //if (fabs(ndotl) < 0.7f) {
    float minimumBlockDistance = 1.2f;
    if (ndotl > 0.707f &&
        (character->GetPosition() - autoAimPosition).GetLength() < minimumBlockDistance &&
        focussedCharacter) {
        //(badGuy && badGuy->GetModelView_const()->GetActiveMoveType() == e_MoveType_Jab)) {

      // block mode
      success = characterModelView->RequestBlock(autoAimDirection);//desiredJabDirection);

    } else {
*/
      // dodge mode
      success = characterModelView->RequestDodge(desiredDefendDirection);

//    }

    //if (success) inputHistory.MarkComboAsProcessed(dodgeCombo, time_ms, dodgeComboTime_ms, false);
    if (success) inputHistory.SetProcessedUpTo(lastEntryID);

  }


  // jump

  std::vector<ButtonStatus> jumpCombo;
  jumpCombo.push_back(ButtonStatus(jumpButton, true));
  //jumpCombo.push_back(ButtonStatus(3, false));
  ignoreFirstButtonTime = jumpCombo.size() > 1;
  unsigned int jumpComboTime_ms = playState->GetCurrentMusic()->GetBeatDuration_ms() * 1.5f;//* 0.95f;//600;
  elapsedTime_ms = 0;
  lastEntryID = 0;
  if (inputHistory.FindCombo(jumpCombo, time_ms, jumpComboTime_ms, ignoreFirstButtonTime, elapsedTime_ms, lastEntryID, false) == true) {
    if (characterModelView->RequestJump(desiredJumpDirection, 1.0f)) { // std::min(1.0f, (float)elapsedTime_ms / (float)jumpComboTime_ms))
      inputHistory.SetProcessedUpTo(lastEntryID);
    }
  }





  // LEGACY / TESTING

  /*
  // DASH!
  std::vector<ButtonStatus> dashCombo;
  dashCombo.push_back(ButtonStatus(1, true));
  unsigned int dashComboTime_ms = 600;
  elapsedTime_ms = 0;
  lastEntryID = 0;
  if (inputHistory.FindCombo(dashCombo, time_ms, dashComboTime_ms, true, elapsedTime_ms, lastEntryID, false) == true) {
    //printf("%f\n", std::min(1.0f, (float)elapsedTime_ms / (float)jabComboTime_ms));
    if (characterModelView->RequestDash(autoKickTargetPosition)) {
      //inputHistory.MarkComboAsProcessed(dashCombo, time_ms, jabComboTime_ms, true);
      inputHistory.SetProcessedUpTo(lastEntryID);
    }
  }
  */

  /*
  // POWER SMASH
  if (keyInput.actionkeys[5] || joyInput.buttons[2]) {
    // kick button
    if (characterModelView->GetActiveMoveState() == e_MoveState_Physics) {
      characterModelView->RequestKickBuffer(desiredKickDirection);
    }
    if (characterModelView->GetActiveMoveState() == e_MoveState_KickBuffer) {
      characterModelView->AddKickBufferPower(desiredKickDirection);
    }
  } else {
    // released kick button
    if (characterModelView->GetActiveMoveState() == e_MoveState_KickBuffer) {
      characterModelView->RequestKick();
    }
  }
  */

}

void CharacterHumanController::OnThrow(const Vector2 &desiredMovement, const Vector2 &toTargetTranslation) {

  // physics version
  //float safeDistance = (character->GetRadius() + 0.2f);
//  Vector2 throwDirection = toTargetTranslation.GetNormalized(playState->GetStage()->GetStadium()->GetFlowDirection());
//  float throwPower = toTargetTranslation.GetLength();//10.0f;//14.0f;//12.0f;

  // single shot
  //float zMovement = 2.5f;
  //playState->GetStage()->SpawnEnemy(e_EnemyType_Grenade, e_Team_GoodGuys, character->GetPosition() + throwDirection * safeDistance, character->GetZPosition() + character->GetHeight() * 0.5f, throwDirection * throwPower, zMovement);
  //Mixtape *mixtape = new Mixtape(game, playState, character->GetPosition() + throwDirection * safeDistance, character->GetZPosition() + character->GetHeight() * 1.2f, throwDirection * throwPower, zMovement);
  //playState->GetStage()->AddEntityOwnership(mixtape);
  /*
  if (character->GetSlaveEntity()) {
    Mixtape *mixtape = static_cast<Mixtape*>(character->GetSlaveEntity());
    mixtape->SetMovement(throwDirection * throwPower);
    mixtape->SetZMovement(zMovement);
    character->SetSlaveEntity(nullptr);
  }*/
  StageEntity *slave = playState->GetStage()->GetSlave(this->character);
  if (slave) {

    // throw object

    playState->GetStage()->RemoveMasterSlaveConnection(this->character, slave);
    //Mixtape *mixtape = static_cast<Mixtape*>(character->GetSlaveEntity());

    float safeDistance = (character->GetRadius() + slave->GetRadius());
    Vector2 throwDirection = toTargetTranslation.GetNormalized(playState->GetStage()->GetStadium()->GetFlowDirection());
    float throwPower = toTargetTranslation.GetLength();//10.0f;//14.0f;//12.0f;
    float zMovement = 2.5f;

    slave->SetPosition(character->GetPosition() + throwDirection * safeDistance);
    slave->SetMovement(throwDirection * throwPower);
    slave->SetZMovement(zMovement);

  } else {

    // fire!!

    float safeDistance = (character->GetRadius() + 0.2f);
    Vector2 throwDirection = toTargetTranslation.GetNormalized(playState->GetStage()->GetStadium()->GetFlowDirection());
    float throwPower = toTargetTranslation.GetLength();//10.0f;//14.0f;//12.0f;

    // hellfire!
    //radian spreadAngle = 0.125f * pi;
    //float step = spreadAngle / 4;
    radian spreadAngle = 0.05f * pi;
    float step = spreadAngle / 2;

  //  // shooting while aiming backwards
  //  if (desiredMovement.coords[playState->GetStage()->GetStadium()->GetFlowAxis()] < -0.25f) {
  //    spreadAngle = 1.0f * pi;
  //    step = spreadAngle / 4;
  //  }

    //if (playState->GetCurrentMusic()->GetBeat() % 2 == 0) spreadAngle = 0;
    for (radian rot = -spreadAngle * 0.5f; rot <= spreadAngle * 0.5f; rot += step) {
      float zMovement = -1.0;
      float middleness = fabs(rot / spreadAngle);

      const Tile *tile = playState->GetStage()->GetStadium()->GetTile(character->GetPosition());
      if (tile) {
        zMovement -= NormalizedClamp(character->GetZPosition() - tile->GetZPosition(), 0.0f, 2.0f) * 8.0f;
      }
      //playState->GetStage()->SpawnEnemy(e_EnemyType_Grenade, e_Team_GoodGuys, character->GetPosition() + throwDirection * safeDistance, character->GetZPosition() + character->GetHeight() * 0.7f, throwDirection.GetRotated(rot) * throwPower, zMovement);
      EnemyGrenade *enemyGrenade = new EnemyGrenade(game, playState, e_Team_GoodGuys, character->GetPosition() + throwDirection * safeDistance, character->GetZPosition() + character->GetHeight() * 0.7f, throwDirection.GetRotated(rot) * (throwPower * (1.0f - middleness * 0.0f)), zMovement);
      playState->GetStage()->AddEntityOwnership(enemyGrenade);
      //entity->SetPosition(physicsState.position + shotDirection * (GetRadius() + entity->GetRadius())); // make sure there's no collision with body
    } // loop

  } // mode

  playState->SoundPlay(e_GlobalSound_SoundBlast, 0.4f);



}
