载入中...
搜索中...
未找到
StylizeAttackVfx.cpp
浏览该文件的文档.
1#include "common/Capability.h"
6
7#include <cstdint>
8#include <memory>
9#include <optional>
10#include <string>
11#include <string_view>
12#include <unordered_map>
13#include <utility>
14#include <variant>
15
16namespace eve::stylize {
17namespace {
18
19float layerParam(const AttackVfxLayerStartRequest& request, const char* key, float fallback) {
20 if (request.layer) {
21 const auto found = request.layer->floatParams.find(key);
22 if (found != request.layer->floatParams.end()) return found->second;
23 }
24 return fallback;
25}
26
27std::string skinHint(const AttackVfxSkin* skin, const char* key) {
28 if (!skin) return {};
29 const auto found = skin->styleHints.find(key);
30 return found == skin->styleHints.end() ? std::string{} : found->second;
31}
32
33std::optional<SkillMeshEffectKind> parseSkillKind(std::string_view name) {
34 if (name == "weaponSlash" || name == "slash") return SkillMeshEffectKind::WeaponSlash;
35 if (name == "impactFlash" || name == "impact" || name == "rim") return SkillMeshEffectKind::ImpactFlash;
36 if (name == "chargeAura" || name == "aura") return SkillMeshEffectKind::ChargeAura;
37 if (name == "burningBody" || name == "ember") return SkillMeshEffectKind::BurningBody;
38 return std::nullopt;
39}
40
41Result<std::unique_ptr<MeshVfxAssetInstance>> makeStyleInstance(const std::string& style,
42 const AttackVfxLayerStartRequest& request) {
43 MeshVfxAsset asset;
44 MeshVfxLayerAsset layer;
45 layer.style = style;
46 layer.playback = {0.02f, 0.12f, 0.18f, false};
47 if (request.layer) {
48 for (const auto& [key, value] : request.layer->floatParams) layer.floatParameters[key] = value;
49 }
50 asset.layers.push_back(std::move(layer));
51 return MeshVfxAssetInstance::create(asset);
52}
53
54class MeshVfxAttackVfxExecutor final : public IAttackVfxLayerExecutor {
55public:
56 AttackVfxLayerRole role() const noexcept override { return AttackVfxLayerRole::MeshVfx; }
57
58 Result<AttackVfxLayerHandle> start(const AttackVfxLayerStartRequest& request) override {
59 if (!request.layer)
61 DiagnosticCode::InvalidArgument, "AttackVfx meshVfx layer is null", "layer"));
62
63 const std::string& uri = request.layer->uri;
64 using Owned =
65 std::variant<std::unique_ptr<MeshVfxAssetInstance>, std::unique_ptr<SkillMeshEffect>>;
66 Owned owned = std::unique_ptr<MeshVfxAssetInstance>{};
67
68 if (uri.rfind("json:", 0) == 0) {
69 auto parsed = MeshVfxAsset::fromJson(uri.substr(5));
70 if (!parsed) return Result<AttackVfxLayerHandle>::failure(parsed.status());
71 auto created = MeshVfxAssetInstance::create(parsed.value());
73 owned = std::move(created).takeValue();
74 } else if (uri.rfind("skill:", 0) == 0) {
75 auto kind = parseSkillKind(uri.substr(6));
76 if (!kind)
78 DiagnosticCode::NotFound, "unknown AttackVfx skill mesh kind", "uri"));
79 owned = std::make_unique<SkillMeshEffect>(*kind);
80 } else if (uri.rfind("style:", 0) == 0) {
81 auto created = makeStyleInstance(uri.substr(6), request);
83 owned = std::move(created).takeValue();
84 } else {
85 std::string token = uri;
86 if (token.rfind("meshvfx://", 0) == 0) token = token.substr(10);
87 std::string style = skinHint(request.skin, "meshStyle");
88 if (style.empty()) {
89 if (auto kind = parseSkillKind(token)) {
90 owned = std::make_unique<SkillMeshEffect>(*kind);
91 } else if (!token.empty()) {
92 style = token;
93 } else {
95 DiagnosticCode::NotFound, "meshVfx layer has no resolvable style", "uri"));
96 }
97 }
98 if (std::holds_alternative<std::unique_ptr<MeshVfxAssetInstance>>(owned) &&
99 !std::get<std::unique_ptr<MeshVfxAssetInstance>>(owned)) {
100 auto created = makeStyleInstance(style, request);
102 owned = std::move(created).takeValue();
103 }
104 }
105
106 if (auto* asset = std::get_if<std::unique_ptr<MeshVfxAssetInstance>>(&owned)) {
107 if (!*asset)
109 DiagnosticCode::Failed, "meshVfx instance was not created", "uri"));
110 (*asset)->play();
111 } else if (auto* skill = std::get_if<std::unique_ptr<SkillMeshEffect>>(&owned)) {
112 (*skill)->play();
113 }
114
115 Live live;
116 live.owned = std::move(owned);
117 const auto id = ++nextId_;
118 live_.emplace(id, std::move(live));
119 return Result<AttackVfxLayerHandle>::success(AttackVfxLayerHandle{id});
120 }
121
122 Result<void> update(AttackVfxLayerHandle handle, double dtSeconds,
123 const AttackVfxLayerStartRequest&) override {
124 const auto found = live_.find(handle.id);
125 if (found == live_.end())
127 DiagnosticCode::StaleHandle, "meshVfx AttackVfx layer handle is stale", "handle"));
128 const float dt = static_cast<float>(dtSeconds);
129 if (auto* asset = std::get_if<std::unique_ptr<MeshVfxAssetInstance>>(&found->second.owned))
130 (*asset)->update(dt);
131 else if (auto* skill = std::get_if<std::unique_ptr<SkillMeshEffect>>(&found->second.owned))
132 (*skill)->update(dt);
133 if (found->second.draining && isFinished(found->second.owned)) {
134 live_.erase(found);
136 }
138 }
139
140 Result<void> stop(AttackVfxLayerHandle handle, AttackVfxStopBehavior behavior) override {
141 const auto found = live_.find(handle.id);
142 if (found == live_.end())
144 DiagnosticCode::StaleHandle, "meshVfx AttackVfx layer handle is stale", "handle"));
145 if (auto* asset = std::get_if<std::unique_ptr<MeshVfxAssetInstance>>(&found->second.owned))
146 (*asset)->stop(behavior == AttackVfxStopBehavior::ClearImmediately ? 0.f : -1.f);
147 else if (auto* skill = std::get_if<std::unique_ptr<SkillMeshEffect>>(&found->second.owned))
148 (*skill)->stop(behavior == AttackVfxStopBehavior::ClearImmediately ? 0.f : 0.1f);
150 live_.erase(found);
152 }
153 // StopEmitting keeps the instance so fade-out can complete through update().
154 found->second.draining = true;
156 }
157
158private:
159 using Owned =
160 std::variant<std::unique_ptr<MeshVfxAssetInstance>, std::unique_ptr<SkillMeshEffect>>;
161 struct Live {
162 Owned owned;
163 bool draining = false;
164 };
165
166 static bool isFinished(const Owned& owned) {
167 if (const auto* asset = std::get_if<std::unique_ptr<MeshVfxAssetInstance>>(&owned))
168 return !*asset || (*asset)->isFinished();
169 if (const auto* skill = std::get_if<std::unique_ptr<SkillMeshEffect>>(&owned))
170 return !*skill || (*skill)->isFinished();
171 return true;
172 }
173
174 std::uint64_t nextId_ = 1;
175 std::unordered_map<std::uint64_t, Live> live_;
176};
177
178class TrailAttackVfxExecutor final : public IAttackVfxLayerExecutor {
179public:
180 AttackVfxLayerRole role() const noexcept override { return AttackVfxLayerRole::Trail; }
181
182 Result<AttackVfxLayerHandle> start(const AttackVfxLayerStartRequest& request) override {
183 if (!request.layer)
185 DiagnosticCode::InvalidArgument, "AttackVfx trail layer is null", "layer"));
186
187 TrailSettings settings;
188 if (const auto maxSamples = layerParam(request, "maxSamples", 0.f); maxSamples > 0.f)
189 settings.maxSamples = static_cast<std::size_t>(maxSamples);
190 if (const auto lifetime = layerParam(request, "lifetime", 0.f); lifetime > 0.f)
191 settings.lifetime = lifetime;
192 if (const auto minDist = layerParam(request, "minSampleDistance", 0.f); minDist > 0.f)
193 settings.minSampleDistance = minDist;
194 if (const auto teleport = layerParam(request, "teleportDistance", 0.f); teleport > 0.f)
195 settings.teleportDistance = teleport;
196
197 Live live;
198 live.emitter = std::make_unique<TrailEmitter>(settings);
199 if (request.playRequest) {
200 live.sourceX = static_cast<float>(request.playRequest->sourceId);
201 live.targetX = static_cast<float>(request.playRequest->targetId);
202 }
203 (void)live.emitter->append({live.sourceX, 0.f, 0.f}, {live.targetX, 0.2f, 0.f});
204
205 const auto id = ++nextId_;
206 live_.emplace(id, std::move(live));
207 return Result<AttackVfxLayerHandle>::success(AttackVfxLayerHandle{id});
208 }
209
210 Result<void> update(AttackVfxLayerHandle handle, double dtSeconds,
211 const AttackVfxLayerStartRequest& request) override {
212 const auto found = live_.find(handle.id);
213 if (found == live_.end())
215 DiagnosticCode::StaleHandle, "trail AttackVfx layer handle is stale", "handle"));
216 found->second.emitter->update(static_cast<float>(dtSeconds));
217 float sourceX = found->second.sourceX;
218 float targetX = found->second.targetX;
219 if (request.playRequest) {
220 sourceX = static_cast<float>(request.playRequest->sourceId);
221 targetX = static_cast<float>(request.playRequest->targetId);
222 }
223 found->second.phase += static_cast<float>(dtSeconds);
224 (void)found->second.emitter->append({sourceX, 0.f, 0.f},
225 {targetX, 0.2f + 0.05f * found->second.phase, 0.f});
227 }
228
229 Result<void> stop(AttackVfxLayerHandle handle, AttackVfxStopBehavior behavior) override {
230 const auto found = live_.find(handle.id);
231 if (found == live_.end())
233 DiagnosticCode::StaleHandle, "trail AttackVfx layer handle is stale", "handle"));
235 found->second.emitter->clear();
236 else
237 found->second.emitter->breakTrail();
238 live_.erase(found);
240 }
241
242private:
243 struct Live {
244 std::unique_ptr<TrailEmitter> emitter;
245 float sourceX = 0.f;
246 float targetX = 1.f;
247 float phase = 0.f;
248 };
249
250 std::uint64_t nextId_ = 1;
251 std::unordered_map<std::uint64_t, Live> live_;
252};
253
254class DistortionAttackVfxExecutor final : public IAttackVfxLayerExecutor {
255public:
256 AttackVfxLayerRole role() const noexcept override { return AttackVfxLayerRole::Distortion; }
257
258 Result<AttackVfxLayerHandle> start(const AttackVfxLayerStartRequest& request) override {
259 if (!request.layer)
260 return Result<AttackVfxLayerHandle>::failure(Diagnostic::error(
261 DiagnosticCode::InvalidArgument, "AttackVfx distortion layer is null", "layer"));
262
263 float strength = layerParam(request, "strength", 0.f);
264 if (strength <= 0.f && request.skin) {
265 const auto found = request.skin->distortionProfile.find("strength");
266 if (found != request.skin->distortionProfile.end()) strength = found->second;
267 }
268 if (strength < 0.f)
269 return Result<AttackVfxLayerHandle>::failure(Diagnostic::error(
270 DiagnosticCode::InvalidArgument, "distortion strength must be non-negative",
271 "floatParams.strength"));
272
273 // Phase 3 owns the authored profile; GPU warp sinks arrive in a later slice.
274 const auto id = ++nextId_;
275 live_.emplace(id, strength);
276 return Result<AttackVfxLayerHandle>::success(AttackVfxLayerHandle{id});
277 }
278
279 Result<void> update(AttackVfxLayerHandle handle, double,
280 const AttackVfxLayerStartRequest&) override {
281 if (!live_.contains(handle.id))
282 return Result<void>::failure(Diagnostic::error(
283 DiagnosticCode::StaleHandle, "distortion AttackVfx layer handle is stale", "handle"));
284 return Result<void>::success(Status::success(StatusCode::NoOp));
285 }
286
287 Result<void> stop(AttackVfxLayerHandle handle, AttackVfxStopBehavior) override {
288 if (!live_.erase(handle.id))
289 return Result<void>::failure(Diagnostic::error(
290 DiagnosticCode::StaleHandle, "distortion AttackVfx layer handle is stale", "handle"));
291 return Result<void>::success(Status::success(StatusCode::Applied));
292 }
293
294private:
295 std::uint64_t nextId_ = 1;
296 std::unordered_map<std::uint64_t, float> live_;
297};
298
299MeshVfxAttackVfxExecutor& meshExecutor() {
300 static MeshVfxAttackVfxExecutor instance;
301 return instance;
302}
303TrailAttackVfxExecutor& trailExecutor() {
304 static TrailAttackVfxExecutor instance;
305 return instance;
306}
307DistortionAttackVfxExecutor& distortionExecutor() {
308 static DistortionAttackVfxExecutor instance;
309 return instance;
310}
311
312bool gRegistered = false;
313
314} // namespace
315
317 if (gRegistered) return;
318 eve::cap::addListener<IAttackVfxLayerExecutor>(&meshExecutor());
319 eve::cap::addListener<IAttackVfxLayerExecutor>(&trailExecutor());
320 eve::cap::addListener<IAttackVfxLayerExecutor>(&distortionExecutor());
321 gRegistered = true;
322}
323
325 if (!gRegistered) return;
326 eve::cap::removeListener<IAttackVfxLayerExecutor>(&distortionExecutor());
327 eve::cap::removeListener<IAttackVfxLayerExecutor>(&trailExecutor());
328 eve::cap::removeListener<IAttackVfxLayerExecutor>(&meshExecutor());
329 gRegistered = false;
330}
331
332} // namespace eve::stylize
double value
Duration start
Pluggable AttackVfx layer backends registered via capability listeners.
ActiveSource owned
float phase
Definition CaveMesh.cpp:58
bool draining
const GltfImportRequest & request
std::uint32_t key
TokenKind kind
std::string name
TileLayer * layer
std::weak_ptr< const void > lifetime
std::string uri
PrimitiveHandle handle
uint64_t token
bool found
int created
float targetX
std::unique_ptr< TrailEmitter > emitter
float sourceX
TerrainThermalSettings settings
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 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
static eve::Result< std::unique_ptr< MeshVfxAssetInstance > > create(const MeshVfxAsset &asset)
Build independent runtime state for every asset layer.
std::unique_ptr< T > Owned
C++ owning storage used by an object factory.
AttackVfxLayerRole
Which existing presentation backend a layer binds to.
AttackVfxStopBehavior
Exit policy when a phase or instance stops.
void unregisterStylizeAttackVfxExecutors()
void registerStylizeAttackVfxExecutors()
static eve::Result< MeshVfxAsset > fromJson(std::string_view json)
Parse, migrate, and validate a JSON asset without mutating live state.