Merge branch '138-toggle-free-look-with-hotkey' into main/gingo-test
[ryzomcore.git] / ryzom / client / src / projectile_manager.cpp
blob6b4e6c7c1ce4cb5a1c9db70c69b86e4c6ec4f4a1
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #include "stdpch.h"
20 #include "projectile_manager.h"
21 #include "animation_fx.h"
22 #include "fx_manager.h"
23 #include "character_cl.h"
24 #include "entities.h"
25 #include "time_client.h"
27 #include "animation_fx.h"
29 #include "nel/3d/u_particle_system_instance.h"
30 #include "nel/3d/u_bone.h"
33 H_AUTO_DECL(RZ_ProjectileManager)
35 static const float BOMB_MISSILE_PARABOLA_HEIGHT_FOR_100_METERS = 10.f;
37 // user params for projectile part
38 static const float ProjectileUserParams[MAGICFX::NUM_SPELL_POWER][4] =
40 { 0.f, 0.f, 0.f, 0.f },
41 { 1.f, 0.f, 0.f, 0.f },
42 { 1.f, 1.f, 0.f, 0.f },
43 { 1.f, 1.f, 1.f, 0.f },
44 { 1.f, 1.f, 1.f, 1.f }
47 // user params for impact part
48 static const float ImpactUserParams[MAGICFX::NUM_SPELL_POWER][4] =
50 { 0.f, 0.f, 0.f, 0.f },
51 { 0.f, 1.f, 0.f, 0.f },
52 { 1.f, 1.f, 0.f, 0.f },
53 { 1.f, 1.f, 1.f, 0.f },
54 { 1.f, 1.f, 1.f, 1.f }
60 // *****************************************************************************************
61 CProjectileManager &CProjectileManager::getInstance()
63 H_AUTO_USE(RZ_ProjectileManager)
64 static CProjectileManager manager;
65 return manager;
68 // *****************************************************************************************
69 const float *CProjectileManager::getProjectileFXUserParams(uint power)
71 if (power < 1 || power > MAGICFX::NUM_SPELL_POWER) return NULL;
72 return &ProjectileUserParams[power - 1][0];
76 // *****************************************************************************************
77 void CProjectileManager::addProjectileToQueue(const CProjectileBuild &pb)
79 H_AUTO_USE(RZ_ProjectileManager)
80 _ProjectileQueue.insert(pb);
83 // *****************************************************************************************
84 void CProjectileManager::addProjectile(const CProjectileBuild &pb)
86 H_AUTO_USE(RZ_ProjectileManager)
88 _ActiveProjectiles.push_front(CProjectile());
89 CProjectile &proj = _ActiveProjectiles.front();
90 proj.AttackInfo = pb.AttackInfo;
91 proj.ProjectileAspect = pb.ProjectileAspect;
92 proj.ImpactAspect = pb.ImpactAspect;
93 proj.ProjectileAimingPoint = pb.ProjectileAimingPoint;
94 proj.LocalizedImpact = pb.LocalizedImpact;
95 proj.Target = pb.Target;
96 proj.Mode = pb.Mode;
97 proj.TargetBoneVisibleLastFrame = true;
98 proj.StartDate = pb.StartDate;
99 proj.EndDate = pb.EndDate;
100 proj.MagicResist = pb.MagicResist;
101 proj.PlayImpactAnim = pb.PlayImpactAnim;
102 proj.LetProjectileStickedOnTarget = pb.LetProjectileStickedOnTarget;
103 proj.TargeterInfo = pb.TargeterInfo;
104 proj.ForcePlayImpact = pb.ForcePlayImpact;
105 // eval start position
106 CCharacterCL *casterEntity = dynamic_cast<CCharacterCL *>(EntitiesMngr.entity(pb.TargeterInfo.Slot));
107 if (casterEntity)
109 proj.LastEntityOrientation = casterEntity->front();
110 evalFXPosition(&pb.TargeterInfo.StickMode, *casterEntity, proj.TargeterInfo.DefaultPos, pb.TargeterInfo.StickOffset);
111 // play additionnal cast fx
112 if (pb.CastAspect)
114 casterEntity->playCastFX(pb.CastAspect, pb.CastPower);
117 else
119 proj.LastEntityOrientation = CVector::I;
121 CEntityCL *targetEntity = EntitiesMngr.entity(pb.Target.Slot);
122 if (targetEntity)
124 evalFXPosition(&(pb.ProjectileAimingPoint), *targetEntity, proj.Target.TargetPos);
126 // else ... if target entity is not valid, default pos given in the target is used
128 // create actual fxs (unless hit is direct)
129 if (pb.EndDate > pb.StartDate)
131 if (pb.ProjectileAspect)
133 if (pb.AttackInfo.Intensity >= 1 && pb.AttackInfo.Intensity <= MAGICFX::NUM_SPELL_POWER)
135 const CAnimationFXSet &projectile = *(pb.ProjectileAspect);
136 for(uint k = 0; k < projectile.FX.size(); ++k)
138 const CAnimationFX &sheet = projectile.FX[k];
139 proj.FX[k] = sheet.createMatchingInstance(ProjectileUserParams[pb.AttackInfo.Intensity - 1]);
140 if (!proj.FX[k].empty())
142 // Force transform to be linked to the root, and not clusterized
143 // This is because we don't compute the cluster system the projectile is in, and as a result,
144 // projectile can't be seen when player is in a cluster system.
145 // To compute the cluster system we would need a PACS primitive, and thus collisions.
146 proj.FX[k].setForceClipRoot(true);
148 proj.FX[k].setTransformMode(NL3D::UTransform::DirectMatrix);
154 float dist = (proj.Target.TargetPos - proj.TargeterInfo.DefaultPos).norm();
155 // eval height of parabola for 'bomb' missile (depends on distance)
156 proj.ParabolaHeight = dist * 0.01f * BOMB_MISSILE_PARABOLA_HEIGHT_FOR_100_METERS;
159 // *****************************************************************************************
160 void CProjectileManager::updateProjectileQueue()
162 H_AUTO_USE(RZ_ProjectileManager)
163 std::multiset<CProjectileBuild>::iterator it = _ProjectileQueue.begin();
164 while (it != _ProjectileQueue.end() && it->StartDate <= TimeInSec)
166 addProjectile(*it); // create projectile from its description
167 _ProjectileQueue.erase(it);
168 it = _ProjectileQueue.begin();
173 // *****************************************************************************************
174 void CProjectileManager::update()
176 H_AUTO_USE(RZ_ProjectileManager)
177 // remove shutting down projectiles
178 for(std::list<CProjectile>::iterator it = _ToShutDownProjectiles.begin(); it != _ToShutDownProjectiles.end(); ++it)
180 if (it->LetProjectileStickedOnTarget)
182 CCharacterCL *target = NULL;
183 if (it->Target.Slot != CLFECOMMON::INVALID_SLOT)
185 target = dynamic_cast<CCharacterCL *>(EntitiesMngr.entity(it->Target.Slot));
187 it->shutDown(target);
189 else
191 it->shutDown(NULL);
194 _ToShutDownProjectiles.clear();
196 updateProjectileQueue();
197 // update each projectile, and removes from list when impact occurs
198 std::list<CProjectile>::iterator it = _ActiveProjectiles.begin();
199 while (it != _ActiveProjectiles.end())
201 std::list<CProjectile>::iterator tmpIt = it;
202 ++ it;
203 CProjectile &proj = *tmpIt;
204 // Eval impact pos
205 proj.updateTargetPos();
206 CEntityCL *target = EntitiesMngr.entity(proj.Target.Slot);
207 const CVector &impactPos = proj.Target.TargetPos;
209 // compute position on segment joining start point to impact
210 bool impact = false;
211 double totalDeltaT = proj.EndDate - proj.StartDate;
212 double deltaT = TimeInSec - proj.StartDate;
213 double segmentRatio = totalDeltaT != 0 ? deltaT / totalDeltaT
214 : 1;
215 if (segmentRatio >= 1.f)
217 impact = true;
218 segmentRatio = 1.f;
220 CVector deltaPos = impactPos - proj.TargeterInfo.DefaultPos;
221 CVector projPos = proj.TargeterInfo.DefaultPos + (float) segmentRatio * deltaPos;
222 // see what minimum distance should have been travelled
223 float minDist = MAGICFX::PROJECTILE_SPEED * (float) deltaT;
224 if (minDist * minDist > (projPos - proj.TargeterInfo.DefaultPos).sqrnorm())
226 float maxDist = deltaPos.norm();
227 if (minDist > maxDist)
229 projPos = impactPos;
230 impact = true;
231 segmentRatio = 1.f;
233 else
235 segmentRatio = maxDist != 0.f ? minDist / maxDist : 1.f;
238 ////////////////////
239 // update fx pos //
240 ////////////////////
242 // build fx matrix from the speed of projectile
243 // For a bomb, Pos(t) = t * E + (1 - t) * S + h * (0 0 -4t^2 + 4t) with S = start point, E = end point, h is the height of parabola, and t ranging from 0 to 1
244 // so The speed direction at a given date t is Speed(t) = dPos(t)/dt = E - S + h * (0 0 -8t + 4)
245 // NB : anim track is not taken in account
246 NLMISC::CVector I;
247 if (proj.Mode == MAGICFX::Bomb)
249 I.set(deltaPos.x, deltaPos.y, deltaPos.z + proj.ParabolaHeight * (4.f - 8.f * (float) segmentRatio));
250 I.normalize();
252 else
254 I = deltaPos.normed();
256 NLMISC::CVector K = (CVector::K - (CVector::K * I) * I).normed();
257 NLMISC::CVector J = K ^ I;
258 NLMISC::CMatrix m;
259 m.setRot(I, J, K, true);
261 // apply track (use track of first fx)
262 if (proj.ProjectileAspect && !proj.ProjectileAspect->FX.empty())
264 NL3D::UTrack *track = proj.ProjectileAspect->FX[0].PosTrack;
265 if (track)
267 NLMISC::CVector trackPos;
268 if (track->interpolate((float) deltaT, trackPos))
270 projPos += m.mulVector(trackPos);
274 // if projectile is a bomb, uses a parabola for the trajectory
275 if (proj.Mode == MAGICFX::Bomb)
277 // for simplicity, the parabola reach its max at the middle of the trajectory
278 // so we got something like f(x) = -4x^2 + 4x
279 projPos.z += (float) (proj.ParabolaHeight * (-4 * segmentRatio * segmentRatio + 4 * segmentRatio));
281 m.setPos(projPos);
282 proj.setMatrix(m);
283 // see if target reached
284 if (impact || TimeInSec > proj.EndDate)
286 // stop projectiles and add them to the fx manager so that they are removed when there are no particle lefts
287 //proj.shutDown(dynamic_cast<CCharacterCL *>(target));
289 static std::vector<NL3D::UParticleSystemInstance> fxInstances;
290 fxInstances.clear();
291 // play impact on the target instantly
292 if (target)
294 CCharacterCL *ccl = dynamic_cast<CCharacterCL *>(target);
295 if (ccl)
297 if (proj.AttackInfo.Intensity >= 1 && proj.AttackInfo.Intensity <= MAGICFX::NUM_SPELL_POWER)
299 if (!proj.MagicResist)
301 if (proj.ImpactAspect)
303 CAttachedFX::CBuildInfo bi;
304 for (uint k = 0; k < proj.ImpactAspect->FX.size(); ++k)
306 CFXStickMode replaceStickMode = proj.ImpactAspect->FX[k].Sheet->StickMode;
307 bi.StickMode = NULL;
308 if (proj.LocalizedImpact)
310 nlassert(proj.ImpactAspect->Sheet);
311 // impact must have same localization than impact damages (when the sheet allows it)
312 if (proj.ImpactAspect->Sheet->CanReplaceStickMode[k])
314 replaceStickMode.Mode = proj.ProjectileAimingPoint.Mode;
316 if (proj.ImpactAspect->Sheet->CanReplaceStickBone[k])
318 replaceStickMode.UserBoneName = proj.ProjectileAimingPoint.UserBoneName;
320 bi.StickMode = &replaceStickMode;
322 CAttachedFX::TSmartPtr fx = new CAttachedFX;
323 bi.Sheet = &(proj.ImpactAspect->FX[k]);
324 fx->create(*ccl, bi, proj.TargeterInfo);
325 if (!fx->FX.empty())
327 /** Force to create each instance, even if not visible, so that the impact won't be deleted at start because it isn't visible..
328 * Though this isn't needed in other cases, spell impact can be quite long. and must remain in sync with the action
330 fx->FX.forceInstanciate();
331 for(uint l = 0; l < 4; ++l)
333 fx->FX.setUserParam(l, ImpactUserParams[proj.AttackInfo.Intensity - 1][l]);
335 ccl->attachFX(fx);
341 if (proj.PlayImpactAnim)
343 // if the target is idle, play 'impact' animation on it
344 if (ccl->animState(MOVE) == CAnimationStateSheet::Idle || proj.ForcePlayImpact)
346 ccl->setAnim(CAnimationStateSheet::Impact);
351 else
353 // play resist instead
354 CAttachedFX::TSmartPtr fx = new CAttachedFX;
355 CAttachedFX::CBuildInfo bi;
356 bi.Sheet = getResistFX(proj.AttackInfo.Intensity);
357 fx->create(*ccl, bi, proj.TargeterInfo);
358 if (!fx->FX.empty())
360 fx->FX.forceInstanciate(); // TODO : if the fx isn't too long, is it useful to force to create it ?
361 ccl->attachFX(fx);
366 // play physcial damage fx (if any)
367 if (proj.AttackInfo.PhysicalImpactIntensity != 0)
369 ccl->meleeImpact(proj.AttackInfo);
372 else
374 if (proj.AttackInfo.Intensity >= 1 && proj.AttackInfo.Intensity <= MAGICFX::NUM_SPELL_POWER)
376 if (!proj.MagicResist)
378 if (proj.ImpactAspect)
380 // no entity to spawn fx on, so create them in place
381 for(uint k = 0; k < proj.ImpactAspect->FX.size(); ++k)
383 NL3D::UParticleSystemInstance ps = proj.ImpactAspect->FX[k].createMatchingInstance(ImpactUserParams[proj.AttackInfo.Intensity - 1]);
384 if (!ps.empty())
386 ps.forceInstanciate();
387 ps.setPos(impactPos);
388 // add to fx manager for fx to be removed when finished
389 FXMngr.addFX(ps);
394 else
396 NL3D::UParticleSystemInstance ps = getResistFX(proj.AttackInfo.Intensity)->createMatchingInstance();
397 if (!ps.empty())
399 ps.forceInstanciate(); // TODO : resist can be can be quite short, so may be not useful to force its instanciation ?
400 ps.setPos(impactPos);
401 // add to fx manager for fx to be removed when finished
402 FXMngr.addFX(ps);
406 // no target, so no impact
408 // put in shutdown list (shutdown occurs at next frame)
409 _ToShutDownProjectiles.splice(_ToShutDownProjectiles.begin(), _ActiveProjectiles, tmpIt);
414 // *****************************************************************************************
415 CProjectileManager::CProjectile::CProjectile()
417 H_AUTO_USE(RZ_ProjectileManager)
418 for(uint k = 0; k < MaxNumFX; ++k) FX[k] = NULL;
419 TargetBoneRelativePosBlendFactor = 0.f;
420 TargetBoneID = -1;
421 ParabolaHeight = 0.f;
422 MagicResist = false;
423 LocalizedImpact = false;
426 // *****************************************************************************************
427 CProjectileManager::CProjectile::~CProjectile()
429 H_AUTO_USE(RZ_ProjectileManager)
430 if (Scene)
432 for(uint k = 0; k < MaxNumFX; ++k)
434 if (!FX[k].empty())
436 Scene->deleteInstance(FX[k]);
437 FX[k] = NULL;
443 // *****************************************************************************************
444 void CProjectileManager::evalFXPosition(const CFXStickMode *stickMode, CEntityCL &entity, NLMISC::CVector &result, const NLMISC::CVector &additionnalOffset /* = NLMISC::CVector::Null*/)
446 H_AUTO_USE(RZ_ProjectileManager)
447 // Force to eval animation if necessary
448 entity.forceEvalAnim();
450 if (!stickMode)
452 CFXStickMode sm;
453 evalFXPosition(&sm, entity, result);
455 else
457 switch(stickMode->Mode)
459 case CFXStickMode::UserBone:
461 if (entity.skeleton())
463 sint boneID = entity.skeleton()->getBoneIdByName(NLMISC::CStringMapper::unmap(stickMode->UserBoneName));
464 if (boneID != -1)
466 if (!entity.skeleton()->isBoneComputed(boneID))
468 entity.skeleton()->forceComputeBone(boneID);
470 NL3D::UBone bone = entity.skeleton()->getBone(boneID);
471 const NLMISC::CMatrix &worldMat = bone.getLastWorldMatrixComputed();
472 result = worldMat.getPos() + worldMat.mulVector(additionnalOffset);
473 return;
475 else
477 nlwarning("Invalid bone : %s. Using entity pos instead", NLMISC::CStringMapper::unmap(stickMode->UserBoneName).c_str());
480 else
482 nlwarning("Can't retrieve entity skeleton. Using entity pos instead of bone pos", NLMISC::CStringMapper::unmap(stickMode->UserBoneName).c_str());
484 // as default, use entity position
485 entity.pos().copyTo(result);
486 result += additionnalOffset;
488 break;
489 default:
491 // use entity position
492 entity.pos().copyTo(result);
493 result += additionnalOffset;
495 break;
500 // *****************************************************************************************
501 sint CProjectileManager::CProjectile::getImpactBoneID(CEntityCL &target)
503 H_AUTO_USE(RZ_ProjectileManager)
504 if (TargetBoneID != -1) return TargetBoneID;
505 if (!target.skeleton()) return -1;
506 sint TargetBoneID = target.skeleton()->getBoneIdByName(NLMISC::CStringMapper::unmap(ProjectileAimingPoint.UserBoneName));
507 return TargetBoneID;
510 // *****************************************************************************************
511 void CProjectileManager::CProjectile::updateTargetPos()
513 H_AUTO_USE(RZ_ProjectileManager)
514 NLMISC::CVector &impactPos = Target.TargetPos;
515 CEntityCL *target = EntitiesMngr.entity(Target.Slot);
516 if (target)
518 if (ProjectileAimingPoint.Mode == CFXStickMode::UserBone ||
519 ProjectileAimingPoint.Mode == CFXStickMode::UserBoneOrientedTowardTargeter
522 bool targetBoneVisible = false;
523 if (!target->getLastClip())
525 // target visible
526 sint impactBoneID = getImpactBoneID(*target);
527 if (impactBoneID != -1)
529 if (target->skeleton() && target->skeleton()->isBoneComputed(impactBoneID))
531 NL3D::UBone bone = target->skeleton()->getBone(impactBoneID);
532 if (TargetBoneRelativePosBlendFactor == 0.f)
534 impactPos = bone.getLastWorldMatrixComputed().getPos();
536 else
538 // do blend with previous clipped position
539 // express last clipped position in world (taking in account the ne orientation & position of model)
540 const NLMISC::CVector &relPos = ClippedTargetBoneRelativePos;
541 const NLMISC::CVector &front = target->front();
542 NLMISC::CVector newEntityPos;
543 NLMISC::CVector clippedPos;
544 target->pos().copyTo(newEntityPos);
545 clippedPos.set(newEntityPos.x + front.y * relPos.x - front.x * relPos.y,
546 newEntityPos.y + front.x * relPos.x + front.y * relPos.y,
547 newEntityPos.z + relPos.z);
548 // do the blend
549 const NLMISC::CVector &visiblePos = bone.getLastWorldMatrixComputed().getPos();
550 impactPos = TargetBoneRelativePosBlendFactor * clippedPos
551 + (1.f - TargetBoneRelativePosBlendFactor) * visiblePos;
552 // decrease blend factor over time
553 const float BLEND_FATCOR_DECREASE_OVER_TIME = 3.f;
554 TargetBoneRelativePosBlendFactor -= DT * BLEND_FATCOR_DECREASE_OVER_TIME;
555 if (TargetBoneRelativePosBlendFactor <= 0.f) TargetBoneRelativePosBlendFactor = 0.f;
557 targetBoneVisible = true;
561 if (!targetBoneVisible)
563 // the target bone is not visible -> use last computed bone position (and make it relative to the parent skeleton)
564 if (TargetBoneVisibleLastFrame)
566 // the relative position of the bone in its skeleton hasn't been computed yet (because the bone was visible at last frame or because it is the first visible frame)
567 const NLMISC::CVector &front = LastEntityOrientation;
568 const NLMISC::CVector deltaPos = impactPos - target->lastFramePos().asVector();
569 // we except that front vector of the entity is normed and (thus its z is 0)
570 ClippedTargetBoneRelativePos.set(front.y * deltaPos.x - front.x * deltaPos.y, deltaPos.x * front.x + deltaPos.y * front.y, deltaPos.z);
572 // Snap the target model if necessary. Note that the skeleton may be visible the bone may have been not computed (because of clod)
573 if (target->getLastClip())
575 target->snapToGround();
577 // compute new position
578 const NLMISC::CVector &relPos = ClippedTargetBoneRelativePos;
579 const NLMISC::CVector &front = target->front();
580 NLMISC::CVector newPos;
581 target->pos().copyTo(newPos);
582 impactPos.set(newPos.x + front.y * relPos.x - front.x * relPos.y,
583 newPos.y + front.x * relPos.x + front.y * relPos.y,
584 newPos.z + relPos.z);
585 TargetBoneRelativePosBlendFactor = 1.f;
588 TargetBoneVisibleLastFrame = targetBoneVisible;
589 LastEntityOrientation = target->front();
591 else
593 // just uses entity pos and snap if necessary
594 if (target->getLastClip())
596 target->snapToGround(); // TODO : avoid to do this each frame for targetted non-visible entities ?
598 target->pos().copyTo(impactPos);
601 // else use value already present in impactPos
604 // *****************************************************************************************
605 void CProjectileManager::CProjectile::setMatrix(const NLMISC::CMatrix &pos)
607 H_AUTO_USE(RZ_ProjectileManager)
608 for(uint k = 0; k < MaxNumFX; ++k)
610 if (!FX[k].empty())
612 FX[k].setMatrix(pos);
617 static void convertFreeFXToAttachedFX(NL3D::UParticleSystemInstance instance,
618 CCharacterCL &holder,
619 float timeOut,
620 const CAnimationFX *aspect,
621 const CFXStickMode &stickMode)
623 instance.setForceClipRoot(false);
624 CAttachedFX::TSmartPtr af = new CAttachedFX;
625 CAttachedFX::CTargeterInfo ti;
626 CAttachedFX::CBuildInfo bi;
627 bi.Sheet = aspect;
628 bi.StickMode = &stickMode;
629 bi.TimeOut = timeOut;
630 af->create(holder, instance, bi, ti);
631 holder.attachFX(af);
634 // *****************************************************************************************
635 void CProjectileManager::CProjectile::shutDown(CCharacterCL *target)
637 H_AUTO_USE(RZ_ProjectileManager)
638 for(uint k = 0; k < MaxNumFX; ++k)
640 if (!FX[k].empty())
642 const float FX_DEFAULT_TIMEOUT = 15.f;
643 const float FX_ERROR_TIMEOUT = 3.f;
644 // must set user matrix of target, if an emitter emit 'on death' using 'user' matrix
645 if (target)
647 NLMISC::CMatrix userMatrix;
648 target->buildAlignMatrix(userMatrix);
649 FX[k].forceSetUserMatrix(userMatrix);
651 if (!FX[k].removeByID(NELID("STOP")) && !FX[k].removeByID(NELID("main")))
653 FX[k].activateEmitters(false);
654 //nlwarning("Projectile with a particle system that has no 'STOP' emitter");
655 if (target)
657 convertFreeFXToAttachedFX(FX[k], *target, FX_ERROR_TIMEOUT, ProjectileAspect ? &ProjectileAspect->FX[k] : NULL, ProjectileAimingPoint);
659 else
661 FXMngr.addFX(FX[k], FX_ERROR_TIMEOUT); // use short timeout to avoid problems
664 else
666 if (target)
668 convertFreeFXToAttachedFX(FX[k], *target, FX_DEFAULT_TIMEOUT, ProjectileAspect ? &ProjectileAspect->FX[k] : NULL, ProjectileAimingPoint);
670 else
672 FXMngr.addFX(FX[k], FX_DEFAULT_TIMEOUT); // gives a time out to avoid remaining particles if the fx main emitter has not been flaged with 'STOP'
675 FX[k] = NULL;
680 // *****************************************************************************************
681 void CProjectileManager::reset()
683 H_AUTO_USE(RZ_ProjectileManager)
684 _ProjectileQueue.clear();
685 _ActiveProjectiles.clear();
686 _ToShutDownProjectiles.clear();
689 // *****************************************************************************************
690 void CProjectileManager::entityRemoved(uint slot)
692 // TODO nico : adding anchor objects (that can be "CRefPtr'ed" ...) to define rays / projectile paths would be a more elegant solution now (and a CAnchorRef object to dlte anchor automatically)
694 H_AUTO_USE(RZ_ProjectileManager)
695 CEntityCL *entity = EntitiesMngr.entity(slot);
696 if (!entity) return;
697 // projectile queue.
698 for(std::multiset<CProjectileBuild>::iterator it = _ProjectileQueue.begin(); it != _ProjectileQueue.end(); ++it)
700 // note : we do not change field that would modify ordering in the set (but const_cast is needed there anyway ..)
701 CProjectileBuild &pb = const_cast<CProjectileBuild &>(*it);
702 if (it->TargeterInfo.Slot == slot)
704 // eval start pos
705 evalFXPosition(&it->TargeterInfo.StickMode, *entity, pb.TargeterInfo.DefaultPos); // note : insertion in the set depends not on this field, hence the const_cast
706 pb.TargeterInfo.Slot = CLFECOMMON::INVALID_SLOT;
708 if (it->Target.Slot == slot)
710 // eval target pos
711 evalFXPosition(&pb.ProjectileAimingPoint, *entity, pb.Target.TargetPos); // note : insertion in the set depends not on this field, hence the const_cast
712 pb.TargeterInfo.Slot = CLFECOMMON::INVALID_SLOT;
715 // currently active projectile.
716 for(std::list<CProjectile>::iterator it = _ActiveProjectiles.begin(); it != _ActiveProjectiles.end(); ++it)
718 if (it->Target.Slot == slot)
720 // eval target pos
721 evalFXPosition(&it->ProjectileAimingPoint, *entity, it->Target.TargetPos);
722 it->Target.Slot = CLFECOMMON::INVALID_SLOT;
724 // caster slot
725 if (it->TargeterInfo.Slot == slot)
727 it->TargeterInfo.Slot = CLFECOMMON::INVALID_SLOT;
732 // *****************************************************************************************
733 const CAnimationFX *CProjectileManager::getResistFX(uint level)
735 H_AUTO_USE(RZ_ProjectileManager)
736 // TMP TMP : harcoded datas for the resist spell
737 static const char *impactResistFXName = "mag_def_impact_resist.ps";
739 // user params of the resist fx for each level
740 static float resistFXUserParams[MAGICFX::NUM_SPELL_POWER][4] =
742 { 0.f, 1.f, 0.f, 0.f},
743 { 0.f, 1.f, 0.f, 0.f},
744 { 1.f, 1.f, 0.f, 0.f},
745 { 1.f, 1.f, 1.f, 0.f},
746 { 1.f, 1.f, 1.f, 1.f}
749 // sheet of the resist spell (hardcoded for now)
750 static CAnimationFXSheet impactResistSheet[MAGICFX::NUM_SPELL_POWER] =
752 CAnimationFXSheet(impactResistFXName, resistFXUserParams[0]),
753 CAnimationFXSheet(impactResistFXName, resistFXUserParams[1]),
754 CAnimationFXSheet(impactResistFXName, resistFXUserParams[2]),
755 CAnimationFXSheet(impactResistFXName, resistFXUserParams[3]),
756 CAnimationFXSheet(impactResistFXName, resistFXUserParams[4])
759 static CAnimationFX impactResistFX[MAGICFX::NUM_SPELL_POWER];
761 static bool init = false;
763 if (!init)
765 for(uint k = 0; k < MAGICFX::NUM_SPELL_POWER; ++k)
767 impactResistFX[k].init(&impactResistSheet[k], NULL);
769 init = true;
771 if (level >= 1 && level <= MAGICFX::NUM_SPELL_POWER)
773 return &impactResistFX[level - 1];
775 return NULL;