1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
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.
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/>.
20 #include "projectile_manager.h"
21 #include "animation_fx.h"
22 #include "fx_manager.h"
23 #include "character_cl.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
;
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
;
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
));
109 proj
.LastEntityOrientation
= casterEntity
->front();
110 evalFXPosition(&pb
.TargeterInfo
.StickMode
, *casterEntity
, proj
.TargeterInfo
.DefaultPos
, pb
.TargeterInfo
.StickOffset
);
111 // play additionnal cast fx
114 casterEntity
->playCastFX(pb
.CastAspect
, pb
.CastPower
);
119 proj
.LastEntityOrientation
= CVector::I
;
121 CEntityCL
*targetEntity
= EntitiesMngr
.entity(pb
.Target
.Slot
);
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
);
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
;
203 CProjectile
&proj
= *tmpIt
;
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
211 double totalDeltaT
= proj
.EndDate
- proj
.StartDate
;
212 double deltaT
= TimeInSec
- proj
.StartDate
;
213 double segmentRatio
= totalDeltaT
!= 0 ? deltaT
/ totalDeltaT
215 if (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
)
235 segmentRatio
= maxDist
!= 0.f
? minDist
/ maxDist
: 1.f
;
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
247 if (proj
.Mode
== MAGICFX::Bomb
)
249 I
.set(deltaPos
.x
, deltaPos
.y
, deltaPos
.z
+ proj
.ParabolaHeight
* (4.f
- 8.f
* (float) segmentRatio
));
254 I
= deltaPos
.normed();
256 NLMISC::CVector K
= (CVector::K
- (CVector::K
* I
) * I
).normed();
257 NLMISC::CVector J
= K
^ I
;
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
;
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
));
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
;
291 // play impact on the target instantly
294 CCharacterCL
*ccl
= dynamic_cast<CCharacterCL
*>(target
);
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
;
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
);
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
]);
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
);
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
);
360 fx
->FX
.forceInstanciate(); // TODO : if the fx isn't too long, is it useful to force to create it ?
366 // play physcial damage fx (if any)
367 if (proj
.AttackInfo
.PhysicalImpactIntensity
!= 0)
369 ccl
->meleeImpact(proj
.AttackInfo
);
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]);
386 ps
.forceInstanciate();
387 ps
.setPos(impactPos
);
388 // add to fx manager for fx to be removed when finished
396 NL3D::UParticleSystemInstance ps
= getResistFX(proj
.AttackInfo
.Intensity
)->createMatchingInstance();
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
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
;
421 ParabolaHeight
= 0.f
;
423 LocalizedImpact
= false;
426 // *****************************************************************************************
427 CProjectileManager::CProjectile::~CProjectile()
429 H_AUTO_USE(RZ_ProjectileManager
)
432 for(uint k
= 0; k
< MaxNumFX
; ++k
)
436 Scene
->deleteInstance(FX
[k
]);
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();
453 evalFXPosition(&sm
, entity
, result
);
457 switch(stickMode
->Mode
)
459 case CFXStickMode::UserBone
:
461 if (entity
.skeleton())
463 sint boneID
= entity
.skeleton()->getBoneIdByName(NLMISC::CStringMapper::unmap(stickMode
->UserBoneName
));
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
);
477 nlwarning("Invalid bone : %s. Using entity pos instead", NLMISC::CStringMapper::unmap(stickMode
->UserBoneName
).c_str());
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
;
491 // use entity position
492 entity
.pos().copyTo(result
);
493 result
+= additionnalOffset
;
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
));
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
);
518 if (ProjectileAimingPoint
.Mode
== CFXStickMode::UserBone
||
519 ProjectileAimingPoint
.Mode
== CFXStickMode::UserBoneOrientedTowardTargeter
522 bool targetBoneVisible
= false;
523 if (!target
->getLastClip())
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();
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
);
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();
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
)
612 FX
[k
].setMatrix(pos
);
617 static void convertFreeFXToAttachedFX(NL3D::UParticleSystemInstance instance
,
618 CCharacterCL
&holder
,
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
;
628 bi
.StickMode
= &stickMode
;
629 bi
.TimeOut
= timeOut
;
630 af
->create(holder
, instance
, bi
, ti
);
634 // *****************************************************************************************
635 void CProjectileManager::CProjectile::shutDown(CCharacterCL
*target
)
637 H_AUTO_USE(RZ_ProjectileManager
)
638 for(uint k
= 0; k
< MaxNumFX
; ++k
)
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
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");
657 convertFreeFXToAttachedFX(FX
[k
], *target
, FX_ERROR_TIMEOUT
, ProjectileAspect
? &ProjectileAspect
->FX
[k
] : NULL
, ProjectileAimingPoint
);
661 FXMngr
.addFX(FX
[k
], FX_ERROR_TIMEOUT
); // use short timeout to avoid problems
668 convertFreeFXToAttachedFX(FX
[k
], *target
, FX_DEFAULT_TIMEOUT
, ProjectileAspect
? &ProjectileAspect
->FX
[k
] : NULL
, ProjectileAimingPoint
);
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'
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
);
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
)
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
)
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
)
721 evalFXPosition(&it
->ProjectileAimingPoint
, *entity
, it
->Target
.TargetPos
);
722 it
->Target
.Slot
= CLFECOMMON::INVALID_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;
765 for(uint k
= 0; k
< MAGICFX::NUM_SPELL_POWER
; ++k
)
767 impactResistFX
[k
].init(&impactResistSheet
[k
], NULL
);
771 if (level
>= 1 && level
<= MAGICFX::NUM_SPELL_POWER
)
773 return &impactResistFX
[level
- 1];