载入中...
搜索中...
未找到
CombatLocomotion.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <utility>
6
7namespace eve::combat {
8namespace {
9
10bool finite(CombatVector2 value) { return std::isfinite(value.x) && std::isfinite(value.z); }
11
12double length(CombatVector2 value) { return std::hypot(value.x, value.z); }
13
14CombatVector2 normalized(CombatVector2 value) {
15 const double magnitude = length(value);
16 return magnitude > 0.0 ? CombatVector2{value.x / magnitude, value.z / magnitude} : CombatVector2{};
17}
18
19Result<void> validateGoal(const CombatNavigationGoal& goal) {
20 if (!finite(goal.position) || !std::isfinite(goal.acceptanceRadius) || goal.acceptanceRadius < 0.0)
22 Diagnostic::error(DiagnosticCode::InvalidArgument, "navigation goal is invalid", "goal"));
23 return Result<void>::success();
24}
25
26Result<void> validateSteering(const CombatNavigationSteering& steering) {
27 if ((steering.phase != CombatNavigationPhase::Moving &&
28 steering.phase != CombatNavigationPhase::Arrived) ||
29 !finite(steering.direction) || !std::isfinite(steering.speedFraction) ||
30 steering.speedFraction < 0.0 || steering.speedFraction > 1.0)
32 "navigation provider returned invalid steering", "steering"));
33 return Result<void>::success();
34}
35
36} // namespace
37
39 if (!subject.isValid())
41 if (ownerId.empty())
43 Diagnostic::error(DiagnosticCode::InvalidArgument, "owner id is empty", "ownerId"));
44 if (!finite(initialPosition) || !std::isfinite(maximumSpeed) || maximumSpeed <= 0.0 ||
45 !std::isfinite(acceleration) || acceleration <= 0.0)
47 Diagnostic::error(DiagnosticCode::InvalidArgument, "locomotion limits are invalid", "movement"));
48 return Result<void>::success();
49}
50
53 (void)tick;
54 auto valid = validateGoal(goal);
56 const CombatVector2 delta{goal.position.x - state.position.x, goal.position.z - state.position.z};
57 const double distance = length(delta);
58 if (distance <= goal.acceptanceRadius)
62 {CombatNavigationPhase::Moving, normalized(delta), 1.0});
63}
64
66 auto valid = definition.validate();
67 if (!valid) return valid;
68 const std::string key = definition.subject.format();
69 if (states_.contains(key))
71 Diagnostic::error(DiagnosticCode::AlreadyExists, "locomotion subject already exists", key));
72 states_.emplace(key, CombatLocomotionState{definition.subject, std::move(definition.ownerId),
73 definition.initialPosition, {}, {1.0, 0.0}, {}, 0.0,
74 definition.maximumSpeed, definition.acceleration, std::nullopt});
76}
77
79 if (!subject.isValid())
81 if (states_.erase(subject.format()) == 0)
83 Diagnostic::error(DiagnosticCode::NotFound, "locomotion subject was not found", subject.format()));
85}
86
88 if (!subject.isValid())
90 Diagnostic::error(DiagnosticCode::InvalidArgument, "subject is nil", "subject"));
91 const auto found = states_.find(subject.format());
92 if (found == states_.end())
94 Diagnostic::error(DiagnosticCode::NotFound, "locomotion subject was not found", subject.format()));
96}
97
99 double speedFraction) {
100 if (!subject.isValid() || !finite(direction) || !std::isfinite(speedFraction) || speedFraction < 0.0 ||
101 speedFraction > 1.0)
103 Diagnostic::error(DiagnosticCode::InvalidArgument, "movement intent is invalid", "intent"));
104 const auto found = states_.find(subject.format());
105 if (found == states_.end())
107 Diagnostic::error(DiagnosticCode::NotFound, "locomotion subject was not found", subject.format()));
108 found->second.moveDirection = normalized(direction);
109 found->second.moveSpeedFraction = length(direction) > 0.0 ? speedFraction : 0.0;
110 found->second.navigationGoal.reset();
112}
113
115 if (!subject.isValid())
116 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "subject is nil", "subject"));
117 if (!navigation_)
119 Diagnostic::error(DiagnosticCode::Unsupported, "combat navigation provider is unavailable", "navigation"));
120 auto valid = validateGoal(goal);
121 if (!valid) return valid;
122 const auto found = states_.find(subject.format());
123 if (found == states_.end())
125 Diagnostic::error(DiagnosticCode::NotFound, "locomotion subject was not found", subject.format()));
126 found->second.navigationGoal = goal;
128}
129
131 const auto found = states_.find(subject.format());
132 if (!subject.isValid() || found == states_.end())
134 Diagnostic::error(DiagnosticCode::NotFound, "locomotion subject was not found", subject.format()));
135 found->second.moveDirection = {};
136 found->second.moveSpeedFraction = 0.0;
137 found->second.navigationGoal.reset();
139}
140
142 if (step.delta < Duration::zero() || step.tick < lastTick_)
144 DiagnosticCode::InvalidArgument, "locomotion step is negative or moves tick backwards", "step"));
145 auto candidate = states_;
147 result.tick = step.tick;
148 const double seconds = step.delta.seconds();
149 for (auto& [key, state] : candidate) {
150 (void)key;
151 bool arrived = false;
152 std::optional<CombatNavigationGoal> activeGoal = state.navigationGoal;
153 if (state.navigationGoal) {
154 if (!navigation_)
156 Diagnostic::error(DiagnosticCode::Unsupported, "active goal has no combat navigation provider",
157 state.subject.format()));
158 auto steering = navigation_->steer(state, *state.navigationGoal, step.tick);
159 if (!steering) return Result<CombatLocomotionAdvance>::failure(steering.status());
160 auto valid = validateSteering(steering.value());
162 arrived = steering.value().phase == CombatNavigationPhase::Arrived;
163 state.moveDirection = arrived ? CombatVector2{} : normalized(steering.value().direction);
164 state.moveSpeedFraction = arrived ? 0.0 : steering.value().speedFraction;
165 if (arrived) {
166 state.navigationGoal.reset();
167 state.velocity = {};
168 }
169 }
170 const CombatVector2 previous = state.position;
171 const CombatVector2 desired{state.moveDirection.x * state.maximumSpeed * state.moveSpeedFraction,
172 state.moveDirection.z * state.maximumSpeed * state.moveSpeedFraction};
173 const CombatVector2 velocityDelta{desired.x - state.velocity.x, desired.z - state.velocity.z};
174 const double deltaLength = length(velocityDelta);
175 const double maximumDelta = state.acceleration * seconds;
176 const double scale = deltaLength > maximumDelta && deltaLength > 0.0 ? maximumDelta / deltaLength : 1.0;
177 state.velocity.x += velocityDelta.x * scale;
178 state.velocity.z += velocityDelta.z * scale;
179 state.position.x += state.velocity.x * seconds;
180 state.position.z += state.velocity.z * seconds;
181 if (activeGoal && !arrived) {
182 const CombatVector2 before{activeGoal->position.x - previous.x,
183 activeGoal->position.z - previous.z};
184 const CombatVector2 after{activeGoal->position.x - state.position.x,
185 activeGoal->position.z - state.position.z};
186 if (length(after) <= activeGoal->acceptanceRadius || before.x * after.x + before.z * after.z <= 0.0) {
187 state.position = activeGoal->position;
188 state.velocity = {};
189 state.moveDirection = {};
190 state.moveSpeedFraction = 0.0;
191 state.navigationGoal.reset();
192 arrived = true;
193 }
194 }
195 if (length(state.velocity) > 0.0) state.facing = normalized(state.velocity);
196 if (state.position.x != previous.x || state.position.z != previous.z)
197 result.events.push_back({state.subject, CombatLocomotionEventKind::Moved, previous, state.position,
198 step.tick});
199 if (arrived)
200 result.events.push_back({state.subject, CombatLocomotionEventKind::Arrived, previous, state.position,
201 step.tick});
202 }
203 states_ = std::move(candidate);
204 lastTick_ = step.tick;
206}
207
208std::vector<CombatLocomotionState> CombatLocomotionRuntime::states() const {
209 std::vector<CombatLocomotionState> result;
210 result.reserve(states_.size());
211 for (const auto& [key, state] : states_) {
212 (void)key;
213 result.push_back(state);
214 }
215 return result;
216}
217
218} // namespace eve::combat
double value
int subject
Definition AnimSmr.cpp:163
float length
Definition CaveMesh.cpp:94
Deterministic player/navigation locomotion adapter for combat subjects.
std::uint32_t key
std::array< float, 3 > scale
bool valid
float distance
graphics::Canvas * previous
bool finite
RoadLaneDirection direction
bool found
SimulationTick tick
float step
Definition TreeMesh.cpp:314
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
static constexpr Duration zero() noexcept
Return zero duration.
Definition Time.h:60
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
Result< void > registerSubject(CombatLocomotionDefinition definition)
Register one unique subject from validated owning definition data.
Result< CombatLocomotionAdvance > advance(const SimulationStep &step)
Atomically advance every subject using only the supplied deterministic step.
Result< void > unregisterSubject(SubjectRef subject)
Remove one subject; missing subjects are rejected.
Result< CombatLocomotionState > state(SubjectRef subject) const
Return an owning state snapshot, or NotFound for a stale subject.
Result< void > stop(SubjectRef subject)
Clear a navigation goal and desired movement for one subject.
Result< void > setMoveIntent(SubjectRef subject, CombatVector2 direction, double speedFraction)
Set normalized player/AI movement and clear any navigation goal.
std::vector< CombatLocomotionState > states() const
Return owning states in stable subject-id order.
Result< void > navigateTo(SubjectRef subject, CombatNavigationGoal goal)
Install a navigation goal after provider and value validation.
Result< CombatNavigationSteering > steer(const CombatLocomotionState &state, const CombatNavigationGoal &goal, SimulationTick tick) override
Resolve steering for one subject without mutating locomotion state.
virtual Result< CombatNavigationSteering > steer(const CombatLocomotionState &state, const CombatNavigationGoal &goal, SimulationTick tick)=0
Resolve steering for one subject without mutating locomotion state.
One deterministic fixed-step emitted by SimulationClock.
Definition Time.h:158
Complete owning result of one committed locomotion frame.
std::vector< CombatLocomotionEvent > events
Immutable registration data for one locomotion subject.
Result< void > validate() const
Validate identity and finite positive movement limits.
Owning read-only projection of one registered combat subject.
Owning navigation goal retained by the locomotion authority.
Finite position or direction in the horizontal combat plane.