载入中...
搜索中...
未找到
ActionAttackVfxBlock.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <string_view>
6#include <utility>
7
8namespace eve::action {
9namespace {
10
11Result<double> numeric(const Value::Object& payload, std::string_view key, double fallback) {
12 const auto found = payload.find(std::string(key));
13 if (found == payload.end()) return Result<double>::success(fallback);
14 double value = 0.0;
15 if (const auto* integer = found->second.getIf<std::int64_t>())
16 value = static_cast<double>(*integer);
17 else if (const auto* decimal = found->second.getIf<double>())
18 value = *decimal;
19 else
21 "AttackVfx field must be numeric", std::string(key)));
22 if (!std::isfinite(value))
24 "AttackVfx field must be finite", std::string(key)));
26}
27
28} // namespace
29
32 ActionAttackVfxBinding candidate;
33
34 const auto recipeId = payload.find("recipeId");
35 const auto uri = payload.find("uri");
36 const bool hasRecipe =
37 recipeId != payload.end() && recipeId->second.getIf<std::string>() &&
38 !recipeId->second.getIf<std::string>()->empty();
39 const bool hasUri =
40 uri != payload.end() && uri->second.getIf<std::string>() && !uri->second.getIf<std::string>()->empty();
41 if (hasRecipe == hasUri)
43 DiagnosticCode::InvalidArgument, "AttackVfx requires exactly one of recipeId or uri", "recipeId"));
44 if (hasRecipe) {
45 auto parsed = LogicalId::parse(*recipeId->second.getIf<std::string>());
46 if (!parsed)
48 DiagnosticCode::InvalidArgument, "AttackVfx recipeId must be namespace:name", "recipeId"));
49 candidate.recipeId = std::move(*parsed);
50 } else {
51 candidate.uri = *uri->second.getIf<std::string>();
52 }
53
54 if (const auto skin = payload.find("skinId"); skin != payload.end()) {
55 const auto* text = skin->second.getIf<std::string>();
56 if (!text || text->empty())
58 DiagnosticCode::InvalidArgument, "AttackVfx skinId must be non-empty text", "skinId"));
59 auto parsed = LogicalId::parse(*text);
60 if (!parsed)
62 DiagnosticCode::InvalidArgument, "AttackVfx skinId must be namespace:name", "skinId"));
63 candidate.skinId = std::move(*parsed);
64 }
65
68 candidate.spatial = std::move(spatial).takeValue();
69
70 auto lifetime = numeric(payload, "lifetimeSeconds", 0.0);
72 if (shape == ActionAttackVfxShape::Instant && lifetime.value() <= 0.0)
74 DiagnosticCode::InvalidArgument, "instant AttackVfx lifetimeSeconds must be positive",
75 "lifetimeSeconds"));
76 if (lifetime.value() < 0.0)
78 DiagnosticCode::InvalidArgument, "AttackVfx lifetimeSeconds must be non-negative", "lifetimeSeconds"));
79 candidate.lifetimeSeconds = lifetime.value();
80
81 if (const auto cues = payload.find("cues"); cues != payload.end()) {
82 const auto* array = cues->second.getIf<Value::Array>();
83 if (!array)
85 Diagnostic::error(DiagnosticCode::InvalidArgument, "AttackVfx cues must be an array", "cues"));
86 candidate.cues.reserve(array->size());
87 for (std::size_t i = 0; i < array->size(); ++i) {
88 const auto* object = (*array)[i].getIf<Value::Object>();
89 const std::string path = "cues[" + std::to_string(i) + "]";
90 if (!object)
92 Diagnostic::error(DiagnosticCode::InvalidArgument, "AttackVfx cue must be an object", path));
94 const auto offset = object->find("offsetSeconds");
95 if (offset == object->end())
97 DiagnosticCode::InvalidArgument, "AttackVfx cue offsetSeconds is required",
98 path + ".offsetSeconds"));
99 auto parsedOffset = numeric(*object, "offsetSeconds", 0.0);
100 if (!parsedOffset) return Result<ActionAttackVfxBinding>::failure(parsedOffset.status());
101 if (parsedOffset.value() < 0.0)
103 DiagnosticCode::InvalidArgument, "AttackVfx cue offsetSeconds must be >= 0",
104 path + ".offsetSeconds"));
105 cue.offsetSeconds = parsedOffset.value();
106 const auto name = object->find("cue");
107 if (name == object->end() || !name->second.getIf<std::string>() ||
108 name->second.getIf<std::string>()->empty())
110 DiagnosticCode::InvalidArgument, "AttackVfx cue name must be non-empty text", path + ".cue"));
111 cue.cue = *name->second.getIf<std::string>();
112 candidate.cues.push_back(std::move(cue));
113 }
114 std::stable_sort(candidate.cues.begin(), candidate.cues.end(),
115 [](const ActionAttackVfxCue& a, const ActionAttackVfxCue& b) {
116 return a.offsetSeconds < b.offsetSeconds;
117 });
118 }
119
120 return Result<ActionAttackVfxBinding>::success(std::move(candidate));
121}
122
123} // namespace eve::action
Typed AttackVfx action-block payload contract.
double value
Value::Object payload
std::uint32_t key
ShaderImageInput shape
std::string text
size_t offset
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::weak_ptr< const void > lifetime
std::string path
Definition PlayHost.cpp:110
bool found
Json object
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 std::optional< LogicalId > parse(std::string_view text)
Parses a scoped logical name.
Definition Identity.cpp:36
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
std::map< std::string, Value > Object
Definition Value.h:34
std::vector< Value > Array
Definition Value.h:33
ActionAttackVfxShape
Authored AttackVfx block shape used to select lifetime validation.
Owning validated AttackVfx settings shared by runtime and editor preview.
static Result< ActionAttackVfxBinding > fromPayload(const Value::Object &payload, ActionAttackVfxShape shape)
Decode one dynamic AttackVfx payload transactionally.
std::vector< ActionAttackVfxCue > cues
One cue scheduled relative to AttackVfx play().
static Result< ActionSpatialBinding > fromPayload(const Value::Object &payload)
Decode and validate the common spatial fields in an owning payload.