199 lines
8.8 KiB
C++
199 lines
8.8 KiB
C++
#include "CharacterMind.h"
|
|
|
|
#include "Camera.h"
|
|
#include "Character.h"
|
|
#include "common.h"
|
|
#include "Keyboard.h"
|
|
#include "Mouse.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
void KeyboardMind::ProduceActions(const Character& character, std::vector<CharacterAction>& 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<CharacterAction>& 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<int>(0, 1)(rng) ? 1.0f : -1.0f;
|
|
strafeWait = std::uniform_real_distribution<float>(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<float>(0.0f, 1.0f)(rng) < dodgeChance) {
|
|
actions.push_back(CharacterAction::Dash(side * strafeSign));
|
|
dashWait = std::uniform_real_distribution<float>(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<float>(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<float>(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<float>(shotWaitMin, shotWaitMax)(rng);
|
|
}
|
|
}
|