12#include <unordered_map>
19float layerParam(
const AttackVfxLayerStartRequest&
request,
const char*
key,
float fallback) {
27std::string skinHint(
const AttackVfxSkin* skin,
const char*
key) {
29 const auto found = skin->styleHints.find(
key);
30 return found == skin->styleHints.end() ? std::string{} :
found->second;
33std::optional<SkillMeshEffectKind> parseSkillKind(std::string_view
name) {
41Result<std::unique_ptr<MeshVfxAssetInstance>> makeStyleInstance(
const std::string& style,
42 const AttackVfxLayerStartRequest&
request) {
44 MeshVfxLayerAsset
layer;
46 layer.playback = {0.02f, 0.12f, 0.18f,
false};
50 asset.layers.push_back(std::move(
layer));
54class MeshVfxAttackVfxExecutor final :
public IAttackVfxLayerExecutor {
58 Result<AttackVfxLayerHandle>
start(
const AttackVfxLayerStartRequest&
request)
override {
65 std::variant<std::unique_ptr<MeshVfxAssetInstance>, std::unique_ptr<SkillMeshEffect>>;
66 Owned owned = std::unique_ptr<MeshVfxAssetInstance>{};
68 if (
uri.rfind(
"json:", 0) == 0) {
74 }
else if (
uri.rfind(
"skill:", 0) == 0) {
75 auto kind = parseSkillKind(
uri.substr(6));
79 owned = std::make_unique<SkillMeshEffect>(*
kind);
80 }
else if (
uri.rfind(
"style:", 0) == 0) {
87 std::string style = skinHint(
request.skin,
"meshStyle");
90 owned = std::make_unique<SkillMeshEffect>(*
kind);
91 }
else if (!
token.empty()) {
98 if (std::holds_alternative<std::unique_ptr<MeshVfxAssetInstance>>(
owned) &&
99 !std::get<std::unique_ptr<MeshVfxAssetInstance>>(
owned)) {
106 if (
auto* asset = std::get_if<std::unique_ptr<MeshVfxAssetInstance>>(&
owned)) {
111 }
else if (
auto* skill = std::get_if<std::unique_ptr<SkillMeshEffect>>(&
owned)) {
116 live.owned = std::move(
owned);
117 const auto id = ++nextId_;
118 live_.emplace(
id, std::move(live));
122 Result<void> update(AttackVfxLayerHandle
handle,
double dtSeconds,
123 const AttackVfxLayerStartRequest&)
override {
125 if (
found == live_.end())
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)) {
142 if (
found == live_.end())
145 if (
auto* asset = std::get_if<std::unique_ptr<MeshVfxAssetInstance>>(&
found->second.owned))
147 else if (
auto* skill = std::get_if<std::unique_ptr<SkillMeshEffect>>(&
found->second.owned))
154 found->second.draining =
true;
160 std::variant<std::unique_ptr<MeshVfxAssetInstance>, std::unique_ptr<SkillMeshEffect>>;
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();
174 std::uint64_t nextId_ = 1;
175 std::unordered_map<std::uint64_t, Live> live_;
178class TrailAttackVfxExecutor final :
public IAttackVfxLayerExecutor {
182 Result<AttackVfxLayerHandle>
start(
const AttackVfxLayerStartRequest&
request)
override {
188 if (
const auto maxSamples = layerParam(
request,
"maxSamples", 0.f); maxSamples > 0.f)
189 settings.maxSamples =
static_cast<std::size_t
>(maxSamples);
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;
198 live.emitter = std::make_unique<TrailEmitter>(
settings);
200 live.sourceX =
static_cast<float>(
request.playRequest->sourceId);
201 live.targetX =
static_cast<float>(
request.playRequest->targetId);
203 (void)live.emitter->append({live.sourceX, 0.f, 0.f}, {live.targetX, 0.2f, 0.f});
205 const auto id = ++nextId_;
206 live_.emplace(
id, std::move(live));
210 Result<void> update(AttackVfxLayerHandle
handle,
double dtSeconds,
211 const AttackVfxLayerStartRequest&
request)
override {
213 if (
found == live_.end())
216 found->second.emitter->update(
static_cast<float>(dtSeconds));
223 found->second.phase +=
static_cast<float>(dtSeconds);
224 (void)
found->second.emitter->append({
sourceX, 0.f, 0.f},
231 if (
found == live_.end())
235 found->second.emitter->clear();
237 found->second.emitter->breakTrail();
250 std::uint64_t nextId_ = 1;
251 std::unordered_map<std::uint64_t, Live> live_;
254class DistortionAttackVfxExecutor final :
public IAttackVfxLayerExecutor {
256 AttackVfxLayerRole role()
const noexcept override {
return AttackVfxLayerRole::Distortion; }
258 Result<AttackVfxLayerHandle>
start(
const AttackVfxLayerStartRequest&
request)
override {
260 return Result<AttackVfxLayerHandle>::failure(Diagnostic::error(
261 DiagnosticCode::InvalidArgument,
"AttackVfx distortion layer is null",
"layer"));
265 const auto found =
request.skin->distortionProfile.find(
"strength");
269 return Result<AttackVfxLayerHandle>::failure(Diagnostic::error(
270 DiagnosticCode::InvalidArgument,
"distortion strength must be non-negative",
271 "floatParams.strength"));
274 const auto id = ++nextId_;
276 return Result<AttackVfxLayerHandle>::success(AttackVfxLayerHandle{
id});
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));
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));
295 std::uint64_t nextId_ = 1;
296 std::unordered_map<std::uint64_t, float> live_;
299MeshVfxAttackVfxExecutor& meshExecutor() {
300 static MeshVfxAttackVfxExecutor instance;
303TrailAttackVfxExecutor& trailExecutor() {
304 static TrailAttackVfxExecutor instance;
307DistortionAttackVfxExecutor& distortionExecutor() {
308 static DistortionAttackVfxExecutor instance;
312bool gRegistered =
false;
317 if (gRegistered)
return;
318 eve::cap::addListener<IAttackVfxLayerExecutor>(&meshExecutor());
319 eve::cap::addListener<IAttackVfxLayerExecutor>(&trailExecutor());
320 eve::cap::addListener<IAttackVfxLayerExecutor>(&distortionExecutor());
325 if (!gRegistered)
return;
326 eve::cap::removeListener<IAttackVfxLayerExecutor>(&distortionExecutor());
327 eve::cap::removeListener<IAttackVfxLayerExecutor>(&trailExecutor());
328 eve::cap::removeListener<IAttackVfxLayerExecutor>(&meshExecutor());
Pluggable AttackVfx layer backends registered via capability listeners.
const GltfImportRequest & request
std::weak_ptr< const void > lifetime
std::unique_ptr< TrailEmitter > emitter
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.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
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.