#include "CharacterMind.h" #include "Camera.h" #include "Character.h" #include "common.h" #include "Keyboard.h" #include "Mouse.h" #include #include void KeyboardMind::ProduceActions(const Character& character, std::vector& actions) { glm::vec3 direction(0.0f); if (enabled) { // Flattened, so looking up or down doesn't slow walking const glm::vec3 forward = camera.t.GetFrontVector(); const glm::vec3 right = camera.t.GetRightVector(); const glm::vec3 flatForward = glm::normalize(glm::vec3(forward.x, 0.0f, forward.z)); const glm::vec3 flatRight = glm::normalize(glm::vec3(right.x, 0.0f, right.z)); // The camera's right vector points to the left of the screen, so A adds it if (keyboard->IsKeyDown(GLFW_KEY_W)) direction += flatForward; if (keyboard->IsKeyDown(GLFW_KEY_S)) direction -= flatForward; if (keyboard->IsKeyDown(GLFW_KEY_A)) direction += flatRight; if (keyboard->IsKeyDown(GLFW_KEY_D)) direction -= flatRight; if (glm::length(direction) > 0.0f) direction = glm::normalize(direction); } // Always sent, so letting go of the keys stops the character. It turns to face where it walks, // or in an ability that pivots, keeps turning to face where the camera looks. glm::vec3 facing(0.0f); if (character.body->combat.IsAbilityPivoting()) { const glm::vec3 look = camera.t.GetFrontVector(); facing = glm::vec3(look.x, 0.0f, look.z); } // Shift let go quickly dashes, held down it sprints const bool shiftDown = enabled && (keyboard->IsKeyDown(GLFW_KEY_LEFT_SHIFT) || keyboard->IsKeyDown(GLFW_KEY_RIGHT_SHIFT)); const double now = glfwGetTime(); if (shiftDown && !prevShiftDown) shiftPressedAt = now; const bool sprint = shiftDown && now - shiftPressedAt > dashTapTime; actions.push_back(CharacterAction::Move(direction, facing, sprint)); if (!shiftDown && prevShiftDown && now - shiftPressedAt <= dashTapTime) actions.push_back(CharacterAction::Dash(direction)); prevShiftDown = shiftDown; const bool jumpDown = enabled && keyboard->IsKeyDown(GLFW_KEY_SPACE); if (jumpDown && !prevJumpDown) actions.push_back(CharacterAction::Jump()); prevJumpDown = jumpDown; // Left click swings the way the camera looks const bool attackDown = enabled && mouseEnabled && mouse->IsLeftDown(); if (attackDown && !prevAttackDown) { const glm::vec3 look = camera.t.GetFrontVector(); actions.push_back(CharacterAction::Attack(glm::vec3(look.x, 0.0f, look.z))); } prevAttackDown = attackDown; // 1 to 9 use its abilities, the way the camera looks const int abilityCount = std::min((int)character.body->combat.abilities.size(), 9); for (int i = 0; i < 9; i++) { const bool down = enabled && mouseEnabled && i < abilityCount && keyboard->IsKeyDown(GLFW_KEY_1 + i); if (down && !prevAbilityDown[i]) { const glm::vec3 look = camera.t.GetFrontVector(); const glm::vec3 aim(look.x, 0.0f, look.z); const float range = character.body->combat.abilities[i].range; actions.push_back(CharacterAction::UseAbility(i, aim, character.body->t.GetTranslation() + aim * range)); } prevAbilityDown[i] = down; } } void RiflemanMind::ProduceActions(const Character& character, std::vector& actions) { // Real time since it last thought, for its timers const double now = glfwGetTime(); const float dt = lastTime < 0.0 ? 0.0f : std::min((float)(now - lastTime), 0.1f); lastTime = now; if (!target || target->IsDead() || target == &character) { actions.push_back(CharacterAction::Move(glm::vec3(0.0f))); return; } const CharacterCombat& combat = character.body->combat; const glm::vec3 me = character.body->t.GetTranslation(); const glm::vec3 them = target->body->t.GetTranslation(); glm::vec3 to(them.x - me.x, 0.0f, them.z - me.z); const float distance = glm::length(to); const glm::vec3 toward = distance > 0.001f ? to / distance : character.GetForwardDirection(); const glm::vec3 side(-toward.z, 0.0f, toward.x); // Mid ability it stands still, keeping on where it aimed rather than following them if (const Ability* current = combat.GetCurrentAbility()) { actions.push_back(CharacterAction::Move(glm::vec3(0.0f), combat.IsAbilityPivoting() ? aim : glm::vec3(0.0f))); return; } int shoot = -1, stab = -1, sweep = -1; for (int i = 0; i < (int)combat.abilities.size(); i++) { if (combat.abilities[i].effect == Ability::Effect::Shot && shoot < 0) shoot = i; if (combat.abilities[i].effect == Ability::Effect::Stab && stab < 0) stab = i; if (combat.abilities[i].effect == Ability::Effect::Sweep && sweep < 0) sweep = i; } // Lying there nothing it has can touch them, so it holds off until they're up const bool canHit = !target->DodgesHits(); const bool stabReady = canHit && stab >= 0 && combat.IsAbilityReady(stab); const bool sweepReady = canHit && sweep >= 0 && combat.IsAbilityReady(sweep); shotWait -= dt; dashWait -= dt; // In reach, stab if (stabReady && distance <= stabReach) { aim = toward; actions.push_back(CharacterAction::Move(glm::vec3(0.0f), toward)); actions.push_back(CharacterAction::UseAbility(stab, toward, them)); return; } // A little further out, sweep their legs, its step taking it in if (sweepReady && distance >= sweepMin && distance <= sweepMax) { aim = toward; actions.push_back(CharacterAction::Move(glm::vec3(0.0f), toward)); actions.push_back(CharacterAction::UseAbility(sweep, toward, them)); return; } // Which way it's strafing, now and then picked again strafeWait -= dt; if (strafeWait <= 0.0f) { strafeSign = std::uniform_int_distribution(0, 1)(rng) ? 1.0f : -1.0f; strafeWait = std::uniform_real_distribution(1.2f, 2.8f)(rng); } // Their shot winding up at it, maybe it dashes out of the way, once for each shot const CharacterCombat& theirs = target->body->combat; const Ability* incoming = theirs.GetCurrentAbility(); if (incoming && incoming->effect == Ability::Effect::Shot && theirs.GetAbilityUse() != seenUse) { seenUse = theirs.GetAbilityUse(); if (dashWait <= 0.0f && std::uniform_real_distribution(0.0f, 1.0f)(rng) < dodgeChance) { actions.push_back(CharacterAction::Dash(side * strafeSign)); dashWait = std::uniform_real_distribution(dashWaitMin, dashWaitMax)(rng); return; } } // Too close with nothing to hit them with, it dashes back out if (dashWait <= 0.0f && distance < stabReach * 1.5f && !stabReady && !sweepReady) { actions.push_back(CharacterAction::Dash(-toward + side * strafeSign * 0.5f)); dashWait = std::uniform_real_distribution(dashWaitMin, dashWaitMax)(rng); return; } // Far off with its sweep ready, it dashes in to close the gap if (dashWait <= 0.0f && sweepReady && distance > sweepMax + 2.0f && distance < sweepMax + 7.0f) { actions.push_back(CharacterAction::Dash(toward)); dashWait = std::uniform_real_distribution(dashWaitMin, dashWaitMax)(rng); return; } glm::vec3 move(0.0f); if (!canHit) move = side * strafeSign; // circles them while they're down, ready for when they're up else if ((stabReady || sweepReady) && distance < rushDistance) move = toward; // close and its knife's ready, in it goes else if (distance > farthest) move = toward + side * strafeSign * 0.4f; else if (distance < closest) move = -toward + side * strafeSign * 0.5f; else move = side * strafeSign; // Back toward the middle near the edge, rather than off it const float edge = arenaHalfSize - 4.0f; if (std::abs(me.x) > edge || std::abs(me.z) > edge) { const glm::vec3 home(-me.x, 0.0f, -me.z); if (glm::length(home) > 0.001f) move += glm::normalize(home) * 1.5f; } if (glm::length(move) > 0.001f) move = glm::normalize(move); // Always facing them, whichever way it goes, running when they're far off actions.push_back(CharacterAction::Move(move, toward, distance > sprintDistance)); // A shot whenever it's had a moment, if they're in range if (canHit && shoot >= 0 && shotWait <= 0.0f && combat.IsAbilityReady(shoot) && !combat.GetCurrentMove() && distance <= combat.abilities[shoot].range) { aim = toward; actions.push_back(CharacterAction::UseAbility(shoot, toward, them)); shotWait = std::uniform_real_distribution(shotWaitMin, shotWaitMax)(rng); } }