载入中...
搜索中...
未找到
Targeting.cpp
浏览该文件的文档.
1#include "sensing/Targeting.h"
2
3#include <algorithm>
4#include <cmath>
5#include <iterator>
6#include <limits>
7#include <set>
8#include <string>
9#include <type_traits>
10#include <utility>
11
12namespace eve::sensing {
13namespace {
14
15bool isWorldSpace(CoordinateSpace space) noexcept {
16 return space == CoordinateSpace::World2D || space == CoordinateSpace::World3D;
17}
18
19bool isGridSpace(CoordinateSpace space) noexcept {
20 return space == CoordinateSpace::Grid2D || space == CoordinateSpace::Grid3D;
21}
22
23bool sameSpace(const TargetLocation& a, const TargetLocation& b) noexcept {
24 return std::visit(
25 [](const auto& left, const auto& right) {
26 return left.isValid() && right.isValid() && left.space() == right.space();
27 },
28 a, b);
29}
30
31bool sameSpace(const TargetLocation& location, CoordinateSpace space) noexcept {
32 return std::visit([space](const auto& value) { return value.isValid() && value.space() == space; }, location);
33}
34
35double distanceSquared(const TargetLocation& a, const TargetLocation& b) noexcept {
36 return std::visit(
37 [](const auto& left, const auto& right) -> double {
38 if constexpr (std::is_same_v<std::decay_t<decltype(left)>, WorldPoint> &&
39 std::is_same_v<std::decay_t<decltype(right)>, WorldPoint>) {
40 const double dx = static_cast<double>(left.x()) - right.x();
41 const double dy = static_cast<double>(left.y()) - right.y();
42 const double dz = static_cast<double>(left.z()) - right.z();
43 return dx * dx + dy * dy + dz * dz;
44 } else if constexpr (std::is_same_v<std::decay_t<decltype(left)>, GridPoint> &&
45 std::is_same_v<std::decay_t<decltype(right)>, GridPoint>) {
46 const double dx = static_cast<double>(left.x()) - right.x();
47 const double dy = static_cast<double>(left.y()) - right.y();
48 const double dz = static_cast<double>(left.z()) - right.z();
49 return dx * dx + dy * dy + dz * dz;
50 } else {
51 return std::numeric_limits<double>::infinity();
52 }
53 },
54 a, b);
55}
56
57bool contains(const TargetingSpec& spec, const TargetLocation& location) noexcept {
58 if (spec.worldArea) {
59 if (const auto* point = std::get_if<WorldPoint>(&location)) return spec.worldArea->contains(*point);
60 return false;
61 }
62 if (spec.gridArea) {
63 if (const auto* point = std::get_if<GridPoint>(&location)) return spec.gridArea->contains(*point);
64 return false;
65 }
66 return true;
67}
68
69bool containsAllTags(const std::vector<std::string>& candidate, const std::vector<std::string>& required,
70 const std::vector<std::string>& excluded) {
71 const std::set<std::string> tags(candidate.begin(), candidate.end());
72 for (const auto& tag : required)
73 if (!tags.contains(tag)) return false;
74 for (const auto& tag : excluded)
75 if (tags.contains(tag)) return false;
76 return true;
77}
78
79bool inZone(const TargetCandidate& candidate, const std::optional<ZoneRef>& zone) {
80 if (!zone) return true;
81 return std::find(candidate.zones.begin(), candidate.zones.end(), *zone) != candidate.zones.end();
82}
83
84bool matches(const TargetingQuery& query, const TargetCandidate& candidate) {
85 if (!candidate.subject.isValid() || !sameSpace(candidate.location, query.spec.space)) return false;
86 if (query.spec.domain != TargetDomain::Any && candidate.domain != query.spec.domain) return false;
87 if (!containsAllTags(candidate.tags, query.spec.requiredTags, query.spec.excludedTags)) return false;
88 if (!inZone(candidate, query.spec.zone) || !contains(query.spec, candidate.location)) return false;
89
90 const double distance = std::sqrt(distanceSquared(query.originLocation, candidate.location));
91 return distance >= static_cast<double>(query.spec.minRange) && distance <= static_cast<double>(query.spec.maxRange);
92}
93
94Result<void> validateWorldPair(WorldPoint minimum, WorldPoint maximum, CoordinateSpace expected,
95 const char* operation) {
96 if (!minimum.isValid() || !maximum.isValid() || minimum.space() != expected || maximum.space() != expected)
99 std::string(operation) + " requires two points in the same coordinate space", {}));
100 if (minimum.x() > maximum.x() || minimum.y() > maximum.y() ||
101 (expected == CoordinateSpace::World3D && minimum.z() > maximum.z()))
104 std::string(operation) + " requires minimum coordinates not greater than maximum", {}));
105 return Result<void>::success();
106}
107
108Result<void> validateGridPair(GridPoint minimum, GridPoint maximum, CoordinateSpace expected, const char* operation) {
109 if (!minimum.isValid() || !maximum.isValid() || minimum.space() != expected || maximum.space() != expected)
112 std::string(operation) + " requires two points in the same grid space", {}));
113 if (minimum.x() > maximum.x() || minimum.y() > maximum.y() ||
114 (expected == CoordinateSpace::Grid3D && minimum.z() > maximum.z()))
117 std::string(operation) + " requires minimum cells not greater than maximum", {}));
118 return Result<void>::success();
119}
120
121} // namespace
122
123std::optional<ZoneRef> ZoneRef::fromLogicalId(LogicalId id) {
124 if (!id.isValid()) return std::nullopt;
125 return ZoneRef(std::move(id));
126}
127
129 if (!std::isfinite(x) || !std::isfinite(y))
131 Diagnostic::error(DiagnosticCode::InvalidArgument, "World2D coordinates must be finite", {}));
133}
134
136 if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z))
138 Diagnostic::error(DiagnosticCode::InvalidArgument, "World3D coordinates must be finite", {}));
140}
141
142GridPoint GridPoint::grid2D(std::int32_t x, std::int32_t y) noexcept {
144}
145
146GridPoint GridPoint::grid3D(std::int32_t x, std::int32_t y, std::int32_t z) noexcept {
148}
149
151 if (!center.isValid() || center.space() != CoordinateSpace::World2D || !std::isfinite(radius) || radius < 0.f)
154 "WorldArea.circle2D requires a finite World2D center and non-negative radius", {}));
156}
157
163
165 if (!center.isValid() || center.space() != CoordinateSpace::World3D || !std::isfinite(radius) || radius < 0.f)
168 "WorldArea.sphere3D requires a finite World3D center and non-negative radius", {}));
170}
171
177
178Result<WorldArea> WorldArea::cone2D(WorldPoint apex, float dirX, float dirY, float halfAngleRadians, float range) {
179 if (!apex.isValid() || apex.space() != CoordinateSpace::World2D || !std::isfinite(dirX) || !std::isfinite(dirY) ||
180 !std::isfinite(halfAngleRadians) || !std::isfinite(range) || halfAngleRadians < 0.f ||
181 halfAngleRadians > 3.14159265f || range < 0.f || !(std::hypot(dirX, dirY) > 0.f)) {
184 "WorldArea.cone2D requires World2D apex, non-zero dir, halfAngle in [0,pi], non-negative range", {}));
185 }
186 auto direction = WorldPoint::world2D(dirX, dirY);
187 if (!direction) return Result<WorldArea>::failure(direction.status());
189 WorldArea(Shape::Cone2D, apex, direction.value(), range, halfAngleRadians));
190}
191
193 if (!valid_ || !point.isValid() || point.space() != space_) return false;
194 if (shape_ == Shape::Circle2D || shape_ == Shape::Sphere3D) {
195 const double dx = static_cast<double>(point.x()) - first_.x();
196 const double dy = static_cast<double>(point.y()) - first_.y();
197 const double dz = static_cast<double>(point.z()) - first_.z();
198 const double distance = dx * dx + dy * dy + dz * dz;
199 return distance <= static_cast<double>(radius_) * radius_;
200 }
201 if (shape_ == Shape::Cone2D) {
202 const float dx = point.x() - first_.x();
203 const float dy = point.y() - first_.y();
204 const float distSq = dx * dx + dy * dy;
205 if (distSq > radius_ * radius_) return false;
206 if (distSq == 0.f) return true;
207 const float facingLen = std::hypot(second_.x(), second_.y());
208 if (!(facingLen > 0.f)) return false;
209 const float invDist = 1.f / std::sqrt(distSq);
210 const float nx = dx * invDist;
211 const float ny = dy * invDist;
212 const float fx = second_.x() / facingLen;
213 const float fy = second_.y() / facingLen;
214 const float dot = std::clamp(nx * fx + ny * fy, -1.f, 1.f);
215 return std::acos(dot) <= halfAngle_;
216 }
217 return point.x() >= first_.x() && point.x() <= second_.x() && point.y() >= first_.y() && point.y() <= second_.y() &&
218 (shape_ == Shape::Box2D || (point.z() >= first_.z() && point.z() <= second_.z()));
219}
220
226
232
233bool GridArea::contains(GridPoint point) const noexcept {
234 if (!valid_ || !point.isValid() || point.space() != space_) return false;
235 return point.x() >= minimum_.x() && point.x() <= maximum_.x() && point.y() >= minimum_.y() &&
236 point.y() <= maximum_.y() &&
237 (shape_ == Shape::Box2D || (point.z() >= minimum_.z() && point.z() <= maximum_.z()));
238}
239
241 if ((isWorldSpace(space) && gridArea) || (isGridSpace(space) && worldArea))
243 "TargetingSpec area uses a different coordinate space", {}));
244 if (worldArea && !worldArea->isValid())
246 Diagnostic::error(DiagnosticCode::InvalidArgument, "TargetingSpec world area must be valid", {}));
247 if (gridArea && !gridArea->isValid())
249 Diagnostic::error(DiagnosticCode::InvalidArgument, "TargetingSpec grid area must be valid", {}));
250 if (worldArea && worldArea->space() != space)
252 "TargetingSpec world area does not match its space", {}));
253 if (gridArea && gridArea->space() != space)
255 Diagnostic::error(DiagnosticCode::InvalidArgument, "TargetingSpec grid area does not match its space", {}));
256 if (minCount > maxCount)
258 Diagnostic::error(DiagnosticCode::InvalidArgument, "TargetingSpec minCount exceeds maxCount", {}));
259 if (!std::isfinite(minRange) ||
260 (!(std::isfinite(maxRange) || maxRange == std::numeric_limits<float>::infinity())) || minRange < 0.f ||
263 Diagnostic::error(DiagnosticCode::InvalidArgument, "TargetingSpec range must be finite and ordered", {}));
264 if (zone && !zone->isValid())
266 Diagnostic::error(DiagnosticCode::InvalidArgument, "TargetingSpec zone must be valid", {}));
267 for (const auto& tag : requiredTags)
268 if (tag.empty())
270 Diagnostic::error(DiagnosticCode::InvalidArgument, "requiredTags cannot contain empty keys", {}));
271 for (const auto& tag : excludedTags)
272 if (tag.empty())
274 Diagnostic::error(DiagnosticCode::InvalidArgument, "excludedTags cannot contain empty keys", {}));
275 return Result<void>::success();
276}
277
279 auto specResult = spec.validate();
280 if (!specResult) return Result<void>::failure(specResult.status());
281 if (!origin.isValid())
283 Diagnostic::error(DiagnosticCode::InvalidArgument, "TargetingQuery origin must be non-nil", {}));
284 if (!sameSpace(originLocation, spec.space))
286 DiagnosticCode::InvalidArgument, "TargetingQuery origin and spec use different coordinate spaces", {}));
287 return Result<void>::success();
288}
289
291 if (!subject.isValid())
293 Diagnostic::error(DiagnosticCode::InvalidArgument, "TargetSet subject must be non-nil", {}));
294 if (std::find(subjects_.begin(), subjects_.end(), subject) != subjects_.end())
296 subjects_.push_back(subject);
298}
299
301 if (!subject.isValid())
303 Diagnostic::error(DiagnosticCode::InvalidArgument, "TargetSet primary must be non-nil", {}));
304 if (std::find(subjects_.begin(), subjects_.end(), subject) == subjects_.end())
306 Diagnostic::error(DiagnosticCode::NotFound, "TargetSet primary must already be a member", {}));
307 primary_ = subject;
309}
310
312 if (!std::visit([](const auto& value) { return value.isValid(); }, point))
314 Diagnostic::error(DiagnosticCode::InvalidArgument, "TargetSet point must be valid", {}));
315 if (point_ && !sameSpace(*point_, point))
317 Diagnostic::error(DiagnosticCode::InvalidArgument, "TargetSet points cannot mix coordinate spaces", {}));
318 if (worldArea_ && (!std::get_if<WorldPoint>(&point) || std::get<WorldPoint>(point).space() != worldArea_->space()))
320 "TargetSet point and world area use different spaces", {}));
321 if (gridArea_ && (!std::get_if<GridPoint>(&point) || std::get<GridPoint>(point).space() != gridArea_->space()))
323 "TargetSet point and grid area use different spaces", {}));
324 point_ = std::move(point);
326}
327
329 if (!area.isValid())
331 Diagnostic::error(DiagnosticCode::InvalidArgument, "TargetSet world area must be valid", {}));
332 if (gridArea_)
334 Diagnostic::error(DiagnosticCode::InvalidArgument, "TargetSet cannot mix world and grid areas", {}));
335 if (point_ && (!std::get_if<WorldPoint>(&*point_) || std::get<WorldPoint>(*point_).space() != area.space()))
337 "TargetSet point and world area use different spaces", {}));
338 worldArea_ = std::move(area);
340}
341
343 if (!area.isValid())
345 Diagnostic::error(DiagnosticCode::InvalidArgument, "TargetSet grid area must be valid", {}));
346 if (worldArea_)
348 Diagnostic::error(DiagnosticCode::InvalidArgument, "TargetSet cannot mix world and grid areas", {}));
349 if (point_ && (!std::get_if<GridPoint>(&*point_) || std::get<GridPoint>(*point_).space() != area.space()))
351 "TargetSet point and grid area use different spaces", {}));
352 gridArea_ = std::move(area);
354}
355
357 if (!candidate.subject.isValid())
359 Diagnostic::error(DiagnosticCode::InvalidArgument, "candidate subject must be non-nil", {}));
360 if (!std::visit([](const auto& value) { return value.isValid(); }, candidate.location))
362 Diagnostic::error(DiagnosticCode::InvalidArgument, "candidate location must be valid", {}));
363 for (const auto& tag : candidate.tags)
364 if (tag.empty())
366 Diagnostic::error(DiagnosticCode::InvalidArgument, "candidate tags cannot contain empty keys", {}));
367 for (const auto& zone : candidate.zones)
368 if (!zone.isValid())
370 Diagnostic::error(DiagnosticCode::InvalidArgument, "candidate zones must be valid", {}));
371 candidates_[candidate.subject.format()] = std::move(candidate);
373}
374
376 if (!subject.isValid())
378 Diagnostic::error(DiagnosticCode::InvalidArgument, "candidate subject must be non-nil", {}));
379 if (candidates_.erase(subject.format()) == 0)
381 Diagnostic::error(DiagnosticCode::NotFound, "candidate subject was not registered", {}));
383}
384
386 auto valid = query.validate();
387 if (!valid) return Result<std::vector<TargetCandidate>>::failure(valid.status());
388 std::vector<TargetCandidate> result;
389 result.reserve(candidates_.size());
390 for (const auto& [key, candidate] : candidates_) {
391 (void)key;
392 if (matches(query, candidate)) result.push_back(candidate);
393 }
394 return Result<std::vector<TargetCandidate>>::success(std::move(result));
395}
396
398 auto valid = query.validate();
399 if (!valid) return Result<std::vector<TargetCandidate>>::failure(valid.status());
400 if (world_ == nullptr)
402 DiagnosticCode::Unsupported, "SensingWorldCandidateProvider has no bound SensingWorld", {}));
403 if (query.spec.space != CoordinateSpace::World2D)
405 Diagnostic::error(DiagnosticCode::Unsupported, "SensingWorldCandidateProvider supports World2D only", {}));
406 if (query.spec.gridArea)
408 DiagnosticCode::Unsupported, "SensingWorldCandidateProvider does not support grid areas", {}));
409 if (query.spec.domain != TargetDomain::Any && !relation_)
412 "TargetDomain filters require an injected FactionRelationFn on SensingWorldCandidateProvider", {}));
413
414 const auto* originPoint = std::get_if<WorldPoint>(&query.originLocation);
415 if (originPoint == nullptr || !originPoint->isValid() || originPoint->space() != CoordinateSpace::World2D)
417 DiagnosticCode::InvalidArgument, "SensingWorldCandidateProvider origin must be a World2D point", {}));
418
419 QuerySpec spec;
420 spec.minRange = query.spec.minRange;
421 spec.maxRange = query.spec.maxRange;
422 spec.requiredTags = query.spec.requiredTags;
423 spec.excludedTags = query.spec.excludedTags;
424 spec.minCount = 0;
425 spec.maxCount = std::numeric_limits<std::uint32_t>::max();
428
429 auto queried = world_->query(QueryOrigin{originPoint->x(), originPoint->y(), query.origin.format()}, spec);
430 if (!queried) return Result<std::vector<TargetCandidate>>::failure(queried.status());
431
432 std::string originFaction;
433 if (const auto found = world_->subjects().find(query.origin.format()); found != world_->subjects().end())
434 originFaction = found->second.faction;
435
436 std::vector<TargetCandidate> result;
437 result.reserve(queried.value().size());
438 for (const auto& ranked : queried.value().ranked()) {
439 auto persistent = PersistentId::parse(ranked.id);
440 if (!persistent)
443 "SensingWorld subject id must be a PersistentId UUID for Targeting adapters", "subject.id"));
444 auto location = WorldPoint::world2D(ranked.x, ranked.y);
445 if (!location) return Result<std::vector<TargetCandidate>>::failure(location.status());
446 if (query.spec.worldArea && !query.spec.worldArea->contains(location.value())) continue;
447
449 if (relation_) {
450 const auto subjectIt = world_->subjects().find(ranked.id);
451 const std::string_view candidateFaction =
452 subjectIt == world_->subjects().end() ? std::string_view{} : std::string_view{subjectIt->second.faction};
453 auto related = relation_(originFaction, candidateFaction);
454 if (!related) return Result<std::vector<TargetCandidate>>::failure(related.status());
455 domain = std::move(related).takeValue();
456 }
457 if (query.spec.domain != TargetDomain::Any && domain != query.spec.domain) continue;
458
459 TargetCandidate candidate;
460 candidate.subject = SubjectRef::fromPersistentId(*persistent);
461 candidate.location = std::move(location).takeValue();
462 candidate.domain = domain;
463 const auto& subjectFacts = world_->subjects().at(ranked.id);
464 candidate.tags.assign(subjectFacts.tags.begin(), subjectFacts.tags.end());
465 candidate.zones.clear();
466 candidate.zones.reserve(subjectFacts.zones.size());
467 for (const auto& zoneText : subjectFacts.zones) {
468 auto logical = LogicalId::parse(zoneText);
469 if (!logical)
472 "SensingWorld subject zone must be a valid LogicalId", "subject.zones"));
473 auto zone = ZoneRef::fromLogicalId(*logical);
474 if (!zone)
477 "SensingWorld subject zone must form a valid ZoneRef", "subject.zones"));
478 candidate.zones.push_back(*zone);
479 }
480 if (query.spec.zone && !inZone(candidate, query.spec.zone)) continue;
481 result.push_back(std::move(candidate));
482 }
483 return Result<std::vector<TargetCandidate>>::success(std::move(result));
484}
485
487 auto valid = query.validate();
488 if (!valid) return Result<TargetSet>::failure(valid.status());
489
490 auto* provider = cap::query<ISensingCandidateProvider>();
491 if (!provider)
493 Diagnostic::error(DiagnosticCode::Unsupported, "Targeting requires an ISensingCandidateProvider", {}));
494
495 auto candidateResult = provider->query(query);
496 if (!candidateResult) return Result<TargetSet>::failure(candidateResult.status());
497 auto candidates = std::move(candidateResult).takeValue();
498
499 ILineOfSightQuery* los = nullptr;
500 if (query.spec.lineOfSight == LineOfSightMode::Required) {
501 los = cap::query<ILineOfSightQuery>();
502 if (!los)
505 "Targeting line-of-sight was requested but no provider is registered", {}));
506 }
507
508 struct Ranked {
509 TargetCandidate candidate;
510 double distance = 0.0;
511 };
512 std::vector<Ranked> accepted;
513 accepted.reserve(candidates.size());
514 for (const auto& candidate : candidates) {
515 if (!candidate.subject.isValid() ||
516 !std::visit([](const auto& value) { return value.isValid(); }, candidate.location))
518 DiagnosticCode::InvariantViolation, "candidate provider returned an invalid candidate", {}));
519 if (!sameSpace(candidate.location, query.spec.space))
521 "candidate provider mixed coordinate spaces", {}));
522 if (!matches(query, candidate)) continue;
523 if (los) {
524 auto visible = los->query(query.originLocation, candidate.location);
525 if (!visible) return Result<TargetSet>::failure(visible.status());
526 if (!std::move(visible).takeValue().visible) continue;
527 }
528 accepted.push_back(Ranked{candidate, std::sqrt(distanceSquared(query.originLocation, candidate.location))});
529 }
530
531 if (query.spec.countPolicy == CountPolicy::TruncateToMax) {
532 std::sort(accepted.begin(), accepted.end(), [](const Ranked& a, const Ranked& b) {
533 if (a.distance != b.distance) return a.distance < b.distance;
534 return a.candidate.subject.format() < b.candidate.subject.format();
535 });
536 if (accepted.size() > static_cast<std::size_t>(query.spec.maxCount))
537 accepted.resize(static_cast<std::size_t>(query.spec.maxCount));
538 if (accepted.size() < static_cast<std::size_t>(query.spec.minCount))
541 "target candidate count is below TargetingSpec.minCount after truncation", {}));
542 } else if (accepted.size() < static_cast<std::size_t>(query.spec.minCount) ||
543 accepted.size() > static_cast<std::size_t>(query.spec.maxCount)) {
545 "target candidate count violates TargetingSpec", {}));
546 }
547
548 TargetSet result;
549 for (const auto& entry : accepted) {
550 auto added = result.addSubject(entry.candidate.subject);
551 std::move(added).expect("TargetingResolver could not add a validated candidate");
552 }
553 return Result<TargetSet>::success(std::move(result));
554}
555
556std::string_view coordinateSpaceName(CoordinateSpace space) noexcept {
557 switch (space) {
558 case CoordinateSpace::World2D: return "world2d";
559 case CoordinateSpace::World3D: return "world3d";
560 case CoordinateSpace::Grid2D: return "grid2d";
561 case CoordinateSpace::Grid3D: return "grid3d";
562 }
563 return "unknown";
564}
565
566} // namespace eve::sensing
ActionParameterOperation operation
double value
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 nx
float ny
float maximum[3]
float minimum[3]
std::uint32_t key
float area
Definition Grass.cpp:62
HexVec3 left
HexVec3 right
Range range
bool required
bool valid
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float distance
const std::string * tag
float radius
float distSq
RoadLaneDirection direction
bool found
float dz
float dy
float dx
Coordinate-safe targeting protocol shared by sensing and gameplay adapters.
bool visible
glm::vec3 point
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
static std::optional< LogicalId > parse(std::string_view text)
Parses a scoped logical name.
Definition Identity.cpp:36
void expect(std::string_view message) const
Require success for a void operation.
Definition Result.h:548
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
static SubjectRef fromPersistentId(PersistentId id) noexcept
Wrap a persistent identity without changing its bytes.
Definition SubjectRef.h:32
std::string format() const
Return the canonical lower-case UUID spelling.
Definition SubjectRef.h:44
bool isValid() const noexcept
Return whether this reference is valid and non-nil.
Definition SubjectRef.h:38
static std::optional< Id128 > parse(std::string_view text) noexcept
Parses canonical UUID text.
Definition Identity.h:151
An integer grid area constraint, kept separate from WorldArea.
Definition Targeting.h:273
GridArea()=default
Constructs an invalid default grid area.
GridPoint minimum() const noexcept
Return the inclusive minimum grid corner.
Definition Targeting.h:292
bool contains(GridPoint point) const noexcept
Tests a grid point without converting coordinate spaces.
static Result< GridArea > box2D(GridPoint minimum, GridPoint maximum)
Creates an inclusive 2D grid rectangle.
GridPoint maximum() const noexcept
Return the inclusive maximum grid corner.
Definition Targeting.h:294
static Result< GridArea > box3D(GridPoint minimum, GridPoint maximum)
Creates an inclusive 3D grid box.
Integer grid point tagged as Grid2D or Grid3D.
Definition Targeting.h:163
static GridPoint grid3D(std::int32_t x, std::int32_t y, std::int32_t z) noexcept
Creates a 3D integer grid point.
static GridPoint grid2D(std::int32_t x, std::int32_t y) noexcept
Creates a 2D integer grid point.
Consumer-owned line-of-sight capability.
Definition Targeting.h:431
virtual Result< LineOfSightResult > query(const TargetLocation &from, const TargetLocation &to) const =0
Tests the segment between two same-space locations.
Result< std::vector< TargetCandidate > > query(const TargetingQuery &query) const override
Returns broad-phase candidates satisfying the coordinate and spatial constraints.
Result< void > remove(SubjectRef subject)
Removes one candidate; NotFound is returned when absent.
Result< void > upsert(TargetCandidate candidate)
Adds or replaces one candidate using its SubjectRef as the key.
Result< std::vector< TargetCandidate > > query(const TargetingQuery &query) const override
Returns broad-phase candidates satisfying the coordinate and spatial constraints.
eve::Result< CandidateQueryResult > query(const QueryOrigin &origin, const QuerySpec &spec)
Runs a configurable candidate query.
Definition Sensing.cpp:406
const std::map< std::string, Subject > & subjects() const noexcept
Returns a borrowed view of stored subjects for adapters; valid until mutation.
Definition Sensing.h:228
Owning target result containing primary-independent subjects and optional geometry.
Definition Targeting.h:384
Result< void > setPoint(TargetLocation point)
Stores a point projection after preserving its coordinate tag.
Result< void > addSubject(SubjectRef subject)
Adds a valid subject once; duplicate subjects are a NoOp success.
Result< void > setArea(WorldArea area)
Stores an area projection after preserving its coordinate tag.
Result< void > setPrimary(SubjectRef subject)
Assigns an existing subject as primary without choosing one implicitly.
const std::optional< TargetLocation > & point() const noexcept
Returns the optional point projection.
Definition Targeting.h:402
Result< TargetSet > resolve(const TargetingQuery &query) const
Resolves a constrained candidate set through registered capabilities.
A world-space area constraint.
Definition Targeting.h:207
static Result< WorldArea > circle2D(WorldPoint center, float radius)
Creates a finite non-negative 2D world circle.
WorldArea()=default
Constructs an invalid default area.
static Result< WorldArea > box3D(WorldPoint minimum, WorldPoint maximum)
Creates an axis-aligned 3D world box from inclusive corners.
static Result< WorldArea > sphere3D(WorldPoint center, float radius)
Creates a finite non-negative 3D world sphere.
static Result< WorldArea > box2D(WorldPoint minimum, WorldPoint maximum)
Creates an axis-aligned 2D world box from inclusive corners.
bool contains(WorldPoint point) const noexcept
Tests a world point without converting coordinate spaces.
float radius() const noexcept
Return circle/sphere radius; zero for boxes.
Definition Targeting.h:244
static Result< WorldArea > cone2D(WorldPoint apex, float dirX, float dirY, float halfAngleRadians, float range)
Creates a 2D world cone/sector.
Finite world-space point tagged as either World2D or World3D.
Definition Targeting.h:116
float x() const noexcept
Returns the X coordinate in the tagged world space.
Definition Targeting.h:136
bool isValid() const noexcept
Returns whether the point was constructed with finite coordinates.
Definition Targeting.h:142
float y() const noexcept
Returns the Y coordinate in the tagged world space.
Definition Targeting.h:138
float z() const noexcept
Returns the Z coordinate; it is zero for World2D.
Definition Targeting.h:140
static Result< WorldPoint > world3D(float x, float y, float z)
Creates a finite 3D world point.
static Result< WorldPoint > world2D(float x, float y)
Creates a finite 2D world point.
WorldPoint()=default
Constructs an invalid default point; use world2D/world3D for values.
CoordinateSpace space() const noexcept
Returns the point coordinate space.
Definition Targeting.h:134
static std::optional< ZoneRef > fromLogicalId(LogicalId id)
Creates a zone reference from a valid logical ID.
ZoneRef()=default
Constructs an invalid zone reference.
bool isValid() const noexcept
Returns whether the reference contains a valid logical ID.
Definition Targeting.h:97
I * query()
Queries .
Definition Capability.h:88
float distanceSquared(float ax, float ay, float bx, float by)
std::variant< WorldPoint, GridPoint > TargetLocation
Either a world point or a grid point, with the tag retained.
Definition Targeting.h:198
CoordinateSpace
Coordinate spaces are not interchangeable, even when dimensions match.
Definition Targeting.h:37
std::string_view coordinateSpaceName(CoordinateSpace space) noexcept
Returns the stable protocol name for a coordinate space.
TargetDomain
Stable relationship class used by generic domain constraints.
Definition Targeting.h:54
@ TruncateToMax
Keep the best maxCount candidates after sorting (legacy circle/box limit).
World2D origin used for range tests and distance sorting.
Definition Sensing.h:117
Configurable candidate query against SensingWorld.
Definition Sensing.h:98
std::vector< std::string > excludedTags
Definition Sensing.h:103
CountPolicy countPolicy
Definition Sensing.h:109
std::uint32_t minCount
Definition Sensing.h:107
std::vector< std::string > requiredTags
Definition Sensing.h:102
std::uint32_t maxCount
Definition Sensing.h:108
Candidate facts returned by sensing.
Definition Targeting.h:368
std::vector< std::string > tags
Stable gameplay tags used by required/excluded tag queries.
Definition Targeting.h:376
TargetLocation location
Candidate location in one of the explicitly tagged spaces.
Definition Targeting.h:372
std::vector< ZoneRef > zones
Logical zones containing this candidate.
Definition Targeting.h:378
TargetDomain domain
Relationship class supplied by the sensing/domain adapter.
Definition Targeting.h:374
SubjectRef subject
Stable subject identity.
Definition Targeting.h:370
A query passed from a domain targeting algorithm to sensing.
Definition Targeting.h:350
TargetLocation originLocation
Origin location in exactly spec.space.
Definition Targeting.h:354
Result< void > validate() const
Validates origin identity, location/spec space and spec constraints.
SubjectRef origin
Subject issuing the query; it is not implicitly selected as a result.
Definition Targeting.h:352
TargetingSpec spec
Generic constraints to apply to candidate facts.
Definition Targeting.h:356
std::optional< GridArea > gridArea
Optional Grid2D/Grid3D area constraint.
Definition Targeting.h:338
Result< void > validate() const
Validates count, range, tags, zone and coordinate-space invariants.
std::vector< std::string > excludedTags
Tags that exclude a candidate when present.
Definition Targeting.h:332
float maxRange
Maximum distance, in world units or grid cells; positive infinity means unbounded.
Definition Targeting.h:328
float minRange
Minimum distance, in world units or grid cells according to space.
Definition Targeting.h:326
std::uint32_t maxCount
Maximum number of candidates accepted.
Definition Targeting.h:318
std::optional< ZoneRef > zone
Optional logical zone membership required from candidates.
Definition Targeting.h:334
std::vector< std::string > requiredTags
All tags that each candidate must contain.
Definition Targeting.h:330
CoordinateSpace space
Required coordinate space for origin, candidates and area.
Definition Targeting.h:312
std::optional< WorldArea > worldArea
Optional World2D/World3D area constraint.
Definition Targeting.h:336
std::uint32_t minCount
Minimum number of candidates required in the resolved set.
Definition Targeting.h:316