载入中...
搜索中...
未找到
CombatEnemyAI.cpp
浏览该文件的文档.
2
3#include <cmath>
4#include <optional>
5#include <utility>
6
7namespace eve::combat {
8namespace {
9
10double distance3(double ax, double ay, double az, double bx, double by, double bz) {
11 const double dx = ax - bx;
12 const double dy = ay - by;
13 const double dz = az - bz;
14 return std::sqrt(dx * dx + dy * dy + dz * dz);
15}
16
17} // namespace
18
21 Diagnostic::error(DiagnosticCode::InvalidArgument, "enemy subject is nil", "subject"));
22 if (ownerId.empty()) return Result<void>::failure(
23 Diagnostic::error(DiagnosticCode::InvalidArgument, "enemy owner id is empty", "ownerId"));
24 if (!std::isfinite(nearRadius) || nearRadius <= 0.0 || !std::isfinite(midRadius) || midRadius < nearRadius)
26 Diagnostic::error(DiagnosticCode::InvalidArgument, "enemy radii are invalid", "radius"));
28 Diagnostic::error(DiagnosticCode::InvalidArgument, "enemy attack cooldown must be positive", "attackCooldownTicks"));
29 return Result<void>::success();
30}
31
33 auto valid = definition.validate();
34 if (!valid) return valid;
35 EnemyState state;
36 state.definition = std::move(definition);
37 enemies_[state.definition.subject.format()] = std::move(state);
39}
40
47
49 if (!subject.isValid()) return Result<void>::failure(
50 Diagnostic::error(DiagnosticCode::InvalidArgument, "subject is nil", "subject"));
51 if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z)) return Result<void>::failure(
52 Diagnostic::error(DiagnosticCode::InvalidArgument, "pose is invalid", "pose"));
53 poses_[subject.format()] = Pose{x, y, z};
55}
56
58 auto found = enemies_.find(subject.format());
59 if (found == enemies_.end())
61 Diagnostic::error(DiagnosticCode::NotFound, "enemy was not found", subject.format()));
62 if (actionId.format().empty()) return Result<void>::failure(
63 Diagnostic::error(DiagnosticCode::InvalidArgument, "light action id is empty", "actionId"));
64 found->second.lightAction = std::move(actionId);
65 found->second.hasAction = true;
67}
68
70 if (!targets_)
72 Diagnostic::error(DiagnosticCode::NotFound, "enemy target runtime is unavailable", "targets"));
73
74 std::optional<action::AbilityIntent> emitted;
75 for (auto& [key, enemy] : enemies_) {
76 (void)key;
77 if (enemy.phase == CombatEnemyPhase::Recover &&
78 tick.value() >= enemy.phaseStartTick + enemy.definition.recoverTicks)
79 enemy.phase = CombatEnemyPhase::Idle;
80 if (emitted) continue;
81 if (!enemy.hasAction) continue;
82 if (enemy.definition.lightGrant.isZero()) continue;
83
84 const auto emitAttack = [&]() {
86 intent.grantId = enemy.definition.lightGrant;
87 intent.request.actionId = enemy.lightAction;
88 enemy.lastAttackTick = tick.value();
89 enemy.phase = enemy.definition.recoverTicks > 0 ? CombatEnemyPhase::Recover : CombatEnemyPhase::Idle;
90 enemy.phaseStartTick = tick.value();
91 emitted = std::move(intent);
92 };
93
94 if (enemy.phase == CombatEnemyPhase::Telegraph) {
95 if (tick.value() >= enemy.phaseStartTick + enemy.definition.telegraphTicks) emitAttack();
96 continue;
97 }
98 if (enemy.phase == CombatEnemyPhase::Recover) continue;
99
100 const auto selfPose = poses_.find(enemy.definition.subject.format());
101 if (selfPose == poses_.end()) continue;
102 auto lock = targets_->state(enemy.definition.subject);
103 if (!lock || !lock.value().target) continue;
104 const auto targetPose = poses_.find(lock.value().target->format());
105 if (targetPose == poses_.end()) continue;
106 const double dist = distance3(selfPose->second.x, selfPose->second.y, selfPose->second.z, targetPose->second.x,
107 targetPose->second.y, targetPose->second.z);
108 if (dist > enemy.definition.nearRadius) continue;
109 if (enemy.lastAttackTick != 0 && tick.value() < enemy.lastAttackTick + enemy.definition.attackCooldownTicks)
110 continue;
111
112 if (enemy.definition.telegraphTicks > 0) {
113 enemy.phase = CombatEnemyPhase::Telegraph;
114 enemy.phaseStartTick = tick.value();
115 continue;
116 }
117 emitAttack();
118 }
119 return Result<std::optional<action::AbilityIntent>>::success(std::move(emitted));
120}
121
123 if (!targets_)
125 Diagnostic::error(DiagnosticCode::NotFound, "enemy target runtime is unavailable", "targets"));
126
127 std::vector<CombatEnemySteering> result;
128 for (const auto& [key, enemy] : enemies_) {
129 (void)key;
130 if (enemy.phase != CombatEnemyPhase::Idle) continue;
131 const auto selfPose = poses_.find(enemy.definition.subject.format());
132 if (selfPose == poses_.end()) continue;
133 auto lock = targets_->state(enemy.definition.subject);
134 if (!lock || !lock.value().target) continue;
135 const auto targetPose = poses_.find(lock.value().target->format());
136 if (targetPose == poses_.end()) continue;
137 const double dist = distance3(selfPose->second.x, selfPose->second.y, selfPose->second.z, targetPose->second.x,
138 targetPose->second.y, targetPose->second.z);
139 if (dist <= enemy.definition.nearRadius) continue;
140
141 CombatVector3 delta{targetPose->second.x - selfPose->second.x, 0.0, targetPose->second.z - selfPose->second.z};
142 const double magnitude = std::hypot(delta.x, delta.z);
143 if (magnitude <= 1e-6) continue;
144 CombatEnemySteering steering;
145 steering.subject = enemy.definition.subject;
146 steering.moveDirection = {delta.x / magnitude, 0.0, delta.z / magnitude};
147 steering.speedFraction = dist <= enemy.definition.midRadius ? 0.7 : 1.0;
148 result.push_back(steering);
149 }
151}
152
154 const auto found = enemies_.find(subject.format());
155 if (found == enemies_.end() || !targets_) return CombatEnemyBand::Far;
156 const auto selfPose = poses_.find(subject.format());
157 if (selfPose == poses_.end()) return CombatEnemyBand::Far;
158 auto lock = targets_->state(subject);
159 if (!lock || !lock.value().target) return CombatEnemyBand::Far;
160 const auto targetPose = poses_.find(lock.value().target->format());
161 if (targetPose == poses_.end()) return CombatEnemyBand::Far;
162 const double dist = distance3(selfPose->second.x, selfPose->second.y, selfPose->second.z, targetPose->second.x,
163 targetPose->second.y, targetPose->second.z);
164 if (dist <= found->second.definition.nearRadius) return CombatEnemyBand::Near;
165 if (dist <= found->second.definition.midRadius) return CombatEnemyBand::Mid;
167}
168
170 const auto found = enemies_.find(subject.format());
171 if (found == enemies_.end()) return CombatEnemyPhase::Idle;
172 return found->second.phase;
173}
174
179
180} // namespace eve::combat
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
int subject
Definition AnimSmr.cpp:163
float phase
Definition CaveMesh.cpp:58
int bz
Definition CaveMesh.cpp:114
int ax
Definition CaveMesh.cpp:113
int ay
Definition CaveMesh.cpp:113
int bx
Definition CaveMesh.cpp:114
int az
Definition CaveMesh.cpp:113
int by
Definition CaveMesh.cpp:114
Minimal ring-based combat enemy intent source.
std::uint32_t key
std::int32_t second
bool valid
bool found
double current
float dz
float dy
float dx
SimulationTick tick
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Human-readable, scoped identifier in the form namespace:name.
Definition Identity.h:411
const std::string & format() const noexcept
Returns the canonical namespace:name representation.
Definition Identity.h:436
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
Strong, domain-neutral reference to a runtime subject.
Definition SubjectRef.h:26
bool isValid() const noexcept
Return whether this reference is valid and non-nil.
Definition SubjectRef.h:38
bool isPunishable(SubjectRef subject) const
True while the enemy is in Telegraph or Recover.
CombatEnemyBand band(SubjectRef subject) const
Current band for debugging; Far when unknown.
Result< std::vector< CombatEnemySteering > > nextSteering(SimulationTick tick) const
Emit approach steering for Mid/Far enemies that are Idle (not punishing).
Result< std::optional< action::AbilityIntent > > nextIntent(SimulationTick tick) override
Emit at most one attack intent for the supplied tick.
Result< void > registerEnemy(CombatEnemyDefinition definition)
Register or replace one enemy definition after validation.
Result< void > unregisterEnemy(SubjectRef subject)
Remove one enemy.
Result< void > setLightAction(SubjectRef subject, LogicalId actionId)
Set the action id copied into emitted intents.
Result< void > setPosition(SubjectRef subject, double x, double y, double z)
Provide world positions for band checks (enemies and their targets).
CombatEnemyPhase phase(SubjectRef subject) const
Current move-set phase; Idle when the subject is unknown.
Result< CombatLockState > state(SubjectRef owner) const
Return owning lock state or NotFound.
CombatEnemyBand
Distance band used by the simple enemy brain.
CombatEnemyPhase
Authored move-set phase; Telegraph and Recover are punish windows.
Owning request to activate one granted ability.
LogicalId actionId
Definition identity this request intends to execute.
Definition Action.h:129
Owning configuration for one enemy fighter.
Result< void > validate() const
Validate subject, owner and positive radii.
Owning planar steering suggestion for Mid/Far bands.
Finite 3D combat vector.