// Ryzom - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
/*
* Update character position notes
* -------------------------------
*
* The update of the character position is done as followed :
*
* For each character
* updatePreCollision
* updatePos
* PACS::move
*
* The characters know where they want to be in 2d (pos().x and pos().y valid).
*
* PACS::evalCollision
*
* For each character
* updatePostCollision
* pacsFinalizepos
* PACS::getGlobalPosition
* If real position too far from PACS position
* PACS::setGlobalPosition
* -- Here pos().z is estimated
* snapToGround
* NL3D::visualCollisionEntities
* -- Here pos().z is good
* updateDisplay
* updateCluster
* updateHeadDirection
* -- Here the character is setuped in the engine
*
*/
/////////////
// INCLUDE //
/////////////
#include "stdpch.h"
// Client.
#include "character_cl.h"
#include "pacs_client.h"
#include "net_manager.h"
#include "entity_animation_manager.h"
#include "time_client.h"
#include "ingame_database_manager.h"
#include "client_chat_manager.h"
#include "motion/user_controls.h"
#include "color_slot_manager.h"
#include "sheet_manager.h"
#include "debug_client.h"
#include "animation_misc.h"
#include "entities.h" // \todo GUIGUI : try to remove this dependency.
#include "misc.h"
#include "string_manager_client.h"
#include "interface_v3/interface_manager.h"
#include "fx_manager.h"
#include "interface_v3/group_in_scene_user_info.h"
#include "interface_v3/group_in_scene_bubble.h"
#include "client_cfg.h"
#include "user_entity.h"
#include "projectile_manager.h"
#include "init_main_loop.h"
#include "nel/misc/cdb_branch.h"
#include "animation_fx_misc.h"
#include "attack_list.h"
#include "animation_fx_id_array.h"
#include "cursor_functions.h"
#include "interface_v3/bar_manager.h"
// sheets
#include "client_sheets/item_fx_sheet.h"
#include "client_sheets/attack_id_sheet.h"
// Misc
#include "nel/misc/geom_ext.h"
#include "nel/misc/random.h"
// 3D
#include "nel/3d/u_scene.h"
#include "nel/3d/u_camera.h"
#include "nel/3d/u_driver.h"
#include "nel/3d/u_play_list.h"
#include "nel/3d/u_particle_system_instance.h"
#include "nel/3d/u_bone.h"
#include "nel/3d/u_track.h"
#include "nel/3d/u_instance.h"
#include "nel/3d/u_instance_material.h"
// PACS
#include "nel/pacs/u_global_position.h"
// SOUND
#include "nel/sound/sound_anim_manager.h"
// Std.
#include
// Game share
#include "game_share/intensity_types.h"
#include "game_share/multi_target.h"
#include "game_share/visual_fx.h"
#include "game_share/range_weapon_type.h"
//
////////////
// DEFINE //
////////////
#define INVALID_POS CVectorD(-1.0,-1.0,-1.0)
#define INVALID_DIST -1.0
#define INVALID_TIME -1.0
//#define MAX_HEAD_H_ROTATION Pi/2.5
#define MAX_HEAD_H_ROTATION Pi/3.0
#define MAX_HEAD_V_ROTATION Pi/4.0
#define DEFAULT_BLEND_LENGTH 0
static const double AURA_SHUTDOWN_TIME = 1.f; // number of seconds for auras and links to shutdown
///////////
// USING //
///////////
using namespace NLMISC;
using namespace NL3D;
using namespace NLPACS;
using namespace NLSOUND;
using namespace std;
using namespace MBEHAV;
using namespace CLFECOMMON;
////////////
// EXTERN //
////////////
extern UScene *Scene;
extern UDriver *Driver;
extern CEntityAnimationManager *EAM;
extern CClientChatManager ChatMngr;
extern UTextContext *TextContext;
extern UMaterial GenericMat;
extern UCamera MainCam;
///////////
// MACRO //
///////////
#if !FINAL_VERSION
std::string LastMethod;
#define ADD_METHOD(header) \
header \
{ \
LastMethod = #header;
#define CHECK(param) \
if((param)==false) \
{ \
nlwarning("entity:%d: Test '%s'", _Slot, #param); \
nlwarning("entity:%d: Last Method called '%s'", _Slot, LastMethod.c_str()); \
nlstop; \
}
#define METHOD_NAME(param) LastMethod = (param);
#else
#define ADD_METHOD(header) \
header \
{
#define CHECK(param)
#define METHOD_NAME(param)
#endif
////////////
// STATIC //
////////////
const std::string CCharacterCL::_EmptyString = "";
const uint8 CCharacterCL::_BadHairIndex = 0xFF;
H_AUTO_DECL ( RZ_Client_Character_CL_Update_Pos_Combat_Float )
H_AUTO_DECL ( RZ_Client_Entity_CL_Update_Pos_Pacs )
H_AUTO_DECL ( RZ_Client_Entity_CL_Update_Pos_Combat_Float )
H_AUTO_DECL ( RZ_Client_Entity_CL_Update_Pos_Compute_Motion )
/////////////
// METHODS //
/////////////
//---------------------------------------------------
// dirEndAnim :
// Set the direction that should have the character at the end of the animation.
// \param vect : vector used to set the direction at the end of the animation.
//---------------------------------------------------
void CCharacterCL::dirEndAnim(const CVector &vect)
{
setVect(_DirEndAnim, vect, true, true);
}// dirEndAnim //
//-----------------------------------------------
// CCharacterCL :
// Constructor.
//-----------------------------------------------
CCharacterCL::CCharacterCL()
: CEntityCL()
{
Type = NPC;
_FirstPos = INVALID_POS; // Initialize the first with a bad position.
_FirstTime = INVALID_TIME; // Initialize the time for the first position with a bad one.
dist2FirstPos(INVALID_DIST); // Initialize the distance to the first position with a bad value.
_RunStartTimeNoPop = INVALID_TIME;
_DestPos = INVALID_POS;
_DestTime = INVALID_TIME;
dist2Dest(INVALID_DIST);
_OldPos = INVALID_POS;
_OldPosTime = INVALID_TIME;
// Initialize the time for the last loop with the current time when entity created.
_LastFrameTime = 0.0;
// The animation should be played from the begin to the end.
_AnimReversed.resize(animTypeCount, false);
// Id among all the animations for each slot
_AnimId.resize(animTypeCount, NL3D::UPlayList::empty);
// Index in the state of the current animation for each slot.
_AnimIndex.resize(animTypeCount, CAnimation::UnknownAnim);
// ID of the current sound animation for each slot.
_SoundId.resize(animTypeCount, -1);
// ID of the current animation state for each slot.
_AnimState.resize(animTypeCount, CAnimationStateSheet::Idle);
// Time offest in the current animation for each slot.
_AnimOffset.resize(animTypeCount, 0.0);
// Subsidiary Key for the Animation State (emote).
_SubStateKey = CAnimationStateSheet::UnknownState;
// The character does not accept special "job animation" by default
_AnimJobSpecialisation= 0;
// Reset Lod.
_LodCharacterAnimEnabled= false;
_LodCharacterMasterAnimSlot= MOVE;
// default POS scale to 1.
_CharacterScalePos= 1.f;
// No sheet pointed.
_Sheet = 0;
// Unknown gender at the entity creation.
_Gender = GSGENDER::unknown;
// The bone for the name is not known for the time
_NameBoneId = -1;
// No UTransform for the name needed if there is no name so not allocated for the time.
_NameTransform = 0;
// default Clod apparition => force compute the bone
_NameCLodDeltaZ = NameCLodDeltaZNotComputed;
// There is no anim set for the time.
_CurrentAnimSet.resize(animTypeCount, 0);
// Same as the animation at the beginning.
_RotationFactor = 1.f;
_CurrentState = 0;
_RightFXActivated = false;
_LeftFXActivated = false;
dirEndAnim(CVector(0.f, 1.f, 0.f));
// No item associated at the beginning but there is a room for them.
_Items.resize(SLOTTYPE::NB_SLOT);
_HeadIdx = CEntityCL::BadIndex;
_FaceIdx = CEntityCL::BadIndex;
// No frame remaining forthe blend at the beginning.
_BlendRemaining = 0;
// Scale for the skeleton according to the gabarit. Default : 1
_CustomScalePos = 1.f;
// Start with "unknown mode wanted by the server"
_ModeWanted = MBEHAV::UNKNOWN_MODE; //MBEHAV::NORMAL;
_Mount = CLFECOMMON::INVALID_SLOT;
_Rider = CLFECOMMON::INVALID_SLOT;
_TheoreticalMount = CLFECOMMON::INVALID_SLOT;
_TheoreticalRider = CLFECOMMON::INVALID_SLOT;
_OwnerPeople = MOUNT_PEOPLE::Unknown;
// Default is : entity has no bone for the head and Neck.
_HeadBoneId = -1;
_IsThereAMode = false;
_ImportantStepTime= 0.0;
_StartDecreaseLCTImpact= 0;
// Entity has no look and so is not displayable for the time.
_LookRdy = false;
// Index of the instance in the right hand (0xFFFFFFFF = no index).
_RHandInstIdx = CEntityCL::BadIndex;
// Index of the instance in the left hand (0xFFFFFFFF = no index).
_LHandInstIdx = CEntityCL::BadIndex;
_HairColor = 0;
_EyesColor = 0;
// No Hair Index at the beginning.
_HairIndex = _BadHairIndex;
_ClothesSheet = 0;
_NbLoopAnim = 0;
_MaxLoop = false;
setAlive();
_InSceneUserInterface = NULL;
_CurrentBubble = NULL;
// Initialize the head offset with a Null Vector.
_HeadOffset = CVector::Null;
_HeadOffsetComputed = false;
// Initialize the Run Factor
runFactor(0.0);
// Initialize the Speed
speed(0.0);
_CurrentAttack = NULL;
_CurrentAttackID.Type = CAttackIDSheet::Unknown;
//_PelvisBoneId = -1;
_ChestBoneId = -1;
_HideSkin = false;
_GuildNameId = 0;
_GuildSymbol = 0;
_EventFactionId = 0;
_PvpMode = PVP_MODE::None;
_LeagueId = 0;
_OutpostId = 0;
_OutpostSide = OUTPOSTENUMS::UnknownPVPSide;
_SelectableBySpace = true;
_LastSelectBoxComputeTime= 0;
_CustomScale = 1.f;
}// CCharacterCL //
//-----------------------------------------------
// ~CCharacterCL:
// Default Destructor
// \warning : Do not remove sheets before the entity.
//-----------------------------------------------
CCharacterCL::~CCharacterCL()
{
// Delete the UTransform used to compute the Bone for the Name.
if(!_NameTransform.empty())
{
if(Scene)
{
if (!_Skeleton.empty())
_Skeleton.detachSkeletonSon(_NameTransform);
Scene->deleteTransform(_NameTransform);
}
_NameTransform = 0;
}
// No more sheet pointed.
_Sheet = NULL;
// Release items (but not their mesh, because they are managed by _Instances)
for(uint k = 0; k < _Items.size(); ++k)
{
_Items[k].release();
}
// Delete previous interface
releaseInSceneInterfaces();
if (_CurrentBubble)
{
_CurrentBubble->unlink();
}
}
//-----------------------------------------------
// computePrimitive :
// Create (or re-create) a primitive.
//-----------------------------------------------
void CCharacterCL::computePrimitive()
{
// Initialize the primitive.
if (_Sheet)
{
initPrimitive(_Sheet->ColRadius*getScale(), _Sheet->ColHeight*getScale(), _Sheet->ColLength, _Sheet->ColWidth, UMovePrimitive::DoNothing, UMovePrimitive::NotATrigger, MaskColNpc, MaskColNone, _Sheet->ClipRadius, _Sheet->ClipHeight);
}
else
{
initPrimitive(0.5f, 2.0f, 0.0f, 0.0f, UMovePrimitive::DoNothing, UMovePrimitive::NotATrigger, MaskColNpc, MaskColNone);
}
if(_Primitive)
_Primitive->insertInWorldImage(dynamicWI);
// Set the position.
pacsPos(pos());
}// computePrimitive //
//-----------------------------------------------
void CCharacterCL::removeAllAttachedFX()
{
_AttachedFXListForCurrentAnim.clear();
_AttachedFXListToRemove.clear();
_StaticFX = NULL;
for(uint k = 0; k < MaxNumAura; ++k)
{
_AuraFX[k] = NULL;
}
_LinkFX = NULL;
}
//-----------------------------------------------
void CCharacterCL::releaseInSceneInterfaces()
{
if (_InSceneUserInterface)
{
CWidgetManager::getInstance()->unMakeWindow(_InSceneUserInterface);
if (_InSceneUserInterface->getParent())
{
_InSceneUserInterface->getParent()->delGroup(_InSceneUserInterface);
}
else
{
delete _InSceneUserInterface;
}
_InSceneUserInterface = NULL;
}
}
//-----------------------------------------------
// stopAttachedFXForCurrrentAnim
// Stop all attached fxs linked to current animation
//-----------------------------------------------
void CCharacterCL::stopAttachedFXForCurrrentAnim(bool stopLoopingFX)
{
// shutdown fxs for current anim
std::list::iterator itAttachedFx = _AttachedFXListForCurrentAnim.begin();
while(itAttachedFx != _AttachedFXListForCurrentAnim.end())
{
std::list::iterator tmpItAttached = itAttachedFx;
++itAttachedFx;
if (!(!stopLoopingFX && (*tmpItAttached)->AniFX && (*tmpItAttached)->AniFX->Sheet->RepeatMode == CAnimationFXSheet::Loop)) // dont remove looping fx if it is requested
{
// test if emitters should be shutdown at the end of the anim
if ((*tmpItAttached)->AniFX &&
(*tmpItAttached)->AniFX->Sheet->RepeatMode != CAnimationFXSheet::Respawn &&
(*tmpItAttached)->StickMode != CFXStickMode::SpawnPermanent
)
{
if(!(*tmpItAttached)->FX.empty())
{
if (!(*tmpItAttached)->FX.removeByID(NELID("STOP")) && !(*tmpItAttached)->FX.removeByID(NELID("main")))
{
(*tmpItAttached)->FX.activateEmitters(false);
}
}
}
_AttachedFXListToRemove.splice(_AttachedFXListToRemove.begin(), _AttachedFXListForCurrentAnim, tmpItAttached);
_AttachedFXListToRemove.front()->TimeOutDate += TimeInSec; // compute absolute timeout date
}
}
/** Removes fx that lives on more that a given count of animation
* Useful if framerate is low to avoid that fx overlap
*/
itAttachedFx = _AttachedFXListToRemove.begin();
while(itAttachedFx != _AttachedFXListToRemove.end())
{
std::list::iterator tmpItAttachedFX = itAttachedFx;
++itAttachedFx;
if ((*tmpItAttachedFX)->AniFX)
{
if ((*tmpItAttachedFX)->MaxAnimCount != 0) // if there a limit to the number of animation during which the fx can live ?
{
(*tmpItAttachedFX)->MaxAnimCount -= 1;
if ((*tmpItAttachedFX)->MaxAnimCount == 0)
{
// remove the fx
_AttachedFXListToRemove.erase(tmpItAttachedFX);
}
}
}
}
}
//-----------------------------------------------
// applyColorSlot :
//-----------------------------------------------
void CCharacterCL::applyColorSlot(SInstanceCL &instance, sint skin, sint userColor, sint hair, sint eyes)
{
CColorSlotManager::TIntCouple array[4];
// Skin
array[0].first = (uint)0; array[0].second = (uint)skin;
// User Color
array[1].first = (uint)1; array[1].second = (uint)userColor;
// Hair Color
array[2].first = (uint)2; array[2].second = (uint)hair;
// Eyes Color
array[3].first = (uint)3; array[3].second = (uint)eyes;
// Set Values.
UInstance inst = instance.createLoadingFromCurrent();
if (!inst.empty())
{
instance.setColors(skin, userColor, hair, eyes);
ColorSlotManager.setInstanceSlot(inst, array, 4);
}
}// applyColorSlot //
//-----------------------------------------------
// changeColors :
//-----------------------------------------------
void CCharacterCL::changeColors(sint userColor, sint hair, sint eyes, sint part) // virtual
{
// Color Just a part of the entity.
if(part >= 0)
{
if((uint)part < _Instances.size())
applyColorSlot(_Instances[part], skin(), userColor, hair, eyes);
}
// Color the whole entity.
else
{
for(uint i=0; i<_Instances.size(); i++)
applyColorSlot(_Instances[i], skin(), userColor, hair, eyes);
}
}// changeColors //
//-----------------------------------------------
// addColoredInstance :
//-----------------------------------------------
uint32 CCharacterCL::addColoredInstance(const std::string &shapeName, const std::string &stickPoint, sint texture, uint32 instIdx, sint color)
{
// Get the instance
uint32 idx = addInstance(shapeName, stickPoint, texture, instIdx);
SInstanceCL *instance = idx2Inst(idx);
if(instance)
applyColorSlot(*instance, skin(), color, _HairColor, _EyesColor);
else
nlwarning("CH::addColoredInstance: cannot create the instance for the shape '%s'.", shapeName.c_str());
return idx;
}// addColoredInstance //
//-----------------------------------------------
// buildEquipment :
// \param slot: structure of the equipement.
// \param visualSlot: visual slot used by this item.
// \return uint32 : index of the instance or 0xFFFFFFFF.
// \todo GUIGUI : find a better choice to avoid all visualSlot checks
//-----------------------------------------------
uint32 CCharacterCL::buildEquipment(const CCharacterSheet::CEquipment &slot, SLOTTYPE::EVisualSlot visualSlot, sint color, uint32 instIdx)
{
uint32 idx = CEntityCL::BadIndex;
// Do something only if the slot is not empty.
if(slot.getItem().empty())
return idx;
sint slotColor = slot.Color;
// This is a reference on an item (the file is store with an UPPER case so check in UPPER CASE).
string ext = CFile::getExtension(slot.getItem());
if((ext == "item") || (ext == "sitem"))
{
// IS the item a valid one ?
CSheetId itemId;
if(itemId.buildSheetId(NLMISC::strlwr(slot.getItem())))
{
// Is it stored in the database ?
CEntitySheet *entitySheet = SheetMngr.get(itemId);
if(entitySheet)
{
_Items[visualSlot].Sheet = dynamic_cast(entitySheet);
if(_Items[visualSlot].Sheet)
{
const CItemSheet &itemSheet = *(_Items[visualSlot].Sheet);
// Compute the bind point
string bindBone;
switch(visualSlot)
{
// Right Hand
case SLOTTYPE::RIGHT_HAND_SLOT:
if( itemSheet.ItemType != ITEM_TYPE::MAGICIAN_STAFF )
bindBone = "box_arme";
break;
// Left Hand
case SLOTTYPE::LEFT_HAND_SLOT:
// Shields are not stick to the same point.
if(itemSheet.getAnimSet() == "s")
bindBone = "Box_bouclier";
else
bindBone = "box_arme_gauche";
break;
default:
bindBone = slot.getBindPoint();
break;
}
// Choose the right colour.
if(color==-1)
{
// Color in the item
if(slotColor < 0)
{
// Get the item color
slotColor = itemSheet.Color;
// Bad item color -> set to 0
if(slotColor < 0)
slotColor = 0;
}
}
else
slotColor = color;
idx = createItemInstance(itemSheet, instIdx, visualSlot, bindBone, slot.Texture, slotColor);
}
else
nlwarning("CH::buildEquipment: the sheet '%s' is not an item one.", slot.getItem().c_str());
}
else
nlwarning("CH::buildEquipment: the sheet '%s' is not stored in the database.", slot.getItem().c_str());
}
else
nlwarning("CH::buildEquipment: item '%s' is not in the Sheet Id list.", slot.getItem().c_str());
}
// This is a shape.
else
{
if(color==-1)
{
if(slotColor < 0)
slotColor = 0;
}
else
slotColor = color;
// Get the instance
idx = addColoredInstance(slot.getItem(), slot.getBindPoint(), slot.Texture, instIdx, slotColor);
}
// Return the index.
return idx;
}// buildEquipment //
//-----------------------------------------------
// computeSomeBoneId :
// Compute the bone for the name, head...
// \warning This method do not check the bone is valid, nor there is a Scene.
//-----------------------------------------------
void CCharacterCL::computeSomeBoneId()
{
// **** Get the Bone for the name.
_NameBoneId = _Skeleton.getBoneIdByName("name");
// Dummy found -> return the name position.
if(_NameBoneId != -1)
{
// Just to force the bone to be compute (else not computed if not used).
_NameTransform = Scene->createTransform();
if(!_NameTransform.empty())
_Skeleton.stickObject(_NameTransform, _NameBoneId);
}
// No Bone for the Name.
else
{
#if !FINAL_VERSION
pushDebugStr("The Bone for the name is missing.");
#endif // FINAL_VERSION
}
// **** Get the head bone.
_HeadBoneId = _Skeleton.getBoneIdByName("Bip01 Head");
// Bone found
if(_HeadBoneId == -1)
{
#if !FINAL_VERSION
pushDebugStr("The Bone for the Head is missing.");
#endif // FINAL_VERSION
}
else
{
_TargetAnimCtrl.EyePos = CVector::Null;
_Skeleton.setBoneAnimCtrl(_HeadBoneId, &_TargetAnimCtrl);
}
// **** Get the "chest" bone. take spine1.
_ChestBoneId = _Skeleton.getBoneIdByName("Bip01 Spine1");
if(_ChestBoneId == -1)
{
#if !FINAL_VERSION
pushDebugStr("The Bone for the Chest 'Bip01 Spine1' is missing.");
#endif // FINAL_VERSION
}
}// computeSomeBoneId //
//-----------------------------------------------
// createPlayList :
// Create the play list for this entity.
//-----------------------------------------------
void CCharacterCL::createPlayList()
{
// Release the old animation playlist.
if(_PlayList)
{
EAM->deletePlayList(_PlayList);
_PlayList = 0;
}
// Create the new animation playlist.
_PlayList = EAM->createPlayList();
if(!_PlayList)
{
pushDebugStr("Cannot create a playlist for the entity.");
return;
}
// Initialize the new playlist.
// MOVE Channel
_PlayList->setSpeedFactor (MOVE, 1.f);
_PlayList->setWrapMode (MOVE, UPlayList::Clamp);
// ACTION Channel
_PlayList->setSpeedFactor (ACTION, 1.f);
_PlayList->setWrapMode (ACTION, UPlayList::Clamp);
}// createPlayList //
//-----------------------------------------------
// getGroundFX :
// retrieve ground fxs for that entity
//-----------------------------------------------
const std::vector *CCharacterCL::getGroundFX() const
{
return &(_Sheet->GroundFX);
}
//-----------------------------------------------
// build :
// Build the entity from a sheet.
//-----------------------------------------------
bool CCharacterCL::build(const CEntitySheet *sheet) // virtual
{
// Cast the sheet in the right type.
_Sheet = dynamic_cast(sheet);
if(!_Sheet)
{
pushDebugStr("This is not a character sheet -> entity not initialized.");
return false;
}
// Type
Type = (_Sheet->Race >= EGSPD::CPeople::Creature) ? Fauna : NPC;
// Names
if (Type == Fauna)
{
// Get the fauna name in the sheet
const ucstring creatureName(STRING_MANAGER::CStringManagerClient::getCreatureLocalizedName(_Sheet->Id));
if (creatureName.find(ucstring("(IngameDbMngr.getNodePtr()->getNode(0));
if(nodeRoot)
{
_DBEntry = dynamic_cast(nodeRoot->getNode(_Slot));
if(_DBEntry == 0)
pushDebugStr("Cannot get a pointer on the DB entry.");
}
}
if (!ClientCfg.Light && !_Sheet->getSkelFilename().empty())
{
// Create the Playlist for the entity.
createPlayList();
}
// Compute the first automaton.
_CurrentAutomaton = automatonType() + "_normal.automaton";
// Get the Character gender.
_Gender = (GSGENDER::EGender)_Sheet->Gender;
// Initialize the internal time.
_LastFrameTime = ((double)T1) * 0.001;
// Set the skeleton.
if(!ClientCfg.Light && !_Sheet->getSkelFilename().empty() && skeleton(_Sheet->getSkelFilename()))
{
// Set the skeleton scale.
skeleton()->setScale(getScale(), getScale(), getScale());
// Can create all characters except NPC.
if(isNPC()== false)
{
// Eyes Color
if(_Sheet->EyesColor >= SheetMngr.nbEyesColor())
{
if (SheetMngr.nbEyesColor() == 0)
_EyesColor = 0;
else
_EyesColor = rand()%SheetMngr.nbEyesColor();
}
else
{
_EyesColor = _Sheet->EyesColor;
}
// Hair Color
if(_Sheet->HairColor >= SheetMngr.nbHairColor())
{
if (SheetMngr.nbHairColor() == 0)
_HairColor = 0;
else
_HairColor = rand()%SheetMngr.nbHairColor();
}
else
{
_HairColor = _Sheet->HairColor;
}
// -- Dress the character --
// Top Items
buildEquipment(_Sheet->Body, SLOTTYPE::CHEST_SLOT);
buildEquipment(_Sheet->Arms, SLOTTYPE::ARMS_SLOT);
buildEquipment(_Sheet->Hands, SLOTTYPE::HANDS_SLOT);
// Bottom Items
buildEquipment(_Sheet->Legs, SLOTTYPE::LEGS_SLOT);
buildEquipment(_Sheet->Feet, SLOTTYPE::FEET_SLOT);
// Face
_FaceIdx = buildEquipment(_Sheet->Face, SLOTTYPE::FACE_SLOT);
// -- Manage the Head --
// Display the helm.
if(!_Sheet->Head.getItem().empty())
{
// Create the Helm.
_HeadIdx = buildEquipment(_Sheet->Head, SLOTTYPE::HEAD_SLOT, -1, _HeadIdx);
// Hide the face
SInstanceCL *pInstFace = getFace();
if(pInstFace)
{
if(!pInstFace->Current.empty())
pInstFace->Current.hide();
else
pInstFace->KeepHiddenWhenLoaded = true;
}
}
// Display the Hair.
else
{
// Create the Hair.
if(_HairIndex != _BadHairIndex)
_HeadIdx = buildEquipment(_Sheet->HairItemList[_HairIndex], SLOTTYPE::HEAD_SLOT, -1, _HeadIdx);
// Display the face.
SInstanceCL *pInstFace = getFace();
if(pInstFace)
if(!pInstFace->Current.empty())
pInstFace->Current.show();
}
// Objects in Hands
_RHandInstIdx = buildEquipment(_Sheet->ObjectInRightHand, SLOTTYPE::RIGHT_HAND_SLOT, -1, _RHandInstIdx); // In The Right Hand
_LHandInstIdx = buildEquipment(_Sheet->ObjectInLeftHand, SLOTTYPE::LEFT_HAND_SLOT, -1, _LHandInstIdx); // In The Left Hand
// Look is now ready.
_LookRdy = true;
}
// Cannot build as long as the alternative look property not received, so hide the entity.
else
skeleton()->hide();
// Compute the animation set (after weapons are set to choose the right animation set).
computeAnimSet();
// Check the animation set is correct.
if(_CurrentAnimSet[MOVE] == 0)
pushDebugStr("Bad animation set");
// Set the animation to idle.
setAnim(CAnimationStateSheet::Idle);
// Compute the bone for the name.
computeSomeBoneId();
// Compute pelvis bone
//_PelvisBoneId = _Skeleton.getBoneIdByName("Bip01 Pelvis");
// Setup Lod Character skeleton and shapes colors, if skeleton exist
// Get Lod Character Id from the sheet.
sint clodId = getLodCharacterId(*Scene, _Sheet->getLodCharacterName());
if(clodId >= 0)
{
// Setup Lod Character shapes, if enabled.
skeleton()->setLodCharacterShape(clodId);
skeleton()->setLodCharacterDistance(_Sheet->LodCharacterDistance);
}
}
// Instances
else
{
uint32 idx= buildEquipment(_Sheet->Body, SLOTTYPE::CHEST_SLOT);
// must set the scale for the BotObject too
if(idx<_Instances.size())
{
float s= getScale();
_Instances[idx].setScale(CVector(s,s,s));
}
}
// Setup _CharacterScalePos
_CharacterScalePos = _Sheet->CharacterScalePos;
// Adjust the custom scale position according to the entity scale.
_CustomScalePos *= getScale();
// Create PACS Primitive.
initPrimitive(_Sheet->ColRadius*getScale(), _Sheet->ColHeight*getScale(), _Sheet->ColLength, _Sheet->ColWidth, UMovePrimitive::DoNothing, UMovePrimitive::NotATrigger, MaskColNpc, MaskColNone, _Sheet->ClipRadius, _Sheet->ClipHeight);
// Compute the element to be able to snap the entity to the ground.
computeCollisionEntity();
// Initialize properties of the entity (selectable/attackable/etc.).
initProperties();
// copy some properties (special bot objects). before buildInSceneInterface
_DisplayInRadar= _Sheet->DisplayInRadar;
_DisplayOSDName= _Sheet->DisplayOSDName;
_DisplayOSDBars= _Sheet->DisplayOSDBars;
_DisplayOSDForceOver= _Sheet->DisplayOSDForceOver;
_Traversable= _Sheet->Traversable;
_CanTurn = _Sheet->Turn;
_SelectableBySpace = _Sheet->SelectableBySpace;
// Rebuild interface
buildInSceneInterface ();
initStaticFX();
// Entity created.
return true;
}// build //
//-----------------------------------------------
// isKami()
//-----------------------------------------------
bool CCharacterCL::isKami() const
{
if (!_Sheet)
return false;
return (_Sheet->Race == EGSPD::CPeople::Kami);
}
//-----------------------------------------------
// isUnknownRace()
//-----------------------------------------------
bool CCharacterCL::isUnknownRace() const
{
if (!_Sheet)
return false;
return (_Sheet->Race == EGSPD::CPeople::Unknown);
}
//-----------------------------------------------
// getAttackHeight :
// Return the atk height.
// \todo GUIGUI : height relative to attacker instead of global height
//-----------------------------------------------
CCharacterCL::TAtkHeight CCharacterCL::getAttackHeight(CEntityCL *target, BODY::TBodyPart localisation, BODY::TSide side) const
{
// Check there is a target.
if(target == 0)
return CCharacterCL::AtkMiddle;
// Get the position for a bone.
float height;
if(target->getBoneHeight(localisation, side, height))
{
// Low
if(height < 1.0f)
return CCharacterCL::AtkLow;
// High
else if(height > 2.0f)
return CCharacterCL::AtkHigh;
}
// Default is Middle atk.
return CCharacterCL::AtkMiddle;
}// getAttackHeight //
//-----------------------------------------------
// getBoneHeight :
//-----------------------------------------------
bool CCharacterCL::getBoneHeight(BODY::TBodyPart localisation, BODY::TSide side, float &height) const // virtual
{
// If there is no skeleton return false
if(_Skeleton.empty())
return false;
// Get the Bone Name
const char *boneName = getBoneNameFromBodyPart(localisation, side);
if(boneName == 0)
return false;
// Get the Bone Id
sint boneId = _Skeleton.getBoneIdByName(std::string(boneName));
if (boneId == -1)
return false;
if(_Skeleton.isBoneComputed(boneId) == false)
return false;
NL3D::UBone bone = _Skeleton.getBone(boneId);
CMatrix BoneMat = bone.getLastWorldMatrixComputed();
height = (float)(BoneMat.getPos().z-pos().z);
if(height < 0.0f)
height = 0.0f;
else if(height > 10.0f)
height = 10.0f;
return true;
}// getBoneHeight //
//-----------------------------------------------
// lookAtItemsInHands :
// Look at items in hands to change the animation set.
// \return true if the mode is a mode where items in hands should be hidden
//-----------------------------------------------
bool CCharacterCL::modeWithHiddenItems() const
{
return ((ClientCfg.PutBackItems && !isFighting()) || isSit() || _Mode==MBEHAV::SWIM || isRiding() || _Mode==MBEHAV::SWIM_DEATH || _Mode==MBEHAV::REST);
}// lookAtItemsInHands //
//-----------------------------------------------
// automatonType :
//-----------------------------------------------
string CCharacterCL::automatonType() const // virtual
{
return _Sheet->getAutomaton();
}// automatonType //
//-----------------------------------------------
// computeAutomaton :
// Compute the current automaton for the entity.
//-----------------------------------------------
void CCharacterCL::computeAutomaton()
{
_CurrentAutomaton = automatonType() + "_" + NLMISC::strlwr(MBEHAV::modeToString(_Mode)) + ".automaton";
}// computeAutomaton //
//-----------------------------------------------
// computeAnimSet :
// Compute the animation set to use according to weapons, mode and race.
//-----------------------------------------------
void CCharacterCL::computeAnimSet()
{
if(ClientCfg.Light)
return;
// Use the generic method to compute the animation set.
if(!::computeAnimSet(_CurrentAnimSet[MOVE], _Mode, _Sheet->getAnimSetBaseName(), _Items[SLOTTYPE::LEFT_HAND_SLOT].Sheet, _Items[SLOTTYPE::RIGHT_HAND_SLOT].Sheet, !modeWithHiddenItems()))
{
//nlwarning("CH:computeAnimSet:%d: pb when trying to compute the animset. Sheet Id '%u(%s)'.", _Slot, _SheetId.asInt(), _SheetId.toString().c_str());
}
}// computeAnimSet //
//-----------------------------------------------
// adjustPI :
// Adjust the Predicted Interval to fix some errors according to the distance.
//-----------------------------------------------
NLMISC::TGameCycle CCharacterCL::adjustPI(float x , float y, float /* z */, const NLMISC::TGameCycle &pI)
{
NLMISC::TGameCycle adjustedPI = pI;
if(ClientCfg.RestrainPI && adjustedPI > 0)
{
double dist = (x-UserEntity->pos().x)*(x-UserEntity->pos().x) + (y-UserEntity->pos().y)*(y-UserEntity->pos().y);
// If under 50m check Predicted Interval
if(dist < 50*50)
{
NLMISC::TGameCycle maxPi = (NLMISC::TGameCycle)(sqrt(dist)/5.0)+1;
if(adjustedPI > maxPi)
{
adjustedPI = maxPi;
if(VerboseAnimSelection && _Slot == UserEntity->selection())
nlinfo("CH:updtVPPos:%d: dist'%f' PI'%d' newPI'%d'.", _Slot, sqrt(dist), pI, adjustedPI);
}
}
}
return adjustedPI;
}// adjustPI //
//-----------------------------------------------
// updateVisualPropertyPos :
// Received a new position for the entity.
// \warning Do not send position for the user
//-----------------------------------------------
void CCharacterCL::updateVisualPropertyPos(const NLMISC::TGameCycle &gameCycle, const sint64 &prop, const NLMISC::TGameCycle &pI)
{
// Check the DB entry (the warning is already done in the build method).
if(_DBEntry == 0)
return;
// Get The property 'Y'.
CCDBNodeLeaf *nodeY = dynamic_cast(_DBEntry->getNode(CLFECOMMON::PROPERTY_POSY));
if(nodeY == 0)
{
nlwarning("CH::updtVPPos:%d: Cannot find the property 'PROPERTY_POSY(%d)'.", _Slot, CLFECOMMON::PROPERTY_POSY);
return;
}
// Get The property 'Z'.
CCDBNodeLeaf *nodeZ = dynamic_cast(_DBEntry->getNode(CLFECOMMON::PROPERTY_POSZ));
if(nodeZ == 0)
{
nlwarning("CH::updtVPPos:%d: Cannot find the property 'PROPERTY_POSZ(%d)'.", _Slot, CLFECOMMON::PROPERTY_POSZ);
return;
}
// Convert Database into a Position
float x = (float)(prop)/1000.0f;
float y = (float)(nodeY->getValue64())/1000.0f;
float z = (float)(nodeZ->getValue64())/1000.0f;
#ifdef TMP_DEBUG_GUIGUI
// Theoretical Position
_TheoreticalPosition = CVectorD((double)x, (double)y, (double)z);
#endif // TMP_DEBUG_GUIGUI
// First position Managed -> set the PACS Position
if(_FirstPosManaged)
{
pacsPos(CVectorD(x, y, z));
_FirstPosManaged = false;
return;
}
// Wait for the entity to be spawned
if(_First_Pos)
return;
// Stock the position (except if this is the user mount because it's the user that control him not the server)
if( !isRiding() || _Rider != 0)
{
// Adjust the Predicted Interval to fix some "bug" into the Prediction Algo.
NLMISC::TGameCycle adjustedPI = adjustPI(x, y, z, pI);
// Add Stage.
_Stages.addStage(gameCycle, PROPERTY_POSX, prop, adjustedPI);
_Stages.addStage(gameCycle, PROPERTY_POSY, nodeY->getValue64());
_Stages.addStage(gameCycle, PROPERTY_POSZ, nodeZ->getValue64());
}
}// updateVisualPropertyPos //
//-----------------------------------------------
// updateVisualPropertyOrient :
// Received a new orientation.
//-----------------------------------------------
void CCharacterCL::updateVisualPropertyOrient(const NLMISC::TGameCycle &gameCycle, const sint64 &prop)
{
#ifdef TMP_DEBUG_GUIGUI
// Backup the last orientation received.
_TheoreticalOrientation = *(float *)(&prop);
#endif // TMP_DEBUG_GUIGUI
// New Mode Received.
if(verboseVP(this))
{
float ori = *(float *)(&prop);
nlinfo("(%05d,%03d) CH::updateVPOri:%d: '%f' received.", sint32(T1%100000), NetMngr.getCurrentServerTick(), _Slot, ori);
}
// if no skeleton we set the orientation
if(_Skeleton.empty())
{
// server forces the entity orientation even if it cannot turn
front(CVector((float)cos(_TheoreticalOrientation), (float)sin(_TheoreticalOrientation), 0.f), true, true, true);
dir(front(), false, false);
if(_Primitive)
_Primitive->setOrientation(_TheoreticalOrientation, dynamicWI);
}
else
{
if( !isRiding() || _Rider != 0 )
{
// Add in right stage.
_Stages.addStage(gameCycle, PROPERTY_ORIENTATION, prop);
}
}
}// updateVisualPropertyOrient //
//-----------------------------------------------
// updateVisualPropertyMode :
// New mode received.
// \warning For the first mode, we must have received the position and orientation (but this should be the case).
// \warning Read the position or orientation from the database when reading the mode (no more updated in updateVisualPropertyPos and updateVisualPropertyOrient).
//-----------------------------------------------
void CCharacterCL::updateVisualPropertyMode(const NLMISC::TGameCycle &gameCycle, const sint64 &prop)
{
if(verboseVP(this))
nlinfo("(%05d,%03d) CH:updtVPMode:%d: '%s(%d)' received.", sint32(T1%100000), NetMngr.getCurrentServerTick(), _Slot, modeToString((MBEHAV::EMode)prop).c_str(), (MBEHAV::EMode)prop);
// New Mode Received : Set the Theoretical Current Mode if different.
if(_TheoreticalMode != (MBEHAV::EMode)(prop & 0xffff))
_TheoreticalMode = (MBEHAV::EMode)(prop & 0xffff);
else
{
nlwarning("CH:updtVPMode:%d: The mode '%s(%d)' sent is the same as the current one.", _Slot, modeToString(_TheoreticalMode).c_str(), _TheoreticalMode);
return;
}
// If it is the first mode, set the mode.
if(_Mode == MBEHAV::UNKNOWN_MODE)
{
// SET THE FIRST POSITION
//-----------------------
// Check the DB entry (the warning is already done in the build method).
if(_DBEntry == 0)
return;
// Get The property 'PROPERTY_POSX'.
CCDBNodeLeaf *nodeX = dynamic_cast(_DBEntry->getNode(CLFECOMMON::PROPERTY_POSX));
if(nodeX == 0)
{
nlwarning("CH::updtVPMode:%d: Cannot find the property 'PROPERTY_POSX(%d)'.", _Slot, CLFECOMMON::PROPERTY_POSX);
return;
}
// Get The property 'PROPERTY_POSY'.
CCDBNodeLeaf *nodeY = dynamic_cast(_DBEntry->getNode(CLFECOMMON::PROPERTY_POSY));
if(nodeY == 0)
{
nlwarning("CH::updtVPMode:%d: Cannot find the property 'PROPERTY_POSY(%d)'.", _Slot, CLFECOMMON::PROPERTY_POSY);
return;
}
// Get The property 'PROPERTY_POSZ'.
CCDBNodeLeaf *nodeZ = dynamic_cast(_DBEntry->getNode(CLFECOMMON::PROPERTY_POSZ));
if(nodeZ == 0)
{
nlwarning("CH::updtVPMode:%d: Cannot find the property 'PROPERTY_POSZ(%d)'.", _Slot, CLFECOMMON::PROPERTY_POSZ);
return;
}
// Next position will no longer be the first one.
_First_Pos = false;
// Insert the primitive into the world.
if(_Primitive)
_Primitive->insertInWorldImage(dynamicWI);
// float makes a few cm error
double x = (double)(nodeX->getValue64())/1000.0;
double y = (double)(nodeY->getValue64())/1000.0;
double z = (double)(nodeZ->getValue64())/1000.0;
// Set the primitive position.
pacsPos(CVectorD(x, y, z));
// SET THE FIRST ORIENTATION
//--------------------------
// Get The property 'PROPERTY_ORIENTATION'.
CCDBNodeLeaf *nodeOri = dynamic_cast(_DBEntry->getNode(CLFECOMMON::PROPERTY_ORIENTATION));
if(nodeOri == 0)
{
nlwarning("CH::updtVPMode:%d: Cannot find the property 'PROPERTY_ORIENTATION(%d)'.", _Slot, CLFECOMMON::PROPERTY_ORIENTATION);
return;
}
const sint64 &ori = nodeOri->getValue64();
float angleZ = *(float *)(&ori);
// server forces the entity orientation even if it cannot turn
front(CVector((float)cos(angleZ), (float)sin(angleZ), 0.f), true, true, true);
dir(front(), false, false);
_TargetAngle = angleZ;
if(_Primitive)
_Primitive->setOrientation(angleZ, dynamicWI);
// SET THE FIRST MODE
//-------------------
// Set the mode Now
_Mode = _TheoreticalMode;
_ModeWanted = _TheoreticalMode;
if((_Mode == MBEHAV::MOUNT_NORMAL) && (_Rider == CLFECOMMON::INVALID_SLOT))
{
_Mode = MBEHAV::NORMAL;
_ModeWanted = MBEHAV::MOUNT_NORMAL;
// See also updateVisualPropertyRiderEntity() for the case when _Rider is received after the mode
computeAutomaton();
computeAnimSet();
setAnim(CAnimationStateSheet::Idle);
// Add the mode to the stage.
_Stages.addStage(gameCycle, PROPERTY_MODE, prop);
}
computeAutomaton();
computeAnimSet();
setAnim(CAnimationStateSheet::Idle);
}
// Not the first mode -> Add to a stage.
else
{
// Add the mode to the stage.
_Stages.addStage(gameCycle, PROPERTY_MODE, prop);
// Float mode push the orientation
if(_TheoreticalMode == MBEHAV::COMBAT_FLOAT)
{
// Get The property 'PROPERTY_ORIENTATION'.
CCDBNodeLeaf *nodeOri = dynamic_cast(_DBEntry->getNode(CLFECOMMON::PROPERTY_ORIENTATION));
if(nodeOri == 0)
{
nlwarning("CH::updtVPMode:%d: Cannot find the property 'PROPERTY_ORIENTATION(%d)'.", _Slot, CLFECOMMON::PROPERTY_ORIENTATION);
return;
}
_Stages.addStage(gameCycle, CLFECOMMON::PROPERTY_ORIENTATION, nodeOri->getValue64());
}
// Any other mode push the position
else
{
if(_TheoreticalMode != MBEHAV::MOUNT_NORMAL)
{
// Check the DB entry (the warning is already done in the build method).
if(_DBEntry == 0)
return;
// Get The property 'PROPERTY_POSX'.
CCDBNodeLeaf *nodeX = dynamic_cast(_DBEntry->getNode(CLFECOMMON::PROPERTY_POSX));
if(nodeX == 0)
{
nlwarning("CH::updtVPMode:%d: Cannot find the property 'PROPERTY_POSX(%d)'.", _Slot, CLFECOMMON::PROPERTY_POSX);
return;
}
// Get The property 'PROPERTY_POSY'.
CCDBNodeLeaf *nodeY = dynamic_cast(_DBEntry->getNode(CLFECOMMON::PROPERTY_POSY));
if(nodeY == 0)
{
nlwarning("CH::updtVPMode:%d: Cannot find the property 'PROPERTY_POSY(%d)'.", _Slot, CLFECOMMON::PROPERTY_POSY);
return;
}
// Get The property 'PROPERTY_POSZ'.
CCDBNodeLeaf *nodeZ = dynamic_cast(_DBEntry->getNode(CLFECOMMON::PROPERTY_POSZ));
if(nodeZ == 0)
{
nlwarning("CH::updtVPMode:%d: Cannot find the property 'PROPERTY_POSZ(%d)'.", _Slot, CLFECOMMON::PROPERTY_POSZ);
return;
}
// Add Stage.
_Stages.addStage(gameCycle, CLFECOMMON::PROPERTY_POSX, nodeX->getValue64());
_Stages.addStage(gameCycle, CLFECOMMON::PROPERTY_POSY, nodeY->getValue64());
_Stages.addStage(gameCycle, CLFECOMMON::PROPERTY_POSZ, nodeZ->getValue64());
}
}
}
}// updateVisualPropertyMode //
//-----------------------------------------------
// updateVisualPropertyBehaviour :
// New Behaviour received.
//-----------------------------------------------
void CCharacterCL::updateVisualPropertyBehaviour(const NLMISC::TGameCycle &gameCycle, const sint64 &prop)
{
// New Behaviour Received.
CBehaviour beh(prop);
if(verboseVP(this))
nlinfo("(%05d,%03d) CH::updateVPBeha:%d: '%s(%d)' received.", sint32(T1%100000), NetMngr.getCurrentServerTick(), _Slot, behaviourToString((EBehaviour)beh.Behaviour).c_str(), (sint)beh.Behaviour);
// Add in right stage.
_Stages.addStage(gameCycle, PROPERTY_BEHAVIOUR, prop);
}// updateVisualPropertyBehaviour //
void CCharacterCL::updateVisualPropertyTargetList(const NLMISC::TGameCycle &gameCycle, const sint64 &prop, uint listIndex)
{
// Add in right stage.
_Stages.addStage(gameCycle, PROPERTY_TARGET_LIST_0 + listIndex, prop);
}
void CCharacterCL::updateVisualPropertyVisualFX(const NLMISC::TGameCycle &gameCycle, const sint64 &prop)
{
_Stages.addStage(gameCycle, PROPERTY_VISUAL_FX, prop);
}
//-----------------------------------------------
// updateVisualPropertyName :
// Received the name Id.
//-----------------------------------------------
void CCharacterCL::updateVisualPropertyName(const NLMISC::TGameCycle &/* gameCycle */, const sint64 &prop)
{
// Update the entity name (do not need to be managed with LCT).
uint32 nameId = *(uint32 *)(&prop);
// Store the name Id
_NameId = nameId;
// STRING_MANAGER::CStringManagerClient::instance()->waitString(nameId, this, &_Name);
STRING_MANAGER::CStringManagerClient::instance()->waitString(nameId, this);
//if(!getEntityName().empty())
// nlwarning("CH::updateVPName:%d: name Id '%d' received but no name allocated.", _Slot, nameId);
//else if(verboseVP(this))
// nlinfo("(%05d,%03d) CH::updateVPName:%d: name '%s(%d)' received.", sint32(T1%100000), NetMngr.getCurrentServerTick(), _Slot, getEntityName().toString().c_str(), nameId);
updateMissionTarget();
}// updateVisualPropertyName //
//-----------------------------------------------
// updateVisualPropertyTarget :
// Received the new target for the entity
// \todo GUIGUI : should be added in a stage.
//-----------------------------------------------
void CCharacterCL::updateVisualPropertyTarget(const NLMISC::TGameCycle &gameCycle, const sint64 &prop)
{
// New target Received.
sint targ = (sint)prop;
if(verboseVP(this))
nlinfo("(%05d,%03d) CH::updateVPTarget:%d: '%d' received.", sint32(T1%100000), NetMngr.getCurrentServerTick(), _Slot, targ);
// New entity target
_TargetSlotNoLag = (CLFECOMMON::TCLEntityId)targ;
// Add in right stage.
_Stages.addStage(gameCycle, PROPERTY_TARGET_ID, prop);
}// updateVisualPropertyTarget //
//-----------------------------------------------
// updateVisualPropertyVpa :
// Received the new target for the entity
// \todo GUIGUI : should be added in a stage.
//-----------------------------------------------
void CCharacterCL::updateVisualPropertyVpa(const NLMISC::TGameCycle &/* gameCycle */, const sint64 &prop)
{
// VPA only useful for NPC
if(isNPC()==false)
{
//nlwarning("CH:updtVPVpa:%d: VPA received but NOT an NPC. Sheet Id '%u(%s)'.", _Slot, _SheetId.asInt(), _SheetId.toString().c_str());
return;
}
// NO SKELETON -> NO VPA
if(_Skeleton.empty())
return;
// Get the alternative look property.
SAltLookProp altLookProp = *(SAltLookProp *)(&prop);
// Display debug infos
if(verboseVP(this))
{
nlinfo("(%05d,%03d) CH:updtVPVpa:%d: TopColor(%d) BotColor(%d) RH(%d) LH(%d) Hat(%d) Seed(%d) HairColor(%d)",
sint32(T1%100000), NetMngr.getCurrentServerTick(), _Slot,
(uint)altLookProp.Element.ColorTop, (uint)altLookProp.Element.ColorBot,
(uint)altLookProp.Element.WeaponRightHand, (uint)altLookProp.Element.WeaponLeftHand,
(uint)altLookProp.Element.Hat, (uint)altLookProp.Element.Seed,
(uint)altLookProp.Element.ColorHair);
}
// Dress the character.
if(!_LookRdy)
{
// The entity is now visually ready.
_LookRdy = true;
// Generate a new random.
NLMISC::CRandom rnd;
rnd.srand((sint)altLookProp.Element.Seed);
// Retrieve the right sheet for clothes.
_ClothesSheet = _Sheet;
if(_Sheet->IdAlternativeClothes.size() > 0)
{
sint32 num = rnd.rand()%(_Sheet->IdAlternativeClothes.size()+1);
if(num > 0)
{
CSheetId altClothesId(_Sheet->getAlternativeClothes(num-1));
const CEntitySheet *sheetAlt = SheetMngr.get(altClothesId);
if(dynamic_cast(sheetAlt))
_ClothesSheet = dynamic_cast(sheetAlt);
}
}
// Eyes Color
if(_Sheet->EyesColor >= SheetMngr.nbEyesColor())
{
if (SheetMngr.nbEyesColor() == 0)
_EyesColor = 0;
else
_EyesColor = (sint8)(rnd.rand()%SheetMngr.nbEyesColor());
}
else
_EyesColor = _Sheet->EyesColor;
// Hair Color
if (SheetMngr.nbHairColor() == 0)
_HairColor = 0;
else
_HairColor = (sint8)altLookProp.Element.ColorHair%SheetMngr.nbHairColor();
// Hair Index
if(_Sheet->HairItemList.size() > 0)
{
sint32 num = rnd.rand()%_Sheet->HairItemList.size();
if(num>=0 && num <_BadHairIndex)
_HairIndex = (uint8)num;
else
nlwarning("CH:updtVPVpa:%d: Bad Hair Index '%d'", _Slot, num);
}
// -- Dress the character -- (all parts that should not change)
/** tmp : remove all fx item
* \TODO delete an item only if changed
*/
buildEquipment(_ClothesSheet->Body, SLOTTYPE::CHEST_SLOT, altLookProp.Element.ColorTop); // Chest
buildEquipment(_ClothesSheet->Arms, SLOTTYPE::ARMS_SLOT, altLookProp.Element.ColorArm); // Arms
buildEquipment(_ClothesSheet->Hands, SLOTTYPE::HANDS_SLOT, altLookProp.Element.ColorGlove); // Gloves
buildEquipment(_ClothesSheet->Legs, SLOTTYPE::LEGS_SLOT, altLookProp.Element.ColorBot); // Legs
buildEquipment(_ClothesSheet->Feet, SLOTTYPE::FEET_SLOT, altLookProp.Element.ColorBoot); // Boots
// Face
_FaceIdx = buildEquipment(_Sheet->Face, SLOTTYPE::FACE_SLOT);
// Entity is now dressed
skeleton()->show();
}
// -- Manage the Head --
// Display the helm.
if(altLookProp.Element.Hat!=0 && !_ClothesSheet->Head.getItem().empty())
{
// Create the Helm.
_HeadIdx = buildEquipment(_ClothesSheet->Head, SLOTTYPE::HEAD_SLOT, altLookProp.Element.ColorHair, _HeadIdx);
// Hide the face.
SInstanceCL *pInstFace = getFace();
if(pInstFace)
{
if(pInstFace->Current.empty() == false)
pInstFace->Current.hide();
else
pInstFace->KeepHiddenWhenLoaded = true;
}
}
// Display the Hair.
else
{
// Create the Hair.
if(_HairIndex != _BadHairIndex)
_HeadIdx = buildEquipment(_Sheet->HairItemList[_HairIndex], SLOTTYPE::HEAD_SLOT, altLookProp.Element.ColorHair, _HeadIdx);
// Display the face.
SInstanceCL *pInstFace = getFace();
if(pInstFace)
if(!pInstFace->Current.empty())
pInstFace->Current.show();
}
// -- Manage weapons -- (weapons can change)
// Right Hand
const CItemSheet *newRightHand = SheetMngr.getItem(SLOTTYPE::RIGHT_HAND_SLOT, (uint)altLookProp.Element.WeaponRightHand);
if (newRightHand != _Items[SLOTTYPE::RIGHT_HAND_SLOT].Sheet) // item changed ?
{
// Remove the old Item in the right hand
if(_RHandInstIdx != CEntityCL::BadIndex)
{
// remove shape
_RHandInstIdx = addInstance("", "", -1, _RHandInstIdx);
// remove fxs
_Items[SLOTTYPE::RIGHT_HAND_SLOT].release();
}
// set new one
_Items[SLOTTYPE::RIGHT_HAND_SLOT].Sheet = newRightHand;
if (newRightHand)
{
if( newRightHand->ItemType != ITEM_TYPE::MAGICIAN_STAFF )
_RHandInstIdx = createItemInstance(*newRightHand, _RHandInstIdx, SLOTTYPE::RIGHT_HAND_SLOT, "box_arme", -1, -1);
else
_RHandInstIdx = createItemInstance(*newRightHand, _RHandInstIdx, SLOTTYPE::RIGHT_HAND_SLOT, "", -1, -1);
}
}
// update fx for right hand (trail may have been activated, or advantage fx)
if (newRightHand)
{
SInstanceCL *instCLRH = idx2Inst(_RHandInstIdx);
if(instCLRH)
{
NL3D::UInstance itemInstance = (!instCLRH->Loading.empty()) ? instCLRH->Loading : instCLRH->Current;
if (!itemInstance.empty())
{
// update fxs
_Items[SLOTTYPE::RIGHT_HAND_SLOT].enableAdvantageFX(itemInstance);
if ( _CurrentBehaviour.Behaviour != MBEHAV::EXTRACTING )
_Items[SLOTTYPE::RIGHT_HAND_SLOT].setTrailSize(altLookProp.Element.RTrail);
}
}
}
// Left Hand
const CItemSheet *newLeftHand = SheetMngr.getItem(SLOTTYPE::LEFT_HAND_SLOT, (uint)altLookProp.Element.WeaponLeftHand);
if (newLeftHand != _Items[SLOTTYPE::LEFT_HAND_SLOT].Sheet) // item changed ?
{
// Remove the old Item in the left hand
if(_LHandInstIdx != CEntityCL::BadIndex)
{
// remove shape
_LHandInstIdx = addInstance("", "", -1, _LHandInstIdx);
// remove fxs
_Items[SLOTTYPE::LEFT_HAND_SLOT].release();
}
// set new one
_Items[SLOTTYPE::LEFT_HAND_SLOT].Sheet = newLeftHand;
if (newLeftHand)
{
string bindBone;
if(newLeftHand->getAnimSet() == "s")
bindBone = "Box_bouclier";
else
bindBone = "box_arme_gauche";
_LHandInstIdx = createItemInstance(*newLeftHand, _LHandInstIdx, SLOTTYPE::LEFT_HAND_SLOT, bindBone, -1, -1);
}
}
// update fx for left hand (trail may have been activated, or advantage fx)
if (newLeftHand)
{
SInstanceCL *instCLLH = idx2Inst(_LHandInstIdx);
if(instCLLH)
{
NL3D::UInstance itemInstance = (!instCLLH->Loading.empty()) ? instCLLH->Loading : instCLLH->Current;
if (!itemInstance.empty())
{
// update fxs
_Items[SLOTTYPE::LEFT_HAND_SLOT].enableAdvantageFX(itemInstance);
_Items[SLOTTYPE::LEFT_HAND_SLOT].setTrailSize((uint) (2 * altLookProp.Element.LTrail));
}
}
}
// -- Update Animation -- (after all those changes animation could change).
computeAnimSet();
setAnim(animState(MOVE));
}// updateVisualPropertyVpb //
//-----------------------------------------------
// updateVisualPropertyVpb :
//-----------------------------------------------
void CCharacterCL::updateVisualPropertyVpb(const NLMISC::TGameCycle &/* gameCycle */, const sint64 &prop)
{
// Get the alternative look property.
SAltLookProp2 altLookProp = *(SAltLookProp2 *)(&prop);
// Display debug infos
if(verboseVP(this))
{
nlinfo("(%05d,%03d) CH:updtVPVpb:%d: Scale(%d)", sint32(T1%100000), NetMngr.getCurrentServerTick(), _Slot,
(uint)altLookProp.PropertySubData.Scale);
}
// Save old scale
float oldCustomScale = _CustomScale;
// Set new scale
if (altLookProp.PropertySubData.Scale==0)
_CustomScale = 1.f;
else
_CustomScale = (float)altLookProp.PropertySubData.Scale/100.f;
// Apply modification
_CustomScalePos /= oldCustomScale;
_CustomScalePos *= _CustomScale;
// change the scale of the skeleton according to the new people
USkeleton * skel = skeleton();
if( skel )
{
skel->setScale(getScale(), getScale(), getScale());
// modify the stick bone scale to not propagate scale to child
sint boneID = skel->getBoneIdByName("stick_1");
if( boneID != -1 )
{
UBone bone = skel->getBone(boneID);
CVector newBoneScale = bone.getScale() * oldCustomScale/_CustomScale;
bone.setScale( newBoneScale );
}
}
// must set the new scale for the BotObject too
else if(!_Instances.empty())
{
float s= getScale();
_Instances[0].setScale(CVector(s,s,s));
}
}// updateVisualPropertyVpb //
//-----------------------------------------------
// updateVisualPropertyEntityMounted :
// Update Entity Mount
//-----------------------------------------------
void CCharacterCL::updateVisualPropertyEntityMounted(const NLMISC::TGameCycle &gameCycle, const sint64 &prop)
{
// New target Received.
sint mountSlot = (sint)prop;
_TheoreticalMount = (CLFECOMMON::TCLEntityId)mountSlot;
if(verboseVP(this))
nlinfo("(%05d,%03d) CH::updateVPMount:%d: '%d' received.", sint32(T1%100000), NetMngr.getCurrentServerTick(), _Slot, mountSlot);
// Add in right stage.
_Stages.addStage(gameCycle, CLFECOMMON::PROPERTY_ENTITY_MOUNTED_ID, prop);
}// updateVisualPropertyEntityMounted //
//-----------------------------------------------
// updateVisualPropertyRiderEntity :
// Update Entity Rider
//-----------------------------------------------
void CCharacterCL::updateVisualPropertyRiderEntity(const NLMISC::TGameCycle &gameCycle, const sint64 &prop)
{
// New target Received.
sint riderSlot = (sint)prop;
_TheoreticalRider = (CLFECOMMON::TCLEntityId)riderSlot;
if(verboseVP(this))
nlinfo("(%05d,%03d) CH::updateVPRider:%d: '%d' received.", sint32(T1%100000), NetMngr.getCurrentServerTick(), _Slot, riderSlot);
// Add in right stage.
_Stages.addStage(gameCycle, CLFECOMMON::PROPERTY_RIDER_ENTITY_ID, prop);
}// updateVisualPropertyRiderEntity //
//-----------------------------------------------
// updateVisualPropertyBars :
// Update Entity Bars
//-----------------------------------------------
void CCharacterCL::updateVisualPropertyBars(const NLMISC::TGameCycle &gameCycle, const sint64 &prop) // virtual
{
CBarManager::CBarInfo barInfo;
// Encode HP to 7 bits
barInfo.Score[SCORES::hit_points] = (sint8)((prop&0x7ff) * 127 / 1023);
// NB: barInfo are sint8, but no problem, since anything following is 7 bits.
barInfo.Score[SCORES::stamina] = (uint8)((prop>>11)&0x7f);
barInfo.Score[SCORES::sap] = (uint8)((prop>>18)&0x7f);
barInfo.Score[SCORES::focus] = (uint8)((prop>>25)&0x7f);
// update The Bar manager
CBarManager *pBM= CBarManager::getInstance();
/* ***********
WHY gameCycle+1 ????? (yoyo)
It's because sometimes I have a bug With target DB update and VP update. This is the scenario
where I suppose the problem rises:
tick=320: EGS::tickUpdate(): player.DBTargetHP.setProp(49)
tick=321: EGS::combat update, target ennemy receives a Hit, VPHp=10 => transmitted to client with timestamp=321
EGS::databaseUpdate(), DB updated, with timestamp=321!!!
Thus I receives on client:
first the VP with Hp=10, timestamp=321
second the DB with Hp=49, timestamp=321 too => replaced => BUG
NB: DB is typically sent at low frequency by FrontEnd, thus received later on client.
Since databaseUpdate() is called every 2 ticks, adding +1 to VP timestamps solve easily the problem.
NB: the problem occurs because tickUpdate() and databaseUpdate() are called typically with 1 tick shift (tickUpdate()
on even ticks, and databaseUpdate() on odd ticks for instance).
NB: moreover, tickupdate() is called every 8 (or 16) ticks, and databaseUpdate() every 2 ticks. So there is one more
possible bug:
318: EGS::tickUpdate(): player.DBTargetHP.setProp(49)
319: EGS::combat update, target ennemy receives a Hit, VPHp=10 => transmitted to client with timestamp=319
EGS::databaseUpdate(), BUT decide to send only a small subset of DB (because lot of things to send)
=> our TargetHP is not updated
320: nothing. tickupdate() is not called, since every 8 ticks
321: EGS::databaseUpdate(), update TargetHP, with timestamp=321 !!!!! => Bug
(remind that we cannot store a timestamp for each DB property, else would be too big to store and to send...)
BTW, this last bug should be very rare, so don't care.
*********** */
pBM->updateBars(dataSetId(), barInfo, gameCycle+1,
CBarManager::HpFlag | CBarManager::StaFlag | CBarManager::SapFlag | CBarManager::FocusFlag);
}// updateVisualPropertyBars //
//-----------------------------------------------
// updateVisualPropertyGuildSymbol :
//
//-----------------------------------------------
void CCharacterCL::updateVisualPropertyGuildSymbol(const NLMISC::TGameCycle &/* gameCycle */, const sint64 &prop)
{
_GuildSymbol = prop;
// maybe need to rebuild the in scene interface
if(_InSceneUserInterface && _InSceneUserInterface->needGuildSymbolId())
buildInSceneInterface();
} // updateVisualPropertyGuildSymbol //
//-----------------------------------------------
// updateVisualPropertyGuildNameID :
//
//-----------------------------------------------
void CCharacterCL::updateVisualPropertyGuildNameID(const NLMISC::TGameCycle &/* gameCycle */, const sint64 &prop)
{
_GuildNameId = uint32(prop);
// maybe need to rebuild the in scene interface
if(_InSceneUserInterface && _InSceneUserInterface->needGuildNameId())
buildInSceneInterface();
} // updateVisualPropertyGuildNameID //
//-----------------------------------------------
// updateVisualPropertyEventFactionID :
//
//-----------------------------------------------
void CCharacterCL::updateVisualPropertyEventFactionID(const NLMISC::TGameCycle &/* gameCycle */, const sint64 &prop)
{
// ODD Hack by Ulukyn
//_EventFactionId = uint32(prop);
_PvpMode = uint32(prop);
buildInSceneInterface();
if (isUser())
{
uint i;
uint numEntity = (uint)EntitiesMngr.entities().size();
for (i=0; i(entity);
if (character)
{
if( character->getPvpMode() != 0 && !character->isUser())
character->buildInSceneInterface ();
}
}
}
}
} // updateVisualPropertyEventFactionID //
//-----------------------------------------------
// updateVisualPropertyPvpMode :
//
//-----------------------------------------------
void CCharacterCL::updateVisualPropertyPvpMode(const NLMISC::TGameCycle &/* gameCycle */, const sint64 &prop)
{
//_PvpMode = uint32(prop);
//buildInSceneInterface();
} // updateVisualPropertyPvpMode //
//-----------------------------------------------
// updateVisualPropertyPvpClan :
//
//-----------------------------------------------
void CCharacterCL::updateVisualPropertyPvpClan(const NLMISC::TGameCycle &/* gameCycle */, const sint64 &prop)
{
_LeagueId = uint32(prop);
buildInSceneInterface();
if (isUser())
{
uint i;
uint numEntity = (uint)EntitiesMngr.entities().size();
for (i=0; i(entity);
if (character)
{
if( character->getPvpMode() != 0 && !character->isUser())
character->buildInSceneInterface ();
}
}
}
}
} // updateVisualPropertyPvpClan //
//-----------------------------------------------
// updateVisualPropertyStatus :
// Update Entity Status
//-----------------------------------------------
void CCharacterCL::updateVisualPropertyStatus(const NLMISC::TGameCycle &/* gameCycle */, const sint64 &/* prop */) // virtual
{
nlinfo("CH:updtVPStatus:%d: received.", _Slot);
}// updateVisualPropertyStatus //
//-----------------------------------------------
// updateVisualPropertyContextual :
// Update Entity Status
//-----------------------------------------------
void CCharacterCL::updateVisualPropertyContextual(const NLMISC::TGameCycle &gameCycle, const sint64 &prop)
{
bool precAttackable= _Properties.attackable();
// call parent
CEntityCL::updateVisualPropertyContextual(gameCycle, prop);
// if attack modified, and npc/fauna, must rebuild the in scene interface,
// cause sheets 'Attackable' property not always correclty filled
if( (isNPC()||isFauna()) && precAttackable!=_Properties.attackable())
buildInSceneInterface();
}
//-----------------------------------------------
// updateVisualPropertyOwnerPeople :
//
//-----------------------------------------------
void CCharacterCL::updateVisualPropertyOwnerPeople(const NLMISC::TGameCycle &/* gameCycle */, const sint64 &prop)
{
if( _OwnerPeople != MOUNT_PEOPLE::TMountPeople(prop) )
{
// reset scale pos
float oldPeopleScaleFactor;
switch( _OwnerPeople )
{
case MOUNT_PEOPLE::Fyros : oldPeopleScaleFactor = ClientCfg.FyrosScale; break;
case MOUNT_PEOPLE::Matis : oldPeopleScaleFactor = ClientCfg.MatisScale; break;
case MOUNT_PEOPLE::Tryker : oldPeopleScaleFactor = ClientCfg.TrykerScale; break;
case MOUNT_PEOPLE::Zorai : oldPeopleScaleFactor = ClientCfg.ZoraiScale; break;
default:
oldPeopleScaleFactor = 1.f;
}
_CustomScalePos /= oldPeopleScaleFactor;
// set the new scale pos
float newPeopleScaleFactor;
_OwnerPeople = MOUNT_PEOPLE::TMountPeople(prop);
switch( _OwnerPeople )
{
case MOUNT_PEOPLE::Fyros : newPeopleScaleFactor = ClientCfg.FyrosScale; break;
case MOUNT_PEOPLE::Matis : newPeopleScaleFactor = ClientCfg.MatisScale; break;
case MOUNT_PEOPLE::Tryker : newPeopleScaleFactor = ClientCfg.TrykerScale; break;
case MOUNT_PEOPLE::Zorai : newPeopleScaleFactor = ClientCfg.ZoraiScale; break;
default:
newPeopleScaleFactor = 1.f;
}
_CustomScalePos *= newPeopleScaleFactor;
// change the scale of the skeleton according to the new people
USkeleton * skel = skeleton();
if( skel )
{
skel->setScale(getScale(), getScale(), getScale());
// modify the stick bone scale to not propagate scale to child
sint boneID = skel->getBoneIdByName("stick_1");
if( boneID != -1 )
{
UBone bone = skel->getBone(boneID);
CVector newBoneScale = bone.getScale() * oldPeopleScaleFactor/newPeopleScaleFactor;
bone.setScale( newBoneScale );
}
}
}
} // updateVisualPropertyOwnerPeople //
//-----------------------------------------------
// updateVisualPropertyOutpostInfos :
//
//-----------------------------------------------
void CCharacterCL::updateVisualPropertyOutpostInfos(const NLMISC::TGameCycle &/* gameCycle */, const sint64 &prop)
{
_OutpostId = ((uint16)prop)&0x7FFF;
uint16 side = (((uint16)prop)&0x8000)>>15;
_OutpostSide = (OUTPOSTENUMS::TPVPSide)side;
nldebug(" prop = %d, id=%d side=%d",(uint16)prop,_OutpostId,_OutpostSide);
buildInSceneInterface();
} // updateVisualPropertyOutpostInfos //
//-----------------------------------------------
// skin :
// Get The Entity Skin
//-----------------------------------------------
sint CCharacterCL::skin() const // virtual
{
return _Sheet->Skin;
}// skin //
//-----------------------------------------------
// initProperties :
// Initialize properties of the entity (according to the class).
//-----------------------------------------------
void CCharacterCL::initProperties()
{
properties().selectable(_Sheet->Selectable);
properties().talkableTo(_Sheet->Talkable);
properties().attackable(_Sheet->Attackable);
properties().givable(_Sheet->Givable);
properties().mountable(_Sheet->Mountable);
properties().invitable(false); // You cannot group with a bot.
properties().afk(false);
switch(_Sheet->HLState)
{
case LHSTATE::LOOTABLE:
properties().lootable(true); // You can loot the creature
properties().harvestable(false); // You cannot harvest the creature
break;
case LHSTATE::HARVESTABLE:
properties().lootable(false); // You cannot loot the creature
properties().harvestable(true); // You can harvest the creature
break;
case LHSTATE::LOOTABLE_HARVESTABLE:
properties().lootable(true); // You can loot the creature
properties().harvestable(true); // You can harvest the creature
break;
default:
properties().lootable(false); // You cannot loot the creature
properties().harvestable(false); // You cannot harvest the creature
break;
}
}// initProperties //
//-----------------------------------------------
// computeTimeStep :
// Compute the elapsed time since last call.
// \param currentTime : current time in sec.
// \return double : elapsed time.
//-----------------------------------------------
double CCharacterCL::computeTimeStep(const double ¤tTime)
{
// Last Time greater than Current Time.
if(_LastFrameTime > currentTime)
{
nlwarning("CCharacterCL::computeTimeStep : Time since last frame is negative (%f). Set _LastFrameTime with currentTime", _LastFrameTime - currentTime);
_LastFrameTime = currentTime;
}
// Time since last Time >= 0
return currentTime - _LastFrameTime;
}// computeTimeStep //
//-----------------------------------------------
// computeSpeed :
// \todo GUIGUI : to do an average speed, there is a problem if time become very small because small frame or _LastFrameTime increase when looping
//-----------------------------------------------
double CCharacterCL::computeSpeed()
{
double spd;
double t = _DestTime - _LastFrameTime;
if(dist2Dest() <= 0.0) // Already at destination.
spd = 0.0;
else if(_LastFrameTime == 0.0) // First frame
spd = 1.0;
else if(t < 0.0001) // We should already be at destination.
spd = 1000.0;//-1.0;
else
{
spd = dist2Dest()/t;
if(spd>0 && spd<0.001)
spd = 0.001;
if(spd > 1000.0)
spd = 1000.0;
}
speed(spd);
return spd;
}// computeSpeed //
//-----------------------------------------------
// computeSpeedFactor :
// Compute and return the speed factor to apply to the animation.
// \param speedToDest : evaluted speed to destination.
// \return double : the speed factor to use for the current animation.
// \todo GUIGUI : review this scale problem and optimize it.
//-----------------------------------------------
double CCharacterCL::computeSpeedFactor(double speedToDest)
{
double speedFactor = 1.0;
// \todo GUIGUI : optimize emotes, currently it's badly designed.
const CAnimationState *animStatePtr;
// If the current animation is an emote, get the right animation state.
if(animState(MOVE) == CAnimationStateSheet::Emote)
animStatePtr = _CurrentAnimSet[MOVE]->getAnimationState(_SubStateKey);
else
animStatePtr = _CurrentAnimSet[MOVE]->getAnimationState(animState(MOVE));
if(animStatePtr)
{
// Get the animation
const CAnimation *anim = animStatePtr->getAnimation(animIndex(MOVE));
if(anim)
{
// If the animation is a move (not idle, turn, etc.).
if(_CurrentState)
{
// Move : adjust speed factor according to the animation.
if(_CurrentState->Move)
{
// Check for oo speed.
if(speedToDest != -1)
{
// Compute the animation average speed according to the scale.
double animSpeed = EAM->getAnimationAverageSpeed(anim->id())*getSheetScale()*_CustomScalePos*_CharacterScalePos;
if(animSpeed > 0.0)
speedFactor = speedToDest / animSpeed;
else
nlwarning("The animation is a move but animation speed is %f !", animSpeed);
}
// \todo GUIGUI : unlimited speed, perhaps return a special value.
// We should be arrived so speed is maximum.
else
speedFactor = 1000.0;
}
// The current animation is a rotation so adjust the rotation speed according to the angle.
if(_CurrentState->Rotation && _RotationFactor > 0.0)
{
speedFactor /= _RotationFactor;
speedFactor = std::min(speedFactor, 1.5);
speedFactor = std::max(speedFactor, 0.5);
}
// This animation must be play in a maximum time when there is someting to do after.
// (must be done after the rotation speed adjustment)
if(_CurrentState->MaxAnimDuration > 0.00f)
{
if(dist2Dest() >= 0.0)
{
double animLength = EAM->getAnimationLength(anim->id());
double speedFactorBackup = speedFactor;
speedFactor = animLength/_CurrentState->MaxAnimDuration;
// If the animation speed should have been greater, let it play faster.
if(speedFactor < speedFactorBackup)
speedFactor = speedFactorBackup;
}
}
// Panic mode (too late => accelerate)
if(!_CurrentState->Move && _ImportantStepTime!=0.0)
{
const float beginPanic= 1.5f; // start panic when too late of 1.5 seconds
const float endPanic= 5.f; // max panic factor at 5 seconds of interval
const float maxPanicSpeedFactor= 10.f;
float t= float(TimeInSec - _ImportantStepTime);
if(t>beginPanic)
{
float lerp= (t-beginPanic)/(endPanic-beginPanic);
clamp(lerp, 0.f, 1.f);
float panicSF= lerp*maxPanicSpeedFactor;
if(panicSF>speedFactor)
speedFactor= panicSF;
}
}
// Special panic mode because there is a position just after
// NB: don't accelerate animations that can be breaked because of move
// But still allow acceleration for mode animation transition (_Mode!=_ModeWanted)
if( !_CurrentState->Move && _RunStartTimeNoPop!=INVALID_TIME &&
(!_CurrentState->BreakableOnMove || _Mode!=_ModeWanted) )
{
const float maxPanicSpeedFactor= 1.5f;
// compare time of rest of animation, to remain time to the first move
float remainAnimTime= float(EAM->getAnimationLength(anim->id()) - animOffset(MOVE));
remainAnimTime= max(remainAnimTime, 0.f);
float panicSF;
// if already too late, then maximize speed
if(TimeInSec>=_RunStartTimeNoPop)
{
panicSF= maxPanicSpeedFactor;
}
// else target the animation speed so it ends at estimated start of move
else
{
panicSF= float(remainAnimTime/(_RunStartTimeNoPop-TimeInSec));
panicSF= min(panicSF, maxPanicSpeedFactor);
}
// only if greater than prec
if(panicSF>speedFactor)
speedFactor= panicSF;
}
// TestYoyo
/*
if(_Slot==WatchedEntitySlot && EntitiesMngr.isLogingStageChange())
{
sint64 refLT= EntitiesMngr.getLogStageChangeStartLocalTime();
NLMISC::createDebug();
NLMISC::DebugLog->displayRawNL("** Entity %d: (t=%3d) animState %s: %.2f/%.2f. move: %d. rstnp: %d. sf: %.2f",
(sint32)_Slot, sint32(T1-refLT),
CAnimationState::getAnimationStateName(animStatePtr->state()).c_str(),
float(animOffset(MOVE)), float(EAM->getAnimationLength(anim->id())),
uint(_CurrentState->Move), _RunStartTimeNoPop==INVALID_TIME?-1:sint32(sint64(_RunStartTimeNoPop*1000)-refLT),
speedFactor
);
}
*/
}
}
}
// \todo GUIGUI : corriger pour enlever le speed factor min.
// Adjuste speed factor
if(speedFactor < 0.5)
speedFactor = 0.5;
// Check negative or null speed factor.
if(speedFactor <= 0.0)
{
nlwarning("CCharacterCL::computeSpeedFactor: speedFactor = %f and this should never be <= 0.", speedFactor);
speedFactor = 1.0;
}
// Return the speed factor.
return speedFactor;
}// computeSpeedFactor //
//-----------------------------------------------
// endAnimTransition :
// Call it at the end of the current animation to choose the next one.
//-----------------------------------------------
void CCharacterCL::endAnimTransition()
{
// One more animation played.
_NbLoopAnim++;
// Hide the entity if needed.
if(_HideSkin)
hideSkin();
// Debug Animation for the selection
if(VerboseAnimSelection && _Slot == UserEntity->selection())
nlinfo("CH:endAnimTransition:%d: current animation finished.", _Slot);
// If the animation is a rotation, set the character direction he should have at the end of the animation.
if(_CurrentState->Rotation)
{
if(isUser())
{
nldebug(" rotation : set dir as end anim dir");
}
dir(dirEndAnim());
}
// Fit the current direction to the target when attacking.
if(_CurrentState->Attack)
{
dir(front());
}
// If user is in range attack and not moving, set dir to target
if( isUser() )
{
if(_CurrentBehaviour.Behaviour == MBEHAV::RANGE_ATTACK && _Mode==MBEHAV::COMBAT && !UserControls.isMoving())
{
dir( dirToTarget() );
float frontYawBefore = frontYaw();
front(dir());
float frontYawAfter = frontYaw();
float deltaYaw= frontYawAfter - frontYawBefore;
UserControls.appendCameraDeltaYaw(-deltaYaw);
if( ClientCfg.AutomaticCamera )
{
UserControls.resetSmoothCameraDeltaYaw();
}
}
}
// If the next mode in the automaton != Current Mode
if(_CurrentState->NextMode != _Mode)
{
// Undo previous behaviour
if (_Mode == MBEHAV::DEATH)
{
// Restore collisions.
if (_Primitive)
{
// TODO: Without this dynamic cast
if (dynamic_cast(this))
_Primitive->setOcclusionMask(MaskColPlayer);
else
_Primitive->setOcclusionMask(MaskColNpc);
}
}
if (ClientCfg.UsePACSForAll && _Primitive)
_Primitive->setCollisionMask(MaskColNone);
//// ADDED ////
switch(_CurrentState->NextMode)
{
// Combat
case MBEHAV::COMBAT:
case MBEHAV::COMBAT_FLOAT:
if(ClientCfg.UsePACSForAll && _Primitive)
_Primitive->setCollisionMask(MaskColPlayer | MaskColNpc | MaskColDoor); // Collision with player and npc.
break;
// Mount
case MBEHAV::MOUNT_NORMAL:
parent(_Mount);
// Remove collisions if needed.
if(_Mount != CLFECOMMON::INVALID_SLOT)
{
if(_Primitive)
_Primitive->setOcclusionMask(MaskColNone);
}
break;
// Death
case MBEHAV::DEATH:
setDead();
break;
// UNKNOWN
case MBEHAV::UNKNOWN_MODE:
if(ClientCfg.Check)
nlstop;
// NO BREAK is Normal here
// Normal
case MBEHAV::NORMAL:
// _Mount = CLFECOMMON::INVALID_SLOT;
// _Rider = CLFECOMMON::INVALID_SLOT;
parent(CLFECOMMON::INVALID_SLOT);
dir(front());
/*
front(CVector(1.f, 0.f, 0.f));
dir(front());
*/
break;
default:
break;
}
// Change the current mode.
if ( _ModeWanted != MBEHAV::UNKNOWN_MODE )
{
_Mode = _CurrentState->NextMode;
}
//else
// nlinfo( "NO mode change from %u to %u, %s S%hu", _Mode, _CurrentState->NextMode, _SheetId.toString().c_str(), _Slot );
// Change the current automaton.
computeAutomaton();
// Update the animation set according to the new automaton.
computeAnimSet();
}
// Select the Default Next Animation.
setAnim(_CurrentState->NextState);
}// endAnimTransition //
//-----------------------------------------------
// onMove :
//-----------------------------------------------
CCharacterCL::TOnMove CCharacterCL::onMove(const CAutomatonStateSheet &curAnimState)
{
// Animation is breakable if the distance to destination is long enough (at least > 0).
if(curAnimState.BreakableOnMove && dist2Dest()>0.0)
{
// \todo GUIGUI : take the next position to current one (it could be possible this position was the same as the first).
CVectorD dirToFirstPos = _FirstPos-pos();
dirToFirstPos.z = 0.0;
if(dirToFirstPos != CVectorD::Null)
dirToFirstPos.normalize();
else
{
nlwarning("CH:onMove:%d: First pos == pos -> take the dir(%f,%f,%f) instead.", _Slot, dir().x, dir().y, dir().z);
dirToFirstPos = dir();
}
// Compute Angle between the front and the first position.
double angToDest = computeShortestAngle(atan2(front().y, front().x), atan2(dirToFirstPos.y, dirToFirstPos.x));
if(!_MaxLoop)
{
// Strafe Left
if(curAnimState.OnMoveLeft != CAnimationStateSheet::UnknownState)
if((angToDest>Pi/3.0) && (angToDest<2.0*Pi/3.0))
return OnMoveLeft;
// Strafe Right
if(curAnimState.OnMoveRight != CAnimationStateSheet::UnknownState)
if((angToDest<-Pi/3.0) && (angToDest>-2.0*Pi/3.0))
return OnMoveRight;
// Backward
if(curAnimState.OnMoveBackward != CAnimationStateSheet::UnknownState)
if(fabs(angToDest)>1.92)
return OnMoveBackward;
}
// Forward (default)
if(curAnimState.OnMoveForward != CAnimationStateSheet::UnknownState)
{
// if(_MaxLoop || fabs(angToDest)<=1.92)
return OnMoveForward;
}
else
nlwarning("CH:onMove:%d: the current state is breakable on Move and the dist to dest is not Null, but there is no animation to move.", _Slot);
}
// No Move
return OnMoveNone;
}// onMove //
//-----------------------------------------------
// onRotation :
//-----------------------------------------------
CCharacterCL::TOnRot CCharacterCL::onRotation(const CAutomatonStateSheet &curAnimState, CVector &dirEndA)
{
// Turn and About Face
if(curAnimState.BreakableOnRotation)
{
CVector bodyDir = dir();
CVector destDir = front();
// when user is attacking, his dest is his target
if( isUser() && _CurrentBehaviour.Behaviour == MBEHAV::RANGE_ATTACK && _Mode==MBEHAV::COMBAT && !UserControls.isMoving() )
{
destDir = dirToTarget();
}
// Compute the angle between the current heading and computed heading.
double angToDest = computeShortestAngle(atan2(bodyDir.y, bodyDir.x), atan2(destDir.y, destDir.x));
// Rotation to the left.
if(angToDest >= (ClientCfg.AnimatedAngleThreshold*Pi/180.0))
{
dirEndA = destDir;
return OnRotLeft;
}
// Rotation to the right.
if(angToDest <= -(ClientCfg.AnimatedAngleThreshold*Pi/180.0))
{
dirEndA = destDir;
return OnRotRight;
}
}
// No Rot
return OnRotNone;
}// onRotation //
//-----------------------------------------------
// onBigBend :
//-----------------------------------------------
CCharacterCL::TOnBigBend CCharacterCL::onBigBend(const CAutomatonStateSheet &curAnimState, CVector &dirEndA)
{
// If the current direction is too different of the direction to the first destination -> play a rotation.
if(curAnimState.BrkOnBigBend)
{
CVector dirToFirstPos;
if(setVect(dirToFirstPos, _FirstPos - pos(), true, false))
{
dirToFirstPos = curAnimState.getMatrix()*dirToFirstPos;
double angToDest = computeShortestAngle(atan2(dir().y, dir().x), atan2(dirToFirstPos.y, dirToFirstPos.x));
// Rotation to the left.
if(angToDest >= 1.5)
{
dirEndA = dirToFirstPos;
return OnBendLeft;
}
// Rotation to the right.
if(angToDest <= -1.5)
{
dirEndA = dirToFirstPos;
return OnBendRight;
}
// Smooth dir
if(fabs(angToDest) > 0.1)
{
double newAngle = atan2(dir().y, dir().x) + angToDest/2.0;
dir(CVector((float)cos(newAngle), (float)sin(newAngle), dir().z));
}
else
dir(dirToFirstPos);
}
}
// No Bend
return OnBendNone;
}// onBigBend //
//-----------------------------------------------
// onBadHeading :
//-----------------------------------------------
bool CCharacterCL::onBadHeading(const CAutomatonStateSheet &curAnimState)
{
// Bad Heading
if(curAnimState.BreakableOnBadHeading && !_MaxLoop)
{
CVector dirToFirstPos;
if(setVect(dirToFirstPos, _FirstPos - pos(), true, false))
{
// Compute the angle between the front and the next body direction.
double angToDest = computeShortestAngle(atan2(front().y, front().x), atan2(dirToFirstPos.y, dirToFirstPos.x));
if(curAnimState.BadHeadingMin <= curAnimState.BadHeadingMax)
{
if((angToDestcurAnimState.BadHeadingMax))
return true;
}
else
{
if((angToDestcurAnimState.BadHeadingMax))
return true;
}
}
}
// No bad Heading
return false;
}// onBadHeading //
//-----------------------------------------------
// setAnim :
// Select a new animation for the entity.
// \todo GUIGUI : better manage when there is no skeleton
// \todo GUIGUI : simplify head control
//-----------------------------------------------
ADD_METHOD(void CCharacterCL::setAnim(TAnimStateKey newKey, TAnimStateKey subKey, uint animID))
if(ClientCfg.Light)
return;
// Debug Animation for the target
if(VerboseAnimSelection && _Slot == UserEntity->selection())
nlinfo("CH:setAnim:%d: state '%s'.", _Slot, CAnimationState::getAnimationStateName (newKey).c_str());
// RELEASE THE OLD ANIMATION
TAnimStateId lastAnimStateId = animState(MOVE); // Bkup the current anim state Id.
// \todo GUIGUI : Faire une fonction pour savoir si on a changer de type d'animation.
// Reset loop counter
if((newKey != animState(MOVE)) || (_OldAutomaton != _CurrentAutomaton))
_NbLoopAnim = 0;
// RESET THE CURRENT ANIMATION
_HideSkin = false;
CAnimation::TAnimId buIndex = animIndex(MOVE);
animIndex (MOVE, CAnimation::UnknownAnim); // Reset the current animation Index
animState (MOVE, CAnimationStateSheet::UnknownState); // Reset the current animation state
animOffset(MOVE, 0.0); // Reset the current animation offset
animIndex (MOVE_BLEND_OUT, CAnimation::UnknownAnim); // Reset the current animation Index
animState (MOVE_BLEND_OUT, CAnimationStateSheet::UnknownState); // Reset the current animation state
animOffset(MOVE_BLEND_OUT, 0.0); // Reset the current animation offset
_RotationFactor = 1.0; // New animation -> default rotation factor for the time.
_RightFXActivated = false;
_LeftFXActivated = false;
_RotationFactor = 1.0f; // Rotation factor as the animation for the time.
_SubStateKey = CAnimationStateSheet::UnknownState; // No SubStateKey for the time.
dirEndAnim(dir()); // For the time the direction at the end of the animation is the current direction.
// CHECK
// If there is no animation set ->There is nothing we can do properly.
if(_CurrentAnimSet[MOVE] == 0)
return;
// INITIALIZE
if(!animationStateKey(MOVE, newKey))
{
nlwarning("CH:setAnim:%d: automaton '%s': animation state key '%s' asked is not valid -> trying Idle.", _Slot, _CurrentAutomaton.c_str(), CAnimationState::getAnimationStateName (newKey).c_str());
if(!animationStateKey(MOVE, CAnimationStateSheet::Idle))
nlwarning("CH:setAnim:%d: Idle is not warking too", _Slot);
}
// Compute Speed
double speedToDest = computeSpeed();
// Compute the direction to the first position (direction = front if there is not first pos)
CVector dirToFirstPos;
if(dist2FirstPos() > 0.0)
dirToFirstPos = (_FirstPos - pos()).normed();
else
dirToFirstPos = front();
uint antiFreezeCounter = 0;
// Loop until the process find a steady state.
KeyChosen:
// Get the state for the current animation.
const CAutomatonStateSheet *state = EAM->mState(_CurrentAutomaton, animState(MOVE));
if(state == 0)
{
nlwarning("CH:setAnim:%d: State '%s' not in the automaton '%s'.", _Slot, CAnimationStateSheet::getAnimationStateName(animState(MOVE)).c_str(), _CurrentAutomaton.c_str());
// No animation playing
if(_PlayList)
_PlayList->setAnimation(MOVE, UPlayList::empty);
return;
}
const CAutomatonStateSheet &curAnimState = *state;
//--------------------//
//--------------------//
// ANTI-FREEZE SYSTEM //
// If too many loop, display some infos
if(antiFreezeCounter > 10)
{
/*
nlwarning("CH:setAnim:anitFreeze:%d: Automaton '%s'", _Slot, _CurrentAutomaton.c_str());
nlwarning("CH:setAnim:anitFreeze:%d: _IsThereAMode '%s'", _Slot, _IsThereAMode?"true":"false");
nlwarning("CH:setAnim:anitFreeze:%d: dist2Dest '%f'", _Slot, dist2Dest());
nlwarning("CH:setAnim:anitFreeze:%d: Mode '%s(%d)'", _Slot, modeToString(_Mode).c_str(), _Mode);
nlwarning("CH:setAnim:anitFreeze:%d: Mode Wanted '%s(%d)'", _Slot, modeToString(_ModeWanted).c_str(), _ModeWanted);
nlwarning("CH:setAnim:anitFreeze:%d: Anim State Move '%s(%d)'", _Slot, CAnimationStateSheet::getAnimationStateName(animState(MOVE)).c_str(), animState(MOVE));
*/
// Once too many more time reached, leave the method.
if(antiFreezeCounter > 20)
return;
}
// Update antiFreezeCounter.
++antiFreezeCounter;
// ANTI-FREEZE SYSTEM //
//--------------------//
//--------------------//
// Is there a mode in the queue
// if(_IsThereAMode && (dist2Dest()==INVALID_DIST))
{
// Is the current mode not already the mode wanted.
if((_Mode != _ModeWanted) && (dist2Dest()==INVALID_DIST))
{
TAnimStateKey transition;
// Check Default Mode Connection.
if(curAnimState.getModeConnection(_ModeWanted, transition))
{
// Mode Mount need to be synchro
if(_ModeWanted == MBEHAV::MOUNT_NORMAL || _ModeWanted == MBEHAV::MOUNT_SWIM)
{
// The Mount
if(_Rider != CLFECOMMON::INVALID_SLOT)
{
if(animationStateKey(MOVE, transition))
goto KeyChosen;
else
nlwarning("CH:setAnim:%d: Mode Wanted '%d' : automaton '%s': state '%s': Transition '%s' is not valid.", _Slot, _ModeWanted, _CurrentAutomaton.c_str(), CAnimationState::getAnimationStateName (curAnimState.MoveState).c_str(), CAnimationState::getAnimationStateName (transition).c_str());
}
// The Rider
else if(_Mount != CLFECOMMON::INVALID_SLOT)
{
CEntityCL *mountTmp = EntitiesMngr.entity(_Mount);
CCharacterCL *mount = dynamic_cast(mountTmp);
if(mount)
{
if(mountTmp->mode() == _ModeWanted)
{
if(animationStateKey(MOVE, transition))
goto KeyChosen;
else
{
nlwarning("CH:setAnim:%d: automaton '%s': state '%s': MountDefaultModeTransition '%s' is not valid.", _Slot, _CurrentAutomaton.c_str(), CAnimationState::getAnimationStateName (curAnimState.MoveState).c_str(), CAnimationState::getAnimationStateName (transition).c_str());
}
}
}
else
{
nlwarning("CH:setAnim:%d: Mode Wanted '%s' but the mount does not exist.", _Slot, MBEHAV::modeToString(_ModeWanted).c_str());
}
}
}
// Other modes have the same code.
else
{
if(animationStateKey(MOVE, transition))
goto KeyChosen;
else
nlwarning("CH:setAnim:%d: Mode Wanted '%d' : automaton '%s': state '%s': Transition '%s' is not valid.", _Slot, _ModeWanted, _CurrentAutomaton.c_str(), CAnimationState::getAnimationStateName (curAnimState.MoveState).c_str(), CAnimationState::getAnimationStateName (transition).c_str());
}
}
}
}
// Should we stop the animation once at destination.
if(curAnimState.BrkAtDest && (dist2Dest() <= ClientCfg.DestThreshold))
{
_MaxLoop = false;
if(animationStateKey(MOVE, CAnimationStateSheet::Idle))
goto KeyChosen;
else
nlwarning("CH:setAnim:%d: automaton '%s': state '%s': BrkAtDest 'idle' is not valid.", _Slot, _CurrentAutomaton.c_str(), CAnimationState::getAnimationStateName (curAnimState.MoveState).c_str());
}
// On Move
switch(onMove(curAnimState))
{
// On Move Forward
case OnMoveForward:
if(animationStateKey(MOVE, curAnimState.OnMoveForward))
goto KeyChosen;
else
nlwarning("CH::setAnim:%d: automaton '%s': state '%s': OnMoveForeward '%s' is not valid.", _Slot, _CurrentAutomaton.c_str(), CAnimationState::getAnimationStateName(curAnimState.MoveState).c_str(), CAnimationState::getAnimationStateName(curAnimState.OnMoveForward).c_str());
break;
// On Move Backward
case OnMoveBackward:
if(animationStateKey(MOVE, curAnimState.OnMoveBackward))
goto KeyChosen;
else
nlwarning("CH::setAnim:%d: automaton '%s': state '%s': OnMoveBackward '%s' is not valid.", _Slot, _CurrentAutomaton.c_str(), CAnimationState::getAnimationStateName(curAnimState.MoveState).c_str(), CAnimationState::getAnimationStateName(curAnimState.OnMoveBackward).c_str());
break;
// On Move Left
case OnMoveLeft:
if(animationStateKey(MOVE, curAnimState.OnMoveLeft))
goto KeyChosen;
else
nlwarning("CH::setAnim:%d: automaton '%s': state '%s': OnMoveLeft '%s' is not valid.", _Slot, _CurrentAutomaton.c_str(), CAnimationState::getAnimationStateName(curAnimState.MoveState).c_str(), CAnimationState::getAnimationStateName(curAnimState.OnMoveLeft).c_str());
break;
// On Move Right
case OnMoveRight:
if(animationStateKey(MOVE, curAnimState.OnMoveRight))
goto KeyChosen;
else
nlwarning("CH::setAnim:%d: automaton '%s': state '%s': OnMoveRight '%s' is not valid.", _Slot, _CurrentAutomaton.c_str(), CAnimationState::getAnimationStateName(curAnimState.MoveState).c_str(), CAnimationState::getAnimationStateName(curAnimState.OnMoveRight).c_str());
break;
default:
break;
}
// On Rotation/About Face
CVector dirEndA(0.0f, 0.0f, 0.0f);
switch(onRotation(curAnimState, dirEndA))
{
// On Rot Left
case OnRotLeft:
if(animationStateKey(MOVE, curAnimState.OnLeftRotation))
{
dirEndAnim(dirEndA);
goto KeyChosen;
}
else
nlwarning("CH::setAnim:%d: automaton '%s': state '%s': OnLeftRotation '%s' is not valid.", _Slot, _CurrentAutomaton.c_str(), CAnimationState::getAnimationStateName(curAnimState.MoveState).c_str(), CAnimationState::getAnimationStateName(curAnimState.OnLeftRotation).c_str());
break;
// On Rot Right
case OnRotRight:
if(animationStateKey(MOVE, curAnimState.OnRightRotation))
{
dirEndAnim(dirEndA);
goto KeyChosen;
}
else
nlwarning("CH::setAnim:%d: automaton '%s': state '%s': OnRightRotation '%s' is not valid.", _Slot, _CurrentAutomaton.c_str(), CAnimationState::getAnimationStateName(curAnimState.MoveState).c_str(), CAnimationState::getAnimationStateName(curAnimState.OnRightRotation).c_str());
break;
default:
break;
}
// Max Loop
if(curAnimState.MaxLoop && curAnimState.MaxLoop<=_NbLoopAnim)
{
if(animationStateKey(MOVE, CAnimationStateSheet::Idle))
{
_MaxLoop = true;
_NbLoopAnim = 0;
goto KeyChosen;
}
}
// On Bad Heading
if(onBadHeading(curAnimState))
{
if(animationStateKey(MOVE, CAnimationStateSheet::Idle))
goto KeyChosen;
else
nlwarning("CH::setAnim:%d: automaton '%s': state '%s': 'Idle' is not valid.", _Slot, _CurrentAutomaton.c_str(), CAnimationState::getAnimationStateName(curAnimState.MoveState).c_str());
}
// On Big Bend
switch(onBigBend(curAnimState, dirEndA))
{
// On Bend Left
case OnBendLeft:
if(animationStateKey(MOVE, curAnimState.OnBigBendLeft))
{
dirEndAnim(dirEndA);
goto KeyChosen;
}
else
nlwarning("CH::setAnim:%d: automaton '%s': state '%s': OnBigBendLeft '%s' is not valid.", _Slot, _CurrentAutomaton.c_str(), CAnimationState::getAnimationStateName(curAnimState.MoveState).c_str(), CAnimationState::getAnimationStateName(curAnimState.OnBigBendLeft).c_str());
break;
// On Bend Right
case OnBendRight:
if(animationStateKey(MOVE, curAnimState.OnBigBendRight))
{
dirEndAnim(dirEndA);
goto KeyChosen;
}
else
nlwarning("CH::setAnim:%d: automaton '%s': state '%s': OnBigBendRight '%s' is not valid.", _Slot, _CurrentAutomaton.c_str(), CAnimationState::getAnimationStateName(curAnimState.MoveState).c_str(), CAnimationState::getAnimationStateName(curAnimState.OnBigBendRight).c_str());
break;
default:
break;
}
// If the animation change according to a high speed and speed high enough or oo.
if(ClientCfg.BlendForward == false)
{
if(curAnimState.OnMaxSpeed.Breakable)
{
if(speedToDest >= _CurrentAnimSet[MOVE]->speedToRun() || speedToDest == -1.0)
{
if(animationStateKey(MOVE, curAnimState.OnMaxSpeed.NextStateKey))
goto KeyChosen;
else
nlwarning("CH::setAnim: Char '%d': automaton '%s': state '%s': OnMaxSpeed.NextStateKey '%s' is not valid.", _Slot, _CurrentAutomaton.c_str(), CAnimationState::getAnimationStateName (curAnimState.MoveState).c_str(), CAnimationState::getAnimationStateName (curAnimState.OnMaxSpeed.NextStateKey).c_str());
}
}
}
// If the animation can change with a low speed.
if(curAnimState.OnMinSpeed.Breakable)
{
// If the speed is low enough (check this is not -1 (infinite speed)) -> update the animation to play.
if(speedToDest != -1.0 && speedToDest <= _CurrentAnimSet[MOVE]->speedToWalk())
{
if(animationStateKey(MOVE, curAnimState.OnMinSpeed.NextStateKey))
goto KeyChosen;
else
nlwarning("CH::setAnim: Char '%d': automaton '%s': state '%s': OnMinSpeed.NextStateKey '%s' is not valid.", _Slot, _CurrentAutomaton.c_str(), CAnimationState::getAnimationStateName (curAnimState.MoveState).c_str(), CAnimationState::getAnimationStateName (curAnimState.OnMinSpeed.NextStateKey).c_str());
}
}
//// END LOOP /////
// \todo GUIGUI : better manage automate change
// Current animation is not of the same kind as the old one.
bool sameAnim = lastAnimStateId == animState(MOVE) && _OldAutomaton == _CurrentAutomaton;
if(!sameAnim)
{
// Stop FXs.
stopItemAttackFXs();
stopAttachedFXForCurrrentAnim(true); // stop all anim fx, including looping fxs
}
else
{
stopAttachedFXForCurrrentAnim(false); // stop all anim fxs, but do not stop looping fxs (because the same anim is repeating)
}
// Compute the current animation state.
_CurrentState = EAM->mState(_CurrentAutomaton, animState(MOVE));
// If the state does not exist.
if(_CurrentState == 0)
{
nlwarning("CH:setAnim:%d: State '%s' not in the automaton '%s'.", _Slot, CAnimationStateSheet::getAnimationStateName (animState(MOVE)).c_str(), _CurrentAutomaton.c_str());
// No animation playing
if(_PlayList)
_PlayList->setAnimation(MOVE, UPlayList::empty);
}
// The state is valid.
else
{
double angToDest = 0.0;
// If the new state is a rotation.
if(_CurrentState->Rotation)
angToDest = computeShortestAngle(atan2(dir().y, dir().x), atan2(dirEndAnim().y, dirEndAnim().x));
// Get the right animation state and choose an animation.
{
// Get the animation state
const CAnimationState *animationState = 0;
if(animState(MOVE) == CAnimationStateSheet::Emote)
{
_SubStateKey = subKey;
animationState = _CurrentAnimSet[MOVE]->getAnimationState(_SubStateKey);
}
else
animationState = _CurrentAnimSet[MOVE]->getAnimationState(animState(MOVE));
if(animationState)
{
// Choose the animation
CAnimation::TAnimId index;
if(sameAnim)
index = buIndex;
else
index = CAnimation::UnknownAnim;
animIndex(MOVE, animationState->chooseAnim(_AnimJobSpecialisation, people(), getGender(), angToDest, index));
if(animID != NL3D::UAnimationSet::NotFound)
animId(MOVE, animID);
// Should the objects in hands be displayed ?
_ObjectsVisible = animationState->areObjectsVisible();
showOrHideBodyParts( _ObjectsVisible );
// in case of user manage the internal view
if( isUser() )
{
UserEntity->updateVisualDisplay();
}
}
}
// Initialize the animation
if(animIndex(MOVE) != CAnimation::UnknownAnim)
{
// If the new state is a rotation.
if(_CurrentState->Rotation)
{
// Get the animation rotation.
double animAngle = CAnimationMisc::getAnimationRotation(EAM->getAnimationSet(), animId(MOVE));
// Compute the rotation factor.
if(animAngle != 0.0)
_RotationFactor = fabs(angToDest/animAngle);
else
_RotationFactor = -1.0; // \todo GUIGUI : see which value we should use if we have a rot anim without rot and which should rotate character
}
// If the animation is an atk or forage extraction -> Start all dynamic FXs.
if (_CurrentBehaviour.Behaviour == MBEHAV::EXTRACTING)
{
// True Extract Animation only for Use AnimationState type
// \todo yoyo: ugly?
if( animState(MOVE)==CAnimationStateSheet::UseLoop )
{
_Items[SLOTTYPE::RIGHT_HAND_SLOT].setTrailSize(_CurrentBehaviour.ForageExtraction.Level);
startItemAttackFXs(true, 1);
}
}
else if (_CurrentState->Attack)
{
if(_CurrentBehaviour.Behaviour == MBEHAV::RANGE_ATTACK)
{
startItemAttackFXs(_CurrentBehaviour.Range.ImpactIntensity != 0, _CurrentBehaviour.Range.ImpactIntensity);
}
else
{
startItemAttackFXs(_CurrentBehaviour.Combat.ImpactIntensity != 0 && _CurrentBehaviour.Combat.HitType != HITTYPE::Failed, _CurrentBehaviour.Combat.ImpactIntensity);
}
}
// Initialize the new animation.
if(_PlayList)
{
// Blend the last animation and the new one.
if(ClientCfg.BlendFrameNumber && _BlendRemaining <= 0 // Blend On ?
&& animIndex(ACTION) != CAnimation::UnknownAnim // Last Animation Valid ?
&& ((lastAnimStateId != animState(MOVE)) || (_OldAutomaton != _CurrentAutomaton)
|| (animState(MOVE) == CAnimationStateSheet::Emote)) // Last anim != or automaton != ?
&& !isRiding()) // No Blend on a mount. \todo GUIGUI trouver un meilleur moyen.
{
// Get the rotation of the last animation for the blend.
if(!_Skeleton.empty())
_OldRotQuat = _Skeleton.getRotQuat();
_BlendRemaining = ClientCfg.BlendFrameNumber;
// Set animation
_PlayList->setAnimation(ACTION, animId(ACTION));
// Compute weight step.
float w = 1.f/((float)(ClientCfg.BlendFrameNumber+1));
// Set Old Anim Weight.
_PlayList->setWeight(ACTION, 1.f-w);
// Set New Anim Weight.
_PlayList->setWeight(MOVE, w);
// Copy Reverse
_AnimReversed[ACTION] = _AnimReversed[MOVE];
}
// Set Animation.
_PlayList->setAnimation (MOVE, animId(MOVE));
// Blend between Walk and Run.
if(ClientCfg.BlendForward && (animState(MOVE) == CAnimationStateSheet::Walk))
{
_CurrentAnimSet[MOVE_BLEND_OUT] = _CurrentAnimSet[MOVE];
animState(MOVE_BLEND_OUT, CAnimationStateSheet::Run);
const CAnimationState *animationBlendState = _CurrentAnimSet[MOVE_BLEND_OUT]->getAnimationState(animState(MOVE_BLEND_OUT));
if(animationBlendState)
{
animIndex(MOVE_BLEND_OUT, animationBlendState->chooseAnim(_AnimJobSpecialisation, people(), getGender()));
_PlayList->setAnimation(MOVE_BLEND_OUT, animId(MOVE_BLEND_OUT));
_PlayList->setWeight(MOVE_BLEND_OUT, 1.0f); // \todo GUIGUI : verify what is happening if animId is "empty".
}
else
nlwarning("setAnim:%d: animationBlendState is Null.", _Slot);
}
// Set children animation.
std::list::iterator it = _Children.begin();
while(it != _Children.end())
{
if(*it)
{
CCharacterCL *child = dynamic_cast(*it);
if(child)
{
// Set the Child as the parent.
child->computeAnimSet();
child->currentAutomaton() = _CurrentAutomaton; // \todo GUIGUI : CA VA PAS MARCHER A CAUSE DU TYPE !!!
child->animOffset(MOVE, animOffset(MOVE));
child->animState(MOVE, animState(MOVE));
child->currentState(currentState());
child->animIndex(MOVE, CAnimation::UnknownAnim);
const CAnimationState *animStatePtr = child->currentAnimSet()[MOVE]->getAnimationState(child->animState(MOVE));
if(animStatePtr)
{
child->animIndex(MOVE, animStatePtr->chooseAnim(_AnimJobSpecialisation, people(), getGender(), angToDest));
child->playList()->setAnimation(MOVE, child->animId(MOVE));
}
// TEMP : \todo GUIGUI : Pour le moment on enlever le Blend sur les montures.
{
child->playList()->setAnimation(ACTION, UPlayList::empty);
child->currentAnimSet()[ACTION] = child->currentAnimSet()[MOVE];
child->animState (ACTION, child->animState (MOVE));
child->animIndex (ACTION, child->animIndex (MOVE));
child->animOffset(ACTION, child->animOffset(MOVE));
}
}
else
nlwarning("CH:setAnim:%d: Child is not a 'CCharacterCL'.", _Slot);
}
else
nlwarning("CH:setAnim:%d: Child not allocated.", _Slot);
// Next Child
++it;
}
}
// Get the animation.
const CAnimationState *animStatePtr = _CurrentAnimSet[MOVE]->getAnimationState((animState(MOVE) == CAnimationStateSheet::Emote)?subKey:animState(MOVE));
if(animStatePtr)
{
const CAnimation *anim = animStatePtr->getAnimation(animIndex(MOVE));
if(anim)
{
_HideSkin = anim->hideAtEndAnim();
// Reverse ?
_AnimReversed[MOVE] = anim->isReverse();
// Is the head controlable.
_TargetAnimCtrl.Enabled = anim->headControlable();
// Select the sound ID
_SoundId[MOVE] = anim->soundId();
// look in behaviour if there's a spell to play
if (_CurrentAttack)
{
if (_CurrentAttackInfo.Intensity >= 1 && _CurrentAttackInfo.Intensity <= MAGICFX::NUM_SPELL_POWER)
{
MAGICFX::TSpellCastStage attackStage;
switch(newKey)
{
case CAnimationStateSheet::OffensiveCastBegin:
case CAnimationStateSheet::CurativeCastBegin:
case CAnimationStateSheet::MixedCastBegin:
case CAnimationStateSheet::AcidCastInit:
case CAnimationStateSheet::BlindCastInit:
case CAnimationStateSheet::ColdCastInit:
case CAnimationStateSheet::ElecCastInit:
case CAnimationStateSheet::FearCastInit:
case CAnimationStateSheet::FireCastInit:
case CAnimationStateSheet::HealHPCastInit:
case CAnimationStateSheet::MadCastInit:
case CAnimationStateSheet::PoisonCastInit:
case CAnimationStateSheet::RootCastInit:
case CAnimationStateSheet::RotCastInit:
case CAnimationStateSheet::ShockCastInit:
case CAnimationStateSheet::SleepCastInit:
case CAnimationStateSheet::SlowCastInit:
case CAnimationStateSheet::StunCastInit:
attackStage = MAGICFX::CastBegin;
break;
case CAnimationStateSheet::OffensiveCastLoop:
case CAnimationStateSheet::CurativeCastLoop:
case CAnimationStateSheet::MixedCastLoop:
case CAnimationStateSheet::AcidCastLoop:
case CAnimationStateSheet::BlindCastLoop:
case CAnimationStateSheet::ColdCastLoop:
case CAnimationStateSheet::ElecCastLoop:
case CAnimationStateSheet::FearCastLoop:
case CAnimationStateSheet::FireCastLoop:
case CAnimationStateSheet::HealHPCastLoop:
case CAnimationStateSheet::MadCastLoop:
case CAnimationStateSheet::PoisonCastLoop:
case CAnimationStateSheet::RootCastLoop:
case CAnimationStateSheet::RotCastLoop:
case CAnimationStateSheet::ShockCastLoop:
case CAnimationStateSheet::SleepCastLoop:
case CAnimationStateSheet::SlowCastLoop:
case CAnimationStateSheet::StunCastLoop:
attackStage = MAGICFX::CastLoop;
break;
case CAnimationStateSheet::OffensiveCastSuccess:
case CAnimationStateSheet::CurativeCastSuccess:
case CAnimationStateSheet::MixedCastSuccess:
case CAnimationStateSheet::AcidCastEnd:
case CAnimationStateSheet::BlindCastEnd:
case CAnimationStateSheet::ColdCastEnd:
case CAnimationStateSheet::ElecCastEnd:
case CAnimationStateSheet::FearCastEnd:
case CAnimationStateSheet::FireCastEnd:
case CAnimationStateSheet::HealHPCastEnd:
case CAnimationStateSheet::MadCastEnd:
case CAnimationStateSheet::PoisonCastEnd:
case CAnimationStateSheet::RootCastEnd:
case CAnimationStateSheet::RotCastEnd:
case CAnimationStateSheet::ShockCastEnd:
case CAnimationStateSheet::SleepCastEnd:
case CAnimationStateSheet::SlowCastEnd:
case CAnimationStateSheet::StunCastEnd:
attackStage = MAGICFX::CastEnd;
break;
case CAnimationStateSheet::OffensiveCastFail:
case CAnimationStateSheet::OffensiveCastFumble:
case CAnimationStateSheet::CurativeCastFail:
case CAnimationStateSheet::CurativeCastFumble:
case CAnimationStateSheet::MixedCastFail:
case CAnimationStateSheet::MixedCastFumble:
case CAnimationStateSheet::AcidCastFail:
case CAnimationStateSheet::BlindCastFail:
case CAnimationStateSheet::ColdCastFail:
case CAnimationStateSheet::ElecCastFail:
case CAnimationStateSheet::FearCastFail:
case CAnimationStateSheet::FireCastFail:
case CAnimationStateSheet::HealHPCastFail:
case CAnimationStateSheet::MadCastFail:
case CAnimationStateSheet::PoisonCastFail:
case CAnimationStateSheet::RootCastFail:
case CAnimationStateSheet::RotCastFail:
case CAnimationStateSheet::ShockCastFail:
case CAnimationStateSheet::SleepCastFail:
case CAnimationStateSheet::SlowCastFail:
case CAnimationStateSheet::StunCastFail:
attackStage = MAGICFX::CastFail;
break;
// attacks
case CAnimationStateSheet::DefaultAtkLow:
case CAnimationStateSheet::DefaultAtkMiddle:
case CAnimationStateSheet::DefaultAtkHigh:
case CAnimationStateSheet::PowerfulAtkLow:
case CAnimationStateSheet::PowerfulAtkMiddle:
case CAnimationStateSheet::PowerfulAtkHigh:
case CAnimationStateSheet::AreaAtkLow:
case CAnimationStateSheet::AreaAtkMiddle:
case CAnimationStateSheet::AreaAtkHigh:
case CAnimationStateSheet::Attack1:
case CAnimationStateSheet::Attack2:
case CAnimationStateSheet::FirstPersonAttack:
attackStage = MAGICFX::CastEnd;
break;
default:
attackStage = MAGICFX::SpellCastStageCount;
break;
}
const CAnimationFXSet *afs = NULL;
switch(attackStage)
{
case MAGICFX::CastBegin: afs = &_CurrentAttack->AttackBeginFX; break;
case MAGICFX::CastLoop: afs = &_CurrentAttack->AttackLoopFX; break;
case MAGICFX::CastEnd: afs = &_CurrentAttack->AttackEndFX; break;
case MAGICFX::CastFail: afs = &_CurrentAttack->AttackFailFX; break;
default: break;
}
playCastFX(afs, _CurrentAttackInfo.Intensity);
}
}
// start forage prospection anim fx(s)
if ( (behaviour() == MBEHAV::PROSPECTING) || (behaviour() == MBEHAV::PROSPECTING_END) )
{
const CAnimationFXSet& fxSet = anim->getFXSet();
std::vector fxInstances;
CAttachedFX::CBuildInfo bi;
CAttachedFX::CTargeterInfo ti;
for (uint k = 0; k < fxSet.FX.size(); ++k)
{
CAttachedFX::TSmartPtr fx = new CAttachedFX;
bi.Sheet = &(fxSet.FX[k]);
fx->create(*this, bi, ti);
if (!fx->FX.empty())
{
attachFXInternal(fx,FXListCurrentAnim);
if (k == 0)
{
// Set user param for size of prospection area
{
fx->FX.setUserParam( 0, ((float)(_CurrentBehaviour.ForageProspection.Range)) / 127.0f );
float up [3];
switch ( _CurrentBehaviour.ForageProspection.Angle )
{
case 0: up[2] = up[1] = up[0] = 0.0f; break;
case 1: up[0]=1.0f; up[1]=0.0f; up[2]=0.0f; break;
case 2: up[0]=1.0f; up[1]=1.0f; up[2]=0.0f; break;
default: up[0]=1.0f; up[1]=1.0f; up[2]=1.0f; break;
}
fx->FX.setUserParam( 1, up[0] );
fx->FX.setUserParam( 2, up[1] );
fx->FX.setUserParam( 3, up[2] );
//nlinfo( "Prospection %s %u %u", behaviour()==MBEHAV::PROSPECTING?"PROSPTG":(behaviour()==MBEHAV::PROSPECTING_END?"PRO_END":"OTHER"), _CurrentBehaviour.ForageProspection.Range, _CurrentBehaviour.ForageProspection.Angle );
}
}
else if (k == 1)
{
// Set user param for level of prospection
float up [4];
switch ( _CurrentBehaviour.ForageProspection.Level )
{
case 0: up[3] = up[2] = up[1] = up[0] = 0.0f; break;
case 1: up[0]=0.0f; up[1]=1.0f; up[2]=0.0f; up[3]=0.0f; break;
case 2: up[0]=1.0f; up[1]=1.0f; up[2]=0.0f; up[3]=0.0f; break;
case 3: up[0]=1.0f; up[1]=1.0f; up[2]=1.0f; up[3]=0.0f; break;
default: up[3] = up[2] = up[1] = up[0] = 1.0f; break;
}
fx->FX.setUserParam( 0, up[0] );
fx->FX.setUserParam( 1, up[1] );
fx->FX.setUserParam( 2, up[2] );
fx->FX.setUserParam( 3, up[3] );
}
}
}
}
// start standard anim fx
else
{
CAttachedFX::CBuildInfo bi;
CAttachedFX::CTargeterInfo ti;
bi.MaxNumAnimCount = MAX_FX_ANIM_COUNT;
for (uint k = 0; k < anim->getFXSet().FX.size(); ++k)
{
// if the fx is in looping mode, & the anim has already done a loop, then don't recreate it
if (anim->getFXSet().FX[k].Sheet->RepeatMode == CAnimationFXSheet::Loop && sameAnim) continue;
CAttachedFX::TSmartPtr fx = new CAttachedFX;
bi.Sheet = &(anim->getFXSet().FX[k]);
//bi.StickMode = &bi.Sheet->FX[k].StickMode;
if (anim->getFXSet().FX[k].Sheet)
{
bi.StickMode = &(anim->getFXSet().FX[k].Sheet->StickMode);
}
fx->create(*this, bi, ti);
if (!fx->FX.empty())
{
attachFXInternal(fx, FXListAuto);
}
}
}
}
}
else
nlwarning("CH:setAnim:%d: cannot get the pointer on the animation.", _Slot);
}
// No animation found -> check if it is just a transition or is there really an animation missing
else
{
// INFO : Verbose mode for Animations of the selection.
if(VerboseAnimSelection && _Slot == UserEntity->selection())
nlinfo("CH:setAnim:%d: Anim Not Found, state '%s'.", _Slot, CAnimationState::getAnimationStateName(animState(MOVE)).c_str());
// If the next animation or automaton is not the same -> this is a transition -> no animation needed.
if(_CurrentState->MoveState != _CurrentState->NextState || (_CurrentState->NextMode != _Mode))
{
// Choose the next animation.
endAnimTransition();
}
// Else -> Animation Missing.
}
}
// Set the LOD Animation.
setAnimLOD(((lastAnimStateId != animState(MOVE)) || (_OldAutomaton != _CurrentAutomaton)));
}// setAnim //
//-----------------------------------------------
// playCastFX
//-----------------------------------------------
void CCharacterCL::playCastFX(const CAnimationFXSet *afs, uint power)
{
if (!afs) return;
if (power <= 0 || power > 5) return;
static const float castFXUserParams[MAGICFX::NUM_SPELL_POWER][4] =
{
{ 0.f, 0.f, 0.f, 0.f},
{ 1.f, 0.f, 0.f, 0.f},
{ 1.f, 1.f, 0.f, 0.f},
{ 1.f, 1.f, 1.f, 0.f},
{ 1.f, 1.f, 1.f, 1.f}
};
CAttachedFX::CBuildInfo bi;
CAttachedFX::CTargeterInfo ti;
for (uint k = 0; k < afs->FX.size(); ++k)
{
CAttachedFX::TSmartPtr fx = new CAttachedFX;
bi.Sheet = &afs->FX[k];
fx->create(*this, bi, ti);
if (!fx->FX.empty())
{
for(uint l = 0; l < 4; ++l)
{
fx->FX.setUserParam(l, castFXUserParams[power - 1][l]);
}
attachFXInternal(fx, FXListCurrentAnim);
}
}
}
//-----------------------------------------------
// showOrHideBodyParts
//-----------------------------------------------
void CCharacterCL::showOrHideBodyParts( bool objectsVisible )
{
// UnHide all user body parts.
for(uint i=0; i<_Instances.size(); ++i)
if(!_Instances[i].Current.empty())
_Instances[i].Current.show();
// hide or show the face
if( _Items[SLOTTYPE::HEAD_SLOT].Sheet && _Items[SLOTTYPE::HEAD_SLOT].Sheet->Family == ITEMFAMILY::ARMOR )
{
// Get the face
SInstanceCL *face = getFace ();
// hide if helmet
if(face)
{
if(!face->Current.empty())
face->Current.hide();
else
face->KeepHiddenWhenLoaded = true;
}
}
else
{
// Get the face
SInstanceCL *face = getFace ();
// hide if helmet
if(face)
{
if(!face->Current.empty())
face->Current.show();
else
face->KeepHiddenWhenLoaded = false;
}
}
// get the instance index for right hand and left hand
uint32 rHandInstIdx;
uint32 lHandInstIdx;
if( isPlayer() || isUser() )
{
rHandInstIdx = SLOTTYPE::RIGHT_HAND_SLOT;
lHandInstIdx = SLOTTYPE::LEFT_HAND_SLOT;
// hide gloves(armor) if player has magician amplifier
if( _Items[rHandInstIdx].Sheet && (_Items[rHandInstIdx].Sheet->ItemType == ITEM_TYPE::MAGICIAN_STAFF) )
{
if( !_Instances[SLOTTYPE::HANDS_SLOT].Current.empty() )
_Instances[SLOTTYPE::HANDS_SLOT].Current.hide();
else
_Instances[SLOTTYPE::HANDS_SLOT].KeepHiddenWhenLoaded = true;
}
}
else
{
rHandInstIdx = _RHandInstIdx;
lHandInstIdx = _LHandInstIdx;
}
// if not already hidden and need to hide :
if( !objectsVisible )
{
// Right Hand
if(rHandInstIdx<_Instances.size())
if( !(_Items[rHandInstIdx].Sheet && _Items[rHandInstIdx].Sheet->NeverHideWhenEquipped ) )
if(!_Instances[rHandInstIdx].Current.empty())
{
_Instances[rHandInstIdx].Current.hide();
_Instances[rHandInstIdx].hideStaticFXs();
}
// Left Hand
if(lHandInstIdx <_Instances.size())
if( !(_Items[lHandInstIdx].Sheet && _Items[lHandInstIdx].Sheet->NeverHideWhenEquipped ) )
if(!_Instances[lHandInstIdx].Current.empty())
{
_Instances[lHandInstIdx].Current.hide();
_Instances[lHandInstIdx].hideStaticFXs();
}
}
else
{
// Right Hand
if(rHandInstIdx<_Instances.size())
if(!_Instances[rHandInstIdx].Current.empty())
{
_Instances[rHandInstIdx].Current.show();
_Instances[rHandInstIdx].showStaticFXs();
}
// Left Hand
if(lHandInstIdx <_Instances.size())
if(!_Instances[lHandInstIdx].Current.empty())
{
_Instances[lHandInstIdx].Current.show();
_Instances[lHandInstIdx].showStaticFXs();
}
}
}
//-----------------------------------------------
// setAnimLOD :
// Set the LOD animation.
//-----------------------------------------------
ADD_METHOD(void CCharacterCL::setAnimLOD(bool changed))
// reset LOD animation.
_LodCharacterAnimEnabled = false;
// Setup new LOD animation.
if(skeleton())
{
// if the entity has a lod Character
sint lodId = skeleton()->getLodCharacterShape();
if(lodId >= 0)
{
// Setup Lod anim.
_LodCharacterAnimEnabled = true;
_LodCharacterMasterAnimSlot = MOVE;
_LodCharacterAnimTimeOffset = 0;
// do complex stuff only if the anim state has really changed.
if(changed)
{
// get the anim state from the set.
const CAnimationState *animStatePtr = _CurrentAnimSet[MOVE]->getAnimationState(animState(MOVE));
// if animStatePtr found.
if(animStatePtr)
{
// get Lod Character animation Name from the anim state.
const string &lodAnimName = animStatePtr->getLodCharacterAnimation();
// Find the anim in the UScene LodCharacterManager
sint animId = Scene->getCLodAnimIdByName(lodId, lodAnimName);
// if Anim not found, get the "idle" anim, with the Id 0.
if(animId < 0)
animId = 0;
// setup the skeleton.
skeleton()->setLodCharacterAnimId(animId);
}
}
}
}
}// setAnimLOD //
//-----------------------------------------------
// updateAnimationState :
// \todo GUIGUI : precalculate distance to destination when receiving Stages.
// \todo GUIGUI : improve, we are setting often 'idle' to recompute orientation at the end of animation instead of doing directly the right one.
//-----------------------------------------------
ADD_METHOD(void CCharacterCL::updateAnimationState())
// If the current state is invalid -> return.
if(_CurrentState == 0)
return;
// Get the Animation Length.
double animLength = EAM->getAnimationLength(animId(MOVE));
// Check Anim length
if(animOffset(MOVE) >= animLength)
{
// Choose the next animation.
endAnimTransition();
}
// Last animation not finished check for some chances to break current animation.
else
{
// Check Modes.
// if(_IsThereAMode && (dist2Dest()==INVALID_DIST))
{
// Is the current mode not already the mode wanted.
if((_Mode != _ModeWanted) && (dist2Dest()==INVALID_DIST))
{
if((_ModeWanted != MBEHAV::MOUNT_NORMAL && _ModeWanted != MBEHAV::MOUNT_SWIM) || (_Rider != CLFECOMMON::INVALID_SLOT))
{
// Is there a possible connection with the mode wanted.
TAnimStateKey transition;
if(_CurrentState->getModeConnection(_ModeWanted, transition))
{
setAnim(transition);
return;
}
}
else
{
//InfoLog->displayRawNL("(%d)_Rider=%d _Mount=%d _ModeWanted=%s _Mode=%s offset=%f length=%f",_Slot,_Rider,_Mount,MBEHAV::modeToString(_ModeWanted).c_str(),MBEHAV::modeToString(_Mode).c_str(),animOffset(MOVE),animLength);
// anti-bug : sometimes _rider is not set yet and we stay in an infinite move
_Rider = _TheoreticalRider;
}
}
}
// Should we stop the animation once at destination.
if(_CurrentState->BrkAtDest && dist2Dest() <= ClientCfg.DestThreshold)
{
_MaxLoop = false;
setAnim(CAnimationStateSheet::Idle);
return;
}
// ON MOVE
switch(onMove(*_CurrentState))
{
// On Move Forward
case OnMoveForward:
setAnim(_CurrentState->OnMoveForward);
return;
// On Move Backward
case OnMoveBackward:
setAnim(_CurrentState->OnMoveBackward);
return;
// On Move Left
case OnMoveLeft:
setAnim(_CurrentState->OnMoveLeft);
return;
// On Move Right
case OnMoveRight:
setAnim(_CurrentState->OnMoveRight);
return;
default:
break;
}
// ON ROTATION
CVector tmp;
switch(onRotation(*_CurrentState, tmp))
{
// Rotation Left
case OnRotLeft:
setAnim(CAnimationStateSheet::Idle);
return;
// Rotation Right
case OnRotRight:
setAnim(CAnimationStateSheet::Idle);
return;
default:
break;
}
// ON BAD HEADING
if(onBadHeading(*_CurrentState))
{
setAnim(CAnimationStateSheet::Idle);
return;
}
// ON BIG BEND
switch(onBigBend(*_CurrentState, tmp))
{
// Big Bend Left and Right
case OnBendLeft:
case OnBendRight:
setAnim(_CurrentState->MoveState);
return;
default:
break;
}
// \todo GUIGUI : changer de place cette partie je pense.
// Adjust the direction to fit the front
if( !(isUser() && ClientCfg.AutomaticCamera==false) )
{
if(_CurrentState->AdjustOri)
{
// Adjust before the half of the attack animation.
const double animL = animLength/2.0;
// Half already reatch, dir should be as the front now
if(animOffset(MOVE) > animL)
dir(front());
else
{
double ang = computeShortestAngle(atan2(dir().y, dir().x), atan2(front().y, front().x));
if(ang)
{
double ang2 = (animOffset(MOVE)/animL)*ang/animL;
double angleZ = ang2+atan2(dir().y, dir().x);
dir(CVector((float)cos(angleZ), (float)sin(angleZ), 0.f));
}
}
}
}
}
}// updateAnimationState //
//-----------------------------------------------
// computeMotion :
//-----------------------------------------------
ADD_METHOD(double CCharacterCL::computeMotion(const double &oldMovingTimeOffset, TAnimationType channel) const)
H_AUTO_USE ( RZ_Client_Entity_CL_Update_Pos_Compute_Motion )
// Check the state is valid.
if(_CurrentState == 0)
return 0.0;
// Calculate movement for given animation segment.
if(_CurrentState->Move)
{
// Animation is unknown, no move in it.
if(animIndex(channel) == CAnimation::UnknownAnim)
return 0.0;
CVector oldPos, newPos;
uint animID = animId(channel);
if(EAM->interpolate(animID, oldMovingTimeOffset, oldPos))
{
if(EAM->interpolate(animID, animOffset(channel), newPos))
{
CVector mov = newPos - oldPos;
// Scale it by the CharacterScalePos, if needed, according to the animation.
bool mustApplyCharacterScalePosFactor = true;
const CAnimationState *animStatePtr = _CurrentAnimSet[channel]->getAnimationState(animState(channel));
if(animStatePtr)
{
const CAnimation *anim = animStatePtr->getAnimation(animIndex(channel));
if(anim)
mustApplyCharacterScalePosFactor = anim->applyCharacterScalePosFactor();
}
// scale it according to the species.
if(mustApplyCharacterScalePosFactor)
mov*= _CharacterScalePos;
// Scale according to the gabarit.
mov *= _CustomScalePos;
// Return a significant move.
double distDone = mov.norm();
if(distDone>0.0 && distDoneisUser() )
{
CVectorD dirToTarget = target->pos() - pos();
dirToTarget.z = 0;
dirToTarget.normalize();
front( dirToTarget );
dir( dirToTarget );
}
switch(behaviour.Behaviour)
{
////////////////
// BEGIN CAST //
// OFFENSIVE CAST BEGIN
case MBEHAV::CAST_OFF:
setAnim(CAnimationStateSheet::OffensiveCastInit);
break;
// CURATIVE CAST BEGIN
case MBEHAV::CAST_CUR:
setAnim(CAnimationStateSheet::CurativeCastInit);
break;
// MIXED CAST BEGIN
case MBEHAV::CAST_MIX:
setAnim(CAnimationStateSheet::MixedCastInit);
break;
// IDLE
case MBEHAV::IDLE:
setAnim(CAnimationStateSheet::Idle);
break;
//
case MBEHAV::CAST_ACID:
setAnim(CAnimationStateSheet::AcidCastInit);
break;
case MBEHAV::CAST_BLIND:
setAnim(CAnimationStateSheet::BlindCastInit);
break;
case MBEHAV::CAST_COLD:
setAnim(CAnimationStateSheet::ColdCastInit);
break;
case MBEHAV::CAST_ELEC:
setAnim(CAnimationStateSheet::ElecCastInit);
break;
case MBEHAV::CAST_FEAR:
setAnim(CAnimationStateSheet::FearCastInit);
break;
case MBEHAV::CAST_FIRE:
setAnim(CAnimationStateSheet::FireCastInit);
break;
case MBEHAV::CAST_HEALHP:
setAnim(CAnimationStateSheet::HealHPCastInit);
break;
case MBEHAV::CAST_MAD:
setAnim(CAnimationStateSheet::MadCastInit);
break;
case MBEHAV::CAST_POISON:
setAnim(CAnimationStateSheet::PoisonCastInit);
break;
case MBEHAV::CAST_ROOT:
setAnim(CAnimationStateSheet::RootCastInit);
break;
case MBEHAV::CAST_ROT:
setAnim(CAnimationStateSheet::RotCastInit);
break;
case MBEHAV::CAST_SHOCK:
setAnim(CAnimationStateSheet::ShockCastInit);
break;
case MBEHAV::CAST_SLEEP:
setAnim(CAnimationStateSheet::SleepCastInit);
break;
case MBEHAV::CAST_SLOW:
setAnim(CAnimationStateSheet::SlowCastInit);
break;
case MBEHAV::CAST_STUN:
setAnim(CAnimationStateSheet::StunCastInit);
break;
default:
break;
}
}// beginCast //
//-----------------------------------------------
// endCast :
//-----------------------------------------------
void CCharacterCL::endCast(const MBEHAV::CBehaviour &behaviour, const MBEHAV::CBehaviour &lastBehaviour)
{
if( !(isUser() && isSit()) )
{
switch(lastBehaviour.Behaviour)
{
case MBEHAV::CAST_ACID:
setAnim(CAnimationStateSheet::AcidCastEnd);
break;
case MBEHAV::CAST_BLIND:
setAnim(CAnimationStateSheet::BlindCastEnd);
break;
case MBEHAV::CAST_COLD:
setAnim(CAnimationStateSheet::ColdCastEnd);
break;
case MBEHAV::CAST_ELEC:
setAnim(CAnimationStateSheet::ElecCastEnd);
break;
case MBEHAV::CAST_FEAR:
setAnim(CAnimationStateSheet::FearCastEnd);
break;
case MBEHAV::CAST_FIRE:
setAnim(CAnimationStateSheet::FireCastEnd);
break;
case MBEHAV::CAST_HEALHP:
setAnim(CAnimationStateSheet::HealHPCastEnd);
break;
case MBEHAV::CAST_MAD:
setAnim(CAnimationStateSheet::MadCastEnd);
break;
case MBEHAV::CAST_POISON:
setAnim(CAnimationStateSheet::PoisonCastEnd);
break;
case MBEHAV::CAST_ROOT:
setAnim(CAnimationStateSheet::RootCastEnd);
break;
case MBEHAV::CAST_ROT:
setAnim(CAnimationStateSheet::RotCastEnd);
break;
case MBEHAV::CAST_SHOCK:
setAnim(CAnimationStateSheet::ShockCastEnd);
break;
case MBEHAV::CAST_SLEEP:
setAnim(CAnimationStateSheet::SleepCastEnd);
break;
case MBEHAV::CAST_SLOW:
setAnim(CAnimationStateSheet::SlowCastEnd);
break;
case MBEHAV::CAST_STUN:
setAnim(CAnimationStateSheet::StunCastEnd);
break;
case MBEHAV::IDLE:
setAnim(CAnimationStateSheet::Idle);
break;
// Old One
default:
{
switch(behaviour.Behaviour)
{
//////////////
// END CAST //
// OFFENSIVE CAST END
case MBEHAV::CAST_OFF_FAIL:
setAnim(CAnimationStateSheet::OffensiveCastFail);
break;
case MBEHAV::CAST_OFF_FUMBLE:
setAnim(CAnimationStateSheet::OffensiveCastFumble);
break;
case MBEHAV::CAST_OFF_SUCCESS:
setAnim(CAnimationStateSheet::OffensiveCastSuccess);
break;
case MBEHAV::CAST_OFF_LINK:
setAnim(CAnimationStateSheet::OffensiveCastLink);
break;
// CURATIVE CAST END
case MBEHAV::CAST_CUR_FAIL:
setAnim(CAnimationStateSheet::CurativeCastFail);
break;
case MBEHAV::CAST_CUR_FUMBLE:
setAnim(CAnimationStateSheet::CurativeCastFumble);
break;
case MBEHAV::CAST_CUR_SUCCESS:
setAnim(CAnimationStateSheet::CurativeCastSuccess);
break;
case MBEHAV::CAST_CUR_LINK:
setAnim(CAnimationStateSheet::CurativeCastLink);
break;
// MIXED CAST END
case MBEHAV::CAST_MIX_FAIL:
setAnim(CAnimationStateSheet::MixedCastFail);
break;
case MBEHAV::CAST_MIX_FUMBLE:
setAnim(CAnimationStateSheet::MixedCastFumble);
break;
case MBEHAV::CAST_MIX_SUCCESS:
setAnim(CAnimationStateSheet::MixedCastSuccess);
break;
case MBEHAV::CAST_MIX_LINK:
setAnim(CAnimationStateSheet::MixedCastLink);
break;
default:
break;
}
}
break;
}
}
}// endCast //
// *************************************************************************************************
void CCharacterCL::updateCurrentAttack()
{
// This is a behaviour for the magic.
if(_CurrentBehaviour.isMagic())
{
_CurrentAttackID.Type = CAttackIDSheet::Magic;
_CurrentAttackID.SpellInfo.Mode = (MAGICFX::TSpellMode) _CurrentBehaviour.Spell.SpellMode;
_CurrentAttackID.SpellInfo.ID = (MAGICFX::TMagicFx) _CurrentBehaviour.Spell.SpellId;
}
// This is a behaviour for the combat.
else if(_CurrentBehaviour.Behaviour == MBEHAV::RANGE_ATTACK)
{
_CurrentAttackID.Type = CAttackIDSheet::Range;
_CurrentAttackID.RangeWeaponType = (RANGE_WEAPON_TYPE::TRangeWeaponType) _CurrentBehaviour.Range.WeaponType;
}
else if (_CurrentBehaviour.isCombat())
{
_CurrentAttackID.Type = CAttackIDSheet::Melee;
}
else if (_CurrentBehaviour.isCreatureAttack())
{
_CurrentAttackID.Type = CAttackIDSheet::Creature;
_CurrentAttackID.CreatureAttackIndex = _CurrentBehaviour.Behaviour == MBEHAV::CREATURE_ATTACK_0 ? 0 : 1;
}
else
{
// the behaviour does not generate an attack
_CurrentAttack = NULL;
_CurrentAttackID.Type = CAttackIDSheet::Unknown;
return;
}
_CurrentAttack = getAttack(_CurrentAttackID);
// update current attack infos
if(_CurrentBehaviour.isMagic())
{
_CurrentAttackInfo.Intensity = _CurrentBehaviour.Spell.SpellIntensity;
// physical damge are irrelevant for magic
_CurrentAttackInfo.DamageType = DMGTYPE::UNDEFINED;
_CurrentAttackInfo.HitType = HITTYPE::Undefined;
_CurrentAttackInfo.Localisation = BODY::UnknownBodyPart;
_CurrentAttackInfo.PhysicalImpactIntensity = 0;
}
// This is a behaviour for the combat.
else if(_CurrentBehaviour.Behaviour == MBEHAV::RANGE_ATTACK)
{
_CurrentAttackInfo.Intensity = _CurrentBehaviour.Range.ImpactIntensity;
_CurrentAttackInfo.DamageType = DMGTYPE::UNDEFINED;
_CurrentAttackInfo.HitType = HITTYPE::Undefined;
_CurrentAttackInfo.Localisation = (BODY::TBodyPart) _CurrentBehaviour.Range.Localisation;
_CurrentAttackInfo.PhysicalImpactIntensity = 0;
}
else if (_CurrentBehaviour.isCombat())
{
_CurrentAttackInfo.Intensity = _CurrentBehaviour.Combat.ImpactIntensity;
_CurrentAttackInfo.DamageType = (DMGTYPE::EDamageType) _CurrentBehaviour.Combat2.DamageType;
_CurrentAttackInfo.HitType = (HITTYPE::THitType) _CurrentBehaviour.Combat.HitType;
_CurrentAttackInfo.Localisation = (BODY::TBodyPart) _CurrentBehaviour.Combat.Localisation;
_CurrentAttackInfo.PhysicalImpactIntensity = _CurrentBehaviour.Combat.ImpactIntensity;
}
else if (_CurrentBehaviour.isCreatureAttack())
{
// creature attack is the most general form
_CurrentAttackInfo.Intensity = _CurrentBehaviour.CreatureAttack.MagicImpactIntensity;
_CurrentAttackInfo.DamageType = (DMGTYPE::EDamageType) _CurrentBehaviour.CreatureAttack2.DamageType;
_CurrentAttackInfo.HitType = (HITTYPE::THitType) _CurrentBehaviour.CreatureAttack2.HitType;
_CurrentAttackInfo.Localisation = (BODY::TBodyPart) _CurrentBehaviour.CreatureAttack.Localisation;
_CurrentAttackInfo.PhysicalImpactIntensity = _CurrentBehaviour.CreatureAttack.ImpactIntensity;
}
_CurrentAttackInfo.Side = (rand() & 1) ? BODY::Left : BODY::Right;
}
// utility function for performCurrentAttackEnd
inline static void getResistAndDistance(uint8 packedInfo, bool isDirectAttack, bool isCombat, bool &resist, uint &distance)
{
// get the distance from attacker to defender (consider 0 if a direct attack)
if (isDirectAttack)
distance = 0;
else
distance = packedInfo & 127;
// resisted?
if(isCombat)
resist= false;
else
resist = (packedInfo & 128) != 0;
}
// *********************************************************************************************
void CCharacterCL::performCurrentAttackEnd(const CBehaviourContext &bc, bool directOffensifSpell, vector &targetHitDates, TAnimStateKey animForCombat)
{
if (!_CurrentAttack) return;
if (!_CurrentAttack->Sheet) return;
if (bc.Targets.Targets.empty())
{
nlwarning ("No target available for current attack.");
return;
}
nlassert(targetHitDates.size() == bc.Targets.Targets.size());
CAttackInfo attackInfo = _CurrentAttackInfo;
const CAttackSheet &sheet = *_CurrentAttack->Sheet;
// should cast FX for static Objects like towers be used ?
bool usesStaticCastFX = _Sheet && !_Sheet->ProjectileCastRay.empty();
CProjectileBuild pb;
// ray of cast for static object with several cast points
CVector castWorldOrigin;
CVector castWorldPos;
CVector additionnalOffset = NLMISC::CVector::Null;
bool castRayValid = false;
uint mainCastFXIntensity = attackInfo.Intensity;
// *** Compute castStartTime (time at which the projectile is casted)
// *** Compute castStartTime (time at which the projectile is casted)
float delay = 0.f; // by default, no delay before impact
double timeFactor = 1;
// Combat (range or melee) => no resist (yoyo: legacy code. Actually the server should set the resist bit to 0 in Target.Info)
bool isCombat= _CurrentBehaviour.isCombat();
// An attack is said 'direct' when no projectile is casted (Generally those are melee attack, but also pistol range attack for instance)
// In this case, all targets are hit at the same time, no matter what date is supplied for them.
bool isDirectAttack = sheet.ProjectileFX.empty();
if (isDirectAttack)
{
///////////////////
// DIRECT ATTACK //
///////////////////
// compute impact date. It is given by the trigger on right hand in the animation
// Get the current animation id.
if (sheet.ForceUseProjectileDelay)
{
delay = sheet.ProjectileDelay;
}
else
if (!directOffensifSpell)
{
if ((_CurrentAttackID.Type != CAttackIDSheet::Range) && (_PlayList != NULL))
{
// default
delay= 0.5f;
// if the animation has been correctly chosen
if(animForCombat!=CAnimationStateSheet::UnknownState)
{
// try to get the MeleeImpactDelay that is stored in the sheet (given by graphists)
const CAnimationSet *animSet= currentAnimSet()[MOVE];
if(animSet)
{
const CAnimationState *animState = animSet->getAnimationState(animForCombat);
if(animState)
delay= animState->getMeleeImpactDelay();
}
}
}
else
{
// see if weapon is known, and in this case, delay the impact by the same amount
const CItemSheet *sheet = _Items[SLOTTYPE::RIGHT_HAND_SLOT].Sheet;
if (sheet) delay = sheet->FX.ImpactFXDelay;
}
}
// all target are reached at the same time
timeFactor = 0;
}
else
{
// there's a projectile
if (usesStaticCastFX)
{
delay = sheet.StaticObjectProjectileDelay;
}
else
{
if (!directOffensifSpell)
{
// if object has a list of cast rays, then we assume it is a static object (like guard towers)
// projectile cast from a character
delay = sheet.ProjectileDelay;
}
}
}
// then castStartTime is just start of behav + animation delay
double castStartTime;
// Special for direct attack. because of lag sometimes behavTime bc.BehavTime+0.5)
castStartTime= bc.BehavTime + 0.5;
else if(TimeInSec > bc.BehavTime)
castStartTime= TimeInSec;
else
castStartTime= bc.BehavTime;
// add the delay
castStartTime+= delay;
}
else
{
// In case of magic or range (with projectile), this is not required because the projectilemanager should take cares of
// cast start time and current time (and therfore advance the projectile if required)
castStartTime= bc.BehavTime + delay;
}
// *** Casts projectiles and Impacts
bool resist;
uint distance;
// there's a projectile
switch(sheet.ProjectileMode)
{
// TODO: homin code for projectile look quite similar, maybe can merge ?
case MAGICFX::Bomb:
{
getResistAndDistance(bc.Targets.Targets[0].Info, isDirectAttack, isCombat, resist, distance);
double mainStartDate = castStartTime;
double mainEndDate = mainStartDate + timeFactor * double(distance * MULTI_TARGET_DISTANCE_UNIT / MAGICFX::PROJECTILE_SPEED);
CCharacterCL *mainTarget = dynamic_cast(EntitiesMngr.entity(bc.Targets.Targets[0].TargetSlot));
if (mainTarget)
{
computeTargetStickMode(*_CurrentAttack->Sheet, attackInfo, pb.ProjectileAimingPoint, *mainTarget);
if (usesStaticCastFX)
{
// compute the cast pos
computeBestCastRay(*mainTarget, pb.ProjectileAimingPoint, castWorldOrigin, castWorldPos, additionnalOffset);
castRayValid = true;
}
// the target hit date
targetHitDates[0]= mainEndDate;
// Add the projectile to queue
if (createCurrentAttackEndPart(pb,
_CurrentAttack,
*mainTarget,
NULL,
mainStartDate,
mainEndDate,
true,
sheet.PlayImpactAnim,
resist,
sheet.IsImpactLocalised,
attackInfo,
additionnalOffset
))
{
CProjectileManager::getInstance().addProjectileToQueue(pb);
}
attackInfo.Intensity = attackInfo.Intensity != 0 ? 1 : 0;
attackInfo.PhysicalImpactIntensity = attackInfo.PhysicalImpactIntensity != 0 ? 1 : 0;
// all subsequent projectiles are casted from the secondary target, from the first impact point, and have level 1
for(uint k = 1; k < bc.Targets.Targets.size(); ++k)
{
getResistAndDistance(bc.Targets.Targets[k].Info, isDirectAttack, isCombat, resist, distance);
double secondaryEndDate = mainEndDate + timeFactor * ((double) (distance * MULTI_TARGET_DISTANCE_UNIT / MAGICFX::PROJECTILE_SPEED));
CCharacterCL *secondaryTarget = dynamic_cast(EntitiesMngr.entity(bc.Targets.Targets[k].TargetSlot));
if (secondaryTarget)
{
// the target hit date
targetHitDates[k]= secondaryEndDate;
// Add the projectile to queue
if (mainTarget->createCurrentAttackEndPart(pb,
_CurrentAttack,
*secondaryTarget,
&pb.ProjectileAimingPoint,
mainEndDate,
secondaryEndDate,
k != 0 ? !sheet.PlayImpactFXOnlyOnMainTarget : true,
sheet.PlayImpactAnim,
resist,
sheet.IsImpactLocalised,
attackInfo
))
{
computeTargetStickMode(*_CurrentAttack->Sheet, attackInfo, pb.ProjectileAimingPoint, *secondaryTarget);
CProjectileManager::getInstance().addProjectileToQueue(pb);
}
}
}
}
}
break;
case MAGICFX::Chain:
{
double currDate = castStartTime;
CCharacterCL *currCaster = this;
const CFXStickMode *projectileStartPoint = NULL; // by default, start at caster hand
CFXStickMode currStickMode;
for(uint k = 0; k < bc.Targets.Targets.size(); ++k)
{
if (k == 1)
{
// for secondary impacts, intensity is 0 or 1
attackInfo.Intensity = attackInfo.Intensity != 0 ? 1 : 0;
attackInfo.PhysicalImpactIntensity = attackInfo.PhysicalImpactIntensity != 0 ? 1 : 0;
}
getResistAndDistance(bc.Targets.Targets[k].Info, isDirectAttack, isCombat, resist, distance);
double nextDate = currDate + timeFactor * ((double) (distance * MULTI_TARGET_DISTANCE_UNIT / MAGICFX::PROJECTILE_SPEED));
CCharacterCL *currTarget = dynamic_cast(EntitiesMngr.entity(bc.Targets.Targets[k].TargetSlot));
if (!currTarget) break;
computeTargetStickMode(*_CurrentAttack->Sheet, attackInfo, pb.ProjectileAimingPoint, *currTarget);
if (k == 0 && usesStaticCastFX)
{
// compute the initial cast pos for objects with multiple possible cast positions
computeBestCastRay(*currTarget, pb.ProjectileAimingPoint, castWorldOrigin, castWorldPos, additionnalOffset);
castRayValid = true;
}
// the target hit date
targetHitDates[k]= nextDate;
// Add the projectile to queue
if (currCaster->createCurrentAttackEndPart(pb,
_CurrentAttack,
*currTarget,
projectileStartPoint,
currDate,
nextDate,
k != 0 ? !sheet.PlayImpactFXOnlyOnMainTarget : true,
sheet.PlayImpactAnim,
resist,
sheet.IsImpactLocalised,
attackInfo,
k == 0 ? additionnalOffset : CVector::Null
))
{
CProjectileManager::getInstance().addProjectileToQueue(pb);
}
currStickMode = pb.ProjectileAimingPoint;
projectileStartPoint = &currStickMode;
currCaster = currTarget;
if (!currCaster) break;
currDate = nextDate;
}
}
break;
case MAGICFX::Spray:
{
for(uint k = 0; k < bc.Targets.Targets.size(); ++k)
{
getResistAndDistance(bc.Targets.Targets[k].Info, isDirectAttack, isCombat, resist, distance);
double startDate = castStartTime;
double endDate = startDate + timeFactor * ((double) (distance * MULTI_TARGET_DISTANCE_UNIT / MAGICFX::PROJECTILE_SPEED));
CCharacterCL *currTarget = dynamic_cast(EntitiesMngr.entity(bc.Targets.Targets[k].TargetSlot));
if (k == 1)
{
// for secondary impacts, intensity is 0 or 1
attackInfo.Intensity = attackInfo.Intensity != 0 ? 1 : 0;
attackInfo.PhysicalImpactIntensity = attackInfo.PhysicalImpactIntensity != 0 ? 1 : 0;
}
if (currTarget)
{
computeTargetStickMode(*_CurrentAttack->Sheet, attackInfo, pb.ProjectileAimingPoint, *currTarget);
if (k == 0 && usesStaticCastFX)
{
// compute the initial cast pos for objects with multiple possible cast positions
computeBestCastRay(*currTarget, pb.ProjectileAimingPoint, castWorldOrigin, castWorldPos, additionnalOffset);
castRayValid = true;
}
// the target hit date
targetHitDates[k]= endDate;
// nb : only main target display the spell with full power
if (createCurrentAttackEndPart(pb,
_CurrentAttack,
*currTarget,
NULL,
startDate,
endDate,
k != 0 ? !sheet.PlayImpactFXOnlyOnMainTarget : true,
sheet.PlayImpactAnim,
resist,
sheet.IsImpactLocalised,
attackInfo,
k == 0 ? additionnalOffset : CVector::Null
))
{
CProjectileManager::getInstance().addProjectileToQueue(pb);
}
}
}
}
break;
default:
break;
}
// if object has a list of cast rays, then we assume it is a static object (like guard towers)
if (usesStaticCastFX && castRayValid)
{
buildStaticObjectCastFX(castWorldOrigin, castWorldPos, *_CurrentAttack->Sheet, mainCastFXIntensity);
}
// *** Play damage shields when melee attack is done
/*
// TODO: to finalize (server code not done).
if (ClientCfg.DamageShieldEnabled && _CurrentBehaviour.isCombat() && _CurrentBehaviour.todoNotRange())
{
for(uint k = 0; k < bc.Targets.Targets.size(); ++k)
{
uint power = bc.Targets.Targets[k].Info & 7;
if (power)
{
uint dmType = bc.Targets.Targets[k].Info >> 3;
//
CAttackIDSheet damageShieldID;
damageShieldID.Type = CAttackIDSheet::DamageShield;
damageShieldID.DamageShieldType = dmType;
CCharacterCL *currTarget = dynamic_cast(EntitiesMngr.entity(bc.Targets.Targets[k].TargetSlot));
if (!currTarget) continue;
const CAttack *damageShieldReaction = currTarget->getAttack(damageShieldID);
if (!damageShieldReaction) continue;
CAttackInfo attackInfo;
attackInfo.Intensity = power;
attackInfo.PhysicalImpactIntensity = 0;
attackInfo.Localisation = BODY::UnknownBodyPart;
// no physical part
const CAttackSheet &damageShieldSheet = *damageShieldReaction->Sheet;
computeTargetStickMode(damageShieldSheet, attackInfo, pb.ProjectileAimingPoint, *this);
if (currTarget->createCurrentAttackEndPart(pb,
damageShieldReaction,
*this,
NULL,
castStartTime,
castStartTime,
true,
damageShieldSheet.PlayImpactAnim,
false,
false,
attackInfo))
{
// play "CastEnd" at the good date (if any ...)
pb.CastAspect = &damageShieldReaction->AttackEndFX;
pb.CastPower = power;
pb.ForcePlayImpact = true;
CProjectileManager::getInstance().addProjectileToQueue(pb);
}
}
}
}
*/
}
// *********************************************************************************************
void CCharacterCL::buildStaticObjectCastFX(const NLMISC::CVector &castWorldOrigin, NLMISC::CVector &castWorldPos, const CAttackSheet &/* sheet */, uint intensity)
{
if (intensity == 0) return;
const float *userParams = CProjectileManager::getProjectileFXUserParams(intensity);
// create additionnal cast fxs on the tower or other static object (if any)
// Build lookat matrix (with respect to axis) looking from castWorldOrigin to castWorldPos
CVector dir = castWorldPos - castWorldOrigin;
CVector I = dir.normed();
CVector K = (CVector::K - (I * CVector::K) * I).normed();
CMatrix castMat;
castMat.setPos(castWorldPos);
castMat.setRot(I, K ^ I, K);
CAttachedFX::CBuildInfo bi;
CAttachedFX::CTargeterInfo ti;
bi.StaticMatrix = &castMat;
const CAnimationFXSet &afs =_CurrentAttack->AttackStaticObjectCastFX;
for (uint k = 0; k < afs.FX.size(); ++k)
{
// if the fx is in looping mode, & the anim has already done a loop, then don't recreate it
CAttachedFX::TSmartPtr fx = new CAttachedFX;
bi.Sheet = &afs.FX[k];
fx->create(*this, bi, ti);
if (!fx->FX.empty())
{
if (userParams)
{
for(uint l = 0; l < 4; ++l)
{
fx->FX.setUserParam(l, userParams[l]);
}
}
attachFX(fx);
}
}
}
// *********************************************************************************************
void CCharacterCL::computeTargetStickMode(const CAttackSheet &sheet, const CAttackInfo &attackInfo, CFXStickMode &dest, CEntityCL &target)
{
bool hasPhysicalImpact = false;
if (attackInfo.Localisation != BODY::UnknownBodyPart &&
attackInfo.PhysicalImpactIntensity >= 1 &&
attackInfo.PhysicalImpactIntensity <= MAGICFX::NUM_SPELL_POWER &&
attackInfo.HitType != HITTYPE::Failed &&
attackInfo.HitType != HITTYPE::Undefined &&
attackInfo.DamageType != DMGTYPE::UNDEFINED)
{
hasPhysicalImpact = true;
}
if (sheet.IsImpactLocalised || hasPhysicalImpact)
{
// get projectile impact point from localisation
// & generate stick mode
const char *targetBoneName = target.getBoneNameFromBodyPart(attackInfo.Localisation, attackInfo.Side);
if (targetBoneName)
{
if (sheet.DefaultAimingPoint.Mode == CFXStickMode::UserBoneOrientedTowardTargeter)
{
dest.Mode = CFXStickMode::UserBoneOrientedTowardTargeter;
}
else
{
dest.Mode = CFXStickMode::UserBone;
}
dest.UserBoneName = CStringMapper::map(targetBoneName);
}
}
else
{
// use default aiming point given in sheet
dest = sheet.DefaultAimingPoint;
}
}
// *********************************************************************************************
bool CCharacterCL::createCurrentAttackEndPart(CProjectileBuild &destPB,
const CAttack *currentAttack,
const CCharacterCL &target,
const CFXStickMode *sm,
double spawnDate,
double hitDate,
bool playImpactFX,
bool playImpactAnim,
bool magicResist,
bool /* mainImpactIsLocalised */,
const CAttackInfo &attackInfo,
const NLMISC::CVector &additionnalOffset /*= NLMISC::CVector::Null*/
)
{
if (!currentAttack) return false;
if (!currentAttack->Sheet) return false;
const CAttackSheet &sheet = *currentAttack->Sheet;
// dates
destPB.StartDate = spawnDate;
destPB.EndDate = hitDate;
// choose fx for projectile
if (attackInfo.Intensity >= 1 && attackInfo.Intensity <= MAGICFX::NUM_SPELL_POWER)
{
destPB.ProjectileAspect = ¤tAttack->ProjectileFX;
}
else
{
destPB.ProjectileAspect = NULL;
}
// choose fx for impact
if (!playImpactFX)
{
destPB.ImpactAspect = NULL;
}
else
{
if (attackInfo.Intensity >= 1 && attackInfo.Intensity <= MAGICFX::NUM_SPELL_POWER) // impact has same intensity than projectile
{
destPB.ImpactAspect = ¤tAttack->ImpactFX;
}
else
{
destPB.ImpactAspect = NULL;
}
}
// FILL PROJECTILE BUILD
destPB.AttackInfo = attackInfo;
destPB.TargeterInfo.Slot = slot();
//
destPB.LocalizedImpact = sheet.IsImpactLocalised;
// If this is a secondary projectile, it may start from another location, which is the impact point of the previous projectile
// (so it doesn't start from the caster hand, or any around settings that is read from the spell sheet)
if (sm) // start stickmode wanted ?
{
destPB.TargeterInfo.StickMode = *sm;
}
else
{
// if no stick mode is not forced, then use the one given in the projectile sheet
if (destPB.ProjectileAspect && !destPB.ProjectileAspect->FX.empty())
{
destPB.TargeterInfo.StickMode = destPB.ProjectileAspect->FX[0].Sheet->StickMode;
}
else
{
// if no projectile is given, then uses the default casting point
destPB.TargeterInfo.StickMode = sheet.DefaultCastingPoint;
}
}
destPB.Mode = sheet.ProjectileMode;
destPB.Target.Slot = target.slot();
destPB.TargeterInfo.StickOffset = CVector::Null;
destPB.PlayImpactAnim = playImpactAnim;
destPB.LetProjectileStickedOnTarget = sheet.LetProjectileStickedOnTarget;
destPB.TargeterInfo.DefaultPos = pos().asVector();
//
destPB.MagicResist = magicResist;
// offset if projectile is launched from a range weapon and projectile is sticked to box_arme
if (destPB.TargeterInfo.StickMode.Mode == CFXStickMode::UserBone && sheet.ApplyItemOffsetToWeaponBone)
{
// should be fired from the 'box_arme' bone, which means it is fired from a weapon
if (CStringMapper::unmap(destPB.TargeterInfo.StickMode.UserBoneName) == "box_arme")
{
NLMISC::CVector projectileOffset = NLMISC::CVector::Null;
const CItemSheet *is = getRightHandItemSheet();
if (is)
{
destPB.TargeterInfo.StickOffset = is->FX.AttackFXOffset;
}
destPB.TargeterInfo.StickOffset += additionnalOffset;
}
}
destPB.TargeterInfo.StickOffset += sheet.AdditionnalStartOffset;
return true;
}
// *********************************************************************************************
void CCharacterCL::computeBestCastRay(CEntityCL &targetEntity,
const CFXStickMode &targetStickMode,
NLMISC::CVector &castWorldOrigin,
NLMISC::CVector &castWorldPos,
NLMISC::CVector &worldOffsetToCasterPivot
) const
{
// additionnal offset taken from sheet. Useful for towers that have no bones, but can fire projectiles anyway
nlassert(_Sheet && !_Sheet->ProjectileCastRay.empty());
// if several offsets are provided, then choose the one that has the smallest angle towards target
CVector target;
CProjectileManager::evalFXPosition(&targetStickMode, targetEntity, target);
float maxDP3 = -FLT_MAX;
// NB : the offset is relative to object pivot, not to a bone
CMatrix casterMatrix;
buildAlignMatrix(casterMatrix);
for(uint k = 0; k < _Sheet->ProjectileCastRay.size(); ++k)
{
CVector currCastWorldPos = casterMatrix * _Sheet->ProjectileCastRay[k].Pos;
CVector currCastWorldOrigin = casterMatrix * _Sheet->ProjectileCastRay[k].Origin;
float dp3 = (target - currCastWorldPos).normed() * (currCastWorldPos - currCastWorldOrigin).normed();
if (dp3 > maxDP3)
{
maxDP3 = dp3;
worldOffsetToCasterPivot = casterMatrix.mulVector(_Sheet->ProjectileCastRay[k].Pos);
castWorldOrigin = currCastWorldOrigin;
castWorldPos = currCastWorldPos;
}
}
}
// *********************************************************************************************
bool CCharacterCL::isCurrentBehaviourAttackEnd() const
{
switch(_CurrentBehaviour.Behaviour)
{
case MBEHAV::CAST_OFF_SUCCESS:
case MBEHAV::CAST_OFF_LINK:
case MBEHAV::CAST_CUR_SUCCESS:
case MBEHAV::CAST_CUR_LINK:
case MBEHAV::CAST_MIX_SUCCESS:
case MBEHAV::CAST_MIX_LINK:
case MBEHAV::RANGE_ATTACK:
case MBEHAV::CREATURE_ATTACK_0:
case MBEHAV::CREATURE_ATTACK_1:
case MBEHAV::DEFAULT_ATTACK:
case MBEHAV::POWERFUL_ATTACK:
case MBEHAV::AREA_ATTACK:
return true;
default:
break;
}
return false;
}
// ***************************************************************************
void CCharacterCL::applyBehaviourFlyingHPs(const CBehaviourContext &bc, const MBEHAV::CBehaviour &behaviour,
const vector &targetHitDates)
{
nlassert(targetHitDates.size()==bc.Targets.Targets.size());
if(!bc.Targets.Targets.empty())
{
if(behaviour.DeltaHP != 0)
{
CRGBA deltaHPColor(0, 0, 0);
// if it's a hit
if( behaviour.DeltaHP < 0 )
{
// if the behaviour is casted by the user
if( slot() == 0 )
{
deltaHPColor = ClientCfg.SystemInfoParams["dgm"].Color;
}
else
// if the behaviour is casted by an entity that target the user
if( targetSlot() == 0 )
{
CEntityCL *actor = EntitiesMngr.entity(slot());
if( actor )
{
// if actor is player : use pvp color
if( actor->isPlayer() )
deltaHPColor = ClientCfg.SystemInfoParams["dgp"].Color;
else
deltaHPColor = ClientCfg.SystemInfoParams["dg"].Color;
}
}
else
{
deltaHPColor = CRGBA(127,127,127);
}
}
else
{
deltaHPColor = CRGBA(0,220,0);
}
// Set the delta HP
for (size_t i=0; iaddHPOutput(behaviour.DeltaHP, deltaHPColor, float(targetHitDates[i]-TimeInSec));
}
}
}
}
//-----------------------------------------------
// Apply the behaviour.
// \param behaviour : the behaviour to apply.
//-----------------------------------------------
void CCharacterCL::applyBehaviour(const CBehaviourContext &bc) // virtual
{
// Backup the current behaviour.
CBehaviour previousBehaviour = _CurrentBehaviour;
_CurrentBehaviour = bc.Behav;
const CBehaviour &behaviour = bc.Behav;
// check if self-target
bool selfSpell = false;
if (bc.Targets.Targets.size() == 1)
{
if (bc.Targets.Targets[0].TargetSlot == 0)
{
selfSpell = true;
}
}
bool isOffensif;
switch(behaviour.Behaviour)
{
case MBEHAV::CAST_OFF:
case MBEHAV::CAST_OFF_FAIL:
case MBEHAV::CAST_OFF_SUCCESS:
case MBEHAV::CAST_OFF_LINK:
isOffensif = true;
break;
default:
isOffensif = false;
break;
}
// Get a pointer on the target.
CEntityCL *target = EntitiesMngr.entity(targetSlot());
// ***** Choose Combat Animation to apply
TAnimStateKey combatAnimState= CAnimationStateSheet::UnknownState;
bool isMovingCombatAnimState= false;
// if the behaviour is for combat, compute now the animation to apply
if( !behaviour.isMagic() && ( behaviour.isCombat() || behaviour.isCreatureAttack() ) )
{
// Atk Animation when moving
if(_CurrentState && _CurrentState->Move && (_CurrentState->OnAtk != CAnimationStateSheet::UnknownState))
{
combatAnimState= _CurrentState->OnAtk;
isMovingCombatAnimState= true;
}
// Atk Animation when NOT moving
else
{
// select the combat animation.
switch(behaviour.Behaviour)
{
case CREATURE_ATTACK_0:
combatAnimState= CAnimationStateSheet::Attack1;
break;
case CREATURE_ATTACK_1:
combatAnimState= CAnimationStateSheet::Attack2;
break;
// Default Animation
case DEFAULT_ATTACK:
switch(getAttackHeight(target, (BODY::TBodyPart)behaviour.Combat.Localisation, BODY::Right))
{
// LOW
case CCharacterCL::AtkLow:
combatAnimState= CAnimationStateSheet::DefaultAtkLow;
break;
// HIGH
case CCharacterCL::AtkHigh:
combatAnimState= CAnimationStateSheet::DefaultAtkHigh;
break;
// MIDDLE or Default
case CCharacterCL::AtkMiddle:
default:
combatAnimState= CAnimationStateSheet::DefaultAtkMiddle;
break;
}
break;
// Powerful Animation
case POWERFUL_ATTACK:
switch(getAttackHeight(target, (BODY::TBodyPart)behaviour.Combat.Localisation, BODY::Right))
{
// LOW
case CCharacterCL::AtkLow:
combatAnimState= CAnimationStateSheet::PowerfulAtkLow;
break;
// HIGH
case CCharacterCL::AtkHigh:
combatAnimState= CAnimationStateSheet::PowerfulAtkHigh;
break;
// MIDDLE or Default
case CCharacterCL::AtkMiddle:
default:
combatAnimState= CAnimationStateSheet::PowerfulAtkMiddle;
break;
}
break;
// Area Animation
case AREA_ATTACK:
switch(getAttackHeight(target, (BODY::TBodyPart)behaviour.Combat.Localisation, BODY::Right))
{
// LOW
case CCharacterCL::AtkLow:
combatAnimState= CAnimationStateSheet::AreaAtkLow;
break;
// HIGH
case CCharacterCL::AtkHigh:
combatAnimState= CAnimationStateSheet::AreaAtkHigh;
break;
// MIDDLE or Default
case CCharacterCL::AtkMiddle:
default:
combatAnimState= CAnimationStateSheet::AreaAtkMiddle;
break;
}
break;
// Range Animation
case RANGE_ATTACK:
combatAnimState= CAnimationStateSheet::Attack1;
break;
default:
break;
}
}
}
// ***** Compute Impact delays and cast missiles
// Default target hit dates
static vector targetHitDates;
targetHitDates.clear();
targetHitDates.resize(bc.Targets.Targets.size(), TimeInSec);
// Update Attack Projectiles and FXs
updateCurrentAttack();
if (isCurrentBehaviourAttackEnd())
{
// retrieve target hit dates, so flying HPs have the correct ones
performCurrentAttackEnd(bc, selfSpell && isOffensif, targetHitDates, combatAnimState);
}
// INFO : display some debug information.
if((VerboseAnimUser && _Slot==0) || (VerboseAnimSelection && _Slot == UserEntity->selection()))
nlinfo("CH:applyBeh:%d: '%d(%s)'", _Slot, (sint)behaviour.Behaviour, behaviourToString((EBehaviour)behaviour.Behaviour).c_str());
// ***** Apply the behaviour according to type
// This is a behaviour for the magic.
if(behaviour.isMagic())
{
// Execute the magic behaviour.
switch(behaviour.Behaviour)
{
////////////////
// BEGIN CAST //
case MBEHAV::CAST_OFF:
case MBEHAV::CAST_CUR:
case MBEHAV::CAST_MIX:
case MBEHAV::CAST_ACID:
case MBEHAV::CAST_BLIND:
case MBEHAV::CAST_COLD:
case MBEHAV::CAST_ELEC:
case MBEHAV::CAST_FEAR:
case MBEHAV::CAST_FIRE:
case MBEHAV::CAST_HEALHP:
case MBEHAV::CAST_MAD:
case MBEHAV::CAST_POISON:
case MBEHAV::CAST_ROOT:
case MBEHAV::CAST_ROT:
case MBEHAV::CAST_SHOCK:
case MBEHAV::CAST_SLEEP:
case MBEHAV::CAST_SLOW:
case MBEHAV::CAST_STUN:
beginCast(behaviour);
break;
//////////////
// END CAST //
case MBEHAV::CAST_OFF_FAIL:
case MBEHAV::CAST_OFF_FUMBLE:
if (!selfSpell) endCast(behaviour, previousBehaviour);
break;
case MBEHAV::CAST_OFF_SUCCESS:
case MBEHAV::CAST_OFF_LINK:
endCast(behaviour, previousBehaviour);
break;
case MBEHAV::CAST_CUR_FAIL:
case MBEHAV::CAST_CUR_FUMBLE:
endCast(behaviour, previousBehaviour);
break;
case MBEHAV::CAST_CUR_SUCCESS:
case MBEHAV::CAST_CUR_LINK:
endCast(behaviour, previousBehaviour);
break;
case MBEHAV::CAST_MIX_FAIL:
case MBEHAV::CAST_MIX_FUMBLE:
endCast(behaviour, previousBehaviour);
break;
case MBEHAV::CAST_MIX_SUCCESS:
case MBEHAV::CAST_MIX_LINK:
endCast(behaviour, previousBehaviour);
break;
default:
break;
}
// DeltaHP
applyBehaviourFlyingHPs(bc, behaviour, targetHitDates);
}
// This is a behaviour for the combat.
else if(behaviour.isCombat() || behaviour.isCreatureAttack())
{
float frontYawBefore = 0.f;
float frontYawAfter = 0.f;
// Atk Animation when NOT moving?
if(target && !isMovingCombatAnimState)
{
// orientate to target
CVectorD dirToTarget = target->pos() - pos();
dirToTarget.z = 0;
dirToTarget.normalize();
if( !(isUser() && ClientCfg.AutomaticCamera == false) )
{
// backup front yaw
frontYawBefore = frontYaw();
front( dirToTarget );
}
dir( dirToTarget );
}
// Apply the state animation chosen before
if(combatAnimState!=CAnimationStateSheet::UnknownState)
setAnim(combatAnimState);
// move camera so view doesn't change
if( isUser() && frontYawBefore != 0.f )
{
frontYawAfter = frontYaw();
float deltaYaw = frontYawAfter - frontYawBefore;
if( deltaYaw !=0 )
{
UserControls.appendCameraDeltaYaw(-deltaYaw);
}
}
// reset yaw smoothly to center view behind user
if( isUser() && target && !target->isUser() && ClientCfg.AutomaticCamera )
{
UserControls.resetSmoothCameraDeltaYaw();
}
// DeltaHP
applyBehaviourFlyingHPs(bc, behaviour, targetHitDates);
}
// Emote
else if(behaviour.isEmote())
{
if(ClientCfg.Light==false && ClientCfg.EAMEnabled)
{
TAnimStateId emot;
if (EAM)
{
/*
// old code : fxs attached to emotes
uint emoteIndex = behaviour.Behaviour-EMOTE_BEGIN;
CTextEmotListSheet *pTELS = dynamic_cast(SheetMngr.get(CSheetId("list.text_emotes")));
if (pTELS)
{
if (emoteIndex < pTELS->TextEmotList.size())
{
const CTextEmotListSheet::STextEmot &emot = pTELS->TextEmotList[emoteIndex];
if (!emot.FXToSpawn.empty())
{
// Compute the direction Matrix
CMatrix fxMatrix;
CVector vi = dir() ^ CVector::K;
CVector vk = vi ^ dir();
fxMatrix.setRot(vi, UserEntity->dir(), vk, true);
fxMatrix.setPos(pos().asVector() + fxMatrix.getJ() * emot.FXSpawnDist);
FXMngr.deferFX(emot.FXToSpawn, fxMatrix, emot.FXSpawnDelay);
}
}
}
*/
if(EAM->getEmot(behaviour.Behaviour-EMOTE_BEGIN, emot))
setAnim(CAnimationStateSheet::Emote, emot);
else
nlwarning("CH:applyBeh:%d: Emot '%d' unknown.", _Slot, behaviour.Behaviour-EMOTE_BEGIN);
}
}
}
// Others
else
{
switch(behaviour.Behaviour)
{
// Loot Begin
case MBEHAV::LOOT_INIT:
setAnim(CAnimationStateSheet::LootInit);
break;
// Loot End
case MBEHAV::LOOT_END:
setAnim(CAnimationStateSheet::LootEnd);
break;
// Prospecting Begin
case MBEHAV::PROSPECTING:
setAnim(CAnimationStateSheet::ProspectingInit);
break;
// Prospecting End
case MBEHAV::PROSPECTING_END:
setAnim(CAnimationStateSheet::ProspectingEnd);
break;
// Extracting Begin
case MBEHAV::EXTRACTING:
// DeltaHP
if(target)
if(behaviour.DeltaHP != 0)
target->addHPOutput(behaviour.DeltaHP,CRGBA(0,220,0));
// If receiving a new DeltaHP in the current extraction, don't reset the animation
if ( previousBehaviour.Behaviour != _CurrentBehaviour.Behaviour )
setAnim(CAnimationStateSheet::UseInit);
break;
// Extracting End
case MBEHAV::EXTRACTING_END:
setAnim(CAnimationStateSheet::UseEnd);
break;
// Care Begin
case MBEHAV::CARE:
setAnim(CAnimationStateSheet::CareInit);
break;
// Care End
case MBEHAV::CARE_END:
setAnim(CAnimationStateSheet::CareEnd);
break;
// Begin to use a tool
case MBEHAV::HARVESTING:
case MBEHAV::FABER:
case MBEHAV::REPAIR:
case MBEHAV::REFINE:
case MBEHAV::TRAINING:
setAnim(CAnimationStateSheet::UseInit);
break;
// End to use a tool
case MBEHAV::HARVESTING_END:
case MBEHAV::FABER_END:
case MBEHAV::REPAIR_END:
case MBEHAV::REFINE_END:
case MBEHAV::TRAINING_END:
setAnim(CAnimationStateSheet::UseEnd);
break;
// Begin Stun
case MBEHAV::STUNNED:
setAnim(CAnimationStateSheet::StunBegin);
break;
// End Stun
case MBEHAV::STUN_END:
setAnim(CAnimationStateSheet::StunEnd);
break;
// Idle
case IDLE:
break;
// Unknown behaviour -> idle.
case UNKNOWN_BEHAVIOUR:
default:
nlwarning("CH::computeBehaviour : Entity in slot %d has an unknown behaviour %d to manage.", _Slot, (sint)behaviour.Behaviour);
break;
}
}
}// computeBehaviour //
//-----------------------------------------------
// impact :
// Play an impact on the entity
// \param impactType : 0=magic, 1=melee
// \param type : see behaviour for spell
// \param intensity : see behaviour for spell
// \param id : see behaviour for spell
//-----------------------------------------------
void CCharacterCL::impact(uint /* impactType */, uint type, uint id, uint intensity) // virtual
{
// Display Magic Debug Infos
if(Verbose & VerboseMagic)
nlinfo("CH:impact:%d: type: %d, id: %d, intensity: %d", _Slot, type, id, intensity);
// No Intensity -> No Impact
if(intensity==0)
return;
// Invalid Intensity -> No Impact
else if(intensity>5)
{
nlwarning("CH:impact:%d: invalid intensity %u", _Slot, intensity);
return;
}
// RESIST : temp until resist is in the enum.
if(type==0)
{
// Create the FX
NL3D::UInstance resistFX = Scene->createInstance("Sp_Resist_Lev5.ps");
if(!resistFX.empty())
resistFX.setPos(pos());
return;
}
// Compute the impact name
string impact;
if(id < ClientCfg.OffImpactFX.size())
impact = ClientCfg.OffImpactFX[id];
// Create the FX
if(!impact.empty())
{
NL3D::UInstance impactFX = Scene->createInstance(impact);
if(!impactFX.empty())
{
impactFX.setPos(pos());
UParticleSystemInstance instFX;
instFX.cast (impactFX);
if(!instFX.empty())
{
// UserParam | Intensity 1 | Intensity 2 | Intensity 3 | Intensity 4 | Intensity 5
// 0 | 0 | 0 | 1 | 1 | 1
// 1 | 0 | 1 | 1 | 1 | 1
// 2 | 0 | 0 | 0 | 1 | 1
// 3 | 0 | 0 | 0 | 0 | 1
float userParam0 = 0.0f;
float userParam1 = 0.0f;
float userParam2 = 0.0f;
float userParam3 = 0.0f;
// WARNING : there is no break and this is correct.
switch(intensity)
{
case 5:
userParam3 = 1.0f;
case 4:
userParam2 = 1.0f;
case 3:
userParam0 = 1.0f;
case 2:
userParam1 = 1.0f;
}
instFX.setUserParam(0, userParam0);
instFX.setUserParam(1, userParam1);
instFX.setUserParam(2, userParam2);
instFX.setUserParam(3, userParam3);
}
}
}
}// impact //
//-----------------------------------------------
// meleeImpact ::
//-----------------------------------------------
void CCharacterCL::meleeImpact(const CAttackInfo &attack)
{
if (_Skeleton.empty()) return;
if (attack.PhysicalImpactIntensity < 1 || attack.PhysicalImpactIntensity > 5) return;
if (attack.HitType == HITTYPE::Failed) return;
const char *boneName = getBoneNameFromBodyPart(attack.Localisation, attack.Side);
if (!boneName) return;
sint boneId = _Skeleton.getBoneIdByName(std::string(boneName));
if (boneId == -1) return;
if (!Scene) return;
// choose good fx depending on the kind of damage
NL3D::UInstance instance;
switch(attack.DamageType)
{
case DMGTYPE::BLUNT: instance = Scene->createInstance("mel_impactblunt.ps"); break;
case DMGTYPE::SLASHING: instance = Scene->createInstance("mel_impactslashing.ps"); break;
case DMGTYPE::PIERCING: instance = Scene->createInstance("mel_impactpiercing.ps"); break;
default:
return; // other types not supported
break;
}
if (instance.empty()) return;
UParticleSystemInstance impact;
impact.cast (instance);
if (impact.empty())
{
Scene->deleteInstance(instance);
return;
}
// the 2 first user params of the fx are used to modulate intensity
static const float intensityUP[5][2] =
{
{ 0.f, 0.f},
{ 0.f, 0.5f},
{ 0.5f, 0.5f},
{ 0.5f, 1.f},
{ 1.f, 1.f}
};
impact.setUserParam(0, intensityUP[attack.PhysicalImpactIntensity - 1][0]);
impact.setUserParam(1, intensityUP[attack.PhysicalImpactIntensity - 1][1]);
impact.setUserParam(2, (attack.HitType == HITTYPE::CriticalHit || attack.HitType == HITTYPE::CriticalHitResidual) ? 1.f : 0.f);
impact.setUserParam(3, (attack.HitType == HITTYPE::HitResidual || attack.HitType == HITTYPE::CriticalHitResidual) ? 1.f : 0.f);
//
_Skeleton.stickObject(impact, boneId);
// delegate managment of the impact to the fx manager
FXMngr.fx2remove(impact);
}// meleeImpact //
//-----------------------------------------------
// magicImpact :
// Play the magic impact on the entity
// \param type : type of the impact (host/good/neutral).
// \param intensity : intensity of the impact.
//-----------------------------------------------
void CCharacterCL::magicImpact(uint type, uint intensity) // virtual
{
// --- FX --- //
// Choose the FX.
string impact;
switch(type)
{
// Resist
case 0:
impact = "Sp_Resist_Lev";
break;
// Good
case 1:
impact = "Sp_Bien_Cure_Lev";
break;
// Neutral
case 2:
impact = "Sp_Neutre_Protect_Lev";
break;
// Bad
case 3:
impact = "Sp_Host_Hurt_Lev";
break;
default:
nlwarning("CH:magicImpact:%d: Unknown type '%d'.", _Slot, type);
return;
}
// Intensity
switch(intensity)
{
// Too weak
case INTENSITY_TYPE::IMPACT_NONE:
return;
case INTENSITY_TYPE::IMPACT_INSIGNIFICANT:
impact += "1.ps";
break;
case INTENSITY_TYPE::IMPACT_VERY_WEAK:
impact += "2.ps";
break;
case INTENSITY_TYPE::IMPACT_WEAK:
impact += "3.ps";
break;
case INTENSITY_TYPE::IMPACT_AVERAGE:
impact += "4.ps";
break;
case INTENSITY_TYPE::IMPACT_STRONG:
impact += "5.ps";
break;
// Unknown
default:
nlwarning("CH:magicImpact:%d: Unknown intensity '%d'.", _Slot, intensity);
}
// Create the FX
if(!impact.empty())
{
NL3D::UInstance resistFX = Scene->createInstance(impact);
if(!resistFX.empty())
resistFX.setPos(pos());
}
}// resist //
//-----------------------------------------------
// getMaxSpeed :
// Return the basic max speed for the entity in meter per sec
//-----------------------------------------------
double CCharacterCL::getMaxSpeed() const // virtual
{
return _Sheet->MaxSpeed;
}// getMaxSpeed //
//-----------------------------------------------
// mode :
// Method called to change the mode (Combat/Mount/etc.).
//-----------------------------------------------
bool CCharacterCL::mode(MBEHAV::EMode m)
{
// DEBUG INFOS
if(VerboseAnimSelection && _Slot == UserEntity->selection())
{
nlinfo("CH::mode:%d: m'%s(%d)', _ModeWanted'%s(%d)', _Mode'%s(%d)'", _Slot,
MBEHAV::modeToString(m ).c_str(), m,
MBEHAV::modeToString(_ModeWanted).c_str(), _ModeWanted,
MBEHAV::modeToString(_Mode ).c_str(), _Mode);
}
// Is the mode wanted valid ?
if(m == MBEHAV::UNKNOWN_MODE || m >= MBEHAV::NUMBER_OF_MODES)
{
nlwarning("CH::mode:%d: Invalid Mode Wanted '%s(%d)' -> keep '%s(%d)'.",
_Slot, MBEHAV::modeToString(m).c_str(), m, MBEHAV::modeToString(_Mode).c_str(), _Mode);
return false;
}
// Set the mode wanted.
_ModeWanted = m;
if(_CurrentState == 0)
_Mode = _ModeWanted;
return true;
}// mode //
//-----------------------------------------------
// applyStage :
// Apply stage modifications.
// \todo GUIGUI ; ameliorer gestion mode.
//-----------------------------------------------
bool CCharacterCL::applyStage(CStage &stage)
{
bool stageDone = true;
// If the Stage has a position, backup the position and the stage time.
if(stage.getPos(_OldPos))
{
// Backup the time
_OldPosTime = stage.time();
// Remove the property.
stage.removeProperty(PROPERTY_POSX);
stage.removeProperty(PROPERTY_POSY);
stage.removeProperty(PROPERTY_POSZ);
}
// Apply orientation.
pair resultTeta = stage.property(PROPERTY_ORIENTATION);
if(resultTeta.first)
{
float angleZ = *(float *)(&resultTeta.second);
// server forces the entity orientation even if it cannot turn
front(CVector((float)cos(angleZ), (float)sin(angleZ), 0.f), true, true, true);
_TargetAngle = (float)angleZ;
// Remove the property.
stage.removeProperty(PROPERTY_ORIENTATION);
}
// Apply Mode.
pair resultMode = stage.property(PROPERTY_MODE);
if(resultMode.first)
{
// Get the mode from stage.
uint8 mo = *(uint8 *)(&resultMode.second);
// If the mode wanted is not the same, change the mode wanted.
if(mo != _ModeWanted)
{
if(mode((MBEHAV::EMode)mo))
{
//stageDone = false;
if(_Mode != _ModeWanted)
stageDone = false;
else
stage.removeProperty(PROPERTY_MODE);
}
else
stage.removeProperty(PROPERTY_MODE);
}
// If the mode wanted is not the same as the current mode -> Stage not done.
else if(_Mode != _ModeWanted)
stageDone = false;
// Property applied -> Remove the property form stage.
else
stage.removeProperty(PROPERTY_MODE);
}
// Apply Behaviour.
pair resultBehaviour = stage.property(PROPERTY_BEHAVIOUR);
if(resultBehaviour.first)
{
CBehaviourContext bc;
bc.Behav = CBehaviour(resultBehaviour.second);
bc.BehavTime = stage.time();
// See if there's a list of target associated with that behaviour (for multitarget spells)
uint64 spellTarget[4];
uint numTargets = 0;
for(uint k = 0; k < 4; ++k)
{
pair stProp = stage.property(PROPERTY_TARGET_LIST_0 + k);
if (!stProp.first) break;
spellTarget[k] = (uint64) stProp.second;
++ numTargets;
stage.removeProperty(PROPERTY_TARGET_LIST_0 + k);
}
if (numTargets > 0)
{
// get the list of targets from the visual properties
bc.Targets.unpack(spellTarget, numTargets);
}
// Compute the beheviour.
applyBehaviour(bc);
// Remove the property.
stage.removeProperty(PROPERTY_BEHAVIOUR);
}
// Apply the target.
pair resultTarget = stage.property(PROPERTY_TARGET_ID);
if(resultTarget.first)
{
// Change the entity target.
targetSlot((CLFECOMMON::TCLEntityId)resultTarget.second);
// Remove the property.
stage.removeProperty(PROPERTY_TARGET_ID);
}
// The Mount
pair resultParent = stage.property(CLFECOMMON::PROPERTY_ENTITY_MOUNTED_ID);
if(resultParent.first)
{
_Mount = (CLFECOMMON::TCLEntityId)resultParent.second;
// Remove the property.
stage.removeProperty(CLFECOMMON::PROPERTY_ENTITY_MOUNTED_ID);
}
// The Rider
pair resultRider = stage.property(CLFECOMMON::PROPERTY_RIDER_ENTITY_ID);
if(resultRider.first)
{
_Rider = (CLFECOMMON::TCLEntityId)resultRider.second;
// Remove the property.
stage.removeProperty(CLFECOMMON::PROPERTY_RIDER_ENTITY_ID);
}
// visual fxs : links and auras
pair resultVisualFX = stage.property(CLFECOMMON::PROPERTY_VISUAL_FX);
if (resultVisualFX.first)
{
applyVisualFX(resultVisualFX.second);
stage.removeProperty(CLFECOMMON::PROPERTY_VISUAL_FX);
}
return stageDone;
}// applyStage //
//-----------------------------------------------
// applyCurrentStage :
// Apply The Current Stage (first stage).
//-----------------------------------------------
bool CCharacterCL::applyCurrentStage()
{
bool bRet = true;
CStageSet::TStageSet::iterator it = _Stages._StageSet.begin();
if(it != _Stages._StageSet.end())
{
// Apply the Stage and remove it if stage done.
if(applyStage((*it).second))
_Stages._StageSet.erase(it);
else
bRet = false;
}
else
nlwarning("CCharacterCL::applyCurrentStage: there is no stage.");
// Update information from remaining stages.
updateStages();
return bRet;
}// applyCurrentStage //
//-----------------------------------------------
// applyAllStagesToFirstPos :
// Apply all stages to the first stage with a position.
//-----------------------------------------------
void CCharacterCL::applyAllStagesToFirstPos()
{
CVectorD stagePos;
CStageSet::TStageSet::iterator it = _Stages._StageSet.begin();
while(it != _Stages._StageSet.end() && !(*it).second.getPos(stagePos))
{
// Apply the Stage.
if(!applyStage((*it).second))
{
updateStages();
return;
}
// Backup the iterator to remove
CStageSet::TStageSet::iterator itTmp = it;
// Next Stage.
++it;
// Remove the stage done.
_Stages._StageSet.erase(itTmp);
}
// Apply the stage with the position.
if(it != _Stages._StageSet.end())
{
// Apply the Stage.
if(applyStage((*it).second))
// Remove the stage done.
_Stages._StageSet.erase(it);
}
else
nlwarning("CH:applyAllStagesToFirstPos:%d: There is no stage with a position.", _Slot);
// Upate information from remaining stages.
updateStages();
}// applyAllStagesToFirstPos //
//-----------------------------------------------
// playToEndAnim :
// Play the time step for the loop and truncate to End Anim if Time Step too big.
//-----------------------------------------------
ADD_METHOD(void CCharacterCL::playToEndAnim(const double &startTimeOffset, double &length))
// Average Speed
double speedToDest = speed();
// Blend Walk/Run
if(ClientCfg.BlendForward && (animState(MOVE) == CAnimationStateSheet::Walk))
{
uint animWalkId = animId(MOVE);
uint animRunId = animId(MOVE_BLEND_OUT);
double animWalkSpeed = EAM->getAnimationAverageSpeed(animWalkId)*getSheetScale()*_CustomScalePos*_CharacterScalePos;
double animRunSpeed = EAM->getAnimationAverageSpeed(animRunId)*getSheetScale()*_CustomScalePos*_CharacterScalePos;
if(animWalkSpeed<=animRunSpeed)
{
double startTimeOffRun = animOffset(MOVE_BLEND_OUT);
double animWalkLength = EAM->getAnimationLength(animWalkId);
double animRunLength = EAM->getAnimationLength(animRunId);
// Current Speed <= Walk Speed, so use the walk animation only.
if(speed() <= animWalkSpeed)
{
runFactor(0.0);
double speedFactor = speed()/animWalkSpeed;
double animTimeOffWalk = animOffset(MOVE) + length*speedFactor;
if(animTimeOffWalk > animWalkLength)
{
animOffset(MOVE, animWalkLength);
animOffset(MOVE_BLEND_OUT, animRunLength);
length = (animWalkLength - startTimeOffset) / speedFactor;
}
// Adjust Time Offset for the Run Channel
else
{
animOffset(MOVE, animTimeOffWalk);
animOffset(MOVE_BLEND_OUT, animRunLength*(animTimeOffWalk/animWalkLength));
}
}
// Current Speed >= Run Speed, so use the run animation only.
else if(speed() >= animRunSpeed)
{
runFactor(1.0);
double speedFactor = speed()/animRunSpeed;
double animTimeOffRun = animOffset(MOVE_BLEND_OUT) + length*speedFactor;
if(animTimeOffRun > animRunLength)
{
animOffset(MOVE, animWalkLength);
animOffset(MOVE_BLEND_OUT, animRunLength);
length = (animRunLength - startTimeOffRun) / speedFactor;
}
// Adjust Time Offset for the Walk Channel
else
{
animOffset(MOVE, animWalkLength*(animTimeOffRun/animRunLength));
animOffset(MOVE_BLEND_OUT, animTimeOffRun);
}
}
// Current Speed > Walk Speed & < Run Speed, so mix Walk and Run animation.
else
{
double t1 = animRunSpeed-animWalkSpeed;
double t2 = speed()-animWalkSpeed;
runFactor(t2/t1);
double mixLength = runFactor()*animRunLength + (1.0-runFactor())*animWalkLength;
double animTimeOffWalk = animOffset(MOVE) + animWalkLength/mixLength*length;
if(animTimeOffWalk > animWalkLength)
{
animOffset(MOVE, animWalkLength);
animOffset(MOVE_BLEND_OUT, animRunLength);
length = (animWalkLength - startTimeOffset) / (animWalkLength/mixLength);
}
else
{
animOffset(MOVE, animTimeOffWalk);
animOffset(MOVE_BLEND_OUT, animRunLength*animTimeOffWalk/animWalkLength); // Same percentage in the animation than the Walk one.
}
}
return;
}
//else
//nlwarning("playToEndAnim:%d: animWalkSpeed > animRunSpeed", _Slot);
}
// No Mix between Walk and Run.
runFactor(0.0);
// Speed Factor
double speedFactor = computeSpeedFactor(speedToDest);
// Compute the desired new time offset.
double animTimeOffMove = animOffset(MOVE) + length * speedFactor;
// Truncate animation time offset if it over-runs end of animation and change the loopTimeOffset too.
double animationLength = EAM->getAnimationLength(animId(MOVE));
if(animTimeOffMove > animationLength)
{
animOffset(MOVE, animationLength);
length = (animationLength - startTimeOffset) / speedFactor;
}
else
animOffset(MOVE, animTimeOffMove);
}// playToEndAnim //
//-----------------------------------------------
// updateStages :
// Call this method to give a time for each stage, compute distance to destination and some more information.
// \todo GUIGUI : clean up
//-----------------------------------------------
void CCharacterCL::updateStages()
{
H_AUTO ( RZ_Client_Entity_CL_updateStages );
_FirstPos = INVALID_POS; // No First Position
_FirstTime = INVALID_TIME; //- No First Position
dist2FirstPos(INVALID_DIST); // No First Position
_DestPos = INVALID_POS; // No Destination
_DestTime = INVALID_TIME; // No Destination
dist2Dest(INVALID_DIST); // No Destination
CVectorD posTmp = pos();
_IsThereAMode = false;
_ImportantStepTime= 0.0;
// ***** update predicted interval: if a new pos B is found after a pos A, then the interval B-A is known!
CStageSet::TStageSet::iterator it = _Stages._StageSet.begin();
CStageSet::TStageSet::iterator itPosPrec= _Stages._StageSet.end();
bool somePosFoundEarly= false;
while(it != _Stages._StageSet.end())
{
// if this stage has a position
if(it->second.isPresent(PROPERTY_POSITION))
{
somePosFoundEarly= true;
// then it's cool we can set the new accurate interval to the prec stage which has a pos
if(itPosPrec!=_Stages._StageSet.end())
{
uint dgc= it->first - itPosPrec->first;
itPosPrec->second.predictedInterval(dgc);
}
// bkup
itPosPrec= it;
}
it++;
}
// ***** Compute the current LCT Impact for this character
// NB: used only in mode CClientConfig::StageUsePosOnlyLCT
sint32 charLCTI;
// If disabled, full LCT impact
if(_StartDecreaseLCTImpact==0)
charLCTI= 256;
else
{
const sint32 decreaseTick= 20; // 2 seconds
// blend according to the start of decrease
sint32 dt= NetMngr.getCurrentServerTick() - _StartDecreaseLCTImpact;
if(dt<=0)
charLCTI= 256;
else if(dt>=decreaseTick)
charLCTI= 0;
else
charLCTI= ((decreaseTick-dt)*256)/decreaseTick;
// hence, at end of blend, charLCTI is 0
}
// ***** Compute Stages to give them a time and get some information from those stages.
// yoyo: use any stage with no LCT, until it is to be played AFTER a position
bool stageForceLCTFound= false;
CStageSet::TStageSet::iterator itTmp;
it = _Stages._StageSet.begin();
while(it != _Stages._StageSet.end())
{
CStage &stage = (*it).second;
// *** retrieve position in stage if any
CVectorD posInStage;
bool hasPos= stage.getPos(posInStage);
// check the first pos is correct
if(hasPos && dist2Dest()==INVALID_DIST)
{
// Compute the distance to the first position
double distToFirst = (CVectorD(posInStage.x, posInStage.y, 0.0) - CVectorD( posTmp.x, posTmp.y, 0.0)).norm();
double distToLimiter = (CVectorD(posInStage.x, posInStage.y, 0.0) - CVectorD(_PositionLimiter.x, _PositionLimiter.y, 0.0)).norm();
// Check if the first pos is Not the same as the current entity pos
if((distToFirst < ClientCfg.DestThreshold)
|| (distToLimiter <= ClientCfg.PositionLimiterRadius))
{
// The FIRST POSITION is the SAME as the CURRENT entity POSITION -> REMOVE POSITION in the stage
stage.removeProperty(CLFECOMMON::PROPERTY_POSX);
stage.removeProperty(CLFECOMMON::PROPERTY_POSY);
stage.removeProperty(CLFECOMMON::PROPERTY_POSZ);
hasPos= false;
//
if(VerboseAnimSelection && _Slot == UserEntity->selection())
nlinfo("CH:updateStages:%d: Bad First, distToFirst(%f), ClientCfg.DestThreshold(%f), distToLimiter(%f), ClientCfg.PositionLimiterRadius(%f)", _Slot,
distToFirst, ClientCfg.DestThreshold, distToLimiter, ClientCfg.PositionLimiterRadius);
}
}
// stage has pos? => force LCT for it and after
stageForceLCTFound= stageForceLCTFound || hasPos;
// *** Compute the estimated Time for the Stage.
// Compute difference in Game Cycle Between the current Game Cycle and the current Stage.
sint32 t;
if(ClientCfg.StageLCTUsage==CClientConfig::StageUseAllLCT)
t= (*it).first - NetMngr.getCurrentClientTick();
else if(ClientCfg.StageLCTUsage==CClientConfig::StageUseNoLCT)
t= (*it).first - NetMngr.getCurrentServerTick();
else
{
// Update LCTImpact for the stage
if(stageForceLCTFound)
{
// Force full impact for stage after or including a POS
stage.setLCTImpact(256);
}
else
{
/* minimize the LCT impact with the current char one
Hence, if the stage had a lct impact lowered, but LCT decrease was canceled,
the stage will keep its LCT impact
*/
stage.setLCTImpact(min(stage.getLCTImpact(), charLCTI));
}
sint32 lcti= stage.getLCTImpact();
// if full impact
if(lcti>=256)
t= (*it).first - NetMngr.getCurrentClientTick();
// if no impact
else if(lcti<=0)
t= (*it).first - NetMngr.getCurrentServerTick();
// else blend
else
{
sint32 twlct= (*it).first - NetMngr.getCurrentClientTick();
sint32 twolct= (*it).first - NetMngr.getCurrentServerTick();
t= (twlct*lcti + twolct*(256-lcti))>>8;
}
}
// Compute the estimated Time for the Stage.
stage.time( (double)(NetMngr.getMachineTimeAtTick() + t*NetMngr.getMsPerTick())/1000.0 );
// *** Important step is used for "Panic mode" animation acceleration. skip the first stage
if(_ImportantStepTime==0.0 && it!=_Stages._StageSet.begin())
{
// Important steps are ones that takes times (pos, orientation, mode, animation etc....)
if( stage.isPresent(PROPERTY_POSITION) ||
stage.isPresent(PROPERTY_MODE) ||
stage.isPresent(PROPERTY_ORIENTATION) ||
stage.isPresent(PROPERTY_ENTITY_MOUNTED_ID) ||
stage.isPresent(PROPERTY_RIDER_ENTITY_ID) ||
stage.isPresent(PROPERTY_BEHAVIOUR))
_ImportantStepTime= stage.time();
}
// *** Compute dist2dest (until a mode) if has pos
if((_IsThereAMode==false) && hasPos)
{
// Set the destination pos and time.
_DestPos= posInStage;
_DestTime = stage.time() + (double)(stage.predictedInterval()*NetMngr.getMsPerTick())/1000.0;
// Update First Pos.
if(dist2Dest() == INVALID_DIST)
{
_FirstPos = _DestPos;
_FirstTime = stage.time();
// Compute the distance to the first position
double distToFirst = (CVectorD(_DestPos.x, _DestPos.y, 0.0) - CVectorD(posTmp.x,posTmp.y, 0.0)).norm();
// Set the Distance to the Destination as the distance to the first position.
dist2Dest(distToFirst);
// Set the distance to First Stage.
dist2FirstPos(distToFirst);
}
// Increase distance to destination.
else
dist2Dest(dist2Dest() + (CVectorD(_DestPos.x, _DestPos.y, 0.0) - CVectorD(posTmp.x, posTmp.y, 0.0)).norm());
// Backup the last pos.
posTmp = _DestPos;
}
// Stop if there is a mode in the stage.
if(stage.isPresent(CLFECOMMON::PROPERTY_MODE))
_IsThereAMode = true;
// *** NEXT STAGE
itTmp = it;
++it;
// REMOVE EMPTY STAGE (because only position and the same as the current one).
if(stage.empty())
_Stages._StageSet.erase(itTmp);
}
// If there is no mode in queue, mode wanted is the current mode
// It must usually be the theorical one except for some mode used only by the client like SWIM
if(!_IsThereAMode)
_ModeWanted = _Mode;
// ***** update _StartDecreaseLCTImpact
// If a stage that force LCT has been found in the list
if(stageForceLCTFound)
// Decrease of LCT is disabled
_StartDecreaseLCTImpact= 0;
else if(_StartDecreaseLCTImpact==0)
// Start to decrease LCT
_StartDecreaseLCTImpact= NetMngr.getCurrentServerTick();
// ***** compute _RunStartTimeNoPop (see _RunStartTimeNoPop)
_RunStartTimeNoPop= INVALID_TIME;
// only if have some pos in the queue
if(somePosFoundEarly)
{
double d2fp = 0.0;
double fpTime= INVALID_TIME;
// if the first pos is computed, use it
if(_FirstTime!=INVALID_TIME)
{
d2fp= dist2FirstPos();
fpTime= _FirstTime;
}
// else try to compute the first pos, WIHTOUT regarding if there is a mode or not
// (because even mode anim can be accelerated....)
else
{
it = _Stages._StageSet.begin();
while(it != _Stages._StageSet.end())
{
CStage &stage = (*it).second;
CVectorD firstPos;
if(stage.getPos(firstPos))
{
fpTime = stage.time() + (double)(stage.predictedInterval()*NetMngr.getMsPerTick())/1000.0;
d2fp= CVectorD(firstPos.x-pos().x, firstPos.y-pos().y, 0.0).norm();
break;
}
it++;
}
}
// with d2fp, fpTime, and maxSpeed, we can estimate the moment where the run should start
if(fpTime!=INVALID_TIME)
{
float maxSpeed= (float)getMaxSpeed();
if(maxSpeed>0)
{
// compute at which time the first move should begin so it doesn't have to accelerate
_RunStartTimeNoPop= fpTime - d2fp/maxSpeed;
}
}
}
}// updateStages //
//-----------------------------------------------
// beginImpact :
// Return if the impact must be played.
// \param anim : pointer on the current animation (MUST NOT BE NULL).
// \param currentTime : current time in the animation.
// \param triggerName : name of the trigger to check.
// \param isActive : read (and can change) the state.
// \param timeFactor : when to activate the impact if there is no track (value has to be between 0 and 1 to be valid).
// \return bool : true if the trigger is valid.
// \warning This method does not check if the animation is Null.
//-----------------------------------------------
ADD_METHOD(bool CCharacterCL::beginImpact(NL3D::UAnimation *anim, NL3D::TAnimationTime currentTime, const std::string &triggerName, bool &isActive, float timeFactor))
// Is the impact already activeated.
if(isActive)
return false;
// Try to find the impact trigger in the animation.
UTrack *Track = anim->getTrackByName(triggerName.c_str());
// No track -> just check with 2/3 animation
if(Track)
{
if(Track->interpolate(currentTime, isActive))
return isActive;
else
nlwarning("CH:beginImpact:%d: Wrong type asked.", _Slot);
}
// No Track or pb with it so try with the animation length.
float length = (float)(anim->getEndTime()-anim->getBeginTime());
isActive = (animOffset(MOVE) >= length*timeFactor);
return isActive;
}// beginImpact //
//-----------------------------------------------
// animEventsProcessing :
// Manage Events that could be created by the animation (like sound).
// \param startTime : time to start processing events from the current animation.
// \param stopTime : time to stop processing events from the current animation.
// \todo GUIGUI : Optimize FXs launch when we would have time
//-----------------------------------------------
void CCharacterCL::animEventsProcessing(double startTime, double stopTime)
{
if (_CurrentState == 0)
return;
// \todo Vianney : temp le temps de savoir comment on joue les son pour le propre joueur
// No sound for the player.
if(_Slot != 0 || _Mode != MBEHAV::NORMAL)
{
// Retreive the surface material
uint32 matId= getGroundType();
// Set the material id var
_SoundContext.Args[0] = matId;
if(_Sheet)
{
// Set the sound family var
_SoundContext.Args[2] = _Sheet->SoundFamily;
// Set the sound variation var
_SoundContext.Args[3] = _Sheet->SoundVariation;
}
else
{
// Set the sound family var
_SoundContext.Args[2] = 0;
// Set the sound variation var
_SoundContext.Args[3] = 0;
}
// Sound Process.
CSoundAnimManager* sndMngr = CSoundAnimManager::instance();
if(sndMngr && (_SoundId[MOVE] != CSoundAnimationNoId))
{
_SoundContext.Position = pos();
// Look for the cluster(s) containing this character...
std::vector clusters;
if (!_Instance.empty())
{
// single meshed
_Instance.getLastParentClusters(clusters);
}
else if (!_Skeleton.empty())
{
// Skel meshed
_Skeleton.getLastParentClusters(clusters);
}
CCluster *pcluster = 0;
// use the first cluster if at leat one available
if (!clusters.empty())
pcluster = clusters.front();
sndMngr->playAnimation(_SoundId[MOVE], (float) startTime, (float) stopTime, pcluster, _SoundContext);
}
}
}// animEventsProcessing //
//-----------------------------------------------
// updatePreCollision :
// Method called each frame to manage the entity.
// \param time : current time of the frame.
// \parem target : pointer on the current entity target.
//-----------------------------------------------
void CCharacterCL::updatePreCollision(const TTime ¤tTimeInMs, CEntityCL *target) // virtual
{
H_AUTO ( RZ_Client_Entity_CL_Update_Pre_Collision );
// Set the Last frame PACS Pos.
if(_Primitive)
_Primitive->getGlobalPosition(_LastFramePACSPos, dynamicWI);
// Set the previous position before changing the current one.
_LastFramePos = _Position;
// Turn towards the target when in COMBAT_FLOAT mode.
if(_Mode == MBEHAV::COMBAT_FLOAT)
{
H_AUTO ( RZ_Client_Entity_CL_Update_Combat )
// Check there is a valid target and it's not the entity itself.
if(targetSlot() != CLFECOMMON::INVALID_SLOT
&& targetSlot() != slot()
&& target)
// Set the new entity direction
front(target->pos() - pos(), true, false);
}
// Update Position if not a child & displayable.
if(parent() == CLFECOMMON::INVALID_SLOT && _Displayable)
{
H_AUTO ( RZ_Client_Entity_CL_Update_Pos )
updatePos(currentTimeInMs, target);
}
}// updatePreCollision //
//-----------------------------------------------
// updateFX :
// Apply track on fxs
// Check if some FX should be removed.
//-----------------------------------------------
inline void CCharacterCL::updateFX()
{
updateAttachedFX();
}// updateFX //
//-----------------------------------------------
// updateAttachedFX :
// Apply track on animated fxs
// Remove those that should be removed
//-----------------------------------------------
void CCharacterCL::updateAttachedFX()
{
// build align matrix
CMatrix alignMatrix;
buildAlignMatrix(alignMatrix);
std::list::iterator itAttachedFxToStart = _AttachedFXListToStart.begin();
while(itAttachedFxToStart != _AttachedFXListToStart.end())
{
if ((*itAttachedFxToStart).DelayBeforeStart < (float)(TimeInSec - (*itAttachedFxToStart).StartTime))
{
uint index = (*itAttachedFxToStart).MaxNumAnimCount;
(*itAttachedFxToStart).MaxNumAnimCount = 0;
CAttachedFX::TSmartPtr fx = new CAttachedFX;
fx->create(*this, (*itAttachedFxToStart), CAttachedFX::CTargeterInfo());
if (!fx->FX.empty())
{
_AuraFX[index] = fx;
}
itAttachedFxToStart = _AttachedFXListToStart.erase(itAttachedFxToStart);
}
else
{
++itAttachedFxToStart;
}
}
// update tracks & pos for anim attachedfxs
std::list::iterator itAttachedFx = _AttachedFXListForCurrentAnim.begin();
while(itAttachedFx != _AttachedFXListForCurrentAnim.end())
{
nlassert(*itAttachedFx);
CAttachedFX &attachedFX = **itAttachedFx;
attachedFX.update(*this, alignMatrix);
if (!attachedFX.FX.empty()) attachedFX.FX.setUserMatrix(alignMatrix);
++itAttachedFx;
}
// Try to remove animation FXs still not removed.
itAttachedFx = _AttachedFXListToRemove.begin();
while(itAttachedFx != _AttachedFXListToRemove.end())
{
// If the FX is not present or valid -> remove the FX.
bool mustDelete = false;
CAttachedFX &attachedFX = **itAttachedFx;
if (attachedFX.SpawnTime != TimeInSec)
{
if(attachedFX.FX.empty() || !attachedFX.FX.isSystemPresent() || !attachedFX.FX.isValid())
{
mustDelete = true;
}
}
if (attachedFX.TimeOutDate != 0)
{
if (TimeInSec >= attachedFX.TimeOutDate)
{
mustDelete = true;
}
}
if (mustDelete)
{
// Remove from the list.
itAttachedFx = _AttachedFXListToRemove.erase(itAttachedFx);
}
else
{
attachedFX.update(*this, alignMatrix);
if (!attachedFX.FX.empty()) attachedFX.FX.setUserMatrix(alignMatrix);
++itAttachedFx;
}
}
// update the aura fx
for(uint k = 0; k < MaxNumAura; ++k)
{
if (_AuraFX[k])
{
if (_AuraFX[k]->TimeOutDate != 0.f) // we use that flag to mark the aura as 'shutting down'
{
if (TimeInSec >= _AuraFX[k]->TimeOutDate)
{
_AuraFX[k] = NULL;
}
else
{
float lifeRatio = (float) ((_AuraFX[k]->TimeOutDate - TimeInSec) / AURA_SHUTDOWN_TIME);
if (!_AuraFX[k]->FX.empty()) _AuraFX[k]->FX.setUserParam(0, 1.f - lifeRatio);
}
}
if (_AuraFX[k]) // not deleted yet ?
{
// update position & orientation
_AuraFX[k]->update(*this, alignMatrix);
}
}
}
// update the link fx
if (_LinkFX)
{
_LinkFX->update(*this, alignMatrix);
}
if (_StaticFX)
{
_StaticFX->FX->update(*this, alignMatrix);
}
}
//-----------------------------------------------
// updateVisible :
//-----------------------------------------------
void CCharacterCL::updateVisible (const TTime ¤tTimeInMs, CEntityCL *target)
{
// Changes the skeleton state
if(!_Skeleton.empty())
{
_Skeleton.show();
}
// Changes the instance position.
else if(!_Instance.empty())
{
_Instance.show();
}
// Snap the entity to the ground.
{
H_AUTO ( RZ_Client_Entity_CL_Update_Snap_To_Ground )
snapToGround();
}
// Apply the new entity position to the visual of the entity (apply x and z movement due to animation).
if(parent() == CLFECOMMON::INVALID_SLOT)
{
H_AUTO ( RZ_Client_Entity_CL_Update_Display )
updateDisplay();
}
// Change the cluster of the entity.
{
H_AUTO ( RZ_Client_Entity_CL_Update_Cluster )
updateCluster();
}
// Update the LodCharacter Animation.
if(_LodCharacterAnimEnabled)
{
H_AUTO ( RZ_Client_Entity_CL_Update_Pos_Lod_Animation )
// set this value to the skeleton
if(skeleton())
skeleton()->setLodCharacterAnimTime(_LodCharacterAnimTimeOffset);
}
// Update FX
{
H_AUTO ( RZ_Client_Entity_CL_Update_FX )
updateFX();
}
// Update Modifiers
if(!_HPModifiers.empty())
{
HPMD mod;
mod.CHPModifier::operator= (*_HPModifiers.begin());
mod.Time = TimeInSec + mod.DeltaT;
_HPDisplayed.push_back(mod);
_HPModifiers.erase(_HPModifiers.begin());
}
// Parent
CEntityCL::updateVisible(currentTimeInMs, target);
}// updateVisible //
//-----------------------------------------------
// updateSomeClipped :
//-----------------------------------------------
void CCharacterCL::updateSomeClipped (const TTime ¤tTimeInMs, CEntityCL *target)
{
// Snap the entity to the ground.
{
H_AUTO ( RZ_Client_Entity_CL_Update_Snap_To_Ground )
snapToGround();
}
// Changes the skeleton position.
if(!_Skeleton.empty())
{
_Skeleton.setPos(pos());
_Skeleton.hide();
}
// Changes the instance position.
else if(!_Instance.empty())
{
_Instance.setPos(pos());
_Instance.hide();
}
if(!ClientCfg.Light)
{
// Update texture Async Loading
updateAsyncTexture();
// Update lod Texture
updateLodTexture();
// Update FX
{
H_AUTO ( RZ_Client_Entity_CL_Update_FX )
updateFX();
}
}
// Remove Modifiers.
_HPModifiers.clear();
_HPDisplayed.clear();
// Parent
CEntityCL::updateSomeClipped(currentTimeInMs, target);
}// updateSomeClipped //
//-----------------------------------------------
// updateClipped :
//-----------------------------------------------
void CCharacterCL::updateClipped (const TTime ¤tTimeInMs, CEntityCL *target)
{
// hide the scene interface
if (_InSceneUserInterface)
{
if (_InSceneUserInterface->getActive())
_InSceneUserInterface->setActive (false);
}
if (_CurrentBubble)
{
if (_CurrentBubble->getActive())
_CurrentBubble->setActive (false);
}
// parent
CEntityCL::updateClipped(currentTimeInMs, target);
}// updateClipped //
//-----------------------------------------------
// updateVisiblePostPos :
// Update the entity after all positions done.
//-----------------------------------------------
void CCharacterCL::updateVisiblePostPos(const NLMISC::TTime ¤tTimeInMs, CEntityCL *target) // virtual
{
// Stuff to do only when alive.
if(!isDead())
{
// Update the head direction.
{
H_AUTO ( RZ_Client_Entity_CL_Update_Head_Direction )
updateHeadDirection(target);
}
// Update Blink.
{
H_AUTO ( RZ_Client_Entity_CL_Update_Blink )
updateBlink(currentTimeInMs);
}
}
// Update in scene interface
if(_InSceneUserInterface || _CurrentBubble)
{
// Draw the entity Name if asked or under the cursor.
bool showIS = mustShowInsceneInterface( (!_Sheet) || (_Sheet->DisplayOSD) );
bool showBubble = true;
// Don't show bubble if lod
if (!_Skeleton.empty() && _Skeleton.isDisplayedAsLodCharacter())
{
showBubble = false;
}
// If the name of the character is unknown, no user info
if (_EntityName.empty() && _Title.empty())
showIS = false;
// if mounted : don't display name
if( _Rider != CLFECOMMON::INVALID_SLOT)
{
showIS = false;
}
// User Info
if (_InSceneUserInterface)
{
// Activate
_InSceneUserInterface->setActive (showIS);
if (showIS)
{
// Update dynamic data
_InSceneUserInterface->updateDynamicData ();
NLMISC::CVectorD pos;
if (getNamePos(pos))
{
// Check the pos validity
if((isValidDouble(pos.x) && isValidDouble(pos.y) && isValidDouble(pos.z)) == false)
{
nlwarning("CH:updateVisiblePostPos:%d: invalid pos %f %f %f", _Slot, pos.x, pos.y, pos.z);
nlstop;
}
_InSceneUserInterface->Position = pos;
}
else
{
pos = (box().getMin() + box().getMax())/2;
pos.z = box().getMax().z;
nlassert(isValidDouble(pos.x) && isValidDouble(pos.y) && isValidDouble(pos.z));
_InSceneUserInterface->Position = pos;
}
}
}
// Bubble ?
if (_CurrentBubble)
{
showBubble &= _CurrentBubble->canBeShown();
// Activate
if (_CurrentBubble->getActive() != showBubble)
_CurrentBubble->setActive (showBubble);
if (showBubble)
{
// Offset X
sint offsetX = 0;
if (_InSceneUserInterface)
offsetX = - 10 - (_InSceneUserInterface->getWReal() / 2);
_CurrentBubble->setOffsetX (offsetX);
NLMISC::CVectorD pos;
if (!getNamePos(pos))
{
pos = (box().getMin() + box().getMax())/2;
pos.z = box().getMax().z;
}
nlassert(isValidDouble(pos.x) && isValidDouble(pos.y) && isValidDouble(pos.z));
_CurrentBubble->Position = pos;
}
}
}
// parent
CEntityCL::updateVisiblePostPos(currentTimeInMs, target);
}// updateVisiblePostPos //
//-----------------------------------------------
// updatePostCollision :
// Method called each frame to manage the entity.
// \param time : current time of the frame.
// \parem target : pointer on the current entity target.
//-----------------------------------------------
void CCharacterCL::updatePostCollision(const TTime &/* currentTimeInMs */, CEntityCL * /* target */) // virtual
{
H_AUTO ( RZ_Client_Entity_CL_Update_Post_Collision )
// Finalize PACS position
{
H_AUTO ( RZ_Client_Entity_CL_Update_Finalize_Move )
pacsFinalizeMove();
// \todo GUIGUI : fait rapidement pour voir les autres se baigner pour la video, faire mieux.
// changed : Malkav , also do this for mektoub (as they can swim)
if(PACS && _Primitive
&& (isPlayer() || isNPC()
|| (_Sheet && (_Sheet->Race == EGSPD::CPeople::MektoubMount || _Sheet->Race == EGSPD::CPeople::MektoubPacker))
)
)
{
// Is in water ?
if(GR)
{
UGlobalPosition gPos;
_Primitive->getGlobalPosition(gPos, dynamicWI);
float waterHeight;
if(GR->isWaterPosition(gPos, waterHeight))
{
if(isSwimming()==false)
{
if(isDead())
{
_Mode = MBEHAV::SWIM_DEATH;
}
else if (isRiding())
{
_Mode = MBEHAV::MOUNT_SWIM;
// also change mounted entity mode
if (_Mount != CLFECOMMON::INVALID_SLOT)
{
CCharacterCL *mount = dynamic_cast(EntitiesMngr.entity(_Mount));
if(mount)
{
// Set the mount.
mount->setMode(MBEHAV::MOUNT_SWIM);
mount->computeAutomaton();
mount->computeAnimSet();
mount->setAnim(CAnimationStateSheet::Idle);
}
}
}
else
{
_Mode = MBEHAV::SWIM;
}
// Compute the current automaton
computeAutomaton();
// Update the animation set according to the mode.
computeAnimSet();
// Animset changed -> update current animation
setAnim(CAnimationStateSheet::Idle);
}
}
else
{
if(isSwimming())
{
if(isDead())
{
_Mode = MBEHAV::DEATH;
}
else if (isRiding())
{
_Mode = MBEHAV::MOUNT_NORMAL;
// also change mounted entity mode
if (_Mount != CLFECOMMON::INVALID_SLOT)
{
CCharacterCL *mount = dynamic_cast(EntitiesMngr.entity(_Mount));
if(mount)
{
// Set the mount.
mount->setMode(MBEHAV::MOUNT_NORMAL);
mount->computeAutomaton();
mount->computeAnimSet();
mount->setAnim(CAnimationStateSheet::Idle);
}
}
}
else
{
_Mode = MBEHAV::NORMAL;
}
// Compute the current automaton
computeAutomaton();
// Update the animation set according to the mode.
computeAnimSet();
// Animset changed -> update current animation
setAnim(CAnimationStateSheet::Idle);
}
}
}
}
}
}// updatePostCollision //
//-----------------------------------------------
// getTheMove :
//-----------------------------------------------
double CCharacterCL::getTheMove(double loopTimeStep, double oldMovingTimeOffset, double oldMovingTimeOffsetRun) const
{
double move;
if(_CurrentState)
{
// A Real Move State
if(_CurrentState->Move)
{
// Get the covered distance from the animation.
move = getTheMove(loopTimeStep, oldMovingTimeOffset, MOVE);
if(runFactor() > 0.0)
{
// Blend the 2 move (Walk & Run).
move = move*(1.0-runFactor()) + getTheMove(loopTimeStep, oldMovingTimeOffsetRun, MOVE_BLEND_OUT)*runFactor();
// The move must be significant.
if((move>0.0) && (moveSlide)
{
move = speed() * loopTimeStep;
// The move must be significant.
if((move>0.0) && (move0.0) && (move0.0) && (move0.0) && (move0.0 && movepos() - pos();
dirToTarget.z = 0.0;
if(ClientCfg.Local
|| ((dirToTarget != CVectorD::Null)
&& fabs(target->pos().x-target->lastFramePos().x)>0.01
&& fabs(target->pos().y-target->lastFramePos().y)>0.01))
{
double angToTarget = atan2(dirToTarget.y, dirToTarget.x);
_DestPos = target->getAttackerPos(angToTarget, attackRadius() + ClientCfg.AttackDist);
}
else
_DestPos = target->getAttackerPos(_TargetAngle, attackRadius() + ClientCfg.AttackDist);
// Compute the distance to destination.
CVectorD vectToDest = _DestPos - pos();
vectToDest.z = 0.0;
// Distance to destination is big enough.
if(vectToDest.norm() > ClientCfg.DestThreshold)
{
dist2Dest(vectToDest.norm());
// Compute the time to reach the destination at the max speed.
double lengthOfTimeToDest = 0.0; // 0 = No Speed Limit
_FirstPos = _DestPos;
_DestTime = _LastFrameTime + lengthOfTimeToDest + ClientCfg.ChaseReactionTime;
_FirstTime = _DestTime;
/*
// The time remaining will be enough to reach the destination
if(frameTimeRemaining >= lengthOfTimeToDest)
{
_FirstPos = _DestPos;
_DestTime = _LastFrameTime + lengthOfTimeToDest + ClientCfg.ChaseReactionTime;
_FirstTime = _DestTime;
}
// The time remaining is not enough to reach the destination at max speed -> compute a first pos possible to reach.
else
{
_FirstPos = pos() + vectToDest*frameTimeRemaining/lengthOfTimeToDest;
_DestTime = _LastFrameTime + lengthOfTimeToDest + ClientCfg.ChaseReactionTime;
_FirstTime = _LastFrameTime + frameTimeRemaining + ClientCfg.ChaseReactionTime;
}
*/
// Compute the distance to the first position.
CVectorD tmp2computeDist2FirstPos = _FirstPos-pos();
tmp2computeDist2FirstPos.z = 0.0;
dist2FirstPos(tmp2computeDist2FirstPos.norm());
updatePosCombatFloatChanged(target);
}
// Destination is too close (will consider to be at destination.
else
{
_FirstPos = _DestPos = pos();
dist2Dest(0.0);
dist2FirstPos(0.0);
_FirstTime = _DestTime = _LastFrameTime;
}
}
// The target is not allocated.
else
{
_FirstPos = _DestPos = pos();
dist2Dest(0.0);
dist2FirstPos(0.0);
_FirstTime = _DestTime = _LastFrameTime;
}
}// updatePosCombatFloat //
//-----------------------------------------------
// updatePos :
// Upadte the player position
// \param time : Time for the position of the entity after the motion.
// \param target : pointer on the current target.
// \todo GUIGUI : compute it when receiving a new stage instead of every frame (should be faster).
// \todo GUIGUI: recompute distance to destination even if the Stage not reached.
//-----------------------------------------------
ADD_METHOD(void CCharacterCL::updatePos(const TTime ¤tTimeInMs, CEntityCL *target))
_OldAutomaton = _CurrentAutomaton;
// Compute the Time Step.
double frameTimeRemaining = computeTimeStep(((double)currentTimeInMs)*0.001);
// Update the LodCharacter Animation.
if(_LodCharacterAnimEnabled)
{
H_AUTO ( RZ_Client_Entity_CL_Update_Pos_Lod_Animation );
// \todo GUIGUI : replace 'getSpeedFactor' by the correct speed factor !!
// update lod anim time. multiply by speed factor of the most important slot.
_LodCharacterAnimTimeOffset += DT * _PlayList->getSpeedFactor(_LodCharacterMasterAnimSlot);
}
// BLEND
if(_PlayList)
{
H_AUTO ( RZ_Client_Entity_CL_Update_Pos_Set_Play_List );
// \todo GUIGUI : do something better for the blend (mounts there).
if(isRiding() || ClientCfg.BlendFrameNumber == 0 || _BlendRemaining <= 0)
{
_BlendRemaining = 0;
_PlayList->setAnimation(ACTION, UPlayList::empty);
_PlayList->setWeight(ACTION, 0.0f);
if(runFactor() < 0.5 || (_CurrentAnimSet[MOVE_BLEND_OUT]==0))
{
if(_CurrentAnimSet[MOVE])
{
_CurrentAnimSet[ACTION] = _CurrentAnimSet [MOVE];
animState (ACTION, animState (MOVE));
animIndex (ACTION, animIndex (MOVE)); // This also call "animId" and set it.
animOffset(ACTION, animOffset(MOVE));
}
else
{
_CurrentAnimSet[ACTION] = 0;
animState (ACTION, CAnimationStateSheet::UnknownState);
animIndex (ACTION, CAnimation::UnknownAnim); // This also call "animId" and set it.
animOffset(ACTION, 0.0);
}
}
else
{
_CurrentAnimSet[ACTION] = _CurrentAnimSet [MOVE_BLEND_OUT];
animState (ACTION, animState (MOVE_BLEND_OUT));
animIndex (ACTION, animIndex (MOVE_BLEND_OUT)); // This also call "animId" and set it.
animOffset(ACTION, animOffset(MOVE_BLEND_OUT));
}
_AnimReversed[ACTION] = false;
}
else
{
double animLength = EAM->getAnimationLength(animId(ACTION));
// Check Anim length
if(animOffset(ACTION)+frameTimeRemaining > animLength)
animOffset(ACTION, animLength);
else
animOffset(ACTION, animOffset(ACTION)+frameTimeRemaining);
// Compute weight step.
float w = (float)_BlendRemaining/(float)(ClientCfg.BlendFrameNumber+1);
// Set Old Anim Weight.
_PlayList->setWeight(ACTION, w);
// Set New Anim Weight.
_PlayList->setWeight(MOVE, 1.f-w);
}
}
uint antiFreezeCounter = 0;
// While the time Step is not Null.
while(frameTimeRemaining > 0)
{
H_AUTO ( RZ_Client_Entity_CL_Update_Pos_WhileStep );
//--------------------//
//--------------------//
// ANTI-FREEZE SYSTEM //
// If too many loop, display some infos
if(antiFreezeCounter > 50)
{
/*
nlwarning("CH:updatePos:antiFreeze:%d: frameTimeRemaining '%f'", _Slot, frameTimeRemaining);
nlwarning("CH:updatePos:antiFreeze:%d: Automaton '%s'", _Slot, _CurrentAutomaton.c_str());
nlwarning("CH:updatePos:antiFreeze:%d: _IsThereAMode '%s'", _Slot, _IsThereAMode?"true":"false");
nlwarning("CH:updatePos:antiFreeze:%d: dist2Dest '%f'", _Slot, dist2Dest());
nlwarning("CH:updatePos:antiFreeze:%d: Mode '%s(%d)'", _Slot, modeToString(_Mode).c_str(), _Mode);
nlwarning("CH:updatePos:antiFreeze:%d: Mode Wanted '%s(%d)'", _Slot, modeToString(_ModeWanted).c_str(), _ModeWanted);
nlwarning("CH:updatePos:antiFreeze:%d: Anim State Move '%s(%d)'", _Slot, CAnimationState::getAnimationStateName(animState(MOVE)).c_str(), animState(MOVE));
*/
// Once too many more time reached, leave the method.
if(antiFreezeCounter > 60)
break;
}
// Update antiFreezeCounter.
++antiFreezeCounter;
// ANTI-FREEZE SYSTEM //
//--------------------//
//--------------------//
// \todo GUIGUI : improve dist2first and dist2dest
// Update Stages
updateStages();
// \todo GUIGUI : Bug with _TargetAngle in fight float, we overwrite here angle sent by the server ?
// If the entity is too far (orientation not received yet), set the front vector as the moving direction.
CVectorD distToUser = pos()-UserEntity->pos();
distToUser.z = 0.0;
if(distToUser.norm()*1000.0 > CLFECOMMON::THRESHOLD_ORIENTATION*0.9)
{
if(_FirstPos != INVALID_POS)
{
CVectorD dirToFirstP = _FirstPos-pos();
dirToFirstP.z = 0.0;
if(dirToFirstP != CVectorD::Null)
{
front(dirToFirstP.normed(), false, false);
_TargetAngle = atan2(front().y, front().x);
}
}
}
// Mode Combat Float :
if(!_IsThereAMode && (_Mode == MBEHAV::COMBAT_FLOAT))
{
// Update the position in combat float.
updatePosCombatFloat(frameTimeRemaining, target);
}
// Compute the average speed to the destination.
// double spd =
computeSpeed();
bool stageReach = false;
bool allToFirstPos = false;
// Compute time to Stage or full Time Step if Stage too far.
double loopTimeStep = frameTimeRemaining;
double buLoopTimeStep = 0.0;
double checkLoopTimeStep = loopTimeStep;
// Update the animation used according to the speed/end anim/etc..
updateAnimationState();
// Backup the old time offset.
double oldMovingTimeOffset = animOffset(MOVE);
double oldMovingTimeOffsetRun = animOffset(MOVE_BLEND_OUT);
// WARNING -> Unknown Animation Selected.
// Play the time step for the loop and truncate to End Anim if Time Step too big.
if((_CurrentState != 0) && (animIndex(MOVE) != CAnimation::UnknownAnim))
playToEndAnim(oldMovingTimeOffset, loopTimeStep);
/////////////////
// -- CHECK -- //
if(loopTimeStep > checkLoopTimeStep)
{
nlwarning("CH:updtPos:%d: loopTimeStep(%f) > checkLoopTimeStep(%f).", _Slot, loopTimeStep, checkLoopTimeStep);
if(ClientCfg.Check)
nlstop;
loopTimeStep = checkLoopTimeStep;
}
checkLoopTimeStep = loopTimeStep;
// -- END CHECK -- //
/////////////////////
// (DEBUG) : Backup the Animation Time Offset after the adjustment with end anim to make some checks.
double backupAnimTimeOff = animOffset(MOVE);
//
bool posInStage = false;
double stageTime = -1.0;
if(!_Stages._StageSet.empty())
{
H_AUTO ( RZ_Client_Entity_CL_Update_Pos_Move );
// Get the reference on the current stage.
CStageSet::TStageSet::iterator it = _Stages._StageSet.begin();
CStage &stage = (*it).second;
if(_Mode == MBEHAV::COMBAT_FLOAT && !_IsThereAMode)
posInStage = false;
else
posInStage = stage.isPresent(CLFECOMMON::PROPERTY_POSITION);
stageTime = stage.time();
}
// dist2FirstPos() should not be Null if the destination is not Null (because of the code in updateStage).
if(dist2FirstPos() > 0.0)
{
H_AUTO ( RZ_Client_Entity_CL_Update_Pos_dist2FirstPos_gt_0 );
// Get the covered distance from the animation.
double move = getTheMove(loopTimeStep, oldMovingTimeOffset, oldMovingTimeOffsetRun);
// The move is big enough to reach the first step with motion.
if(move >= dist2FirstPos()) // dist2FirstPos() > 0 -> move > 0.
{
double percent = dist2FirstPos() / move;
// Adjust loopTimeStep
loopTimeStep *= percent;
if(loopTimeStep > checkLoopTimeStep) // prevent bugs because of the double's precision
loopTimeStep = checkLoopTimeStep;
if(loopTimeStep < 0.0)
loopTimeStep = 0.0;
// Update Animation Time Offset (move greater than the dist to next stage; update animation time to get them equal).
animOffset(MOVE, oldMovingTimeOffset + (animOffset(MOVE) -oldMovingTimeOffset )*percent);
animOffset(MOVE_BLEND_OUT, oldMovingTimeOffsetRun + (animOffset(MOVE_BLEND_OUT)-oldMovingTimeOffsetRun)*percent);
// \todo GUIGUI : check if the following line is necessary
buLoopTimeStep = loopTimeStep;
// First Position Reached
pos(_FirstPos);
dist2FirstPos(0.0); // Current entity position is now the same as the First position so dis is Null.
// Complete the Stage.
if(_Mode != MBEHAV::COMBAT_FLOAT || _IsThereAMode)
{
if(posInStage)
stageReach = true;
else
allToFirstPos = true;
}
}
// Even if the movement is not enough to reach the first position, move the entity to this position.
else if(move > 0.0)
{
// Compute the vector to the first stage with a position.
CVectorD vectToFirstPos = _FirstPos - pos();
vectToFirstPos.z = 0.0f;
// Update entity position.
if(vectToFirstPos != CVectorD::Null)
pos(pos() + vectToFirstPos*(move/dist2FirstPos()));
}
// Else : There is no move.
}
else
{
CHECK(posInStage==false && dist2Dest()<=0.0);
}
// If there is no position in the next stage and the stage should be done already.
if(!_Stages._StageSet.empty() && !posInStage && !stageReach && !allToFirstPos && ((_LastFrameTime+loopTimeStep) >= stageTime))
{
// Backup 'loopTimeStep' just in case of the stage could not be done.
buLoopTimeStep = loopTimeStep;
// Adjust loopTimeStep
loopTimeStep = stageTime - _LastFrameTime;
if(loopTimeStep > checkLoopTimeStep) // prevent bugs because of the double's precision
loopTimeStep = checkLoopTimeStep;
if(loopTimeStep < 0.0)
loopTimeStep = 0.0;
//
// \todo GUIGUI : adjust timeOffset, because we stopped the loop before
//
// Stage complete.
stageReach = true;
}
/////////////////
// -- CHECK -- //
// Check the Animation Time Offset is not became greater than the old.
if(animOffset(MOVE) > backupAnimTimeOff)
{
nlwarning("CH:updtPos:%d: backupAnimTimeOff(%f) < AnimationsTimeOffset(%f) animLen(%f) -> animOffset(MOVE) = backupAnimTimeOff",
_Slot, backupAnimTimeOff, animOffset(MOVE), EAM->getAnimationLength(animId(MOVE)));
if(ClientCfg.Check)
nlstop;
animOffset(MOVE, backupAnimTimeOff);
}
// Check loopTimeStep is not < 0;
if(loopTimeStep < 0.0)
{
nlwarning("CH:updtPos:%d: loopTimeStep(%f) < 0 -> loopTimeStep=0.0.", _Slot, loopTimeStep);
if(ClientCfg.Check)
nlstop;
loopTimeStep = 0.0;
}
// time spent could not be bigger than the time remaining
if(loopTimeStep > frameTimeRemaining)
{
nlwarning("CH:updtPos:%d: loopTimeStep(%f) > frameTimeRemaining(%f) -> loopTimeStep=frameTimeRemaining.", _Slot, loopTimeStep, frameTimeRemaining);
if(ClientCfg.Check)
nlstop;
loopTimeStep = frameTimeRemaining;
}
// -- END CHECK -- //
/////////////////////
// Manage Events that could be created by the animation (like sound).
{
H_AUTO ( RZ_Client_Entity_CL_Update_Pos_Anim_Event )
animEventsProcessing(oldMovingTimeOffset, animOffset(MOVE));
}
// Apply all stages until the first stage with a pos.
if(allToFirstPos)
{
H_AUTO ( RZ_Client_Entity_CL_Update_Pos_Apply_All_Stage_To_First_Pos );
applyAllStagesToFirstPos();
}
// Stage is complete, apply modifications.
else if(stageReach)
{
H_AUTO ( RZ_Client_Entity_CL_Update_Pos_Apply_Current_Stage );
if(!applyCurrentStage())
loopTimeStep = buLoopTimeStep;
}
// Compute the remaining Time Step.
frameTimeRemaining -= loopTimeStep;
// Update the last Time.
_LastFrameTime += loopTimeStep;
}// while(frameTimeRemaining > 0) //
////////////////////////////////
// UPDATE THE ENTITY POSITION //
// Set the new position into PACS.
{
H_AUTO_USE ( RZ_Client_Entity_CL_Update_Pos_Pacs );
pacsMove(pos());
}
/// (DEBUG) ///
// Check frameTimeRemaining is perfectly equal to 0.
if(frameTimeRemaining < 0.0)
nlwarning("CCharacterCL::updatePos : frameTimeRemaining(%f) < 0 ! This should never happen.", frameTimeRemaining);
/// (END DEBUG) ///
// Update the children display.
{
H_AUTO ( RZ_Client_Entity_CL_Update_Pos_Children );
std::list::iterator itTmp, it = _Children.begin();
while(it != _Children.end())
{
itTmp = it;
// Next Child (done before just in case child is detached during his processFrame).
it++;
if(*itTmp)
{
CCharacterCL *child = dynamic_cast(*itTmp);
if(child)
{
if ( ! ClientCfg.Light )
{
// Update the animation offset for the child.
double animLength = EAM->getAnimationLength(animId(MOVE));
if(animLength > 0.0)
{
double factor = animOffset(MOVE) / animLength;
if(factor > 1.0)
factor = 1.0;
double childTimeOffset = factor*EAM->getAnimationLength(child->animId(MOVE));
child->animOffset(MOVE, childTimeOffset);
}
else
child->animOffset(MOVE, 0.0);
child->processFrame(currentTimeInMs);
}
child->pacsMove(pos()); // Move the child at the same position than the parent.
}
else
nlwarning("Character '%d': Child is not a 'CCharacterCL'.", _Slot);
}
else
nlwarning("Character '%d': Child not allocated.", _Slot);
}
}
}// updatePos //
//-----------------------------------------------
// updateVisiblePostRender :
// Update the entity after the render like for the head offset.
//-----------------------------------------------
void CCharacterCL::updateVisiblePostRender() // virtual
{
// Compute the headoffset
if(_HeadBoneId != -1 && !_Skeleton.empty())
{
if(_Skeleton.getLastClippedState() && _Skeleton.isBoneComputed(_HeadBoneId))
{
UBone headBone=_Skeleton.getBone(_HeadBoneId);
const CMatrix &headMat = headBone.getLastWorldMatrixComputed();
_HeadOffset = headMat.getPos()-pos();
_HeadOffsetComputed= true;
}
}
}// updateVisiblePostRender //
//-----------------------------------------------
void CCharacterCL::updateAllPostRender()
{
}
//-----------------------------------------------
// processFrame :
//-----------------------------------------------
void CCharacterCL::processFrame(const TTime ¤tTimeInMs)
{
// Prepare stages and update information from them.
updateStages();
// Compute the time remaining until frame completely processed.
double timeRemaining = computeTimeStep(((double)currentTimeInMs)*0.001);
// Compute the time spent between 2 frames.
while(timeRemaining > 0.0)
{
// Time already processed until now.
double timeProcessed = timeRemaining;
// Process Stages.
if(!_Stages._StageSet.empty())
{
// Get the reference on the current stage.
CStageSet::TStageSet::iterator it = _Stages._StageSet.begin();
CStage &stage = (*it).second;
// Check if the stage should be done already.
if((_LastFrameTime+timeProcessed) >= stage.time())
{
// Stage Done during the Frame.
if(stage.time() > _LastFrameTime)
timeProcessed = stage.time() - _LastFrameTime;
// This Stage should have been done already before last frame
else
timeProcessed = 0.0;
// Process the stage.
processStage(stage);
// Stage complete.
_Stages._StageSet.erase(it);
}
}
// Compute the remaining Time Step.
timeRemaining -= timeProcessed;
// Update the last Time.
_LastFrameTime += timeProcessed;
}
// Just to be sure, Last frame time = current time once all is done.
_LastFrameTime = ((double)currentTimeInMs)*0.001;
}// processFrame //
//-----------------------------------------------
// processStage :
// Process the stage.
//-----------------------------------------------
void CCharacterCL::processStage(CStage &stage)
{
// Apply Mode (if there is a mode, there is a position too).
pair resultMode = stage.property(PROPERTY_MODE);
if(resultMode.first)
{
uint8 mo = *(uint8 *)(&resultMode.second);
MBEHAV::EMode mode = (MBEHAV::EMode)mo;
if(mode != _Mode)
{
// release the old mode.
if ( (_Mode == MBEHAV::MOUNT_NORMAL || _Mode == MBEHAV::MOUNT_SWIM)
&& (mode != MBEHAV::MOUNT_NORMAL && mode != MBEHAV::MOUNT_SWIM)
)
{
// Unlink the mount and the rider.
parent(CLFECOMMON::INVALID_SLOT);
_Mount = CLFECOMMON::INVALID_SLOT;
_Rider = CLFECOMMON::INVALID_SLOT;
// Restore collisions.
if(_Primitive)
{
// \todo GUIGUI : do that without dynamic cast
if(dynamic_cast(this))
_Primitive->setOcclusionMask(MaskColPlayer);
else
_Primitive->setOcclusionMask(MaskColNpc);
}
// Get stage position.
if(stage.getPos(_OldPos))
{
// Backup the time
_OldPosTime = stage.time();
// Unseat the entity at the position given in the stage.
pacsMove(_OldPos);
}
else
nlwarning("CH:processStage:%d: The stage should have a position with the mode.", _Slot);
}
// Set the new mode.
_Mode = mode;
_ModeWanted = mode;
// Compute the automaton
computeAutomaton();
computeAnimSet();
setAnim(CAnimationStateSheet::Idle);
}
}
// Not a mode -> so search for a position.
}// processStage //
//-----------------------------------------------
// updateBlink :
// Update the player blink state
//-----------------------------------------------
void CCharacterCL::updateBlink(const TTime ¤tTimeInMs)
{
// Only for homine
GSGENDER::EGender gender = getGender();
if ((gender == GSGENDER::male) || (gender == GSGENDER::female))
{
float blend;
// Some parameters
static const double blinkTime = 100.f;
static const double minBlinkLength = 500.f;
static const double maxBlinkLength = 5000.f;
// Next blink time is valid ?
bool validTime = (_NextBlinkTime + blinkTime >= currentTimeInMs) && (_NextBlinkTime <= (currentTimeInMs + maxBlinkLength));
// Blink end ?
bool blinkEnd = (currentTimeInMs >= _NextBlinkTime + blinkTime);
// Blink is finished or next blink time is invalid ?
if ( blinkEnd || !validTime )
{
blend = 0;
// Compute next time
_NextBlinkTime = (TTime)(((double)rand () / (double)RAND_MAX) * (maxBlinkLength - minBlinkLength) + minBlinkLength + (double)currentTimeInMs);
}
else
{
// Blink time ?
if (currentTimeInMs >= _NextBlinkTime)
{
blend = 100.f;
}
else
{
// Do nothing
return;
}
}
// Get the face
SInstanceCL *face = getFace ();
// Set the blend shape
if(face && !face->Current.empty())
face->Current.setBlendShapeFactor ("visage_100", blend, true);
}
}// updateBlink //
//-----------------------------------------------
// getFace :
// Update eyes blink. For the moment, called by updatePos.
//-----------------------------------------------
CEntityCL::SInstanceCL *CCharacterCL::getFace ()
{
// Implemented in CPlayerCL
return idx2Inst(_FaceIdx);
}
//---------------------------------------------------
// updateDisplay :
// Get the entity position and set all visual stuff with it.
// \todo GUIGUI : put this method 'virtual' to have a different code for the user (no playlist).
// \todo GUIGUI : manage the parent better.
//---------------------------------------------------
ADD_METHOD(void CCharacterCL::updateDisplay(CEntityCL *parent))
// Animable ?
if(_PlayList)
{
// Reverse the animation if needed.
const double animOffsetMOV = _AnimReversed[MOVE] ? EAM->getAnimationLength(animId(MOVE)) - animOffset(MOVE) : animOffset(MOVE);
const double animOffsetACT = _AnimReversed[ACTION] ? EAM->getAnimationLength(animId(ACTION)) - animOffset(ACTION) : animOffset(ACTION);
const double animOffsetBLE = _AnimReversed[MOVE_BLEND_OUT] ? EAM->getAnimationLength(animId(MOVE_BLEND_OUT)) - animOffset(MOVE_BLEND_OUT): animOffset(MOVE_BLEND_OUT);
// Update Speed Factor
_PlayList->setTimeOrigin(MOVE, TimeInSec-animOffsetMOV);
_PlayList->setTimeOrigin(ACTION, TimeInSec-animOffsetACT);
_PlayList->setTimeOrigin(MOVE_BLEND_OUT, TimeInSec-animOffsetBLE);
float weight;
if(_BlendRemaining)
weight = _PlayList->getLocalWeight(MOVE, TimeInSec);
else
weight = 1.0f;
_PlayList->setWeight(MOVE, weight*(float)(1.0-runFactor()));
_PlayList->setWeight(MOVE_BLEND_OUT, weight*(float)runFactor());
// If the animation exist update the display.
if(animIndex(MOVE) != CAnimation::UnknownAnim)
{
// POSITION //
// Get the 3D position for the current time in the animation (Vector Null if animation has no move).
CVector currentAnimPos;
if(!EAM->interpolate(animId(MOVE), animOffsetMOV, currentAnimPos))
currentAnimPos = CVector::Null;
else
{
// If the current animation state is a move, do not take the Y move in the animation because it's the code that compute the move.
if(_CurrentState && _CurrentState->Move)
{
CVector currentAnimPosStart;
if(!EAM->interpolate(animId(MOVE), 0.0, currentAnimPosStart))
currentAnimPosStart = CVector::Null;
if(_CurrentState->XFactor) currentAnimPos.x = currentAnimPosStart.x;
if(_CurrentState->YFactor) currentAnimPos.y = currentAnimPosStart.y;
if(_CurrentState->ZFactor) currentAnimPos.z = currentAnimPosStart.z;
}
// Scale the animation with the Character Scale Pos if the animation need it.
{
bool applyCharacterScalePosFactor = true;
// \todo GUIGUI : faire cette histoire de emote beaucoup mieux, C NULL.
const CAnimationState *animStatePtr;
// If the current animation is an emote, get the right animation state.
if(animState(MOVE) == CAnimationStateSheet::Emote)
animStatePtr = _CurrentAnimSet[MOVE]->getAnimationState(_SubStateKey);
else
animStatePtr = _CurrentAnimSet[MOVE]->getAnimationState(animState(MOVE));
if(animStatePtr)
{
const CAnimation *anim = animStatePtr->getAnimation(animIndex(MOVE));
if(anim)
applyCharacterScalePosFactor = anim->applyCharacterScalePosFactor();
}
// scale it.
if(applyCharacterScalePosFactor)
currentAnimPos *= _CharacterScalePos;
}
// Scale according to the gabarit (choose at the character creation).
currentAnimPos *= _CustomScalePos;
}
// Blend Walk/Run
if(runFactor() > 0.0)
{
CVector currentAnimPosRun;
if(!EAM->interpolate(animId(MOVE_BLEND_OUT), animOffsetBLE, currentAnimPosRun))
currentAnimPosRun = CVector::Null;
else
{
// If the current animation state is a move, do not take the Y move in the animation because it's the code that compute the move.
if(_CurrentState && _CurrentState->Move)
{
CVector currentAnimPosStart;
if(!EAM->interpolate(animId(MOVE_BLEND_OUT), 0.0, currentAnimPosStart))
currentAnimPosStart = CVector::Null;
if(_CurrentState->XFactor) currentAnimPosRun.x = currentAnimPosStart.x;
if(_CurrentState->YFactor) currentAnimPosRun.y = currentAnimPosStart.y;
if(_CurrentState->ZFactor) currentAnimPosRun.z = currentAnimPosStart.z;
}
// Scale it by the CharacterScalePos, if needed, according to the animation.
bool applyCharacterScalePosFactor = true;
const CAnimationState *animStatePtr = _CurrentAnimSet[MOVE_BLEND_OUT]->getAnimationState(animState(MOVE_BLEND_OUT));
if(animStatePtr)
{
const CAnimation *anim = animStatePtr->getAnimation(animIndex(MOVE_BLEND_OUT));
if(anim)
applyCharacterScalePosFactor = anim->applyCharacterScalePosFactor();
}
// scale it.
if(applyCharacterScalePosFactor)
currentAnimPosRun *= _CharacterScalePos;
// Scale according to the gabarit.
currentAnimPosRun *= _CustomScalePos;
}
currentAnimPos = currentAnimPos*(float)(1.0-runFactor()) + currentAnimPosRun*(float)runFactor();
}
// ROTATION //
// Get the rotation for the current time in the animation (Rotation Null if animation has no rotation).
CQuat currentAnimRot;
if(!EAM->interpolate(animId(MOVE), animOffsetMOV, currentAnimRot))
currentAnimRot = CQuat::Identity;
else
{
// If the animation is a rotation -> Do just a part of the animation.
if(parent==0 && _CurrentState && _CurrentState->Rotation && _RotationFactor!=-1.0)
{
// Get the Rotation at the beginning of the animation.
CQuat currentAnimRotStart;
if(!EAM->interpolate(_PlayList->getAnimation(MOVE), 0.0, currentAnimRotStart))
currentAnimRotStart = CQuat::Identity;
double animLength = EAM->getAnimationLength(animId(MOVE));
// Get the Rotation at the beginning of the animation.
CQuat currentAnimRotEnd;
if(!EAM->interpolate(_PlayList->getAnimation(MOVE), animLength, currentAnimRotEnd))
currentAnimRotEnd = CQuat::Identity;
// Get the angle done by the animation from the beginning
CQuat rotStartTmp = currentAnimRotStart;
rotStartTmp.invert();
CQuat rotTmp = rotStartTmp * currentAnimRot;
float ang = rotTmp.getAngle();
currentAnimRot = applyRotationFactor(currentAnimRot, (float)_RotationFactor, currentAnimRotStart, currentAnimRotEnd, (float)(animOffsetMOV/animLength));
// Get the angle done once scaled.
rotTmp = rotStartTmp * currentAnimRot;
CMatrix rotMat;
rotMat.identity();
rotMat.rotateZ(_CurrentState->RotFactor*(ang-rotTmp.getAngle()));
// Apply the scaled rotation to the position.
currentAnimPos = rotMat*currentAnimPos;
//// OLD ////
/*
// Get the Rotation at the beginning of the animation.
CQuat currentAnimRotStart;
if(!EAM->interpolate(_PlayList->getAnimation(MOVE), 0.0, currentAnimRotStart))
currentAnimRotStart = CQuat::Identity;
// Find the closest quat.
// currentAnimRotStart.makeClosest(currentAnimRot);
// Get the angle done by the animation from the beginning
CQuat rotStartTmp = currentAnimRotStart;
rotStartTmp.invert();
CQuat rotTmp = rotStartTmp * currentAnimRot;
float ang = rotTmp.getAngle();
// Get the Rotation scaled.
currentAnimRot = CQuat::slerp(currentAnimRotStart, currentAnimRot, (float)_RotationFactor);
// Get the angle done once scaled.
rotTmp = rotStartTmp * currentAnimRot;
CMatrix rotMat;
rotMat.identity();
rotMat.rotateZ(_CurrentState->RotFactor*(ang-rotTmp.getAngle()));
// Apply the scaled rotation to the position.
currentAnimPos = rotMat*currentAnimPos;
*/
//// FIN OLD ////
}
}
// Blend Walk/Run
if(runFactor() > 0.0)
{
// Get the rotation for the current time in the animation (Rotation Null if animation has no rotation).
CQuat currentAnimRotRun;
if(!EAM->interpolate(animId(MOVE_BLEND_OUT), animOffsetBLE, currentAnimRotRun))
currentAnimRotRun = CQuat::Identity;
currentAnimRotRun.makeClosest(currentAnimRot);
currentAnimRot = CQuat::slerp(currentAnimRot, currentAnimRotRun, (float)runFactor());
}
// Animation Matrix
CMatrix AnimMatrixRot;
AnimMatrixRot.identity();
AnimMatrixRot.setRot(currentAnimRot);
// Rotation 180 degrees Matrix
CMatrix rot180;
rot180.identity();
if(parent == 0)
rot180.rotateZ((float)Pi);
// Logical entity Matrix.
CMatrix current;
if(parent == 0)
current = _DirMatrix;
else
current.identity();
// Convert the anim position in a world position.
currentAnimPos = (current*rot180)*currentAnimPos;
rot180 *= AnimMatrixRot;
current *= rot180;
// Get the rotation for the current time in the animation (Rotation Null if animation has no rotation).
if(ClientCfg.BlendFrameNumber && _BlendRemaining > 0)
{
CQuat tmpQuat;
_OldRotQuat.makeClosest(current.getRot());
tmpQuat = CQuat::slerp(current.getRot(), _OldRotQuat, ((float)_BlendRemaining/(float)(ClientCfg.BlendFrameNumber+1)));
current.setRot(tmpQuat);
// 1 more frame played.
_BlendRemaining--;
}
// Compute the position for the instance.
CVectorD tmpPos;
if(parent == 0)
{
tmpPos = pos();
tmpPos += currentAnimPos;
}
// If the entity is on a mount, just adjust the position with the animation.
else
tmpPos = currentAnimPos;
// Set the skeleton position and rotation.
if(!_Skeleton.empty())
{
_Skeleton.setRotQuat(current.getRot());
_Skeleton.setPos(tmpPos);
}
// Only Instances with no skeleton (objects).
else if(!_Instances.empty() && !_Instances[0].Current.empty())
{
_Instances[0].Current.setRotQuat(current.getRot());
_Instances[0].Current.setPos(tmpPos);
}
// Set the instance position and rotation.
else if(!_Instance.empty())
{
_Instance.setRotQuat(current.getRot());
_Instance.setPos(tmpPos);
}
else
{
static bool once = false;
if (!once)
{
nlwarning("CH::updtDisp:%d: no instance nor skeleton. Sheet Id '%d(%s)'.", _Slot, _SheetId.asInt(), _SheetId.toString().c_str());
once = true;
}
}
}
// Else Keep the lastest correct display.
else
{
H_AUTO ( RZ_Client_Entity_CL_Update_Display_Unknown_Anim )
// Rotation 90 degrees Matrix
CMatrix rot90;
rot90.identity();
if(parent == 0)
rot90.rotateZ((float)(Pi/2.0));
// Logical entity Matrix.
CMatrix current;
if(parent == 0)
current = _DirMatrix;
// else
// current.identity();
current *= rot90;
// Changes the skeleton position.
if(!_Skeleton.empty())
{
_Skeleton.setRotQuat(current.getRot());
if(parent == 0)
_Skeleton.setPos(pos());
// else
// _Skeleton.setPos(currentAnimPos);
}
// Only Instances with no skeleton (objects).
else if(!_Instances.empty() && !_Instances[0].Current.empty())
{
_Instances[0].Current.setRotQuat(current.getRot());
if(parent == 0)
_Instances[0].Current.setPos(pos());
}
}
}
else
{
// Changes the skeleton position.
if(!_Skeleton.empty())
{
_Skeleton.setPos(pos());
}
// Only Instances with no skeleton (objects).
else if(!_Instances.empty() && !_Instances[0].Current.empty())
{
// Logical entity Matrix.
CMatrix current;
if(parent == 0)
current = _DirMatrix;
_Instances[0].Current.setRotQuat(current.getRot());
_Instances[0].Current.setPos(pos());
}
// Changes the instance position.
else if(!_Instance.empty())
{
_Instance.setPos(pos());
}
}
if(!ClientCfg.Light)
{
// update texture Async Loading
updateAsyncTexture();
// update lod Texture
updateLodTexture();
}
// Update the children display.
std::list::iterator it = _Children.begin();
while(it != _Children.end())
{
// Update the display for the child
(*it)->updateDisplay(this);
// Next Child.
++it;
}
}// updateDisplay //
//---------------------------------------------------
// getHeadPos :
// Method to get the position of the head (in the world).
// \param headPos: will be set with the head position if succeed.
// \return 'true' if the param has been updated.
// \warning this method do NOT check if there is a skeleton.
//---------------------------------------------------
bool CCharacterCL::getHeadPos(NLMISC::CVector &headPos)
{
// if never computed (eg: clipped or lod)
if(!_HeadOffsetComputed)
{
// force compute the bone
if(_HeadBoneId != -1 && !_Skeleton.empty())
{
_Skeleton.forceComputeBone(_HeadBoneId);
UBone headBone=_Skeleton.getBone(_HeadBoneId);
const CMatrix &headMat = headBone.getLastWorldMatrixComputed();
_HeadOffset = headMat.getPos()-pos();
}
_HeadOffsetComputed= true;
}
// return the pos with the last head offset computed
headPos = pos()+_HeadOffset;
return true;
}// getHeadPos //
//---------------------------------------------------
// updateHeadDirection :
// Update the head Direction.
//---------------------------------------------------
void CCharacterCL::updateHeadDirection(CEntityCL *target)
{
// Does the entity got a target to track with the head ?
// No head targeting if the target slot is the same as the entity slot
if(_TargetSlot!=CLFECOMMON::INVALID_SLOT && _TargetSlot!=_Slot)
{
// Is the target allocated.
if(target != 0)
{
// Do not orientate the head to the target if too far.
CVectorD vectDist = target->pos() - pos();
if((fabs(vectDist.x) + fabs(vectDist.y)) <= ClientCfg.MaxHeadTargetDist && vectDist != CVectorD::Null)
{
// Do not orientate the head to the target if behind.
vectDist.normalize();
if(fabs(angleBetween2Vect(dir(), vectDist)) < Pi/3.0)
{
CVector targetheadPos;
if(target->getHeadPos(targetheadPos))
{
_TargetAnimCtrl.Mode = CTargetAnimCtrl::TargetMode;
_TargetAnimCtrl.WorldTarget = targetheadPos;
return;
}
}
}
}
}
_TargetAnimCtrl.Mode = CTargetAnimCtrl::DirectionMode;
CMatrix frontMat;
frontMat.setRot(CVector::I, front(), CVector::K, true);
frontMat.normalize(CMatrix::YZX);
_TargetAnimCtrl.CurrentWorldDirection = frontMat.getRot();
}// updateHeadDirection //
//---------------------------------------------------
// displayName :
// Display the entity name.
//---------------------------------------------------
void CCharacterCL::displayName()
{
// There is no Context -> Cannot display a name.
if(TextContext == 0)
return;
NLMISC::CVector namePos;
// There is a skeleton.
if(!_Skeleton.empty() && !ClientCfg.Light)
{
// Only if the skeleton is visible.
if(!isVisible())
return;
// Do not display the name in LoD.
if(_Skeleton.isDisplayedAsLodCharacter())
return;
// If the entity was not displayed last frame (clipped) -> do not display the name.
if(!_Skeleton.getLastClippedState())
return;
// Is there a Bone for the Name ?
if(_NameBoneId != -1)
namePos = _Skeleton.getBone(_NameBoneId).getLastWorldMatrixComputed().getPos();
// No Bone for the name -> Draw it at a default position.
else
namePos = pos() + CVector(0.f, 0.f, 2.0f);
}
// If there is no skeleton -> compute a position for the name.
else
{
namePos = pos() + CVector(0.f, 0.f, 2.0f);
}
// Create the matrix and set the orientation according to the camera.
CMatrix matrix;
matrix.identity();
matrix.setRot(MainCam.getRotQuat());
matrix.setPos(namePos);
CVector distPos = MainCam.getPos()-pos();
float scale = distPos.norm();
// don't display too far names
if (ClientCfg.Light && scale > 20)
return;
if(scale <= 1.0f)
scale = 1.0f;
else if(scale > ClientCfg.ConstNameSizeDist)
scale = ClientCfg.ConstNameSizeDist;
// Too Far to display a name.
else if(scale > ClientCfg.MaxNameDist)
return;
// Compute the final scale.
matrix.scale(ClientCfg.NameScale*scale);
// Draw the name.
drawName(matrix);
}// displayName //
//---------------------------------------------------
// drawName :
// Draw the name.
//---------------------------------------------------
void CCharacterCL::drawName(const NLMISC::CMatrix &mat) // virtual
{
const ucstring &ucname = getEntityName();
if(!getEntityName().empty())
{
// If there is no extended name, just display the name
if(_NameEx.empty())
TextContext->render3D(mat, ucname);
// If there is an extended name, display the extended name at the name place and the name above.
else
{
// Display the Extended Name at right place.
TextContext->render3D(mat, _NameEx);
// Compute the position for the Name.
CMatrix mat2;
mat2.identity();
mat2.setRot(MainCam.getRotQuat());
CVector v = mat.getPos()+mat.getK().normed()*ClientCfg.NamePos*mat.getScaleUniform();
mat2.setPos(v);
mat2.scale(mat.getScaleUniform());
// Diaplay the name.
TextContext->render3D(mat2, ucname);
}
}
// Name from Sheet
else
{
if(_Sheet != 0)
{
const ucstring name(STRING_MANAGER::CStringManagerClient::getCreatureLocalizedName(_Sheet->Id));
if (name.find(ucstring("render3D(mat, name);
}
}
}// drawName //
//---------------------------------------------------
// displayModifiers :
// Display the Hp Bar
//---------------------------------------------------
void CCharacterCL::displayModifiers() // virtual
{
// if none, no op
if( _HPDisplayed.empty())
return;
// **** get the name pos
NLMISC::CVectorD namePos;
if(!getNamePos(namePos))
namePos = pos() + CVector(0.f, 0.f, 2.0f);
// remove but keep the Z to the ground
float currentDeltaZ= float(namePos.z - pos().z);
CVector groundPos= namePos;
groundPos.z-= currentDeltaZ;
// **** compute the scale
float dist = (MainCam.getPos()-pos()).norm();
float scale= 1.f;
if(dist > ClientCfg.MaxNameDist)
return;
if ( dist < ClientCfg.ConstNameSizeDist )
scale = 1.0f;
else
scale = ClientCfg.ConstNameSizeDist / dist;
// **** Display HP modifiers.
CInterfaceManager *pIM= CInterfaceManager::getInstance();
std::list::iterator itTmp;
std::list::iterator it = _HPDisplayed.begin();
while(it != _HPDisplayed.end())
{
HPMD &mod = *it;
//
const float totalDuration= 3.f;
const float noFadeDuration= 1.f;
const float fadeDuration= totalDuration-noFadeDuration;
if(TimeInSec > (mod.Time+totalDuration))
{
itTmp = it;
++it;
_HPDisplayed.erase(itTmp);
}
else if (TimeInSec >= mod.Time)
{
ucstring hpModifier;
if (mod.Text.empty())
hpModifier = ucstring(toString("%d", mod.Value));
else
hpModifier = mod.Text;
double t = TimeInSec-mod.Time;
// for character, keep the deltaZ the first time it is displayed, and apply the same each frame
// (avoid Z movement of the flying text because of animation)
if(mod.DeltaZ==-FLT_MAX)
mod.DeltaZ= currentDeltaZ;
// Compute the position for the Modifier.
float dynT= sqrtf((float)t/totalDuration); // a sqrt just so it looks much more "jumpy"
CVector pos= groundPos + CVector(0.0f, 0.0f, mod.DeltaZ + dynT*1.f);
// fade
if(tFlyingTextManager.getOffsetXForCharacter();
if(UserEntity && UserEntity->slot()==slot())
deltaX*= -1;
pIM->FlyingTextManager.addFlyingText(&mod, hpModifier, pos, mod.Color, scale, deltaX);
// Next
++it;
}
else
{
// Next
++it;
}
}
}// displayModifiers //
//---------------------------------------------------
// drawPath :
// Draw Pathes
//---------------------------------------------------
void CCharacterCL::drawPath() // virtual
{
// Pivot
CLineColor line;
CVector pl0 = pos();
CVector pl1 = pos()+CVector(0.f, 0.f, 2.f);
line = CLine(pl0, pl1);
line.Color0 = CRGBA(150,0,255);
line.Color1 = CRGBA(150,0,255);
Driver->drawLine(line, GenericMat);
line = CLine(_PositionLimiter, _PositionLimiter+CVector(0.f, 0.f, 2.f));
line.Color0 = CRGBA(255,64,128);
line.Color1 = CRGBA(255,64,128);
Driver->drawLine(line, GenericMat);
CVector p0 = pos();
p0.z += 1.f;
// Draw Front
line = CLine(p0, p0+front());
line.Color0 = CRGBA(0,255,0);
line.Color1 = CRGBA(0,255,0);
Driver->drawLine(line, GenericMat);
// Draw Direction
line = CLine(p0, p0+dir());
line.Color0 = CRGBA(255,255,0);
line.Color1 = CRGBA(255,255,0);
Driver->drawLine(line, GenericMat);
// Go to the First Stage.
CStageSet::TStageSet::iterator it = _Stages._StageSet.begin();
// Compute the distance over all Stages.
while(it != _Stages._StageSet.end())
{
// Compute Distance.
CVectorD stagePos;
if((*it).second.getPos(stagePos))
{
CVector p1 = stagePos;
CVector p2 = p0;
p2.z = (float)stagePos.z;
p2 = p2 + (stagePos-p2).normed();
getCollisionEntity()->snapToGround(p1);
p1.z += 0.05f;
getCollisionEntity()->snapToGround(p2);
p2.z += 0.03f;
line = CLine(p0, p2);
line.Color0 = CRGBA(0,0,255);
line.Color1 = CRGBA(0,0,255);
Driver->drawLine(line, GenericMat);
p1.z += 0.03f;
line = CLine(p0, p1);
line.Color0 = CRGBA(255,0,0);
line.Color1 = CRGBA(255,0,0);
Driver->drawLine(line, GenericMat);
p0 = p1;
}
// Next Stage.
++it;
}
}// drawPath //
//---------------------------------------------------
// drawBox :
// Draw the selection Box.
//---------------------------------------------------
void CCharacterCL::drawBox() // virtual
{
if(!ClientCfg.Light)
::drawBox(_Aabbox.getMin(), _Aabbox.getMax(), CRGBA(0,250,0));
// Draw the PACS box (adjust the color according to the PACS valid or not).
NLMISC::CAABBox PACSBox = _Aabbox;
CVector halfSize = PACSBox.getHalfSize();
halfSize.x = 0; halfSize.y = 0;
PACSBox.setCenter(_FinalPacsPos+halfSize);
UGlobalPosition gPos;
if(_Primitive)
_Primitive->getGlobalPosition(gPos, dynamicWI);
::drawBox(PACSBox.getMin(), PACSBox.getMax(), ((gPos.InstanceId == -1) && (T1%1000)>500)?CRGBA(255,0,0):CRGBA(0,250,250));
if(!ClientCfg.Light)
{
::drawBox(selectBox().getMin(), selectBox().getMax(), CRGBA(250,250,0));
// Draw the clip Sphere
CVector clipPos = _Aabbox.getCenter();
clipPos.z+= _ClipDeltaZ - _Aabbox.getHalfSize().z; // _ClipDeltaZ is relative to pos on ground
::drawSphere(clipPos, _ClipRadius, CRGBA(0,0,250));
}
}// drawBox //
//---------------------------------------------------
// selectBox :
// Return the selection box.
//---------------------------------------------------
const NLMISC::CAABBox &CCharacterCL::selectBox() // virtual
{
// recompute the selection box?
if(_LastSelectBoxComputeTime &fxList, const CLFECOMMON::TCLEntityId &slotRemoved)
{
std::list::iterator it = fxList.begin();
while (it != fxList.end())
{
std::list::iterator tmpIt = it;
++ it;
if ((*tmpIt)->StickMode &&
((*tmpIt)->StickMode == CFXStickMode::OrientedTowardTargeter ||
(*tmpIt)->StickMode == CFXStickMode::UserBoneOrientedTowardTargeter ||
(*tmpIt)->StickMode == CFXStickMode::UserBoneRay
)
)
{
if ((*tmpIt)->TargeterInfo.Slot == slotRemoved)
{
fxList.erase(tmpIt);
}
}
}
}
//---------------------------------------------------
// slotRemoved :
// To Inform about an entity removed (to remove from selection for example).
// This will remove the entity from the target.
// \param slot : Slot of the entity that will be removed.
//---------------------------------------------------
void CCharacterCL::slotRemoved(const CLFECOMMON::TCLEntityId &slotRemoved)
{
// If the target is the entity that will be removed -> no target
if(_TargetSlot == slotRemoved)
_TargetSlot = CLFECOMMON::INVALID_SLOT;
// invalidate targeter slots in anim fxs
updateAttachedFXListForSlotRemoved(_AttachedFXListForCurrentAnim, slotRemoved);
updateAttachedFXListForSlotRemoved(_AttachedFXListToRemove, slotRemoved);
}// slotRemoved //
//---------------------------------------------------
// nbStage :
// Return number of stage remaining.
//---------------------------------------------------
uint CCharacterCL::nbStage()
{
return (uint)_Stages._StageSet.size();
}// nbStage //
//---------------------------------------------------
// attackRadius :
// Method to return the attack radius of an entity (take the scale into account).
//---------------------------------------------------
double CCharacterCL::attackRadius() const
{
return _Sheet->DistToFront * getScale();
}// attackRadius //
//---------------------------------------------------
// getAttackerPos :
// Return the position the attacker should have to combat according to the attack angle.
// \param ang : 0 = the front, >0 and -Pi = right side.
// \todo : GUIGUI precalculate entity matrix
//---------------------------------------------------
CVectorD CCharacterCL::getAttackerPos(double ang, double dist) const
{
// Compute the local angle
ang = computeShortestAngle(atan2(front().y, front().x), ang);
ang += Pi;
if(ang > Pi)
ang -= 2*Pi;
// Compute the local position.
CVectorD p;
float distToSide, distToFront, distToBack;
bool useComplexShape = false;
if (useComplexShape) // Keep this code for when AIS become complex shape aware
{
distToSide = _Sheet->DistToSide;
distToFront = _Sheet->DistToFront;
distToBack = _Sheet->DistToBack;
}
else // use round shape here
{
distToSide = _Sheet->ColRadius;
distToFront = _Sheet->ColRadius;
distToBack = _Sheet->ColRadius;
}
p.x = getScale()*distToSide*sin(-ang) + dist*sin(-ang); // or: pos.x = _Sheet->DistToSide*cos(ang) + dist*cos(ang); but 0 should be right side.
p.y = dist*cos(ang);
if(fabs(ang) <= Pi/2.0)
p.y += getScale()*distToFront * cos(ang);
else
p.y += getScale()*distToBack * cos(ang);
p.z = 0.0;
// Compute the world position.
// Create the target matrix.
CVector vj = front();
vj.z = 0;
CVector vk(0,0,1);
CVector vi = vj^vk;
CMatrix bodyBase;
bodyBase.setRot(vi,vj,vk,true);
bodyBase.setPos(pos());
// Get the destination in the world.
return bodyBase * p;
}// getAttackerPos //
//---------------------------------------------------
// isPlacedToFight :
// Return true if the opponent is well placed.
//---------------------------------------------------
bool CCharacterCL::isPlacedToFight(const NLMISC::CVectorD &posAtk, const NLMISC::CVector &dirAtk, double attackerRadius) const // virtual
{
NLMISC::CVectorD vDist = pos()-posAtk;
if(vDist != NLMISC::CVectorD::Null)
{
// Attacker Distance
const double distToAttacker = vDist.norm();
// Get the Ideal Position
vDist.normalize();
CVectorD rightPos = getAttackerPos(atan2(vDist.y, vDist.x), attackerRadius);
// Vector from the Ideal Position
NLMISC::CVectorD vDist2 = pos()-rightPos;
// Check the Distance.
if(distToAttacker <= vDist2.norm()+ClientCfg.FightAreaSize)
{
// Check the orientation.
NLMISC::CVector vAng = dirAtk;
vAng.z = 0.0f;
vDist.z = 0.0;
return (fabs(angleBetween2Vect(vAng, vDist)) <= NLMISC::Pi/3.0);
}
}
// User is on the target, do not check dist or angle
else
return true;
// Something wrong
return false;
// NLMISC::CVectorD vDist = pos()-posAtk;
// const double dist = vDist.norm();
// double radius;
// // Get current entity radius
// if(_Primitive)
// radius = _Primitive->getRadius();
// else
// radius = 0.0;
// // Attack is possible if not too close or too far.
// if(dist>=radius && dist<=(radius+ClientCfg.FightAreaSize))
// {
// // Check Angle
// NLMISC::CVector vAng = dirAtk;
// vAng.z = 0.0f;
// vDist.z = 0.0;
// return (fabs(angleBetween2Vect(vAng, vDist)) <= NLMISC::Pi/3.0);
// }
// return false;
}// isPlacedToFight //
//---------------------------------------------------
// \param pos : result given in this variable. Only valid if return 'true'.
// \return bool : 'true' if the 'pos' has been filled.
//---------------------------------------------------
bool CCharacterCL::getNamePos(CVectorD &pos) // virtual
{
// If there is no skeleton -> cannot display the name.
if(_Skeleton.empty())
return false;
// If the entity was not displayed last frame (clipped) -> do not display the name.
if(!_Skeleton.getLastClippedState())
return false;
if(_NameBoneId == -1)
return false;
// Take x and y in pos() else we a have a frame late.
pos.x = this->pos().x;
pos.y = this->pos().y;
float namePosZ;
if (ClientCfg.StaticNameHeight)
namePosZ = getNamePosZ();
else
namePosZ = 0.f;
// use bone position if no name position is given
if (namePosZ == 0.f)
{
// if displayed as lod, the NameId bone may not be computed
float skeletonZ = _Skeleton.getLastWorldMatrixComputed().getPos().z;
if(_Skeleton.isDisplayedAsLodCharacter())
{
// if never computed
if(_NameCLodDeltaZ==NameCLodDeltaZNotComputed)
{
_Skeleton.forceComputeBone(_NameBoneId);
float boneZ= _Skeleton.getBone(_NameBoneId).getLastWorldMatrixComputed().getPos().z;
_NameCLodDeltaZ= boneZ - skeletonZ;
}
pos.z= skeletonZ + _NameCLodDeltaZ;
}
else
{
float boneZ= _Skeleton.getBone(_NameBoneId).getLastWorldMatrixComputed().getPos().z;
pos.z = boneZ;
// update delta Z, for when enter in CLod form
_NameCLodDeltaZ= boneZ - skeletonZ;
}
// reset name pos history
if (_NamePosHistory.isInitialized())
{
_NamePosHistory.LastNamePosZ = 0.f;
_NamePosHistory.LastBonePosZ = 0.f;
}
return true;
}
const float baseZ = float( this->pos().z );
// if displayed as lod, skip smooth transition stuff
if (_Skeleton.isDisplayedAsLodCharacter())
{
pos.z = baseZ + namePosZ;
// reset name pos history
if (_NamePosHistory.isInitialized())
{
_NamePosHistory.LastNamePosZ = 0.f;
_NamePosHistory.LastBonePosZ = 0.f;
}
return true;
}
const float boneZ = _Skeleton.getBone(_NameBoneId).getLastWorldMatrixComputed().getPos().z;
float deltaNamePosZ;
float deltaBonePosZ;
if (_NamePosHistory.isInitialized())
{
deltaNamePosZ = namePosZ - _NamePosHistory.LastNamePosZ;
deltaBonePosZ = (boneZ - baseZ) - _NamePosHistory.LastBonePosZ;
}
else
{
deltaNamePosZ = 0.f;
deltaBonePosZ = 0.f;
}
if (deltaNamePosZ != 0.f)
{
// generate a smooth transition following the bone movement
if (deltaBonePosZ != 0.f && (deltaBonePosZ > 0.f) == (deltaNamePosZ > 0.f))
{
namePosZ = _NamePosHistory.LastNamePosZ + deltaBonePosZ;
}
else
{
const float defaultSpeed = 1.f; // in meters per sec.
float deltaZ = defaultSpeed * DT;
if (deltaNamePosZ < 0.f)
deltaZ = -deltaZ;
if ( fabs(deltaZ) < fabs(deltaNamePosZ) )
namePosZ = _NamePosHistory.LastNamePosZ + deltaZ;
}
}
pos.z = baseZ + namePosZ;
// update history
_NamePosHistory.LastNamePosZ = namePosZ;
_NamePosHistory.LastBonePosZ = boneZ - baseZ;
return true;
}// getNamePos //
//---------------------------------------------------
// Return name position on Z axis defined in sheet
//---------------------------------------------------
float CCharacterCL::getNamePosZ() const
{
if (!_Sheet)
return 0.f;
float namePosZ;
switch (_ModeWanted)
{
case MBEHAV::DEATH:
case MBEHAV::SIT:
case MBEHAV::REST:
namePosZ = _Sheet->NamePosZLow;
break;
case MBEHAV::MOUNT_NORMAL:
case MBEHAV::MOUNT_SWIM:
namePosZ = _Sheet->NamePosZHigh;
break;
default:
namePosZ = _Sheet->NamePosZNormal;
break;
}
if (namePosZ == 0.f)
namePosZ = _Sheet->NamePosZNormal;
return namePosZ * getScale();
}// getNamePosZ //
//---------------------------------------------------
// \param pos : result given in this variable. Only valid if return 'true'.
// \return bool : 'true' if the 'pos' has been filled.
//---------------------------------------------------
bool CCharacterCL::getChestPos(CVectorD &pos) const // virtual
{
// If there is no skeleton -> cannot display the Chest.
if(_Skeleton.empty())
return false;
// If the entity was not displayed last frame (clipped) -> do not display the Chest.
if(!_Skeleton.getLastClippedState())
return false;
if(_ChestBoneId == -1)
return false;
// Take x and y in pos() else we a have a frame late.
pos.x = this->pos().x;
pos.y = this->pos().y;
pos.z = _Skeleton.getBone(_ChestBoneId).getLastWorldMatrixComputed().getPos().z;
return true;
}// getChestPos //
//---------------------------------------------------
// getSheetScale :
// Return the entity sheet scale. (return 1.0 if there is any problem).
//---------------------------------------------------
float CCharacterCL::getSheetScale() const // virtual
{
if(!_Sheet)
return 1.f;
else
return _Sheet->Scale;
} // getSheetScale //
//---------------------------------------------------
// getColRadius :
// Return the entity collision radius. (return 0.5 if there is any problem).
//---------------------------------------------------
float CCharacterCL::getSheetColRadius() const
{
if(!_Sheet)
return 0.5f;
else
return _Sheet->ColRadius;
}
//---------------------------------------------------
// getScale :
// Return the entity scale. (return 1.0 if there is any problem).
//---------------------------------------------------
float CCharacterCL::getScale() const // virtual
{
switch( _OwnerPeople )
{
case MOUNT_PEOPLE::Fyros : return getSheetScale() * ClientCfg.FyrosScale * _CustomScale;
case MOUNT_PEOPLE::Matis : return getSheetScale() * ClientCfg.MatisScale * _CustomScale;
case MOUNT_PEOPLE::Tryker : return getSheetScale() * ClientCfg.TrykerScale * _CustomScale;
case MOUNT_PEOPLE::Zorai : return getSheetScale() * ClientCfg.ZoraiScale * _CustomScale;
default:
return getSheetScale() * _CustomScale;
}
}// getScale //
///////////
// DEBUG //
///////////
//---------------------------------------------------
// currentAnimationName :
// Return the current animation name.
//---------------------------------------------------
const std::string &CCharacterCL::currentAnimationName() const
{
if(_PlayList)
{
uint idCurrentAnimation = _PlayList->getAnimation(MOVE);
if(idCurrentAnimation != UPlayList::empty)
if(EAM && EAM->getAnimationSet())
return EAM->getAnimationSet()->getAnimationName(idCurrentAnimation);
}
// No animation yet.
return CCharacterCL::_EmptyString;
}// currentAnimationName //
//---------------------------------------------------
// currentAnimationSetName :
// Return the current animation set name.
//---------------------------------------------------
std::string CCharacterCL::currentAnimationSetName(TAnimationType animType) const
{
if( animType < animTypeCount )
{
if( uint(animType) < _CurrentAnimSet.size() )
{
if( _CurrentAnimSet[animType] )
{
return _CurrentAnimSet[animType]->getSheetName();
}
}
}
return CCharacterCL::_EmptyString;
}// currentAnimationSetName //
/////////////
// PRIVATE //
//---------------------------------------------------
// shapeFromItem :
// Return the shape pointer from tha item and according to the character gender.
// \param itemSheet : reference on the item sheet.
// \return string & : reference on the shape name.
//---------------------------------------------------
std::string CCharacterCL::shapeFromItem(const CItemSheet &itemSheet) const
{
if(_Gender == GSGENDER::female && !itemSheet.getShapeFemale().empty())
return itemSheet.getShapeFemale();
else
return itemSheet.getShape();
}// shapeFromItem //
//---------------------------------------------------
// createItemInstance :
// Create the instance from an item
//---------------------------------------------------
uint32 CCharacterCL::createItemInstance(const CItemSheet &itemSheet, uint32 instIdx, SLOTTYPE::EVisualSlot visualSlot, const string &bindBone, sint8 texture, sint color)
{
uint32 idx = CEntityCL::BadIndex;
// Get the right shape according to the gender of the character.
const string &shape = shapeFromItem(itemSheet);
// Check the shape.
if(!shape.empty())
{
// Check the item need a shape.
if(shape != "none.shape")
{
UInstance instance;
// Get the instance
idx = addColoredInstance(shape, bindBone, texture, instIdx, color);
SInstanceCL *pInst = idx2Inst(idx);
nlassert( (pInst == NULL) || (pInst != NULL && !pInst->Loading.empty()) );
if (pInst != NULL)
instance = pInst->Loading;
// Check the shape creation has been is well done.
if(!instance.empty())
{
// Create the FX associated to the item in a given visual slot.
_Items[visualSlot].initFXs(visualSlot, instance);
}
else
nlwarning("CH:createItemInstance: cannot create the instance for the shape '%s'.", shape.c_str());
}
}
else
nlwarning("CH:createItemInstance: the item has no shape.");
return idx;
}// createItemInstance //
//-----------------------------------------------
// setAlive :
// Method to Flag the character as alive and do everything needed.
//-----------------------------------------------
void CCharacterCL::setAlive() // virtual
{
}// setAlive //
//---------------------------------------------------
// displayDebug :
// Display Debug Information.
//---------------------------------------------------
ADD_METHOD(void CCharacterCL::displayDebug(float x, float &y, float lineStep)) // virtual
CInterfaceManager *IM = CInterfaceManager::getInstance ();
CEntityCL::displayDebug(x, y, lineStep);
// Mode Wanted
// Display the Target Mode.
TextContext->printfAt(x, y, "Mode Wanted: %d(%s)", (sint)_ModeWanted, MBEHAV::modeToString(_ModeWanted).c_str());
y += lineStep;
// Stage Remaining
TextContext->printfAt(x, y, "Stages remaining: %d", _Stages._StageSet.size());
y += lineStep;
// Current Automaton
TextContext->printfAt(x, y, "Automaton: %s", _CurrentAutomaton.c_str());
y += lineStep;
// Current Speed
TextContext->printfAt(x, y, "Speed: %f (_DestTime(%f) - _LastFrameTime(%f)) = %f", speed(), _DestTime, _LastFrameTime, _DestTime-_LastFrameTime);
y += lineStep;
// Display the Run Factor.
TextContext->printfAt(x, y, "(Walk)Run Factor: %f", runFactor());
y += lineStep;
// Display the current animation name(id)(offset)(nbloop) for channel MOVE.
TextContext->printfAt(x, y, "Current Animation: %s(%u)(%lf)(%u loops)", animId(MOVE)==std::numeric_limits::max()?"[NONE]":currentAnimationName().c_str(), animId(MOVE), animOffset(MOVE), _NbLoopAnim);
y += lineStep;
// First Pos
if(_First_Pos)
TextContext->printfAt(x, y, "No Position Received", _First_Pos);
else
TextContext->printfAt(x, y, "At least 1 Position Received", _First_Pos);
y += lineStep;
// Primitive Ptr
TextContext->printfAt(x, y, "Prim Ptr: %p", _Primitive);
y += lineStep;
// Primitive Position
if(_Primitive)
{
CVectorD primFinalPos = _Primitive->getFinalPosition(dynamicWI);
TextContext->printfAt(x, y, "Prim Pos: %f %f %f", primFinalPos.x, primFinalPos.y, primFinalPos.z);
y += lineStep;
}
// Skeleton Ptr, Animset Ptr and Current State Ptr
TextContext->printfAt(x, y, "Skel Ptr: %p - AnimSet Ptr: %p - State Ptr: %p", &_Skeleton, _CurrentAnimSet[MOVE], _CurrentState);
y += lineStep;
// Display the target mount and rider.
TextContext->printfAt(x, y, "Mount: %3u(Theoretical: %3u) Rider: %3u(Theoretical: %3u)", mount(), _TheoreticalMount, rider(), _TheoreticalRider);
y += lineStep;
// VPA
sint64 prop = NLGUI::CDBManager::getInstance()->getDbProp("SERVER:Entities:E"+toString("%d", _Slot)+":P"+toString("%d", CLFECOMMON::PROPERTY_VPA))->getValue64();
if(isPlayer() || isUser())
{
SPropVisualA visualA = *(SPropVisualA *)(&prop);
TextContext->printfAt(x, y, "VPA: %"NL_I64"X : Chest(%d,%d) Legs(%d,%d) Arms(%d,%d) Hat(%d,%d) RH(%d) LH(%d)", prop,
(uint)visualA.PropertySubData.JacketModel, (uint)visualA.PropertySubData.JacketColor,
(uint)visualA.PropertySubData.TrouserModel, (uint)visualA.PropertySubData.TrouserColor,
(uint)visualA.PropertySubData.ArmModel, (uint)visualA.PropertySubData.ArmColor,
(uint)visualA.PropertySubData.HatModel, (uint)visualA.PropertySubData.HatColor,
(uint)visualA.PropertySubData.WeaponRightHand,
(uint)visualA.PropertySubData.WeaponLeftHand);
}
else
TextContext->printfAt(x, y, "VPA: %"NL_I64"X", prop);
y += lineStep;
// VPB
prop = NLGUI::CDBManager::getInstance()->getDbProp("SERVER:Entities:E"+toString("%d", _Slot)+":P"+toString("%d", CLFECOMMON::PROPERTY_VPB))->getValue64();
if(isPlayer() || isUser())
{
SPropVisualB visualB = *(SPropVisualB *)(&prop);
TextContext->printfAt(x, y, "VPB: %"NL_I64"X : Hands(%d,%d) Feet(%d,%d).", prop,
(uint)visualB.PropertySubData.HandsModel, (uint)visualB.PropertySubData.HandsColor,
(uint)visualB.PropertySubData.FeetModel, (uint)visualB.PropertySubData.FeetColor);
}
else
TextContext->printfAt(x, y, "VPB: %"NL_I64"X", prop);
y += lineStep;
// VPC
prop = NLGUI::CDBManager::getInstance()->getDbProp("SERVER:Entities:E"+toString("%d", _Slot)+":P"+toString("%d", CLFECOMMON::PROPERTY_VPC))->getValue64();
if(isPlayer() || isUser())
{
SPropVisualC visualC = *(SPropVisualC *)(&prop);
TextContext->printfAt(x, y, "VPC: %"NL_I64"X : EyesColor(%d) Tattoo(%d).", prop, visualC.PropertySubData.EyesColor, visualC.PropertySubData.Tattoo);
}
else
TextContext->printfAt(x, y, "VPC: %"NL_I64"X", prop);
y += lineStep;
}// displayDebug //
//-----------------------------------------------
// displayDebugPropertyStages
//-----------------------------------------------
void CCharacterCL::displayDebugPropertyStages(float x, float &y, float lineStep)
{
CStageSet::TStageSet::iterator it= _Stages._StageSet.begin();
for(;it!=_Stages._StageSet.end();it++)
{
CStage &stage= it->second;
uint32 gc= it->first % 100;
// build the string of props present in this stage
string strProps;
for(uint i=0;iprintfAt(x, y, "%02d %s", gc, strProps.c_str());
y += lineStep;
}
}
//---------------------------------------------------
// readWrite :
// Read/Write Variables from/to the stream.
//---------------------------------------------------
void CCharacterCL::readWrite(class NLMISC::IStream &f) throw(NLMISC::EStream)
{
CEntityCL::readWrite(f);
// PUBLIC
// PROTECTED
f.serial(_Stages);
// std::vector _Animations;
// std::vector _SoundId;
// NLSOUND::CSoundContext _SoundContext;
// std::vector _AnimationsTimeOffset;
// std::vector _AnimationsStateKey;
// const CAnimationSet *_CurrentAnimSet;
f.serial(_LastFrameTime);
f.serial(_LodCharacterAnimEnabled);
f.serial(_LodCharacterAnimTimeOffset);
// uint _LodCharacterMasterAnimSlot;
f.serial(_CharacterScalePos);
f.serial(_FirstPos);
f.serial(_FirstTime);
f.serial(_DistToFirst);
f.serial(_DestPos);
f.serial(_DestTime);
f.serial(_DistToDest);
f.serial(_OldPos);
f.serial(_OldPosTime);
// GSGENDER::EGender _Gender;
// sint _NameBoneId;
// NL3D::UTransform _NameTransform;
// std::vector _Items;
f.serial(_HeadIdx);
f.serial(_FaceIdx);
// sint _HeadBoneId;
f.serial(_RotationFactor);
f.serial(_DirEndAnim);
f.serial(_RotAngle);
f.serial(_CurrentAutomaton);
// const CAutomatonStateSheet *_CurrentState;
// MBEHAV::EMode _ModeWanted;
// sint _BlendRemaining;
f.serial(_OldAutomaton);
f.serial(_OldRotQuat);
f.serial(_CustomScalePos);
// TTime _NextBlinkTime;
f.serial(_NbLoopAnim);
// std::vector _FXs;
// std::list _CurrentAnimFXList;
// std::list _RemoveAnimFXList;
// NL3D::UParticleSystemInstance _CurrentAnimFX;
f.serial(_RightFXActivated);
f.serial(_LeftFXActivated);
// sint _IndexRightFX;
// sint _IndexLeftFX;
f.serial(_Mount);
f.serial(_Rider);
f.serial(_IsThereAMode);
f.serial(_HairColor);
f.serial(_EyesColor);
f.serial(_HairIndex);
f.serial(_LookRdy);
f.serial(_Speed);
f.serial(_RunFactor);
// PRIVATE
// uint32 _RHandInstIdx;
// uint32 _LHandInstIdx;
}// readWrite //
//---------------------------------------------------
// load :
// To call after a read from a stream to re-initialize the entity.
//---------------------------------------------------
void CCharacterCL::load() // virtual
{
CInterfaceManager *IM = CInterfaceManager::getInstance ();
// If the entity should be in the world already
if(_First_Pos == false)
{
// Insert the primitive into the world.
if(_Primitive)
_Primitive->insertInWorldImage(dynamicWI);
// Insert the entity into PACS
pacsPos(pos());
}
if(_LookRdy)
{
_LookRdy = false;
// Visual properties A
_HeadIdx = CEntityCL::BadIndex;
sint64 prop = NLGUI::CDBManager::getInstance()->getDbProp("SERVER:Entities:E"+toString("%d", _Slot)+":P"+toString("%d", CLFECOMMON::PROPERTY_VPA))->getValue64();
updateVisualPropertyVpa(0, prop);
}
}// load //
//---------------------------------------------------
// buildPlaylist :
//---------------------------------------------------
void CCharacterCL::buildPlaylist()
{
computeAnimSet();
// Release the old animation playlist.
if(_PlayList)
{
EAM->deletePlayList(_PlayList);
_PlayList = 0;
}
// Create the new animation playlist.
_PlayList = EAM->createPlayList();
if(!_PlayList)
{
nlwarning("Cannot create a playlist for the entity.");
return;
}
// Register the skeleton to the playlist.
_PlayList->registerTransform(_Skeleton);
// Animation should not move alone.
uint posChannel = EAM->getAnimationSet()->getChannelIdByName("pos");
if(posChannel != NL3D::UAnimationSet::NotFound)
_PlayList->enableChannel(posChannel, false);
else
nlwarning("Channel 'pos' not found.");
// Animation should not rotate alone.
uint rotquatChannel = EAM->getAnimationSet()->getChannelIdByName("rotquat");
if(rotquatChannel != NL3D::UAnimationSet::NotFound)
_PlayList->enableChannel(rotquatChannel, false);
else
nlwarning("Channel 'rotquat' not found.");
// Initialize the new playlist.
// MOVE Channel
_PlayList->setSpeedFactor (MOVE, 1.f);
_PlayList->setWrapMode (MOVE, NL3D::UPlayList::Clamp);
// ACTION Channel
_PlayList->setAnimation (ACTION, NL3D::UPlayList::empty);
_PlayList->setSpeedFactor (ACTION, 1.f);
_PlayList->setWrapMode (ACTION, NL3D::UPlayList::Clamp);
// Compute the current animation state.
_CurrentState = EAM->mState(_CurrentAutomaton, animState(MOVE));
if(_CurrentState == 0)
{
_PlayList->setAnimation(MOVE, NL3D::UPlayList::empty);
return;
}
// Get the right animation state and choose an animation.
{
// Get the animation state
const CAnimationState *animationState = 0;
if(animState(MOVE) == CAnimationStateSheet::Emote)
animationState = _CurrentAnimSet[MOVE]->getAnimationState(_SubStateKey);
else
animationState = _CurrentAnimSet[MOVE]->getAnimationState(animState(MOVE));
if(animationState)
{
// Choose the animation
animIndex(MOVE, animationState->chooseAnim(_AnimJobSpecialisation, people(), getGender(), 0.0));
// Should the objects in hands be displayed ?
_ObjectsVisible = animationState->areObjectsVisible();
}
}
// Set animation
_PlayList->setAnimation(MOVE, animId(MOVE));
}// buildPlaylist //
//--------------//
// ENTITY INFOS //
//--------------//
//-----------------------------------------------
// getSpeed :
// Return the entity speed
//-----------------------------------------------
double CCharacterCL::getSpeed() const // virtual
{
return speed();
}// getSpeed //
//-----------------------------------------------
// dist2FirstPos :
// Set the Distance from the current entity position to the First Position.
//-----------------------------------------------
void CCharacterCL::dist2FirstPos(double d2FP)
{
CHECK((d2FP == INVALID_DIST) || (d2FP == 0.0) || (d2FP >= ClientCfg.DestThreshold));
_DistToFirst = d2FP;
}// dist2FirstPos //
//-----------------------------------------------
// dist2FirstPos :
// Return the Distance from the current entity position to the First Position.
//-----------------------------------------------
double CCharacterCL::dist2FirstPos() const
{
CHECK((_DistToFirst == INVALID_DIST) || (_DistToFirst == 0.0) || (_DistToFirst >= ClientCfg.DestThreshold));
return _DistToFirst;
}// dist2FirstPos //
//-----------------------------------------------
// dist2Dest :
// Set the Distance from the current entity position to the Destination.
//-----------------------------------------------
void CCharacterCL::dist2Dest(double d2D)
{
CHECK((d2D == INVALID_DIST) || (d2D == 0.0) || (d2D >= ClientCfg.DestThreshold));
_DistToDest = d2D;
}// dist2Dest //
//-----------------------------------------------
// dist2Dest :
// Return the Distance from the current entity position to the Destination.
//-----------------------------------------------
double CCharacterCL::dist2Dest() const
{
CHECK((_DistToDest == INVALID_DIST) || (_DistToDest == 0.0) || (_DistToDest >= ClientCfg.DestThreshold));
return _DistToDest;
}// dist2Dest //
//-----------------------------------------------
// speed :
// Set the Entity Current Speed.
//-----------------------------------------------
void CCharacterCL::speed(double s)
{
CHECK((s == -1.0) || (s == 0.0) || ((s >= 0.001) && (s <= 1000.0)));
_Speed = s;
}// speed //
//-----------------------------------------------
// speed :
// Return the Entity Current Speed.
//-----------------------------------------------
double CCharacterCL::speed() const // virtual
{
CHECK((_Speed == -1.0) || (_Speed == 0.0) || ((_Speed >= 0.001) && (_Speed <= 1000.0)));
return _Speed;
}// getSpeed //
//-----------------------------------------------
// Build the in scene interface
//-----------------------------------------------
void CCharacterCL::buildInSceneInterface ()
{
// Delete previous interface
releaseInSceneInterfaces();
_InSceneUserInterface = CGroupInSceneUserInfo::build (this);
// parent
CEntityCL::buildInSceneInterface();
}
//-----------------------------------------------
void CCharacterCL::setBubble (CGroupInSceneBubble *bubble)
{
if (_CurrentBubble != NULL)
{
CGroupInSceneBubble *old = _CurrentBubble;
_CurrentBubble = NULL;
old->unlink();
}
nlassert (_CurrentBubble == NULL);
_CurrentBubble = bubble;
}
//-----------------------------------------------
//-----------------------------------------------
// runFactor :
// Set the Factor between Walk & Run
//-----------------------------------------------
void CCharacterCL::runFactor(double factor)
{
CHECK((factor >= 0.0) && (factor <= 1.0));
CHECK((factor == 0.0) || (animState(MOVE) == CAnimationStateSheet::Walk));
_RunFactor = factor;
}// runFactor //
//-----------------------------------------------
// runFactor :
// Get the Factor between Walk & Run
//-----------------------------------------------
double CCharacterCL::runFactor() const
{
CHECK((_RunFactor >= 0.0) && (_RunFactor <= 1.0));
CHECK((_RunFactor == 0.0) || (animState(MOVE) == CAnimationStateSheet::Walk));
return _RunFactor;
}// runFactor //
//-----------------------------------------------
// animOffset :
// Set the animation time offset for an animation channel.
//-----------------------------------------------
void CCharacterCL::animOffset(TAnimationType channel, double timeOffset)
{
if(ClientCfg.Light)
return;
// Check the channel
CHECK((uint)channel < _AnimOffset.size());
// Check Animation Time Offset is greater or equal to 0.
CHECK(timeOffset >= 0.0);
// Check the animation time offset is not greater to the animation length.
CHECK(timeOffset <= EAM->getAnimationLength(animId(channel)));
// Set the Value
_AnimOffset[channel] = timeOffset;
}// animOffset //
//-----------------------------------------------
// animOffset :
// Return the animation time offset for an animation channel.
//-----------------------------------------------
double CCharacterCL::animOffset(TAnimationType channel) const
{
if(ClientCfg.Light)
return 0.0;
// Check the channel
CHECK((uint)channel < _AnimOffset.size());
// Check Animation Time Offset is greater or equal to 0.
CHECK(_AnimOffset[channel] >= 0.0);
// Check the animation time offset is not greater to the animation length.
CHECK(_AnimOffset[channel] <= EAM->getAnimationLength(animId(channel)));
// Return the Value
return _AnimOffset[channel];
}// animOffset //
//---------------------------------------------------
// animationStateKey :
// Set the animation state key.
// \todo GUIGUI : a remplacer par animState directement.
//---------------------------------------------------
bool CCharacterCL::animationStateKey(TAnimationType channel, TAnimStateId value)
{
// Is the new key valid ?
if(value == CAnimationStateSheet::UnknownState)
{
nlwarning("CH::animationStateKey: Char '%d': new state key is Null.", _Slot);
return false;
}
// Set the new key.
animState(channel, value);
// Debug Animation for the selection
if(VerboseAnimSelection && _Slot == UserEntity->selection())
nlinfo("CH:animationStateKey:%d: state '%s'.", _Slot, CAnimationState::getAnimationStateName(value).c_str());
//
return true;
}// animationStateKey //
//-----------------------------------------------
// animState :
// Set the Animation 'State' for an animation channel.
//-----------------------------------------------
void CCharacterCL::animState(TAnimationType channel, TAnimStateId state)
{
// Check the channel
CHECK((uint)channel < _AnimState.size());
// Set the new State
_AnimState[channel] = state;
// Reset the Run Factor when the state change.
runFactor(0.0);
}// animState //
//-----------------------------------------------
// animState :
// Get the Animation 'State' for an animation channel.
//-----------------------------------------------
TAnimStateId CCharacterCL::animState(TAnimationType channel) const
{
// Check the channel
CHECK((uint)channel < _AnimState.size());
// Return the Animation State
return _AnimState[channel];
}// animState //
//-----------------------------------------------
// animIndex :
// Set the Animation 'Index' in the 'State' for an animation channel.
//-----------------------------------------------
void CCharacterCL::animIndex(TAnimationType channel, CAnimation::TAnimId index)
{
// Check the channel
CHECK((uint)channel < _AnimState.size());
// Set the animation index in the state
_AnimIndex[channel] = index;
// Set the animation Id
// If the current animation Index is not a valid one, return empty
if(_AnimIndex[channel] == CAnimation::UnknownAnim)
animId(channel, NL3D::UPlayList::empty);
else
{
// Check the AnimSet needed to get the animation Id.
CHECK(_CurrentAnimSet[channel]);
// Get the Pointer on the animation state, if Null, return empty
const CAnimationState *animStatePtr = _CurrentAnimSet[channel]->getAnimationState( (animState(channel)==CAnimationStateSheet::Emote)?_SubStateKey:animState(channel));
if(animStatePtr == 0)
animId(channel, NL3D::UPlayList::empty);
else
{
// Get the Animation Pointer, if Null, return Empty
const CAnimation *anim = animStatePtr->getAnimation(animIndex(channel));
if(anim == 0)
animId(channel, NL3D::UPlayList::empty);
// Return The Animation ID
else
animId(channel, anim->id());
}
}
}// animIndex //
//-----------------------------------------------
// animIndex :
// Get the Animation 'Index' in the 'State' for an animation channel.
//-----------------------------------------------
CAnimation::TAnimId CCharacterCL::animIndex(TAnimationType channel) const
{
// Check the channel
CHECK((uint)channel < _AnimState.size());
// Return the Animation Index in the State
return _AnimIndex[channel];
}//animIndex //
//-----------------------------------------------
// animId :
// Set the Animation 'Id' among all the animations for an animation channel.
//-----------------------------------------------
void CCharacterCL::animId(TAnimationType channel, uint id)
{
// Check the channel
CHECK((uint)channel < _AnimId.size());
// Set the Id
_AnimId[channel] = id;
}// animId //
//-----------------------------------------------
// animId :
// Get the Animation 'Id' among all the animations for an animation channel.
//-----------------------------------------------
uint CCharacterCL::animId(TAnimationType channel) const
{
// Check the channel
CHECK((uint)channel < _AnimId.size());
// Get the Id
return _AnimId[channel];
}// animId //
//-----------------------------------------------
// Align the given FX so that it is oriented like that entity
//-----------------------------------------------
void CCharacterCL::alignFX(UParticleSystemInstance instance, float scale /* = 1.f */, const NLMISC::CVector &localOffset /*= NLMISC::CVector::Null*/) const
{
// copy matrix from parent
CMatrix fxMatrix;
fxMatrix.identity();
buildAlignMatrix(fxMatrix);
alignFX(instance, fxMatrix, scale, localOffset);
}
void CCharacterCL::alignFX(UParticleSystemInstance instance, const CMatrix &matrix, float scale /*=1.f*/, const NLMISC::CVector &localOffset /*=NLMISC::CVector::Null*/) const
{
if(instance.empty())
return;
CMatrix fxMatrix = matrix;
if (scale != 1.f) fxMatrix.scale(scale);
fxMatrix.setPos(fxMatrix.getPos() + fxMatrix.mulVector(localOffset));
instance.setTransformMode(NL3D::UTransform::DirectMatrix);
instance.setMatrix(fxMatrix);
if(!_Skeleton.empty())
{
instance.setClusterSystem(_Skeleton.getClusterSystem());
}
}
//------------------------------------------------------------
// build a matrix aligned on that entity (includes dir & pos)
//------------------------------------------------------------
void CCharacterCL::buildAlignMatrix(NLMISC::CMatrix &dest) const
{
// if not in clod, pelvis bone has been computed -> use it to get the current orientation
// use the dir matrix otherwise
/*CVector forward;
if (pelvisBone() != -1 && _Skeleton)
{
if (_Skeleton.isBoneComputed(pelvisBone()))
{
// the direction is given by the y axis
NL3D::UBone pelvisBone = _Skeleton.getBone(pelvisBone());
forward = pelvisBone->getMatrix().getJ();
forward.z = 0.f; // project onto XY plane
forward.normalize();
}
else
{
// must be in clod -> use the direction
forward = _Front;
}
}
else
{
// bone not found, use the direction
forward = _Front;
}
dest.setRot(- forward, forward ^ CVector::K, CVector::K);
*/
dest.setRot(CVector::K ^ _Front, - _Front, CVector::K);
dest.setPos(pos());
}
//-----------------------------------------------
// attachFXInternal
//-----------------------------------------------
void CCharacterCL::attachFXInternal(const CAttachedFX::TSmartPtr fx, TAttachedFXList targetList)
{
if (!fx) return;
switch(targetList)
{
case FXListToRemove:
fx->TimeOutDate += TimeInSec; // in remove list timeout date is absolute
_AttachedFXListToRemove.push_front(fx);
break;
case FXListCurrentAnim:
_AttachedFXListForCurrentAnim.push_front(fx);
break;
case FXListAuto:
{
if (fx->AniFX->Sheet)
{
switch(fx->AniFX->Sheet->RepeatMode)
{
case CAnimationFXSheet::Respawn:
fx->TimeOutDate += TimeInSec; // in remove list timeout date is absolute
_AttachedFXListToRemove.push_front(fx);
break;
default:
_AttachedFXListForCurrentAnim.push_front(fx);
break;
}
}
else
{
fx->TimeOutDate += TimeInSec; // in remove list timeout date is absolute
_AttachedFXListToRemove.push_front(fx);
}
}
break;
default:
nlassert(0);
break;
}
}
//-----------------------------------------------
// attachFX
//-----------------------------------------------
void CCharacterCL::attachFX(const CAttachedFX::TSmartPtr fx)
{
attachFXInternal(fx, FXListToRemove);
}
// ***********************************************************************************************************************
void CCharacterCL::setAuraFX(uint index, const CAnimationFX *sheet)
{
nlassert(index < MaxNumAura);
// no-op if same aura
if (_AuraFX[index] && _AuraFX[index]->AniFX == sheet) return;
if (sheet == NULL)
{
std::list::iterator itAttachedFxToStart = _AttachedFXListToStart.begin();
while(itAttachedFxToStart != _AttachedFXListToStart.end())
{
if ((*itAttachedFxToStart).MaxNumAnimCount == index)
itAttachedFxToStart = _AttachedFXListToStart.erase(itAttachedFxToStart);
else
++itAttachedFxToStart;
}
// if there's already an aura attached, and if it is not already shutting down
if (_AuraFX[index] && _AuraFX[index]->TimeOutDate == 0.f)
{
_AuraFX[index]->TimeOutDate = TimeInSec + AURA_SHUTDOWN_TIME;
}
}
else
{
std::list::iterator itAttachedFxToStart = _AttachedFXListToStart.begin();
while(itAttachedFxToStart != _AttachedFXListToStart.end())
{
if ((*itAttachedFxToStart).MaxNumAnimCount == index)
return;
}
// remove previous aura
_AuraFX[index] = NULL;
CAttachedFX::CBuildInfo bi;
bi.Sheet = sheet;
bi.TimeOut = 0.f;
if (sheet->Sheet->PSName == "misc_caravan_teleportout.ps")
{
bi.MaxNumAnimCount = index;
bi.StartTime = TimeInSec;
bi.DelayBeforeStart = 12.5f;
_AttachedFXListToStart.push_front(bi);
}
else if (sheet->Sheet->PSName == "misc_kami_teleportout.ps")
{
bi.MaxNumAnimCount = index;
bi.StartTime = TimeInSec;
bi.DelayBeforeStart = 11.5f;
_AttachedFXListToStart.push_front(bi);
}
else
{
CAttachedFX::TSmartPtr fx = new CAttachedFX;
fx->create(*this, bi, CAttachedFX::CTargeterInfo());
if (!fx->FX.empty())
{
_AuraFX[index] = fx;
}
}
}
}
// ***********************************************************************************************************************
void CCharacterCL::setLinkFX(const CAnimationFX *fx, const CAnimationFX *dispell)
{
// no-op if same link
if (_LinkFX && _LinkFX->AniFX == fx) return;
if (_LinkFX)
{
if (dispell)
{
CAttachedFX::TSmartPtr fx = new CAttachedFX;
CAttachedFX::CBuildInfo bi;
bi.Sheet = dispell;
fx->create(*this, bi, CAttachedFX::CTargeterInfo());
attachFX(fx);
}
}
_LinkFX = NULL;
if (!fx) return;
CAttachedFX::TSmartPtr linkFX = new CAttachedFX;
CAttachedFX::CBuildInfo bi;
bi.Sheet = fx;
linkFX->create(*this, bi, CAttachedFX::CTargeterInfo());
if (!linkFX->FX.empty())
{
_LinkFX = linkFX;
}
}
// ***********************************************************************************************************************
void CCharacterCL::startItemAttackFXs(bool activateTrails, uint intensity)
{
uint numItems = (uint)_Items.size();
forceEvalAnim(); // force to eval bones at least once when fx are created
for(uint k = 0; k < numItems; ++k)
{
_Items[k].startAttackFX(_Skeleton, intensity, (SLOTTYPE::EVisualSlot) k, activateTrails);
}
}
// ***********************************************************************************************************************
void CCharacterCL::stopItemAttackFXs()
{
uint numItems = (uint)_Items.size();
for(uint k = 0; k < numItems; ++k)
{
if (_Items[k].Sheet)
{
_Items[k].stopAttackFX();
}
}
}
//-----------------------------------------------
// isNeutral :
//-----------------------------------------------
bool CCharacterCL::isNeutral() const
{
return !isEnemy();
}
//-----------------------------------------------
// isFriend :
//-----------------------------------------------
bool CCharacterCL::isFriend () const
{
return !isEnemy();
}
//-----------------------------------------------
// isEnemy :
//-----------------------------------------------
bool CCharacterCL::isEnemy () const
{
// Suppose enemy if attackable
if( properties().attackable() )
{
return true;
}
return isAnOutpostEnemy();
}
//-----------------------------------------------
// isAlly :
//-----------------------------------------------
bool CCharacterCL::isAlly () const
{
return this->isAnOutpostAlly();
}
//-----------------------------------------------
// applyVisualFX
//-----------------------------------------------
void CCharacterCL::applyVisualFX(sint64 prop)
{
CVisualFX vfx;
vfx.unpack(prop);
const CAnimationFX *auraFX = NULL;
if (vfx.Aura != 0)
{
auraFX = CAttackListManager::getInstance().getAuras().getFX(vfx.Aura);
}
setAuraFX(0, auraFX);
const CAnimationFX *auraReceiptFX = NULL;
if (vfx.AuraReceipt)
{
auraReceiptFX = CAttackListManager::getInstance().getAuras().getFX(0);
}
setAuraFX(1, auraReceiptFX);
const CAnimationFX *linkFX = NULL;
if (vfx.Link != 0)
{
linkFX = CAttackListManager::getInstance().getLinks().getFX(vfx.Link);
}
const CAnimationFX *dispellFX = NULL;
dispellFX = CAttackListManager::getInstance().getLinks().getFX(0);
setLinkFX(linkFX, dispellFX);
}
// *********************************************************************************************
const char *CCharacterCL::getBoneNameFromBodyPart(BODY::TBodyPart part, BODY::TSide side) const
{
if (!_Sheet) return CEntityCL::getBoneNameFromBodyPart(part, side);
return _Sheet->BodyToBone.getBoneName(part, side);
}
// *********************************************************************************************
const CItemSheet *CCharacterCL::getRightHandItemSheet() const
{
if (_RHandInstIdx == CEntityCL::BadIndex) return NULL;
return _Items[_RHandInstIdx].Sheet;
}
// *********************************************************************************************
const CItemSheet *CCharacterCL::getLeftHandItemSheet() const
{
if (_LHandInstIdx == CEntityCL::BadIndex) return NULL;
return _Items[_LHandInstIdx].Sheet;
}
// ***************************************************************************
void CCharacterCL::resetAllSoundAnimId()
{
for(uint i=0;i<_SoundId.size();i++)
{
_SoundId[i]= CSoundAnimationNoId;
}
}
/////////////////////////////
// CCharacterCL::CWornItem //
/////////////////////////////
// ***********************************************************************************************************************
void CCharacterCL::CWornItem::startAttackFX(NL3D::USkeleton skeleton, uint intensity, SLOTTYPE::EVisualSlot visualSlot, bool activateTrail)
{
if (intensity < 1 || intensity > 5) return;
// activate trail
if (activateTrail && !Trail.empty())
{
Trail.start();
}
// create the attack fx
if (Sheet)
{
const CItemFXSheet &fxSheet = Sheet->FX;
std::string shapeName = fxSheet.getAttackFX();
if (!shapeName.empty())
{
const char *stickPoint = NULL;
if(!skeleton.empty())
{
switch(visualSlot)
{
case SLOTTYPE::RIGHT_HAND_SLOT:
if( Sheet->ItemType != ITEM_TYPE::MAGICIAN_STAFF )
stickPoint = "box_arme";
break;
case SLOTTYPE::LEFT_HAND_SLOT:
if(Sheet && Sheet->getAnimSet()=="s")
stickPoint = "Box_bouclier";
else
stickPoint = "box_arme_gauche";
break;
default:
break;
}
}
if (stickPoint)
{
sint boneId = skeleton.getBoneIdByName(std::string(stickPoint));
if (boneId != -1)
{
NL3D::UInstance instance = Scene->createInstance(shapeName);
if (!instance.empty())
{
instance.show();
if (skeleton.getVisibility() == UTransform::Hide)
{
// force to compute the bone at least once
skeleton.forceComputeBone(boneId);
}
UParticleSystemInstance atk;
atk.cast (instance);
if (!atk.empty())
{
// set the user params
static const float up[5][4] =
{
{ 0.f, 0.f, 0.f, 0.f},
{ 1.f, 0.f, 0.f, 0.f},
{ 1.f, 1.f, 0.f, 0.f},
{ 1.f, 1.f, 1.f, 0.f},
{ 1.f, 1.f, 1.f, 1.f}
};
for(uint k = 0; k < 4; ++k)
{
atk.setUserParam(k, up[intensity - 1][k]);
}
atk.setTransformMode(UTransform::RotEuler);
atk.setPos(fxSheet.AttackFXOffset);
const float degreeToRad = (float) Pi / 180.f;
atk.setRotEuler(fxSheet.AttackFXRot.x * degreeToRad, fxSheet.AttackFXRot.y * degreeToRad, fxSheet.AttackFXRot.z * degreeToRad);
skeleton.stickObject(atk, boneId);
// delegate mng of this object lifetime to the fx manager
FXMngr.fx2remove(atk);
}
else
{
Scene->deleteInstance(atk);
}
}
}
}
}
}
}
// ***********************************************************************************************************************
void CCharacterCL::CWornItem::stopAttackFX()
{
if (!Trail.empty()) Trail.stop();
}
// ***********************************************************************************************************************
void CCharacterCL::CWornItem::initFXs(SLOTTYPE::EVisualSlot /* visualSlot */, NL3D::UInstance parent)
{
releaseFXs();
if (!Sheet) return;
// create trail fx
const CItemFXSheet &sheet = Sheet->FX;
std::string shapeName = sheet.getTrail();
if (!shapeName.empty())
{
Trail = Scene->createInstance(shapeName);
if (Trail.empty())
{
nlwarning("Cannot create instance %s", shapeName.c_str());
return;
}
// Initialize the remanence. Must substract the object matrix since parent(parent)
CMatrix mat = parent.getMatrix();
mat.invert();
mat *= Trail.getMatrix();
Trail.setTransformMode(UTransformable::DirectMatrix);
Trail.setMatrix(mat);
// Initialize instance.
Trail.parent(parent);
}
}
// ***********************************************************************************************************************
void CCharacterCL::CWornItem::enableAdvantageFX(NL3D::UInstance parent)
{
if (!Sheet) return;
bool enabled = Sheet->HasFx;
if ((enabled && !AdvantageFX.empty()) || (!enabled && AdvantageFX.empty()))
return; // state did not change
if (!enabled)
{
// well, it is unlikely that player will loses its ability to master an item after he gained it, but manage the case anyway.
if (!AdvantageFX.removeByID(NELID("STOP")) && !AdvantageFX.removeByID(NELID("main")))
{
AdvantageFX.activateEmitters(false);
}
FXMngr.fx2remove(AdvantageFX);
AdvantageFX = NULL;
}
else
{
std::string shapeName = Sheet->FX.getAdvantageFX();
if (!shapeName.empty())
{
NL3D::UInstance fx = Scene->createInstance(shapeName);
if (fx.empty()) return;
AdvantageFX.cast (fx);
if (AdvantageFX.empty())
{
Scene->deleteInstance(fx);
return;
}
CMatrix mat = parent.getMatrix();
mat.invert();
mat *= AdvantageFX.getMatrix();
AdvantageFX.setTransformMode(UTransformable::DirectMatrix);
AdvantageFX.setMatrix(mat);
AdvantageFX.parent(parent);
}
}
}
// ***********************************************************************************************************************
void CCharacterCL::CWornItem::releaseFXs()
{
if (Scene)
{
if (!Trail.empty())
Scene->deleteInstance(Trail);
if (!AdvantageFX.empty())
Scene->deleteInstance(AdvantageFX);
}
}
// ***********************************************************************************************************************
void CCharacterCL::CWornItem::setTrailSize(uint size)
{
if (Trail.empty()) return;
if (!Sheet) return;
clamp(size, (uint) 0, (uint) 15);
if (size == 0)
{
Trail.setSliceTime(0.f);
}
else
{
float ratio = (size - 1) / 15.f;
Trail.setSliceTime(ratio * Sheet->FX.TrailMaxSliceTime + (1.f - ratio) * Sheet->FX.TrailMinSliceTime);
}
}
// ***********************************************************************************************************************
const CAttack *CCharacterCL::getAttack(const CAttackIDSheet &id) const
{
if (!_Sheet) return NULL;
return getAttack(id, _Sheet->AttackLists);
}
// ***********************************************************************************************************************
const CAttack *CCharacterCL::getAttack(const CAttackIDSheet &id, const std::vector &attackList) const
{
for(std::vector::const_reverse_iterator it = attackList.rbegin(); it != attackList.rend(); ++it)
{
const CAttackList *al = CAttackListManager::getInstance().getAttackList(ClientSheetsStrings.get(*it));
if (al)
{
const CAttack *attk = al->getAttackFromID(id);
if (attk) return attk;
}
}
return NULL;
}
// ***********************************************************************************************************************
void CCharacterCL::initStaticFX()
{
_StaticFX = NULL;
if (ClientCfg.Light) return;
std::string staticFX = _Sheet->getStaticFX();
if (!staticFX.empty())
{
CEntitySheet *sheet = SheetMngr.get(NLMISC::CSheetId(staticFX));
if (sheet)
{
if (sheet->Type == CEntitySheet::ANIMATION_FX)
{
CAnimationFXSheet *afs = NLMISC::safe_cast(sheet);
_StaticFX = new CStaticFX;
_StaticFX->AF.init(afs, EAM ? EAM->getAnimationSet() : NULL);
_StaticFX->FX = new CAttachedFX;
CAttachedFX::CBuildInfo bi;
bi.Sheet = &_StaticFX->AF;
_StaticFX->FX->create(*this, bi, CAttachedFX::CTargeterInfo());
if (_StaticFX->FX->FX.empty())
{
_StaticFX = NULL;
return;
}
}
}
else
{
nlwarning("Static fx %s not found", staticFX.c_str());
}
}
}
// ***************************************************************************
EGSPD::CPeople::TPeople CCharacterCL::people() const
{
if(_Sheet)
return _Sheet->Race;
else
return EGSPD::CPeople::Unknown;
}
// ***************************************************************************
void CCharacterCL::setPeople(EGSPD::CPeople::TPeople /* people */)
{
}
// ***************************************************************************
#if !FINAL_VERSION
// temp : begin cast of projectile (start begin anim and loop anim)
NLMISC_COMMAND(beginCast, "Start spell cast", " []")
{
if (args.size() < 2) return false;
if (args.size() > 5) return false;
uint casterSlot = 0;
if (args.size() >= 4)
{
fromString(args[3], casterSlot);
}
CCharacterCL *ch = dynamic_cast(EntitiesMngr.entity(casterSlot));
if (!ch) return false;
CBehaviourContext bc;
// TODO : the type of missile is contained in the spell id
if (args.size() >= 5)
{
sint missileType;
fromString(args[4], missileType);
bc.Behav.Behaviour = (MBEHAV::EBehaviour) (MBEHAV::CAST_OFF + missileType);
}
else
{
bc.Behav.Behaviour = MBEHAV::CAST_OFF;
}
//
uint16 spellId;
fromString(args[0], spellId); // spell id is unused
bc.Behav.Spell.SpellId = spellId;
//bc.Behav.Spell.Resist = false;
uint16 spellIntensity;
fromString(args[1], spellIntensity);
bc.Behav.Spell.SpellIntensity = spellIntensity;
if (args.size() == 3)
{
uint16 spellMode;
fromString(args[2], spellMode);
bc.Behav.Spell.SpellMode = spellMode;
}
else
{
bc.Behav.Spell.SpellMode = MAGICFX::Bomb; // 'bomb' is the default
}
ch->applyBehaviour(bc);
return true;
}
// temp : test fail of a cast
NLMISC_COMMAND(failCast, "Simulate failure of a spell cast", " []")
{
if (args.size() < 2) return false;
if (args.size() > 3) return false;
CCharacterCL *ch = dynamic_cast(EntitiesMngr.entity(0));
if (!ch) return false;
CBehaviourContext bc;
// TODO : the type of missile is contained in the spell id
bc.Behav.Behaviour = MBEHAV::CAST_OFF_FAIL;
//
uint16 spellId;
fromString(args[0], spellId); // spell id is unused
bc.Behav.Spell.SpellId = spellId;
//bc.Behav.Spell.Resist = false;
uint16 spellIntensity;
fromString(args[1], spellIntensity);
bc.Behav.Spell.SpellIntensity = spellIntensity;
if (args.size() == 3)
{
uint16 spellMode;
fromString(args[2], spellMode);
bc.Behav.Spell.SpellMode = spellMode;
}
else
{
bc.Behav.Spell.SpellMode = MAGICFX::Bomb; // 'bomb' is the default
}
ch->applyBehaviour(bc);
return true;
}
// temp to test cast of a projectile on another entity
NLMISC_COMMAND(projectile, "Cast a projectile on another entity", " [