khanat-opennel-code/code/ryzom/client/src/user_entity.cpp
2011-05-31 22:16:17 +02:00

4459 lines
129 KiB
C++

// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
// 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 <http://www.gnu.org/licenses/>.
/////////////
// INCLUDE //
/////////////
#include "stdpch.h"
// Misc.
#include "nel/misc/vectord.h"
#include "nel/misc/matrix.h"
#include "nel/misc/quat.h"
// 3D Interface.
#include "nel/3d/u_scene.h"
#include "nel/3d/u_visual_collision_manager.h"
#include "nel/3d/viewport.h"
#include "nel/3d/u_bone.h"
#include "nel/3d/u_instance_material.h"
#include "nel/3d/u_play_list.h"
#include "nel/3d/u_point_light.h"
#include "nel/3d/u_particle_system_instance.h"
#include "nel/3d/u_camera.h"
// Pacs Interface
#include "nel/pacs/u_global_position.h"
// Client.
#include "user_entity.h"
#include "motion/user_controls.h"
#include "pacs_client.h"
#include "net_manager.h"
#include "time_client.h"
#include "entity_animation_manager.h"
#include "sheet_manager.h"
#include "sound_manager.h"
#include "interface_v3/interface_manager.h"
#include "entities.h"
#include "debug_client.h"
#include "misc.h"
#include "interface_v3/bot_chat_manager.h"
#include "fx_manager.h"
#include "main_loop.h"
#include "interface_v3/group_in_scene_bubble.h"
#include "interface_v3/inventory_manager.h"
#include "interface_v3/group_html.h"
#include "interface_v3/people_interraction.h"
#include "init_main_loop.h"
#include "view.h"
#include "interface_v3/sphrase_manager.h"
#include "interface_v3/sbrick_manager.h"
#include "interface_v3/action_phrase_faber.h"
#include "interface_v3/bar_manager.h"
#include "interface_v3/skill_manager.h"
#include "far_tp.h"
#include "npc_icon.h"
// game share
#include "game_share/slot_types.h"
#include "game_share/player_visual_properties.h"
#include "game_share/mode_and_behaviour.h"
#include "game_share/inventories.h"
#include "game_share/animal_type.h"
#include "game_share/bot_chat_types.h"
// Sound animation
#include "nel/sound/sound_anim_manager.h"
#include "nel/sound/sound_animation.h"
#include "nel/sound/sound_anim_marker.h"
// r2
#include "r2/editor.h"
///////////
// USING //
///////////
using namespace NLMISC;
using namespace NLPACS;
using namespace std;
using NL3D::UScene;
using NL3D::UVisualCollisionManager;
using NL3D::UTextContext;
////////////
// EXTERN //
////////////
extern UScene *Scene;
extern UVisualCollisionManager *CollisionManager;
extern CEntityAnimationManager *EAM;
extern UTextContext *TextContext;
extern NL3D::UCamera MainCam;
// Context help
extern void contextHelp (const std::string &help);
extern void beastOrder (const std::string &orderStr, const std::string &beastIndexStr, bool confirmFree = true);
// Out game received position
NLMISC::CVectorD UserEntityInitPos;
NLMISC::CVector UserEntityInitFront;
CUserEntity *UserEntity = NULL;
uint32 CharFirstConnectedTime = 0;
uint32 CharPlayedTime = 0;
const double MaxExtractionDistance = 1.0f;
////////////
// GLOBAL //
////////////
// Hierarchical timer
H_AUTO_DECL ( RZ_Client_Update_Sound )
//////////////
// FUNCTION //
//////////////
//string chooseRandom( const vector<string>& sounds, uint32& previousIndex );
//-----------------------------------------------
// CUserEntity :
// Constructor.
//-----------------------------------------------
CUserEntity::CUserEntity()
: CPlayerCL()
{
Type = User;
_Run = false;
_RunWhenAble = false;
_WalkVelocity = 1.0f;
_RunVelocity = 2.0f;
_CurrentVelocity = _WalkVelocity;
_FrontVelocity = 0.0f;
_LateralVelocity = 0.0f;
// \todo GUIGUI : do it more generic.
_First_Pos = false;
// No selection, trader, interlocutor at the beginning.
_Selection = CLFECOMMON::INVALID_SLOT;
_Trader = CLFECOMMON::INVALID_SLOT;
_Interlocutor = CLFECOMMON::INVALID_SLOT;
// Not selectable at the beginning.
_Selectable = false;
// Your are not on a mount at the beginning.
_OnMount = false;
_AnimAttackOn = false;
_ViewMode = FirstPV;
_PermanentDeath = false;
_FollowMode = false;
_CheckPrimitive = 0;
// The user is not in collision with someone else.
_ColOn = false;
// Collisions are not removed.
_ColRemoved = false;
// No Move To at the beginning.
_MoveToSlot = CLFECOMMON::INVALID_SLOT;
_MoveToAction= CUserEntity::None;
_MoveToDist= 0.0;
_MoveToColStartTime= 0;
_FollowForceHeadPitch= false;
_ForceLookSlot= CLFECOMMON::INVALID_SLOT;
_LastExecuteCombatSlot= CLFECOMMON::INVALID_SLOT;
_R2CharMode= R2::TCharMode::Player;
}// CUserEntity //
//-----------------------------------------------
// ~CUserEntity :
// Destructor.
//-----------------------------------------------
CUserEntity::~CUserEntity()
{
// Remove observers
_SpeedFactor.release();
_MountHunger.release();
_MountSpeeds.release();
CInterfaceManager *pIM = CInterfaceManager::getInstance();
{
CCDBNodeLeaf *node = pIM->getDbProp("SERVER:USER:IS_INVISIBLE", false);
if (node)
{
ICDBNode::CTextId textId;
node->removeObserver(&_InvisibleObs, textId);
}
}
for(uint i=0;i<EGSPD::CSPType::EndSPType;i++)
{
CCDBNodeLeaf *node= pIM->getDbProp(toString("SERVER:USER:SKILL_POINTS_%d:VALUE", i), false);
if(node)
{
ICDBNode::CTextId textId;
node->removeObserver(_SkillPointObs+i, textId);
}
}
for( uint i=0; i<_FamesObs.size(); ++i )
{
uint32 factionIndex = _FamesObs[i]->FactionIndex;
uint32 fameIndexInDatabase = CStaticFames::getInstance().getDatabaseIndex(factionIndex);
string sDBPath = toString("SERVER:FAME:PLAYER%d:VALUE",fameIndexInDatabase);
CCDBNodeLeaf * node = pIM->getDbProp(sDBPath, false);
if(node)
{
ICDBNode::CTextId textId;
node->removeObserver(_FamesObs[i], textId);
}
}
contReset(_FamesObs);
CNPCIconCache::getInstance().removeObservers();
// Remove the Primitive used for check (because ~CEntityCL() will call CEntityCL::removePrimitive(), not CUserEntity::removePrimitive())
removeCheckPrimitive();
CNPCIconCache::release();
}// ~CUserEntity //
//-----------------------------------------------
// initProperties :
// Initialize properties of the entity (according to the class).
//-----------------------------------------------
void CUserEntity::initProperties()
{
properties().selectable(true);
}// initProperties //
//-----------------------------------------------
// build :
// Build the entity from a sheet.
//-----------------------------------------------
bool CUserEntity::build(const CEntitySheet *sheet) // virtual
{
// Init received position
pos(UserEntityInitPos);
front(UserEntityInitFront);
dir(front());
setHeadPitch(0);
// Cast the sheet in the right type.
_PlayerSheet = dynamic_cast<const CRaceStatsSheet *>(sheet);
if(_PlayerSheet == 0)
{
pushDebugStr("User Sheet is not a valid '.race_stats'.");
return false;
}
else
pushInfoStr("User Sheet is a valid '.race_stats'.");
// Get the DB Entry
if(IngameDbMngr.getNodePtr())
{
CCDBNodeBranch *nodeRoot = dynamic_cast<CCDBNodeBranch *>(IngameDbMngr.getNodePtr()->getNode(0));
if(nodeRoot)
{
_DBEntry = dynamic_cast<CCDBNodeBranch *>(nodeRoot->getNode(_Slot));
if(_DBEntry == 0)
pushDebugStr("Cannot get a pointer on the DB entry.");
}
}
disableFollow();
// Walk/Run ?
if(ClientCfg.RunAtTheBeginning != _Run)
switchVelocity();
// Set the up of the user.
up(CVector(0,0,1));
// Init User infos.
eyesHeight(ClientCfg.EyesHeight);
walkVelocity(ClientCfg.Walk);
runVelocity(ClientCfg.Run);
// Compute the first automaton.
_CurrentAutomaton = automatonType() + "_normal.automaton";
// Build the PACS Primitive.
if(initPrimitive(0.5f, 2.0f, 0.0f, 0.0f, UMovePrimitive::Slide, (UMovePrimitive::TTrigger)(UMovePrimitive::OverlapTrigger | UMovePrimitive::EnterTrigger), MaskColPlayer, MaskColPlayer | MaskColNpc | MaskColDoor))
_Primitive->insertInWorldImage(dynamicWI);
// Compute the element to be able to snap the entity to the ground.
computeCollisionEntity();
// Initialize properties of the client.
initProperties();
// Initialize the observer for the speed factor and mount stuff
_SpeedFactor.init();
_MountHunger.init();
_MountSpeeds.init();
// Create the user playlist
createPlayList();
// Initialize the internal time.
_LastFrameTime = ((double)T1) * 0.001;
// Set the gender in local mode.
if(ClientCfg.Local)
{
_Mode = MBEHAV::NORMAL;
_ModeWanted = MBEHAV::NORMAL;
_Gender = ClientCfg.Sex;
SPropVisualA visualA = buildPropVisualA(_PlayerSheet->GenderInfos[_Gender]);
SPropVisualB visualB = buildPropVisualB(_PlayerSheet->GenderInfos[_Gender]);
SPropVisualC visualC;
visualA.PropertySubData.Sex = _Gender;
visualC.PropertyC = 0;
visualC.PropertySubData.CharacterHeight = 0;
visualC.PropertySubData.ArmsWidth = 7;
visualC.PropertySubData.LegsWidth = 7;
visualC.PropertySubData.TorsoWidth = 7;
visualC.PropertySubData.BreastSize = 7;
// Set the Database
sint64 *prop = (sint64 *)&visualA;
CInterfaceManager::getInstance()->getDbProp("SERVER:Entities:E0:P"+toString("%d", CLFECOMMON::PROPERTY_VPA))->setValue64(*prop); // Set the database
prop = (sint64 *)&visualB;
CInterfaceManager::getInstance()->getDbProp("SERVER:Entities:E0:P"+toString("%d", CLFECOMMON::PROPERTY_VPB))->setValue64(*prop); // Set the database
prop = (sint64 *)&visualC;
CInterfaceManager::getInstance()->getDbProp("SERVER:Entities:E0:P"+toString("%d", CLFECOMMON::PROPERTY_VPC))->setValue64(*prop); // Set the database
// Apply Changes.
updateVisualProperty(0, CLFECOMMON::PROPERTY_VPA);
}
// \todo GUIGUI Retrieve the player's appearence during the avatar selection.
// Get Visual Properties From the character selection window.
else
{
}
// Rebuild interface
buildInSceneInterface ();
// Add observer on invisible property
CInterfaceManager *pIM = CInterfaceManager::getInstance();
{
CCDBNodeLeaf *node = pIM->getDbProp("SERVER:USER:IS_INVISIBLE", false);
if (node)
{
ICDBNode::CTextId textId;
node->addObserver(&_InvisibleObs, textId);
}
}
// Add an observer on skill points
for(uint i=0;i<EGSPD::CSPType::EndSPType;i++)
{
_SkillPointObs[i].SpType= i;
CCDBNodeLeaf *node= pIM->getDbProp(toString("SERVER:USER:SKILL_POINTS_%d:VALUE", i), false);
if(node)
{
ICDBNode::CTextId textId;
node->addObserver(_SkillPointObs+i, textId);
}
}
// Add an observer on Fames
for( uint i=(uint)PVP_CLAN::BeginClans; i<=(uint)PVP_CLAN::EndClans; ++i )
{
uint32 factionIndex = PVP_CLAN::getFactionIndex((PVP_CLAN::TPVPClan)i);
uint32 fameIndexInDatabase = CStaticFames::getInstance().getDatabaseIndex(factionIndex);
string sDBPath = toString("SERVER:FAME:PLAYER%d:VALUE",fameIndexInDatabase);
CFameObserver * fameObs = new CFameObserver();
if( fameObs )
{
fameObs->FactionIndex = factionIndex;
CCDBNodeLeaf * node = pIM->getDbProp(sDBPath, false);
if(node)
{
ICDBNode::CTextId textId;
node->addObserver(fameObs, textId);
}
_FamesObs.push_back(fameObs);
}
}
// Add an observer on Mission Journal
CNPCIconCache::getInstance().addObservers();
// Initialize the camera distance.
View.cameraDistance(ClientCfg.CameraDistance);
// char and account time properties
CSkillManager *pSM = CSkillManager::getInstance();
if( pSM )
{
pSM->tryToUnblockTitleFromCharOldness( CharFirstConnectedTime );
pSM->tryToUnblockTitleFromCharPlayedTime( CharPlayedTime );
}
// Entity created.
return true;
}// build //
//-----------------------------------------------
// eyesHeight :
// \todo GUIGUI : do it better in mount mode
//-----------------------------------------------
float CUserEntity::eyesHeight()
{
if(!_OnMount)
return _EyesHeight * _CharacterScalePos;
else
return _EyesHeight * _CharacterScalePos;
}// eyesHeight //
/////////////////////////////////////////////////
/////////////////////////////////////////////////
/////////////// VISUAL PROPERTIES ///////////////
/////////////////////////////////////////////////
/////////////////////////////////////////////////
//-----------------------------------------------
// updateVisualPropertyPos :
// Update Entity Position.
//-----------------------------------------------
void CUserEntity::updateVisualPropertyPos(const NLMISC::TGameCycle &/* gameCycle */, const sint64 &/* prop */, const NLMISC::TGameCycle &/* pI */)
{
}// updateVisualPropertyPos //
//-----------------------------------------------
// updateVisualPropertyOrient :
// Update Entity Orientation.
//-----------------------------------------------
void CUserEntity::updateVisualPropertyOrient(const NLMISC::TGameCycle &/* gameCycle */, const sint64 &/* prop */)
{
}// updateVisualPropertyOrient //
void CUserEntity::updateVisualPropertyTargetList(const NLMISC::TGameCycle &/* gameCycle */, const sint64 &/* prop */, uint /* listIndex */)
{
}
void CUserEntity::updateVisualPropertyVisualFX(const NLMISC::TGameCycle &/* gameCycle */, const sint64 &prop)
{
applyVisualFX(prop);
}
//-----------------------------------------------
// updateVisualPropertyBehaviour :
// Update Entity Behaviour.
//-----------------------------------------------
void CUserEntity::updateVisualPropertyBehaviour(const NLMISC::TGameCycle &/* gameCycle */, const sint64 &prop)
{
// Compute the behaviour.
CBehaviourContext bc;
bc.Behav = MBEHAV::CBehaviour(prop);
bc.BehavTime = TimeInSec;
if(VerboseAnimUser)
{
nlinfo("UE::updateVPBeha: '%d(%s)'.", (sint)bc.Behav.Behaviour, MBEHAV::behaviourToString(bc.Behav.Behaviour).c_str());
}
CCDBNodeLeaf *targetList0 = dynamic_cast<CCDBNodeLeaf *>(_DBEntry->getNode(CLFECOMMON::PROPERTY_TARGET_LIST_0));
CCDBNodeLeaf *targetList1 = dynamic_cast<CCDBNodeLeaf *>(_DBEntry->getNode(CLFECOMMON::PROPERTY_TARGET_LIST_1));
CCDBNodeLeaf *targetList2 = dynamic_cast<CCDBNodeLeaf *>(_DBEntry->getNode(CLFECOMMON::PROPERTY_TARGET_LIST_1));
CCDBNodeLeaf *targetList3 = dynamic_cast<CCDBNodeLeaf *>(_DBEntry->getNode(CLFECOMMON::PROPERTY_TARGET_LIST_1));
if (targetList0 && targetList1 && targetList2 && targetList3)
{
uint64 vp[4] =
{
(uint64) targetList0->getValue64(),
(uint64) targetList1->getValue64(),
(uint64) targetList2->getValue64(),
(uint64) targetList3->getValue64()
};
bc.Targets.unpack(vp, 4);
}
applyBehaviour(bc);
}// updateVisualPropertyBehaviour //
//-----------------------------------------------
// updateVisualPropertyName :
// Update Entity Name.
//-----------------------------------------------
void CUserEntity::updateVisualPropertyName(const NLMISC::TGameCycle &gameCycle, const sint64 &prop)
{
uint32 oldNameId = _NameId;
CPlayerCL::updateVisualPropertyName(gameCycle, prop);
// Name changed ?
if (oldNameId != _NameId)
{
CInterfaceManager *pIM = CInterfaceManager::getInstance();
CInterfaceElement *element = pIM->getElementFromId("ui:interface:mailbox:content:html");
if (element)
{
CGroupHTML *html = dynamic_cast<CGroupHTML*>(element);
if (html)
html->browse("home");
}
}
}// updateVisualPropertyName //
//-----------------------------------------------
// updateVisualPropertyTarget :
// Update Entity Target.
//-----------------------------------------------
void CUserEntity::updateVisualPropertyTarget(const NLMISC::TGameCycle &/* gameCycle */, const sint64 &/* prop */)
{
// Don't override the Player Target, cause client side entirely => no lag.
//targetSlot((CLFECOMMON::TCLEntityId)prop);
}// updateVisualPropertyTarget //
//-----------------------------------------------
// updateVisualPropertyMode :
// New mode received -> immediately change the mode for the user.
// \warning Read the position or orientation from the database when reading the mode (no more updated in updateVisualPropertyPos and updateVisualPropertyOrient).
//-----------------------------------------------
void CUserEntity::updateVisualPropertyMode(const NLMISC::TGameCycle &/* gameCycle */, const sint64 &prop)
{
// Combat Float Check
if((MBEHAV::EMode)prop == MBEHAV::COMBAT_FLOAT)
{
nlwarning("UE:updateVPMode: the user should never have the COMBAT_FLOAT mode.");
return;
}
// New Mode Received.
_TheoreticalMode = (MBEHAV::EMode)prop;
// Change the user mode.
mode(_TheoreticalMode);
}// updateVisualPropertyMode //
//-----------------------------------------------
// updateVisualPropertyVpa :
// Update Entity Visual Property A
//-----------------------------------------------
void CUserEntity::updateVisualPropertyVpa(const NLMISC::TGameCycle &gameCycle, const sint64 &prop)
{
CPlayerCL::updateVisualPropertyVpa(gameCycle, prop);
// Special for user: Disable Character Lod, because always want it at full rez.
if(!_Skeleton.empty())
{
_Skeleton.setLodCharacterShape(-1);
}
updateVisualDisplay();
}// updateVisualPropertyVpa //
//-----------------------------------------------
// updateVisualPropertyVpb :
// Update Entity Visual Property B
//-----------------------------------------------
void CUserEntity::updateVisualPropertyVpb(const NLMISC::TGameCycle &gameCycle, const sint64 &prop)
{
CPlayerCL::updateVisualPropertyVpb(gameCycle, prop);
updateVisualDisplay();
}// updateVisualPropertyVpb //
//-----------------------------------------------
// updateVisualPropertyVpc :
// Update Entity Visual Property C
//-----------------------------------------------
void CUserEntity::updateVisualPropertyVpc(const NLMISC::TGameCycle &gameCycle, const sint64 &prop)
{
CPlayerCL::updateVisualPropertyVpc(gameCycle, prop);
updateVisualDisplay();
}// updateVisualPropertyVpc //
//-----------------------------------------------
// updateVisualPropertyEntityMounted :
// Update Entity Mount
//-----------------------------------------------
void CUserEntity::updateVisualPropertyEntityMounted(const NLMISC::TGameCycle &gameCycle, const sint64 &prop)
{
if(isFighting())
CPlayerCL::updateVisualPropertyEntityMounted(gameCycle, prop);
else
_Mount = (CLFECOMMON::TCLEntityId)prop;
}// updateVisualPropertyEntityMounted //
//-----------------------------------------------
// updateVisualPropertyRiderEntity :
// Update Entity Rider
//-----------------------------------------------
void CUserEntity::updateVisualPropertyRiderEntity(const NLMISC::TGameCycle &gameCycle, const sint64 &prop)
{
if(isFighting())
CPlayerCL::updateVisualPropertyRiderEntity(gameCycle, prop);
else
_Rider = (CLFECOMMON::TCLEntityId)prop;
}// updateVisualPropertyRiderEntity //
//-----------------------------------------------
//-----------------------------------------------
void CUserEntity::updateVisualPropertyPvpMode(const NLMISC::TGameCycle &gameCycle, const sint64 &prop)
{
CPlayerCL::updateVisualPropertyPvpMode(gameCycle, prop);
// Additionaly, inform interface of the change
CInterfaceManager *pIM= CInterfaceManager::getInstance();
// For PVP ZoneFaction
CCDBNodeLeaf *pDB= pIM->getDbProp("UI:VARIABLES:USER:TRACK_PVP_CHANGE_MODE");
if(pDB)
{
sint32 val= pDB->getValue32();
pDB->setValue32(val+1);
}
// For Any PVP change
pDB= pIM->getDbProp("UI:VARIABLES:USER:TRACK_PVP_CHANGE_ANY");
if(pDB)
{
sint32 val= pDB->getValue32();
pDB->setValue32(val+1);
}
}
//-----------------------------------------------
//-----------------------------------------------
void CUserEntity::updateVisualPropertyOutpostInfos(const NLMISC::TGameCycle &gameCycle, const sint64 &prop)
{
CPlayerCL::updateVisualPropertyOutpostInfos(gameCycle, prop);
// For Any PVP change
CInterfaceManager *pIM= CInterfaceManager::getInstance();
CCDBNodeLeaf *pDB= pIM->getDbProp("UI:VARIABLES:USER:TRACK_PVP_CHANGE_ANY");
if(pDB)
{
sint32 val= pDB->getValue32();
pDB->setValue32(val+1);
}
}
//-----------------------------------------------
//-----------------------------------------------
void CUserEntity::updateVisualPropertyPvpClan(const NLMISC::TGameCycle &gameCycle, const sint64 &prop)
{
CPlayerCL::updateVisualPropertyPvpClan(gameCycle, prop);
// For Any PVP change
CInterfaceManager *pIM= CInterfaceManager::getInstance();
CCDBNodeLeaf *pDB= pIM->getDbProp("UI:VARIABLES:USER:TRACK_PVP_CHANGE_ANY");
if(pDB)
{
sint32 val= pDB->getValue32();
pDB->setValue32(val+1);
}
}
/////////////////////////////////////////////////
/////////////////////////////////////////////////
/////////////////////////////////////////////////
/////////////////////////////////////////////////
//-----------------------------------------------
// mode :
// Method called to change the mode (Combat/Mount/etc.).
// \todo GUIGUI : apply stage in combat modes instead of just removing them.
// \todo GUIGUI : go or teleport the player to the mode position (see how to manage it).
//-----------------------------------------------
bool CUserEntity::mode(MBEHAV::EMode m)
{
if(Verbose & VerboseAnim)
nlinfo("UE::mode: old mode '%s(%d)' new mode '%s(%d)'.", MBEHAV::modeToString(_Mode).c_str(), _Mode, MBEHAV::modeToString(m).c_str(), m);
// Nothing to do if the mode is the same as the previous one.
if(m == _Mode)
return true;
// Release the old Mode.
switch(_Mode)
{
// Leave COMBAT Mode
case MBEHAV::COMBAT:
case MBEHAV::COMBAT_FLOAT:
{
// If there are some stage not complete -> remove them
if(_Stages._StageSet.size() != 0)
_Stages._StageSet.clear();
}
break;
// Leave MOUNTED Mode
case MBEHAV::MOUNT_NORMAL:
case MBEHAV::MOUNT_SWIM:
{
if ( m == MBEHAV::REST )
{
// can't go afk while mounting
return false;
}
// if changing mode for another mount mode, do nothing
if (m != MBEHAV::MOUNT_NORMAL && m != MBEHAV::MOUNT_SWIM)
{
// Display the mount again.
CCharacterCL *mount = dynamic_cast<CCharacterCL *>(EntitiesMngr.entity(_Mount));
if(mount)
{
// Set the mount.
mount->rider(CLFECOMMON::INVALID_SLOT);
mount->_Stages._StageSet.clear();
mount->setMode(MBEHAV::NORMAL);
mount->computeAutomaton();
mount->computeAnimSet();
mount->setAnim(CAnimationStateSheet::Idle);
if(mount->getPrimitive())
mount->getPrimitive()->setOcclusionMask(MaskColNpc); // the mount is an npc
mount->_ForbidClipping = false;
}
//
_Mount = CLFECOMMON::INVALID_SLOT;
// Restore the user Primitive
if(_Primitive)
{
_Primitive->setRadius( std::min(0.5f, (float)(RYZOM_ENTITY_SIZE_MAX/2)) );
_Primitive->setHeight(2);
}
_OnMount = false;
// Shift the player position (not to stand inside the mount)
CVectorD unmountShift;
unmountShift.sphericToCartesian(1.0, frontYaw() + NLMISC::Pi/2, 0);
pacsPos(pos() + unmountShift);
// Restore running if necessary
if (!_Run && _RunWhenAble)
{
switchVelocity();
}
}
if (_Mode == MBEHAV::MOUNT_SWIM && ( m == MBEHAV::COMBAT || m == MBEHAV::COMBAT_FLOAT))
{
//TODO : display "you can't fight while swimming"
return true;
}
}
break;
// Leave DEATH Mode
case MBEHAV::DEATH:
// Restaure the last view.
viewMode(viewMode());
break;
case MBEHAV::SWIM:
if( m == MBEHAV::COMBAT || m == MBEHAV::COMBAT_FLOAT)
{
//TODO : display "you can't fight while swimming"
return true;
}
break;
default:
nlwarning("Invalid behaviour change.");
}
// Reset Parent, unless we stay in mount mode
if ((_Mode != MBEHAV::MOUNT_SWIM && _Mode != MBEHAV::MOUNT_NORMAL)
|| (m != MBEHAV::MOUNT_SWIM && m != MBEHAV::MOUNT_NORMAL)
)
{
parent(CLFECOMMON::INVALID_SLOT);
}
// Change the Mode for the user ( if user sits down or stands up we wait in order to play the sit/stand transition anim)
if( m != MBEHAV::SIT && _Mode != MBEHAV::SIT )
_Mode = m;
_ModeWanted = m;
// Initialize the new Mode.
switch(m)
{
// Combat mode
case MBEHAV::COMBAT:
case MBEHAV::COMBAT_FLOAT:
{
// Compute the angle
const string propName = toString("SERVER:Entities:E%d:P%d", _Slot, CLFECOMMON::PROPERTY_ORIENTATION);
sint64 ang = CInterfaceManager::getInstance()->getDbProp(propName)->getValue64();
_TargetAngle = *(float *)(&ang);
// Initialize controls for the combat.
UserControls.startCombat();
// Context help
contextHelp ("action_bar");
}
break;
// Mount Normal or mount swim
case MBEHAV::MOUNT_NORMAL:
{
CCharacterCL *mount = dynamic_cast<CCharacterCL *>(EntitiesMngr.entity(parent()));
if(mount)
{
mount->_Stages.removePosWithNoMode();
dir(mount->dir());
}
}
case MBEHAV::MOUNT_SWIM:
{
// Hide the mount unless we come from another mounted mode
if (_Mode != MBEHAV::MOUNT_SWIM && _Mode != MBEHAV::MOUNT_NORMAL)
{
if(_Mount != CLFECOMMON::INVALID_SLOT) // if _Mount is still invalid, the following code will be done in updatePos()
{
setOnMount();
}
}
// refresh target
UserEntity->selection(_Selection);
}
break;
// Dead mode.
case MBEHAV::DEATH:
setDead();
if(isSwimming())
_Mode = MBEHAV::SWIM_DEATH;
break;
// Normal or Default mode.
case MBEHAV::NORMAL:
_CurrentBehaviour.Behaviour = MBEHAV::IDLE;
default:
//
setAlive();
viewMode(viewMode());
break;
}
bool doSetAnim = true;
// if user sits down or stands up we set transition anim before changing animset
if( m == MBEHAV::SIT )
{
setAnim(CAnimationStateSheet::SitMode);
_Mode = m;
doSetAnim = false;
}
else
if( _Mode == MBEHAV::SIT && m!=MBEHAV::DEATH )
{
setAnim(CAnimationStateSheet::SitEnd);
_Mode = m;
doSetAnim = false;
}
// Show/Hide all or parts of the body.
updateVisualDisplay();
if( ClientCfg.AutomaticCamera )
{
// Set the direction as the front.
dir(front());
}
// Compute the current automaton
computeAutomaton();
// Update the animation set according to the mode.
computeAnimSet();
if( doSetAnim )
{
// Animset changed -> update current animation
setAnim(CAnimationStateSheet::Idle);
}
// Changing the mode well done.
return true;
}// mode //
/*
* Mount the mount in _Mount
*/
void CUserEntity::setOnMount()
{
CCharacterCL *mount = dynamic_cast<CCharacterCL *>(EntitiesMngr.entity(_Mount));
if(mount)
{
// Update primitives for the mount and the rider.
if(_Primitive && mount->getPrimitive())
{
_Primitive->setRadius( std::min(mount->getPrimitive()->getRadius(), (float)(RYZOM_ENTITY_SIZE_MAX/2)) );
_Primitive->setHeight(mount->getPrimitive()->getHeight());
mount->getPrimitive()->setOcclusionMask(MaskColNone); // Remove collisions.
}
// Now on a mount
_OnMount = true;
// Link the mount and the user.
parent(_Mount);
// Refresh the View Mode
viewMode(viewMode());
// Close the crafting window if open
closeFaberCastWindow();
// Keep run in mind
_RunWhenAble = _Run;
mount->_ForbidClipping = true;
}
}
//-----------------------------------------------
// getVelocity :
// compute and return the entity velocity
//-----------------------------------------------
CVector CUserEntity::getVelocity() const
{
static const CVector lateral(0,0,1);
static CVector velocity;
velocity = front() * _FrontVelocity + ( lateral ^ front()) * _LateralVelocity;
velocity.normalize();
// User is Dead
if(isDead())
{
velocity *= 0.0;
}
// User is sitting
else if(isSit())
{
velocity *= 0.0;
}
// User is swimming
else if(isSwimming())
{
// We are a Ring DM so speed up a litle
// Forward Run or Walk
if(_FrontVelocity > 0.0f)
{
if(_Run)
velocity *= 3.0;
else
velocity *= 1.0;
}
// Lateral or Backward Walk
else
velocity *= 1.0;
if (_R2CharMode == R2::TCharMode::Editer || _R2CharMode == R2::TCharMode::Dm)
{
velocity *= (float(ClientCfg.DmRun) / 6.0f); // velocity max = max run / 2
}
}
else if(isRiding())
{
// Forward Run or Walk
if(_FrontVelocity > 0.0f)
{
if(_Run)
velocity *= getMountRunVelocity();
else
velocity *= getMountWalkVelocity();
}
// Lateral or Backward Walk (currently, not used)
else
velocity *= 0.66f;//getMountWalkVelocity();
}
else
{
// Forward Run or Walk
if(_FrontVelocity > 0.0f)
velocity *= currentVelocity();
// Lateral or Backward Walk
else
velocity *= _WalkVelocity;
}
return velocity;
}// getVelocity //
//-----------------------------------------------
// speed :
// Return the Entity Current Speed.
//-----------------------------------------------
double CUserEntity::speed() const // virtual
{
return CPlayerCL::speed();
// return (double)getVelocity().norm();
}// speed //
//-----------------------------------------------
// applyMotion :
// Apply the motion to the entity.
//-----------------------------------------------
void CUserEntity::applyMotion(CEntityCL *target)
{
// default each frame
_ForceLookSlot= CLFECOMMON::INVALID_SLOT;
bool lastHasMoved = _HasMoved;
_HasMoved = false;
// Remove Positions in stages
_Stages.removePosWithNoMode();
// Remove Positions in stages for the mount.
CCharacterCL *mount = 0;
if(parent() != CLFECOMMON::INVALID_SLOT)
{
mount = dynamic_cast<CCharacterCL *>(EntitiesMngr.entity(parent()));
if(mount)
mount->_Stages.removePosWithNoMode();
}
// NO PRIMITIVE -> NO MOVE
if(_Primitive == 0)
return;
// BAD CONNECTION -> NO MOVE
if(NetMngr.getConnectionQuality() == false)
return;
// MS per TICK <=0 -> NO MOVE
if(NetMngr.getMsPerTick() <= 0)
return;
// Compute Speed Vector
NLMISC::CVectorD speed;
if(_MoveToSlot != CLFECOMMON::INVALID_SLOT)
{
// Check the Target.
if(target == 0 || target == this)
return;
// Compute the vector between the user and the target.
CVectorD dir2targ = target->pos() - pos();
dir2targ.z = 0.0;
if(dir2targ == CVectorD::Null)
dir2targ = front();
const double angToTarget = atan2(dir2targ.y, dir2targ.x);
CVectorD aimingPos = target->getAttackerPos(angToTarget, attackRadius() + ClientCfg.AttackDist);
// Aiming Position
CVectorD dirToAimingPos = aimingPos-pos();
dirToAimingPos.z = 0.0;
const double distToAimingPos = dirToAimingPos.norm();
// Decide if the target is reached or not
bool targetReached= false;
if(distToAimingPos > 0.5)
{
// Because of Tryker Counter, may increase the Threshold, when the player is stalled
if(distToAimingPos<_MoveToDist)
{
// If the player is stalled too much time, abort the move and launch the action
float actualSpeed= float((_Position - _LastFramePos).norm() / DT);
// if player effectively runs twice slower, start colision timer
if( actualSpeed*2 < getMaxSpeed() )
{
if(!_MoveToColStartTime)
_MoveToColStartTime= T1;
}
// else ok, reset colision timer
else
_MoveToColStartTime= 0;
// if too much time stalled, stop run.
if(_MoveToColStartTime && (T1 - _MoveToColStartTime >= ClientCfg.MoveToTimeToStopStall) )
targetReached= true;
}
else
_MoveToColStartTime= 0;
}
else
targetReached= true;
// If the target is reached
if(targetReached)
{
// stop and execute action
speed = CVectorD::Null;
moveToAction(target);
}
// else continue follow
else
{
// Look at the entity. delay it after pacs evalCollision(), for correct orientation
_ForceLookSlot= target->slot();
// but still estimate now an approximative front (may be used between now and applyForceLook())
forceLookEntity(dir2targ, false);
// Normalize
dirToAimingPos.normalize();
// Set the Velocity Direction
speed = dirToAimingPos*distToAimingPos;
speed /= DT;
if(speed.norm() > getMaxSpeed())
speed = dirToAimingPos*getMaxSpeed();
}
}
else if(follow())
{
// Check the Target.
if(target == 0 || target == this)
return;
// If the target is moving, orientate the user to the target.
// if(target->hasMoved())
{
// Compute the vector between the user and the target.
CVectorD dir2targ = target->pos() - pos();
dir2targ.z = 0.0;
if(dir2targ != CVectorD::Null)
{
// Look at the entity. delay it after pacs evalCollision(), for correct orientation
_ForceLookSlot= target->slot();
// but still estimate now an approximative front (may be used between now and applyForceLook())
forceLookEntity(dir2targ, false);
}
}
// Compute the angle in the world to attack the target.
const double angToTarget = frontYaw();
// Get the best position to attack the target with the given angle.
const CVectorD aimingPos = target->getAttackerPos(angToTarget, attackRadius() + ClientCfg.AttackDist);
// Aiming Position
CVectorD dirToAimingPos = aimingPos-pos();
dirToAimingPos.z = 0.0;
const double distToAimingPos = dirToAimingPos.norm();
// If the User was not moving and the distance to re-move is not enough big, stay idle
if(lastHasMoved
|| (isFighting() && distToAimingPos >= 0.5)
|| (!isFighting() && distToAimingPos >= 3.0))
{
dirToAimingPos.normalize();
// User is in combat mode -> follow as close as possible.
if(isFighting())
{
// Target is moving
if(target->hasMoved())
{
// Get closer if too far.
if(distToAimingPos >= 0.2)
{
// Set the Velocity Direction
speed = dirToAimingPos*distToAimingPos;
speed /= DT;
if(speed.norm() > getMaxSpeed())
speed = dirToAimingPos*getMaxSpeed();
}
else
{
// Try to have the same speed as the target.
if(target->getSpeed() > getMaxSpeed())
speed = target->dir()* ((float)getMaxSpeed());
else
speed = target->dir()* ((float)target->getSpeed());
}
}
// Target is not moving.
else
{
// Get closer if too far.
if(distToAimingPos >= 0.1)
{
// Set the Velocity Direction
speed = dirToAimingPos*distToAimingPos;
speed /= DT;
if(speed.norm() > getMaxSpeed())
speed = dirToAimingPos*getMaxSpeed();
}
else
speed = CVectorD::Null;
}
}
// User is not in combat mode -> follow not so close.
else
{
// Too far, get closer as fast as possible
if(distToAimingPos >= 3.0)
{
// Set the Velocity Direction
speed = dirToAimingPos*distToAimingPos;
speed /= DT;
if(speed.norm() > getMaxSpeed())
speed = dirToAimingPos*getMaxSpeed();
}
// Just far enough, adjust the user speed to the target
else if(target->hasMoved() && distToAimingPos >= 1.0)
{
// Try to have the same speed as the target.
if(target->getSpeed() > getMaxSpeed())
speed = target->dir()* ((float)getMaxSpeed());
else
speed = target->dir()* ((float)target->getSpeed());
}
// Too close, Stop
else
speed = CVectorD::Null;
}
}
// User was stop and the next pos is to close to begin to move.
else
speed = CVectorD::Null;
}
else
speed = getVelocity()*_SpeedFactor.getValue();
// SPEED VECTOR NULL -> NO MOVE
if(speed == CVectorD::Null)
return;
// First Person View
if(UserControls.isInternalView())
{
// If the server is slow, the client move slower too (only needed in FPV).
double modif = (100.0f/(float)NetMngr.getMsPerTick());
// don't increase speed
clamp(modif, 0.0, 1.0);
speed *= modif;
// Move
_HasMoved = true;
_Primitive->move(speed, dynamicWI);
if(mount && mount->getPrimitive())
mount->getPrimitive()->move(speed, dynamicWI);
}
// Third Person View
else
{
speed += pos();
sint64 x = (sint64)((sint32)(speed.x * 1000.0));
sint64 y = (sint64)((sint32)(speed.y * 1000.0));
sint64 z = (sint64)((sint32)(speed.z * 1000.0));
const uint time = 10; // = 1sec because the speed is in Meter per Sec.
_Stages.addStage(NetMngr.getCurrentClientTick()+time, CLFECOMMON::PROPERTY_POSX, x, 0);
_Stages.addStage(NetMngr.getCurrentClientTick()+time, CLFECOMMON::PROPERTY_POSY, y);
_Stages.addStage(NetMngr.getCurrentClientTick()+time, CLFECOMMON::PROPERTY_POSZ, z);
// Move the Mount
if(mount)
{
mount->_Stages.addStage(NetMngr.getCurrentClientTick()+time, CLFECOMMON::PROPERTY_POSX, x, 0);
mount->_Stages.addStage(NetMngr.getCurrentClientTick()+time, CLFECOMMON::PROPERTY_POSY, y);
mount->_Stages.addStage(NetMngr.getCurrentClientTick()+time, CLFECOMMON::PROPERTY_POSZ, z);
}
}
}// applyMotion //
//---------------------------------------------------
//---------------------------------------------------
void CUserEntity::applyForceLook()
{
if(_ForceLookSlot!=CLFECOMMON::INVALID_SLOT)
{
CEntityCL *target= EntitiesMngr.entity(_ForceLookSlot);
if(target && target!=this)
{
// Compute the vector between the user and the target.
CVectorD dir2targ = target->pos() - pos();
dir2targ.z = 0.0;
if(dir2targ == CVectorD::Null)
dir2targ = front();
// Look at the entity
forceLookEntity(dir2targ, true);
}
}
}
//---------------------------------------------------
// updatePosCombatFloatChanged :
//---------------------------------------------------
void CUserEntity::updatePosCombatFloatChanged(CEntityCL * /* target */) // virtual
{
if(viewMode() == FirstPV)
{
pos(_FirstPos);
}
}// updatePosCombatFloatChanged //
//-----------------------------------------------
// forceIndoorFPV //
// Return true if the user is indoor and the CFG want to force the FPV Indoor.
//-----------------------------------------------
bool CUserEntity::forceIndoorFPV()
{
return (ClientCfg.ForceIndoorFPV && indoor());
}// forceIndoorFPV //
//-----------------------------------------------
// enableFollow :
//-----------------------------------------------
void CUserEntity::disableFollow()
{
if (_FollowMode==false)
return;
_FollowMode = false;
// Send the message to the server.
CBitMemStream out;
if(GenericMsgHeaderMngr.pushNameToStream("TARGET:NO_FOLLOW", out))
NetMngr.push(out);
else
nlwarning("UE:follow: unknown message named 'TARGET:NO_FOLLOW'.");
}// follow //
//-----------------------------------------------
// enableFollow :
//-----------------------------------------------
void CUserEntity::enableFollow(bool resetCameraRot)
{
if (_FollowMode == true)
return;
if( _Mode == MBEHAV::DEATH )
return;
_FollowMode = true;
// Send the message to the server.
CBitMemStream out;
if(GenericMsgHeaderMngr.pushNameToStream("TARGET:FOLLOW", out))
NetMngr.push(out);
else
nlwarning("UE:follow: unknown message named 'TARGET:FOLLOW'.");
// Disable any autowalk (else when follow re-disabled, it will effect, which is weird)
UserControls.autowalkState(false);
// disable afk mode
setAFK(false);
// if want to reset camera rotation
if(resetCameraRot)
startForceLookEntity(targetSlot());
}// follow //
//-----------------------------------------------
// resetAnyMoveTo
//-----------------------------------------------
void CUserEntity::resetAnyMoveTo()
{
// if we cancel a MoveToPhrase action, MUST dec the action counter, and hide the Action Icon
if(_MoveToAction==CUserEntity::CombatPhrase || _MoveToAction==CUserEntity::ExtractRM)
{
// the clientExecute has not been called in case of "ExtractRM autoFind"
bool autoFindExtractRM= _MoveToAction==CUserEntity::ExtractRM && _MoveToPhraseMemoryLine == std::numeric_limits<uint>::max();
if(!autoFindExtractRM)
{
CSPhraseManager *pPM= CSPhraseManager::getInstance();
pPM->cancelClientExecute(_MoveToPhraseCyclic);
}
}
// reset to no moveTo
_MoveToSlot = CLFECOMMON::INVALID_SLOT;
_MoveToAction = CUserEntity::None;
_MoveToDist = 0.0;
_MoveToColStartTime= 0;
}
//-----------------------------------------------
// moveToCheckStartDist :
// Check if the user is not already well placed.
//-----------------------------------------------
void CUserEntity::moveToCheckStartDist(CLFECOMMON::TCLEntityId slot, double dist, TMoveToAction /* action */)
{
if(_MoveToSlot != CLFECOMMON::INVALID_SLOT)
{
// Start a new Force Look entity
startForceLookEntity(_MoveToSlot);
// Disable any autowalk (else when moveTo re-disabled, it will effect, which is weird)
UserControls.autowalkState(false);
// disable afk mode
setAFK(false);
// if sufficiently near, launch the action
CEntityCL *target = EntitiesMngr.entity(slot);
if(target)
{
CVectorD dir2targ = target->pos() - pos();
dir2targ.z = 0.0;
if((dir2targ==CVectorD::Null) || (dir2targ.norm() < dist))
{
moveToAction(target);
}
}
}
}// moveToCheckStartDist //
//-----------------------------------------------
// moveTo :
// Method to move to someone else.
//-----------------------------------------------
void CUserEntity::moveTo(CLFECOMMON::TCLEntityId slot, double dist, TMoveToAction action)
{
resetAnyMoveTo();
// setup new state
_MoveToSlot = slot;
_MoveToDist = dist;
_MoveToAction = action;
moveToCheckStartDist(_MoveToSlot, _MoveToDist, _MoveToAction);
}// moveTo //
//-----------------------------------------------
// moveToMission :
// Method to move to someone else for a mission. NB: if prec action was a CombatPhrase action, action counter are decremented
//-----------------------------------------------
void CUserEntity::moveToMission(CLFECOMMON::TCLEntityId slot, double dist, uint32 id)
{
resetAnyMoveTo();
// setup new state
_MoveToSlot = slot;
_MoveToDist = dist;
_MoveToAction = CUserEntity::Mission;
_MoveToMissionId = id;
moveToCheckStartDist(_MoveToSlot, _MoveToDist, _MoveToAction);
}// moveToMission //
//-----------------------------------------------
// moveToMissionRing :
// Method to move to someone else for a ring mission. NB: if prec action was a CombatPhrase action, action counter are decremented
//-----------------------------------------------
void CUserEntity::moveToMissionRing(CLFECOMMON::TCLEntityId slot, double dist, uint32 id)
{
resetAnyMoveTo();
// setup new state
_MoveToSlot = slot;
_MoveToDist = dist;
_MoveToAction = CUserEntity::MissionRing;
_MoveToMissionId = id;
moveToCheckStartDist(_MoveToSlot, _MoveToDist, _MoveToAction);
}// moveToMissionRing //
//-----------------------------------------------
// moveTo :
// Method to move to someone else.
//-----------------------------------------------
void CUserEntity::moveToCombatPhrase(CLFECOMMON::TCLEntityId slot, double dist, uint phraseMemoryLine, uint phraseMemorySlot, bool phraseCyclic)
{
resetAnyMoveTo();
// setup new state
_MoveToSlot = slot;
_MoveToDist = dist;
_MoveToAction = CUserEntity::CombatPhrase;
_MoveToPhraseMemoryLine= phraseMemoryLine;
_MoveToPhraseMemorySlot= phraseMemorySlot;
_MoveToPhraseCyclic= phraseCyclic;
moveToCheckStartDist(_MoveToSlot, _MoveToDist, _MoveToAction);
}
//-----------------------------------------------
// Method to move to someone else, for foraging extraction
// The caller MUST call after CSPhraseManager::clientExecute(), to increment action counter
//-----------------------------------------------
void CUserEntity::moveToExtractionPhrase(CLFECOMMON::TCLEntityId slot, double dist, uint phraseMemoryLine, uint phraseMemorySlot, bool cyclic)
{
// Test if forage tool in hand, otherwise auto-equip with it
bool validForageToolInHand = false;
CInventoryManager * inv = CInventoryManager::getInstance();
if ( !inv )
{
return;
}
uint32 rightHandSheet = inv->getRightHandItemSheet();
if ( rightHandSheet )
{
validForageToolInHand = inv->isForageToolItem( rightHandSheet );
}
if ( !validForageToolInHand )
{
// Find a forage tool in the bag
uint i;
for ( i=0; i<MAX_BAGINV_ENTRIES; ++i )
{
uint32 itemSheet = inv->getBagItem(i).getSheetID();
if ( itemSheet && inv->isForageToolItem(itemSheet) )
{
break;
}
}
if ( i != MAX_BAGINV_ENTRIES && ClientCfg.AutoEquipTool )
{
// remember last used weapon(s)
rememberWeaponsInHand();
// Clear hands
inv->unequip( "LOCAL:INVENTORY:HAND:1" );
inv->unequip( "LOCAL:INVENTORY:HAND:0" );
// Equip hands
string bagPath = toString( "LOCAL:INVENTORY:BAG:%u", i );
inv->equip( bagPath, "LOCAL:INVENTORY:HAND:0" );
}
else
{
// No forage tool in bag
CInterfaceManager::getInstance()->displaySystemInfo( CI18N::get("uiForageToolMissing"), "CHK" );
return;
}
}
resetAnyMoveTo();
// setup new state
_MoveToSlot = slot;
_MoveToDist = dist;
_MoveToAction = CUserEntity::ExtractRM;
_MoveToPhraseMemoryLine= phraseMemoryLine;
_MoveToPhraseMemorySlot= phraseMemorySlot;
_MoveToPhraseCyclic= cyclic;
moveToCheckStartDist(_MoveToSlot, _MoveToDist, _MoveToAction);
}
//-----------------------------------------------
// Method to begin a spire construction
// The caller MUST call after CSPhraseManager::clientExecute(), to increment action counter
//-----------------------------------------------
void CUserEntity::moveToTotemBuildingPhrase(CLFECOMMON::TCLEntityId slot, double dist, uint phraseMemoryLine, uint phraseMemorySlot, bool cyclic)
{
resetAnyMoveTo();
// setup new state
_MoveToSlot = slot;
_MoveToDist = dist;
_MoveToAction = CUserEntity::BuildTotem;
_MoveToPhraseMemoryLine= phraseMemoryLine;
_MoveToPhraseMemorySlot= phraseMemorySlot;
_MoveToPhraseCyclic= cyclic;
moveToCheckStartDist(_MoveToSlot, _MoveToDist, _MoveToAction);
}
//-----------------------------------------------
// moveToAction :
// Launch the Action Once the Move is done.
// \param CEntityCL * : pointer on the destination entity.
// \warning entity pointer must be valid(allocated).
//-----------------------------------------------
void CUserEntity::moveToAction(CEntityCL *ent)
{
// Check entity pointer
nlassert(ent);
UserControls.needReleaseForward();
// Get the interface instance.
CInterfaceManager *IM = CInterfaceManager::getInstance();
switch(_MoveToAction)
{
// Attack
case CUserEntity::Attack:
UserEntity->attack();
break;
// Quartering
case CUserEntity::Quarter:
if((ent->properties()).harvestable())
IM->runActionHandler("context_quartering", 0);
break;
// Loot
case CUserEntity::Loot:
if((ent->properties()).lootable())
IM->runActionHandler("context_loot", 0);
break;
// Pick Up
case CUserEntity::PickUp:
nlwarning("UE:checkMoveTo: not yet implemented");
break;
case CUserEntity::ExtractRM:
extractRM();
break;
// Trade Item
case CUserEntity::TradeItem:
IM->runActionHandler("context_trade_item", 0);
break;
// Trade Phrase
case CUserEntity::TradePhrase:
IM->runActionHandler("context_trade_phrase", 0);
break;
// Trade Pact
case CUserEntity::TradePact:
IM->runActionHandler("context_trade_pact", 0);
break;
// Mission
case CUserEntity::Mission:
{
string param = toString("id=%d", _MoveToMissionId);
IM->runActionHandler("mission_option", 0, param);
}
break;
// Dynamic Mission
case CUserEntity::DynamicMission:
IM->runActionHandler("context_dynamic_mission", 0);
break;
// Static Mission
case CUserEntity::StaticMission:
IM->runActionHandler("context_choose_mission", 0);
break;
// Mission
case CUserEntity::MissionRing:
{
string param = toString("id=%d", _MoveToMissionId);
IM->runActionHandler("mission_ring", 0, param);
}
break;
// Create Guild
case CUserEntity::CreateGuild:
IM->runActionHandler("context_create_guild", 0);
break;
// News
case CUserEntity::News:
IM->runActionHandler("context_talk", 0);
break;
// Trade Teleport
case CUserEntity::TradeTeleport:
IM->runActionHandler("context_trade_teleport", 0);
break;
// Trade Faction items
case CUserEntity::TradeFaction:
IM->runActionHandler("context_trade_faction", 0);
break;
// Trade Cosmetic
case CUserEntity::TradeCosmetic:
IM->runActionHandler("context_trade_cosmetic", 0);
break;
// Talk
case CUserEntity::Talk:
nlwarning("UE:checkMoveTo: not yet implemented");
break;
// CombatPhrase
case CUserEntity::CombatPhrase:
UserEntity->attackWithPhrase();
break;
// Mount
case CUserEntity::Mount:
{
string orderStr = "mount";
string beastIndexStr = "@UI:GCM_BEAST_SELECTED";
bool confirmFree = true;
beastOrder(orderStr,beastIndexStr,confirmFree);
break;
}
// WebPage
case CUserEntity::WebPage:
IM->runActionHandler("context_web_page", 0);
break;
// Outpost
case CUserEntity::Outpost:
IM->executeLuaScript("game:outpostBCOpenStateWindow()", 0);
break;
// BuildTotem
case CUserEntity::BuildTotem:
buildTotem();
break;
default:
break;
}
// Move To Done.
resetAnyMoveTo();
}// moveToAction //
//-----------------------------------------------
// sendToServer :
// Send the position and orientation to the server.
//-----------------------------------------------
bool CUserEntity::sendToServer(CBitMemStream &out)
{
if(GenericMsgHeaderMngr.pushNameToStream("POSITION", out))
{
// Backup the position sent.
if (_Primitive) _Primitive->getGlobalPosition(_LastGPosSent, dynamicWI);
// Send Position & Orientation
CPositionMsg positionMsg;
positionMsg.X = (sint32)(pos().x * 1000);
positionMsg.Y = (sint32)(pos().y * 1000);
positionMsg.Z = (sint32)(pos().z * 1000);
positionMsg.Heading = frontYaw();
out.serial(positionMsg);
return true;
}
else
{
nlwarning("UE:sendToServer: unknown message named 'POSITION'.");
return false;
}
}// sendToServer //
//-----------------------------------------------
// msgForCombatPos :
// Fill the msg to know if the user is well placed to fight.
//-----------------------------------------------
bool CUserEntity::msgForCombatPos(NLMISC::CBitMemStream &out)
{
static bool wellPosition = false;
bool wellP = false;
// if(isFighting())
{
// Is the user well Placed
CEntityCL *target = EntitiesMngr.entity(UserEntity->targetSlot());
if(target)
wellP = target->isPlacedToFight(UserEntity->pos(), front(), attackRadius() + ClientCfg.AttackDist);
}
// If different from the last time
if(wellPosition != wellP)
{
wellPosition = wellP;
// Send state to server.
if(GenericMsgHeaderMngr.pushNameToStream("COMBAT:VALIDATE_MELEE", out))
{
uint8 flag = wellP?1:0;
out.serial(flag);
return true;
}
else
nlwarning("UE:msgForCombatPos: unknown message named 'COMBAT:TOGGLE_COMBAT_FLOAT_MODE'.");
}
// Do not send the msg.
return false;
}// msgForCombatPos //
//-----------------------------------------------
// checkPos :
// Check the User Position according to the server code.
// \todo GUIGUI : put checks on GPos
// \todo GUIGUI : refact the check primitive when creating a PACS again
//-----------------------------------------------
void CUserEntity::checkPos()
{
if(PACS && _Primitive)
{
// Is in water ?
if(GR)
{
UGlobalPosition gPos;
_Primitive->getGlobalPosition(gPos, dynamicWI);
float waterHeight;
if(GR->isWaterPosition(gPos, waterHeight))
{
if(isSwimming() == false)
{
// Player is Dead set the right mode (swim and dead)
if(isDead())
mode(MBEHAV::SWIM_DEATH);
else
{
// Do not swim when in combat mode.
if(isFighting())
{
_HasMoved = false;
pacsPos(_LastPositionValidated, _LastGPosValidated);
}
// \todo GUIGUI : if the move was cancelled, do not switch to swimming mode.
else if (isRiding())
{
mode(MBEHAV::MOUNT_SWIM);
// also change mounted entity mode
CCharacterCL *mount = dynamic_cast<CCharacterCL *>(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);
}
}
}
}
else
{
if(isSwimming())
{
if(isDead())
{
mode(MBEHAV::DEATH);
}
else if (isRiding())
{
mode(MBEHAV::MOUNT_NORMAL);
// also change mounted entity mode
CCharacterCL *mount = dynamic_cast<CCharacterCL *>(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);
}
}
}
}
// Create the Primitive used to check if the move will be accepted by the server
if(_CheckPrimitive == 0)
{
_Primitive->getGlobalPosition(_LastGPosSent, dynamicWI);
_Primitive->getGlobalPosition(_LastGPosValidated, dynamicWI);
_CheckPrimitive = PACS->addNonCollisionablePrimitive(_Primitive);
_CheckPrimitive->setOcclusionMask(MaskColNone); // Collide with nothing
_CheckPrimitive->setCollisionMask(MaskColNone); // Collide with nothing
_LastPositionValidated = pos();
}
if(_CheckPrimitive)
{
_CheckPrimitive->setGlobalPosition(_LastGPosSent, dynamicWI);
CVectorD speed = _Primitive->getFinalPosition(dynamicWI) - _CheckPrimitive->getFinalPosition(dynamicWI);
_CheckPrimitive->move(speed, dynamicWI);
if(PACS->evalNCPrimitiveCollision(1.0, _CheckPrimitive, dynamicWI) == false)
nlwarning("UE:checkPos: _CheckPrimitive is a collisionable primitive !!!");
CVectorD vectDist = _Primitive->getFinalPosition(dynamicWI) - _CheckPrimitive->getFinalPosition(dynamicWI);
if(vectDist.norm() > 0.001)
{
// The server will not be able to reproduce this move, restoring the last one.
_HasMoved = false;
pacsPos(_LastPositionValidated,_LastGPosValidated);
}
else
{
_LastPositionValidated = _Primitive->getFinalPosition(dynamicWI);
_Primitive->getGlobalPosition(_LastGPosValidated, dynamicWI);
}
}
}
}// checkPos //
//-----------------------------------------------
// testPacsPos :
// test the move from posToTest to current pos
//-----------------------------------------------
bool CUserEntity::testPacsPos( CVectorD& posToTest )
{
if(PACS && _Primitive && _CheckPrimitive)
{
_CheckPrimitive->setGlobalPosition(posToTest, dynamicWI);
CVectorD speed = _Primitive->getFinalPosition(dynamicWI) - _CheckPrimitive->getFinalPosition(dynamicWI);
_CheckPrimitive->move(speed, dynamicWI);
if(PACS->evalNCPrimitiveCollision(1.0, _CheckPrimitive, dynamicWI) == false)
nlwarning("UE:testPacsPos: _CheckPrimitive is a collisionable primitive !!!");
CVectorD vectDist = _Primitive->getFinalPosition(dynamicWI) - _CheckPrimitive->getFinalPosition(dynamicWI);
if(vectDist.norm() > 0.001)
{
return false;
}
else
return true;
}
return false;
} // testPacsPos //
//-----------------------------------------------
// tp :
// Teleport the player (remove selection, re-init checkPos, etc.).
//-----------------------------------------------
void CUserEntity::tp(const CVectorD &dest)
{
// Remove the selection.
UserEntity->selection(CLFECOMMON::INVALID_SLOT);
// Update PACS
pacsPos(dest);
// Update the primitive useful to check the user position.
_LastPositionValidated = dest;
// Get the Global position
if(_Primitive)
{
// this is the last PACS position validated too
_Primitive->getGlobalPosition(_LastGPosValidated, dynamicWI);
// consider this is the last position sent to server (since actually received!)
_Primitive->getGlobalPosition(_LastGPosSent, dynamicWI);
// Set the new position of the 'check' primitive
if(_CheckPrimitive)
_CheckPrimitive->setGlobalPosition(_LastGPosSent, dynamicWI);
}
else
nlwarning("UE:tp: the entity has a Null primitive.");
// Return to interface mode.
viewMode(UserEntity->viewMode());
// User well oriented.
dir(UserEntity->front());
// Set Normal Mode after a teleport.
mode(MBEHAV::NORMAL);
// Set animation with idle.
setAnim(CAnimationStateSheet::Idle);
}// tp //
//-----------------------------------------------
// correctPos :
// Teleport the player to correct his position.
//-----------------------------------------------
void CUserEntity::correctPos(const NLMISC::CVectorD &dest)
{
nlinfo("UE:correctPos: new user position %f %f %f", dest.x, dest.y, dest.z);
// Change the user poisition.
UserEntity->pacsPos(dest);
// Update the primitive useful to check the user position.
_LastPositionValidated = dest;
// Get the Global position
if(_Primitive)
{
// this is the last PACS position validated too
_Primitive->getGlobalPosition(_LastGPosValidated, dynamicWI);
// consider this is the last position sent to server (since actually received!)
_Primitive->getGlobalPosition(_LastGPosSent, dynamicWI);
// Set the new position of the 'check' primitive
if(_CheckPrimitive)
_CheckPrimitive->setGlobalPosition(_LastGPosSent, dynamicWI);
}
else
nlwarning("UE:correctPos: the entity has a Null primitive.");
// Correct the pos of the mount, if riding
if ( isRiding() )
{
if ( _Mount < EntitiesMngr.entities().size() )
{
CEntityCL *mount = EntitiesMngr.entities()[_Mount];
if ( mount )
mount->pacsPos( dest );
}
}
}// correctPos //
/*
* Check if the mount is able to run, and force walking mode if not
* (if the player clicked on run, set back to walk).
*/
void CUserEntity::checkMountAbleToRun()
{
if ( isRiding() )
{
if ( running() )
{
// Make the mount walk if she's hungry
if ( ! _MountHunger.canRun() )
switchVelocity( false );
}
else
{
// Make the mount run if she's not hungry anymore
if ( _RunWhenAble && _MountHunger.canRun() )
switchVelocity( false );
}
}
}
//-----------------------------------------------
// Update the position of the entity after the motion.
// \param t : Time for the position of the entity after the motion.
// \param target : pointer on the current target.
//-----------------------------------------------
void CUserEntity::updatePos(const TTime &t, CEntityCL *target)
{
// Update Mount if mounting (if _Mount was still not valid when mode received)
if((_Mount != CLFECOMMON::INVALID_SLOT && isRiding()) && _OnMount==false)
{
setOnMount();
}
// External view are managed like other entities.
if(!UserControls.isInternalView())
{
// Update position according to the animations.
CPlayerCL::updatePos(t, target);
_LastFrameTime = t*0.001;
// Set The FPV when indoor.
if(forceIndoorFPV())
viewMode(FirstPV, false);
return;
}
/*
else
{
// update anim state at least (needed when a spell is cast to play the cast fx at the right time, because they may be visible)
updateAnimationState();
}*/
// Compute the Time Step.
double frameTimeRemaining = computeTimeStep(((double)t)*0.001);
// Do not update animation if Client Light
if (!ClientCfg.Light)
{
// Attack Animation.
if(_AnimAttackOn)
{
if(animIndex(MOVE) != ::CAnimation::UnknownAnim)
{
if(animOffset(MOVE) >= EAM->getAnimationLength(animId(MOVE)))
{
_AnimAttackOn = false;
updateVisualDisplay();
}
}
}
// Increase the time in the current animation.
double previousTimeOffset = animOffset(MOVE);
double offset = previousTimeOffset + frameTimeRemaining;
// Check Anim length
double animLength = EAM->getAnimationLength(animId(MOVE));
if(offset > animLength)
animOffset(MOVE, animLength);
else
animOffset(MOVE, offset);
// Manage Events that could be created by the animation (like sound).
animEventsProcessing(previousTimeOffset, animOffset(MOVE));
}
// Get the right pos from PACS.
if(_Primitive && GR)
{
UGlobalPosition gPos;
_Primitive->getGlobalPosition(gPos, dynamicWI);
// Set the new current pos.
pos(GR->getGlobalPosition(gPos));
}
// Set the direction.
if( ClientCfg.AutomaticCamera )
{
//dir(front());
}
// Reset the TPV when outdoor if needed.
if(_Primitive && GR)
{
UGlobalPosition gPos;
_Primitive->getGlobalPosition(gPos, dynamicWI);
if(!GR->isInterior(gPos))
if(!ClientCfg.FPV)
viewMode(ThirdPV, false);
}
// Current time is now the entity time too.
_LastFrameTime = t*0.001;
}// updatePos //
//-----------------------------------------------
// updateDisplay :
// Update the PACS position after the evalCollision. The entity position is set too. This is fast.
// If the entity position is too far from its PACS position, setGlobalPosition is called.
// After this call, the position.z is valid.
//-----------------------------------------------
void CUserEntity::pacsFinalizeMove() // virtual
{
if(_Primitive == 0)
return;
// Get the global position
_FinalPacsPos = _Primitive->getFinalPosition(dynamicWI);
// Get the global position
UGlobalPosition gPos;
_Primitive->getGlobalPosition(gPos, dynamicWI);
if(gPos.InstanceId != -1)
{
pos(_FinalPacsPos);
if(_Mount != CLFECOMMON::INVALID_SLOT)
{
CEntityCL *mount = EntitiesMngr.entity(_Mount);
if(mount)
mount->pacsPos(pos());
}
}
else
_SetGlobalPositionDone = false;
}// pacsFinalizeMove //
//-----------------------------------------------
// applyBehaviour :
// Apply the behaviour for the user.
//-----------------------------------------------
void CUserEntity::applyBehaviour(const CBehaviourContext &behaviourContext) // virtual
{
const MBEHAV::CBehaviour &behaviour = behaviourContext.Behav;
// Special code for the First Person View
if((viewMode() == FirstPV) && behaviour.isCombat())
{
// Backup the current behaviour.
_CurrentBehaviour = behaviourContext.Behav;
dir(front());
// check if self-target
bool selfSpell = false;
if (behaviourContext.Targets.Targets.size() == 1)
{
if (behaviourContext.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;
}
// Default target hit dates
static vector<double> targetHitDates;
targetHitDates.clear();
targetHitDates.resize(behaviourContext.Targets.Targets.size(), TimeInSec);
// cast projectiles/impact linked to behaviour
updateCurrentAttack();
if (isCurrentBehaviourAttackEnd())
{
// In First Person (don't care), use a default animation => will choose a default delay of 0.5
performCurrentAttackEnd(behaviourContext, selfSpell && isOffensif,
targetHitDates, CAnimationStateSheet::UnknownState);
}
//
_RightFXActivated = false;
_LeftFXActivated = false;
if(EAM)
{
animState (MOVE, CAnimationStateSheet::FirstPersonAttack);
animIndex (MOVE, CAnimation::UnknownAnim);
animOffset(MOVE, 0.0);
_CurrentState = EAM->mState(_CurrentAutomaton, animState(MOVE));
if(_CurrentState && _CurrentAnimSet[MOVE])
{
const CAnimationState *animStatePtr = _CurrentAnimSet[MOVE]->getAnimationState(animState(MOVE));
if(animStatePtr)
{
animIndex(MOVE, animStatePtr->chooseAnim(_AnimJobSpecialisation, people(), getGender(), 0.0));
if(animIndex(MOVE) != CAnimation::UnknownAnim)
{
if(_PlayList)
{
_PlayList->setAnimation (MOVE, animId(MOVE));
_PlayList->setTimeOrigin (MOVE, ryzomGetLocalTime ()*0.001);//_LastFrameTime);
_AnimAttackOn = true;
updateVisualDisplay();
}
}
}
}
}
// Start FX to play at the animation beginning like trails.
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);
}
// DeltaHP
applyBehaviourFlyingHPs(behaviourContext, behaviour, targetHitDates);
}
// In third person view (or camera mode), play the same way than for the others.
else
CPlayerCL::applyBehaviour(behaviourContext);
}// applyBehaviour //
//---------------------------------------------------
// setDead :
// Method to Flag the character as dead and do everything needed.
//---------------------------------------------------
void CUserEntity::setDead() // virtual
{
// User is dead -> NO FOLLOW
disableFollow();
// Remove the selection.
UserEntity->selection(CLFECOMMON::INVALID_SLOT);
// Death Mode Control and view.
UserControls.mode(CUserControls::DeathMode);
// Play the FX for the death
NL3D::UParticleSystemInstance deathFX = FXMngr.instantFX(ClientCfg.DeadFXName);
if(!deathFX.empty())
deathFX.setPos(selectBox().getCenter());
// Last teleport is a death
// get player's kami fame
CInterfaceManager *pIM = CInterfaceManager::getInstance();
sint8 kamiFame = 0;
uint kamiFameIndex = CStaticFames::getInstance().getFactionIndex("kami");
if (pIM && kamiFameIndex != CStaticFames::INVALID_FACTION_INDEX)
{
CCDBNodeLeaf *pLeafKamiFame = pIM->getDbProp(toString("SERVER:FAME:PLAYER%d:VALUE", kamiFameIndex - 1), false);
if (pLeafKamiFame != NULL)
kamiFame = pLeafKamiFame->getValue8();
}
// get player's karavan fame
sint8 karavanFame = 0;
uint karavanFameIndex = CStaticFames::getInstance().getFactionIndex("karavan");
if (pIM && karavanFameIndex != CStaticFames::INVALID_FACTION_INDEX)
{
CCDBNodeLeaf *pLeafKaravanFame = pIM->getDbProp(toString("SERVER:FAME:PLAYER%d:VALUE", karavanFameIndex - 1), false);
if (pLeafKaravanFame != NULL)
karavanFame = pLeafKaravanFame->getValue8();
}
// set loading background depending on player's faction fame
if (kamiFame > karavanFame)
LoadingBackground = ResurectKamiBackground;
else
LoadingBackground = ResurectKaravanBackground;
// disable or enable shadowing
updateCastShadowMap();
// enable death warning popup
//CInterfaceManager * pIM = CInterfaceManager::getInstance();
if( pIM )
{
pIM->runActionHandler("set",NULL,"dblink=UI:VARIABLES:DEATH_WARNING_WANTED|value=1");
}
}// setDead //
//-----------------------------------------------
// skillUp :
// Skill Up
//-----------------------------------------------
void CUserEntity::skillUp()
{
// Play the FX for the death
NL3D::UParticleSystemInstance skillUpFX = FXMngr.instantFX(ClientCfg.SkillUpFXName);
skillUpFX.setClusterSystem(getClusterSystem());
if(!skillUpFX.empty())
skillUpFX.setPos(pos());
}// skillUp //
//-----------------------------------------------
// startColTimer :
// After a few time, if the user is still in collision with someone else, remove collisions with other entitites.
//-----------------------------------------------
void CUserEntity::startColTimer()
{
if(_ColOn)
{
if(_ColRemoved==false)
{
if((T1-_ColStartTime) > ClientCfg.TimeToRemoveCol)
{
EntitiesMngr.removeColUserOther();
_ColRemoved = true;
}
}
}
else
{
_ColOn = true;
_ColStartTime = T1;
}
}// startColTimer //
//-----------------------------------------------
// stopColTimer :
// Called when the user is no more in collision with another entity.
//-----------------------------------------------
void CUserEntity::stopColTimer()
{
// Restore collisions.
if(_ColRemoved)
{
EntitiesMngr.restoreColUserOther();
_ColRemoved = false;
}
_ColOn = false;
}// stopColTimer //
//-----------------------------------------------
// getMaxSpeed
// Return the current max speed for the entity in meter per sec
// The method return a max according to the speed factor (given by the server)
// It's also return a value according to the landscape (water)
// Also managed mounts
//-----------------------------------------------
double CUserEntity::getMaxSpeed() const // virtual
{
// Max Defined speed according to the current factor (slow, or speed increased)
double maxSpeed = _SpeedFactor.getValue();
// User is Dead
if(isDead())
{
maxSpeed *= 0.0;
}
// User is sitting
else if(isSit())
{
maxSpeed *= 0.0;
}
// User is swimming
else if(isSwimming())
{
// We are a Ring DM so speed up a litle
if (_R2CharMode == R2::TCharMode::Editer || _R2CharMode == R2::TCharMode::Dm)
{
maxSpeed *= ClientCfg.DmRun / 2;
}
else
{
// Run
maxSpeed *= 3.0;
}
}
else if(isRiding())
{
// Run if mount not hungry, otherwise, use walk velocity
if (_MountHunger.canRun())
maxSpeed *= (double)getMountRunVelocity();
else
maxSpeed *= (double)getMountWalkVelocity();
}
else
{
// Run
maxSpeed *= runVelocity();
}
// Return the current max
return maxSpeed;
// return ClientCfg.Run * _SpeedFactor.getValue();
}// getMaxSpeed //
//-----------------------------------------------
// snapToGround :
// Snap the user to the ground.
//-----------------------------------------------
void CUserEntity::snapToGround()
{
CEntityCL::snapToGround();
updateSound (ryzomGetLocalTime ());
}// // snapToGround //
//-----------------------------------------------
// updateSound :
//-----------------------------------------------
void CUserEntity::updateSound(const TTime &time)
{
H_AUTO_USE ( RZ_Client_Update_Sound );
// no sound manager, no need to update sound
if (SoundMngr == 0)
return;
SoundMngr->setListenerPos(pos());
const CMatrix &camMat = MainCam.getMatrix();
SoundMngr->setListenerOrientation(camMat.getJ(), camMat.getK());
if (ClientCfg.Light)
return;
// step sound of self : refind the sound animations associated to the walk and run animation
static bool computeAnim = true;
static bool lastMode = false;
static TTime previousTime = 0;
static TTime stepTime = 0;
static bool leftRight = false;
static NLSOUND::CSoundAnimMarker *leftStep = 0;
static NLSOUND::CSoundAnimMarker *rightStep = 0;
// TODO : Remove when bug corrected:
if (_Gender == GSGENDER::unknown)
{
nlwarning("CUserEntity::updateSound : The gender is unknown, forcing it to male");
_Gender = GSGENDER::male;
}
// force recompute of anim if walk/run change.
computeAnim = computeAnim || (lastMode != _Run);
// Check the sound animation to find the time between to step
if(computeAnim && SoundMngr && _CurrentAnimSet[MOVE] != 0) // && _SoundId[MOVE] != NLSOUND::CSoundAnimationNoId)
{
lastMode = _Run;
TAnimStateId mode = _Run ? CAnimationStateSheet::Run : CAnimationStateSheet::Walk;
const CAnimationState *animStatePtr = _CurrentAnimSet[MOVE]->getAnimationState (mode);
if (animStatePtr)
{
::CAnimation::TAnimId animId = animStatePtr->chooseAnim (_AnimJobSpecialisation, people(), getGender(), 0);
if (animId != ::CAnimation::UnknownAnim)
{
const ::CAnimation *anim = animStatePtr->getAnimation (animId);
if(anim)
{
// Select the sound ID
_SoundId[MOVE] = anim->soundId();
if (_SoundId[MOVE] != NLSOUND::CSoundAnimationNoId)
{
// retrieve the anim
NLSOUND::CSoundAnimManager *mgr = NLSOUND::CSoundAnimManager::instance();
string name = mgr->idToName(_SoundId[MOVE]);
if (!name.empty())
{
NLSOUND::CSoundAnimation *sanim = mgr->findAnimation(name);
if (sanim->countMarkers() != 2)
{
static set<string> warnOnce;
if (warnOnce.find(sanim->getName()) == warnOnce.end())
{
nlwarning("Sound animation '%s' has not 2 markers, not a biped ? (Display Once)", sanim->getName().c_str());
warnOnce.insert(sanim->getName());
}
}
else
{
stepTime = TTime((sanim->getMarker(1)->getTime() - sanim->getMarker(0)->getTime()) * 1000);
rightStep = sanim->getMarker(0);
leftStep = sanim->getMarker(1);
computeAnim = false;
}
}
}
}
}
}
}
if( SoundMngr && _Mode == MBEHAV::NORMAL)
{
if (!getVelocity().isNull())
{
if (stepTime > 0 && time-previousTime>=stepTime)
{
previousTime = time;
// set the sound 1 meter below listener
_SoundContext.Position = pos() + CVector(0,0,-1);
_SoundContext.RelativeGain = SoundMngr->getUserEntitySoundLevel();;
uint32 matId= getGroundType();
//nldebug("Current material = %u", matId);
_SoundContext.Args[0] = matId;
if (leftRight)
{
if (leftStep)
// TODO : find the correct cluster
leftStep->play(SoundMngr->getMixer(), NULL, _SoundContext);
}
else
{
if (rightStep)
// TODO : find the correct cluster
rightStep->play(SoundMngr->getMixer(), NULL, _SoundContext);
// recompute a new sound anim
computeAnim = true;
}
leftRight = !leftRight;
}
}
else
{
// resets the counter
previousTime = 0;
}
}
}// updateSound //
//-----------------------------------------------
// rotate :
// rotate the body on the left or right (front changes).
//-----------------------------------------------
void CUserEntity::rotate(float ang)
{
// Rotate the body.
CMatrix m;
m.identity();
m.rotateZ(ang);
front(m * front().normed());
}// rotate //
//-----------------------------------------------
// rotHeadVertically :
// rotate the head vertically.
//-----------------------------------------------
void CUserEntity::rotHeadVertically(float ang)
{
setHeadPitch(_HeadPitch+ang);
}// rotHeadVertically //
//-----------------------------------------------
// setHeadPitch(double hp)
//-----------------------------------------------
void CUserEntity::setHeadPitch(double hp)
{
_HeadPitch= hp;
const double bound= Pi/2 - 0.01; // epsilon to avoid gimbal lock
clamp(_HeadPitch, -bound, bound);
}
//---------------------------------------------------
// 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 CUserEntity::slotRemoved(const CLFECOMMON::TCLEntityId &slot)
{
// parent call
CPlayerCL::slotRemoved(slot);
// reset also selection
if(selection() == slot)
selection(CLFECOMMON::INVALID_SLOT);
}// slotRemoved //
//---------------------------------------------------
// selection :
// Change the entity selected.
// \param slot : slot now selected (CLFECOMMON::INVALID_SLOT for an empty selection).
// \warning Can be different from the entity targeted (in combat mode for example).
//---------------------------------------------------
void CUserEntity::selection(const CLFECOMMON::TCLEntityId &slot) // virtual
{
//allows reselection in Ring client: even if the selected slots is equal to the selection,
//the client must send the messages.
if ((_Selection == slot) && !ClientCfg.R2EDEnabled)
return;
// The selection will be the entity to watch
WatchedEntitySlot = slot;
disableFollow();
// Send the entity selected to the server.
NetMngr.pushTarget(slot);
// Target the slot on client, don't wait NetWork response
targetSlot(slot);
_TargetSlotNoLag= slot;
if (ClientCfg.R2EDEnabled)
{
R2::getEditor().inGameSelection(slot);
}
// Change the current selection so un color the current selection.
CEntityCL *sel = EntitiesMngr.entity(_Selection);
if(sel != NULL)
sel->visualSelectionStop(); // Blink off == restore to normal
// Set the entity selected
_Selection = slot;
// Update visual selection and interface
if ( sel && sel->isForageSource() )
sel->buildInSceneInterface(); // remove focus on previous selected source
sel = EntitiesMngr.entity(_Selection);
if(sel != NULL)
{
sel->visualSelectionStart();
if ( sel->isForageSource() )
sel->buildInSceneInterface(); // set focus on new selected source
}
// **** Update Target interface
//get the new target slot and set it in the database
CInterfaceManager *pIM = CInterfaceManager::getInstance();
pIM->getDbProp("UI:VARIABLES:TARGET:SLOT")->setValue64(slot);
// Get the new target UID, and set in Database
uint tgtSlot= _Selection;
uint32 tgtEntityId= CLFECOMMON::INVALID_CLIENT_DATASET_INDEX;
CEntityCL *entity = NULL;
if (tgtSlot!=CLFECOMMON::INVALID_SLOT)
{
entity = EntitiesMngr.entity(tgtSlot);
if (entity)
tgtEntityId= entity->dataSetId();
}
// Set the User Target
CCDBNodeLeaf *prop = pIM->getDbProp("UI:VARIABLES:TARGET:UID", false);
if(prop)
prop->setValue32(tgtEntityId);
// Bar Manager. Update now the Target View (so it takes VP if data available or 0... but result is immediate)
CBarManager::getInstance()->setLocalTarget(tgtEntityId);
// **** Update DB for InGameMenu
// clear the entries for mission option
for(uint k = 0; k < NUM_MISSION_OPTIONS; ++k)
{
CCDBNodeLeaf *missionOption = pIM->getDbProp(toString("LOCAL:TARGET:CONTEXT_MENU:MISSIONS_OPTIONS:%d:TITLE", (int) k), false);
if (missionOption)
{
missionOption->setValue32(0);
}
CCDBNodeLeaf *playerGiftNeeded = pIM->getDbProp(toString("LOCAL:TARGET:CONTEXT_MENU:MISSIONS_OPTIONS:%d:PLAYER_GIFT_NEEDED", (int) k), false);
if (playerGiftNeeded)
{
playerGiftNeeded->setValue32(0);
}
//
missionOption = pIM->getDbProp(toString("SERVER:TARGET:CONTEXT_MENU:MISSIONS_OPTIONS:%d:TITLE", (int) k), false);
if (missionOption)
{
missionOption->setValue32(0);
}
playerGiftNeeded = pIM->getDbProp(toString("SERVER:TARGET:CONTEXT_MENU:MISSIONS_OPTIONS:%d:PLAYER_GIFT_NEEDED", (int) k), false);
if (playerGiftNeeded)
{
playerGiftNeeded->setValue32(0);
}
}
// update pvp tags
CViewBase * tagView = dynamic_cast<CViewBase*>(pIM->getElementFromId("ui:interface:target:pvp_tags"));
CViewBase * contentView = dynamic_cast<CViewBase*>(pIM->getElementFromId("ui:interface:target:content"));
if ((tgtSlot!=CLFECOMMON::INVALID_SLOT) && entity)
{
CPlayerCL *pPlayer = dynamic_cast<CPlayerCL*>(entity);
if (pPlayer)
{
for (uint8 i = 0; i < 7; i++)
{
CViewBitmap * tag = dynamic_cast<CViewBitmap*>(pIM->getElementFromId("ui:interface:target:pvp_tags:tag_"+toString(i)));
if (tag)
{
if ((pPlayer->getPvpMode()&PVP_MODE::PvpFaction || pPlayer->getPvpMode()&PVP_MODE::PvpFactionFlagged) && pPlayer->isPvpAlly(i))
{
tag->setTexture("pvp_ally_"+toString(i)+".tga");
}
else if ((pPlayer->getPvpMode()&PVP_MODE::PvpFaction || pPlayer->getPvpMode()&PVP_MODE::PvpFactionFlagged) && pPlayer->isPvpEnnemy(i))
{
tag->setTexture("pvp_enemy_"+toString(i)+".tga");
}
else
{
tag->setTexture("alpha_10.tga");
}
}
}
}
}
// clear web page
prop= pIM->getDbProp("LOCAL:TARGET:CONTEXT_MENU:WEB_PAGE_URL", false);
if(prop) prop->setValue32(0);
prop= pIM->getDbProp("LOCAL:TARGET:CONTEXT_MENU:WEB_PAGE_TITLE", false);
if(prop) prop->setValue32(0);
// clear mission ring
for(uint k = 0; k < BOTCHATTYPE::MaxR2MissionEntryDatabase; ++k)
{
prop= pIM->getDbProp(toString("LOCAL:TARGET:CONTEXT_MENU:MISSION_RING:%d:TITLE", k), false);
if(prop) prop->setValue32(0);
}
// clear programs
prop= pIM->getDbProp("LOCAL:TARGET:CONTEXT_MENU:PROGRAMMES", false);
if(prop) prop->setValue32(0);
prop= pIM->getDbProp("SERVER:TARGET:CONTEXT_MENU:PROGRAMMES");
if(prop) prop->setValue32(0);
// increment db counter for context menu
pIM->incLocalSyncActionCounter();
}// selection //
//---------------------------------------------------
// moveToAttack :
// Method to place the user to attack the target and attack.
//---------------------------------------------------
void CUserEntity::moveToAttack()
{
// **** For clarity, try to launch a "default attack" found in the memory bar instead of an "anonymous" action
CSPhraseManager *pPM= CSPhraseManager::getInstance();
uint32 memLine, memSlot;
CEntityCL *target= EntitiesMngr.entity(selection());
CInventoryManager *inv = CInventoryManager::getInstance();
// auto-equip with valid weapon
if( ClientCfg.AutoEquipTool )
{
if(inv)
{
// if no valid weapons in had -> auto-equip with last used weapons
bool validWeaponInHand = true;
uint32 rightHandSheet = inv->getRightHandItemSheet();
if(rightHandSheet)
{
validWeaponInHand = inv->isMeleeWeaponItem(rightHandSheet) || inv->isRangeWeaponItem(rightHandSheet);
}
if( !validWeaponInHand )
{
autoEquipWithLastUsedWeapons();
}
// remember last used weapon(s)
rememberWeaponsInHand();
}
}
if(target && pPM->findDefaultAttack(memLine, memSlot))
{
// launch instead a phrase execution with this phrase
executeCombatWithPhrase(target, memLine, memSlot, true);
}
// **** Else launch an anonymous "default attack"
else
{
// melee or range?
bool melee = true;
if(inv)
{
uint32 rightHandSheet = inv->getRightHandItemSheet();
if(rightHandSheet)
melee = inv->isMeleeWeaponItem(rightHandSheet);
}
// Move to target if melee weapon
if(melee)
moveTo(selection(), 2.0, CUserEntity::Attack);
// Just attack if range weapon.
else
attack();
}
}// moveToAttack //
//---------------------------------------------------
// attack :
// Method to attack the target.
//---------------------------------------------------
void CUserEntity::attack()
{
// execute the default attack
CSPhraseManager *pPM= CSPhraseManager::getInstance();
pPM->executeDefaultAttack();
bool melee = true;
CInventoryManager *inv = CInventoryManager::getInstance();
if(inv)
{
uint32 rightHandSheet = inv->getRightHandItemSheet();
if(rightHandSheet)
melee = inv->isMeleeWeaponItem(rightHandSheet);
}
// If option ON, follow when attacking.
if(ClientCfg.FollowOnAtk)
{
// Follow only if attacking with a melee weapon
if(melee)
// enable, but don't reset camera rotation
enableFollow(false);
}
}// attack //
//---------------------------------------------------
// attack :
// Method to attack the target, with a special phrase
//---------------------------------------------------
void CUserEntity::attackWithPhrase()
{
if( !canEngageCombat() )
return;
CSPhraseManager *pPM= CSPhraseManager::getInstance();
// validate the execution on server
pPM->sendExecuteToServer(_MoveToPhraseMemoryLine, _MoveToPhraseMemorySlot, _MoveToPhraseCyclic);
// If the Attack is a "Next", and not a cycle, do a default attack
if(!_MoveToPhraseCyclic)
pPM->executeDefaultAttack();
// If option ON, follow when attacking.
if(ClientCfg.FollowOnAtk)
{
bool melee = true;
CInventoryManager *inv = CInventoryManager::getInstance();
if(inv)
{
uint32 rightHandSheet = inv->getRightHandItemSheet();
if(rightHandSheet)
melee = inv->isMeleeWeaponItem(rightHandSheet);
}
// Follow only if attacking with a melee weapon
if(melee)
// enable, but don't reset camera rotation
enableFollow(false);
}
// Because sendExecuteToServer() has been called, must NOT cancelClientExecute() at resetAnyMoveTo()
_MoveToAction= CUserEntity::None;
}
//-----------------------------------------------
// assist :
// your current target become the target of your current one.
//-----------------------------------------------
void CUserEntity::assist()
{
assist(targetSlot());
}// assist //
//-----------------------------------------------
void CUserEntity::assist(uint slot)
{
// Check the current target
if(slot == CLFECOMMON::INVALID_SLOT || slot == _Slot)
return;
// Check the target
CEntityCL *target = EntitiesMngr.entity(slot);
if(target == 0)
return;
// Check the new slot.
CLFECOMMON::TCLEntityId newSlot = target->targetSlot();
if(newSlot == CLFECOMMON::INVALID_SLOT || newSlot == _Slot)
return;
// Select the new target.
selection(newSlot);
}
//---------------------------------------------------
// disengage :
// Method to disengage the target.
//---------------------------------------------------
void CUserEntity::disengage()
{
// Set the database in local.
if(ClientCfg.Local)
{
IngameDbMngr.setProp("Entities:E0:P" + toString(CLFECOMMON::PROPERTY_MODE), MBEHAV::NORMAL); // Mode
IngameDbMngr.setProp("Entities:E0:P" + toString(CLFECOMMON::PROPERTY_TARGET_ID), _Selection); // Target
UserEntity->updateVisualProperty(0, CLFECOMMON::PROPERTY_MODE);
UserEntity->updateVisualProperty(0, CLFECOMMON::PROPERTY_TARGET_ID);
return;
}
// Disengage MSG.
const string msgName = "COMBAT:DISENGAGE";
CBitMemStream out;
if(GenericMsgHeaderMngr.pushNameToStream(msgName, out))
NetMngr.push(out);
else
nlwarning("UE::disengage: unknown message named '%s'.", msgName.c_str());
// Change the current mode.
mode(MBEHAV::NORMAL);
}// disengage //
//-----------------------------------------------
// sit :
// Ask for the client to sit/stand ('true' to sit).
//-----------------------------------------------
bool CUserEntity::sit(bool s)
{
bool ok= false;
// SIT
if(s)
{
if(canSit() == true)
{
// disable afk mode
setAFK(false);
// Sit MSG.
if(mode(MBEHAV::SIT))
{
// autowalk disabled
UserControls.autowalkState(false);
const string msgName = "COMMAND:SIT";
CBitMemStream out;
if(GenericMsgHeaderMngr.pushNameToStream(msgName, out))
{
out.serial(s);
NetMngr.push(out);
}
else
nlwarning("UE:sit: unknown message named '%s'.", msgName.c_str());
// mode changed
ok= true;
// display sit msg
CInterfaceManager *pIM= CInterfaceManager::getInstance();
ucstring msg = CI18N::get("msgUserIsSitting");
string cat = getStringCategory(msg, msg);
pIM->displaySystemInfo(msg, cat);
}
}
}
// STAND
else
{
if(_Mode == MBEHAV::SIT)
{
if(mode(MBEHAV::NORMAL))
{
const string msgName = "COMMAND:SIT";
CBitMemStream out;
if(GenericMsgHeaderMngr.pushNameToStream(msgName, out))
{
out.serial(s);
NetMngr.push(out);
}
else
nlwarning("UE:sit: unknown message named '%s'.", msgName.c_str());
// mode changed
ok= true;
// display stand msg
CInterfaceManager *pIM= CInterfaceManager::getInstance();
ucstring msg = CI18N::get("msgUserIsStanding");
string cat = getStringCategory(msg, msg);
pIM->displaySystemInfo(msg, cat);
}
}
}
// if mode changed, Write to the UI database, to update views
if(ok)
{
CInterfaceManager *pIM= CInterfaceManager::getInstance();
CCDBNodeLeaf *node= pIM->getDbProp("UI:VARIABLES:PLAYER_STAND", false);
if(node)
node->setValue32(_Mode != MBEHAV::SIT);
}
// mode changed
return ok;
}// sit //
//-----------------------------------------------
// canSit :
// Return true if the user can sit.
//-----------------------------------------------
bool CUserEntity::canSit() const
{
// If the user is not already sitting or is on a mount
if(!isSit() && (!isRiding()) && !isDead() && !isSwimming())
{
return true;
}
else
return false;
}// canSit //
//-----------------------------------------------
// setAFK
//-----------------------------------------------
void CUserEntity::setAFK(bool b, string afkTxt)
{
if( isAFK() == b ) return;
if (b)
{
if( isDead() || isRiding() || moveTo() || follow() )
return;
CInterfaceManager *pIM = CInterfaceManager::getInstance();
//sint64 start = pIM->getDbProp("SERVER:USER:ACT_TSTART")->getValue64();
//sint64 end = pIM->getDbProp("SERVER:USER:ACT_TEND")->getValue64();
//sint64 type = pIM->getDbProp("SERVER:USER:ACT_TYPE")->getValue64();
//sint64 num = pIM->getDbProp("SERVER:USER:ACT_NUMBER")->getValue64();
if( pIM )
{
if( pIM->getDbProp("SERVER:USER:ACT_TYPE")->getValue64() != 0 )
return;
}
if( !isSit() && !isSwimming() )
{
if( !mode(MBEHAV::REST) )
return;
}
}
else
{
if( !isSit() && !isSwimming() )
{
if (isDead())
return;
else
mode(MBEHAV::NORMAL);
}
}
// send afk state
string msgName = "COMMAND:AFK";
CBitMemStream out;
if(GenericMsgHeaderMngr.pushNameToStream(msgName, out))
{
out.serial(b);
NetMngr.push(out);
}
else
nlwarning("CUserEntity:setAFK: unknown message named '%s'.", msgName.c_str());
// custom afk txt
ucstring ucstr;
ucstr.fromUtf8( afkTxt );
CBitMemStream outTxt;
msgName = "STRING:AFK_TXT";
if( GenericMsgHeaderMngr.pushNameToStream(msgName,outTxt) )
{
outTxt.serial( ucstr );
NetMngr.push( outTxt );
}
else
{
nlwarning("CUserEntity:setAFK: unknown message named '%s'.", msgName.c_str());
}
}// setAFK //
//-----------------------------------------------
// rollDice
//-----------------------------------------------
void CUserEntity::rollDice(sint16 min, sint16 max)
{
const string msgName = "COMMAND:RANDOM";
CBitMemStream out;
if (GenericMsgHeaderMngr.pushNameToStream(msgName, out))
{
out.serial(min);
out.serial(max);
NetMngr.push(out);
}
else
nlwarning("CUserEntity:rollDice: unknown message named '%s'.", msgName.c_str());
}// rollDice //
//---------------------------------------------------
// canEngageCombat :
// return true if user can engage melee combat, else return false and display system msg
//---------------------------------------------------
bool CUserEntity::canEngageCombat()
{
if( isSit() )
{
// display "you can't fight while sitting" message)
CInterfaceManager *pIM= CInterfaceManager::getInstance();
ucstring msg = CI18N::get("msgCantFightSit");
string cat = getStringCategory(msg, msg);
pIM->displaySystemInfo(msg, cat);
return false;
}
if( isSwimming() )
{
// display "you can't fight while swiming" message)
CInterfaceManager *pIM= CInterfaceManager::getInstance();
ucstring msg = CI18N::get("msgCantFightSwim");
string cat = getStringCategory(msg, msg);
pIM->displaySystemInfo(msg, cat);
return false;
}
if ( isRiding() )
{
// display "you can't fight while swimming" message)
CInterfaceManager *pIM= CInterfaceManager::getInstance();
ucstring msg = CI18N::get("msgCantFightRide");
string cat = getStringCategory(msg, msg);
pIM->displaySystemInfo(msg, cat);
return false;
}
return true;
} // canEngageCombat //
//---------------------------------------------------
// viewMode :
// Change the View (First/Third Person View).
//---------------------------------------------------
void CUserEntity::viewMode(CUserEntity::TView viewMode, bool changeView)
{
switch(viewMode)
{
// First Person View
case FirstPV:
if(changeView)
ClientCfg.FPV = true;
if(_Mount != CLFECOMMON::INVALID_SLOT)
{
CEntityCL *mount = EntitiesMngr.entity(_Mount);
if(mount)
mount->displayable(false);
}
// Change Controls.
if( isRiding() )
{
bool autoWalk = UserControls.autowalkState();
UserControls.mode(CUserControls::MountMode);
if( autoWalk )
UserControls.autowalkState( true );
}
else
UserControls.mode(CUserControls::InterfaceMode);
break;
// Third Person View
case ThirdPV:
if(changeView)
ClientCfg.FPV = false;
if(_Mount != CLFECOMMON::INVALID_SLOT)
{
CEntityCL *mount = EntitiesMngr.entity(_Mount);
if(mount)
mount->displayable(true);
}
// Change Controls.
UserControls.mode(CUserControls::ThirdMode);
break;
// Unknown
default:
nlwarning("UE:viewMode: Unknown View Asked '%d'.", (sint)viewMode);
return;
}
// Change the current View like asked.
_ViewMode = viewMode;
// disable or enable shadowing
updateCastShadowMap();
}// viewMode //
//-----------------------------------------------
// toggleCamera :
// Toggle Camera (First/Third Person)
//-----------------------------------------------
void CUserEntity::toggleCamera()
{
// You cannot change the camera view when dead.
if(isDead())
return;
// Only if not inside a building.
if(!UserEntity->forceIndoorFPV())
{
// Leave the 1st Person Mode -> Enter the 3rd Person View Mode
if (UserEntity->viewMode() == CUserEntity::FirstPV)
UserEntity->viewMode(CUserEntity::ThirdPV);
// Leave the 3rd Person Mode -> Enter the 1st Person View Mode
else
UserEntity->viewMode(CUserEntity::FirstPV);
}
}// toggleCamera //
//---------------------------------------------------
// getScale :
// Return the entity scale. (return 1.0 if there is any problem).
// \todo GUIGUI : do we have to take care of the user's race kwnowing it can favour him ?
//---------------------------------------------------
float CUserEntity::getScale() const // virtual
{
// Default Scale.
return 1.0f;
}// getScale //
//---------------------------------------------------
// removeCheckPrimitive :
// Remove the check primitive
//---------------------------------------------------
void CUserEntity::removeCheckPrimitive()
{
if(PACS && _CheckPrimitive)
PACS->removePrimitive(_CheckPrimitive);
_CheckPrimitive = 0;
}
//---------------------------------------------------
// removePrimitive :
// Remove the primitive
//---------------------------------------------------
void CUserEntity::removePrimitive() // virtual (will not be called by ~CEntityCL())
{
// Remove the Primitive used for check
removeCheckPrimitive();
// Remove the other primitive
CPlayerCL::removePrimitive();
}// removePrimitive //
//---------------------------------------------------
// computePrimitive :
// Create a primitive for the entity.
//---------------------------------------------------
void CUserEntity::computePrimitive() // virtual
{
// Initialize the primitive.
if(initPrimitive(0.5f, 2.0f, 0.0f, 0.0f, UMovePrimitive::Slide, (UMovePrimitive::TTrigger)(UMovePrimitive::OverlapTrigger | UMovePrimitive::EnterTrigger), MaskColPlayer, MaskColPlayer | MaskColNpc | MaskColDoor))
_Primitive->insertInWorldImage(dynamicWI);
// Set the position.
pacsPos(pos());
}// computePrimitive //
//---------------------------------------------------
// isBusy :
// Return if the user is already busy (combat/bo chat/loot/ etc.).
//---------------------------------------------------
bool CUserEntity::isBusy() const
{
CInterfaceManager *IM = CInterfaceManager::getInstance ();
// Check Trade.
// TODO : put the right DB entry !
CCDBNodeLeaf *nod = IM->getDbProp("SERVER:INVENTORY:EXCHANGE:BEGUN", false);
if(nod)
{
if(nod->getValueBool())
return true;
}
// else
// nlwarning("UE:isBusy: Cannot get the nod 'SERVER:INVENTORY:EXCHANGE:BEGUN'.");
/*
// Check Loot
static const uint nbSlot = 4;
uint i;
for(i=0; i<nbSlot; ++i)
{
nod = IM->getDbProp(NLMISC::toString("SERVER:INVENTORY:%d:%d:SHEET", INVENTORIES::pickup, i), false);
if(nod)
{
if(nod->getValue32() != 0)
return true;
}
else
nlwarning("UE:isBusy: Cannot get the nod 'SERVER:INVENTORY:%d:%d:SHEET'.", INVENTORIES::pickup, i);
}
// Check Harvest
for(i=0; i<nbSlot; ++i)
{
nod = IM->getDbProp(NLMISC::toString("SERVER:INVENTORY:%d:%d:SHEET", INVENTORIES::harvest, i), false);
if(nod)
{
if(nod->getValue32() != 0)
return true;
}
else
nlwarning("UE:isBusy: Cannot get the nod 'SERVER:INVENTORY:%d:%d:SHEET'.", INVENTORIES::harvest, i);
}
*/
// Check Bot chat.
CBotChatPage * currPage = CBotChatManager::getInstance()->getCurrPage();
if( currPage!= NULL )
{
return true;
}
// Not Busy
return false;
}// isBusy //
//---------------------------------------------------
// updateVisualDisplay :
// Show/Hide all or parts of the user body.
// todo GUIGUI : it's bad for the _Face to be a separated instance
//---------------------------------------------------
void CUserEntity::updateVisualDisplay()
{
// We need a skeleton.
if(_Skeleton.empty())
return;
// 1st person View
if(UserControls.isInternalView() || View.forceFirstPersonView())
{
// Hide the mount
CCharacterCL *mount = dynamic_cast<CCharacterCL *>(EntitiesMngr.entity(_Mount));
if(mount)
mount->displayable(false);
// Hide all user body parts.
for(uint i=0; i<_Instances.size(); ++i)
if(!_Instances[i].Current.empty())
{
_Instances[i].Current.hide();
_Instances[i].hideStaticFXs();
}
// Hide the face
if(!_Face.Current.empty())
_Face.Current.hide();
// We want to display weapons in 1st person view when attacking.
if(modeWithHiddenItems() == false)
{
if( _Mode == MBEHAV::COMBAT_FLOAT || _Mode == MBEHAV::COMBAT )
{
if( _ObjectsVisible )
{
// Show just Few parts
if(!_Instances[SLOTTYPE::HANDS_SLOT].Current.empty())
{
_Instances[SLOTTYPE::HANDS_SLOT].Current.show();
_Instances[SLOTTYPE::HANDS_SLOT].showStaticFXs();
}
if(!_Instances[SLOTTYPE::ARMS_SLOT].Current.empty())
{
_Instances[SLOTTYPE::ARMS_SLOT].Current.show();
_Instances[SLOTTYPE::ARMS_SLOT].showStaticFXs();
}
if(!_Instances[SLOTTYPE::RIGHT_HAND_SLOT].Current.empty())
{
_Instances[SLOTTYPE::RIGHT_HAND_SLOT].Current.show();
_Instances[SLOTTYPE::RIGHT_HAND_SLOT].showStaticFXs();
}
if(!_Instances[SLOTTYPE::LEFT_HAND_SLOT].Current.empty())
{
_Instances[SLOTTYPE::LEFT_HAND_SLOT].Current.show();
_Instances[SLOTTYPE::LEFT_HAND_SLOT].showStaticFXs();
}
}
}
}
}
else
{
// Show the mount
CCharacterCL *mount = dynamic_cast<CCharacterCL *>(EntitiesMngr.entity(_Mount));
if(mount)
{
mount->displayable(true);
showOrHideBodyParts( objectsVisible() );
}
// Show the face
/*
if(!_Face.Current.empty())
_Face.Current.show();
*/
}
}// updateVisualDisplay //
//---------------------------------------------------
// light:
// Show/Hide the user light.
//---------------------------------------------------
void CUserEntity::light()
{
// Create the light
if(_Light.empty())
{
// Check there is a skeleton and a bone to stick the light before to create it.
if(!_Skeleton.empty() && _NameBoneId!=-1)
{
_Light = Scene->createPointLight();
if(!_Light.empty())
{
// front of the player
_Light.setPos(0.f,0.3f,0.f);
// Setup the light
_Light.setupAttenuation(12.0f, 20.0f);
// Attach the light
_Skeleton.stickObject(_Light, _NameBoneId);
// The player light is the only one who can interact with Lightmapped objects
_Light.setInfluenceLightMap(true);
// TestYoyo
/*
NL3D::UInstance inst;
inst= Scene->createInstance("box.shape");
if(!inst.empty())
{
inst.setScale(0.2f, 0.2f, 0.2f);
inst.parent(_Light);
}
*/
}
}
else
nlwarning("UE:light: there is no skeleton or Name Bone to stick the light.");
}
// Turn On/Off the Light
_LightOn = !_LightOn;
if(!_Light.empty())
{
if(_LightOn)
_Light.show();
else
_Light.hide();
}
}// light //
//---------------------------------------------------
// CSpeedFactor::init :
// Initialize the Observer for the Speed Factor.
//---------------------------------------------------
void CUserEntity::CSpeedFactor::init()
{
_Value = 1.0f; // Default speed factor is 1.
CInterfaceManager *IM = CInterfaceManager::getInstance ();
CCDBNodeLeaf *pNodeLeaf = IM->getDbProp("SERVER:USER:SPEED_FACTOR", false);
if(pNodeLeaf)
{
/* if(!pNodeLeaf->addToLeaves(this))
nlwarning("UE:SP:init: cannot add the observer");*/
ICDBNode::CTextId textId;
pNodeLeaf->addObserver(this, textId);
if ( pNodeLeaf->getValue64() != 0 )
_Value = ((float)pNodeLeaf->getValue64())/100.0f; // may have been received before
}
else
nlwarning("UE:SP:init: 'SERVER:USER:SPEED_FACTOR' does not exist.");
}// CSpeedFactor::init //
//---------------------------------------------------
// CMountHunger::init :
//---------------------------------------------------
void CUserEntity::CMountHunger::init()
{}
//---------------------------------------------------
// CMountSpeeds::init :
//---------------------------------------------------
void CUserEntity::CMountSpeeds::init()
{
CInterfaceManager *IM = CInterfaceManager::getInstance ();
CCDBNodeLeaf *pNodeLeaf = IM->getDbProp( "SERVER:USER:MOUNT_WALK_SPEED", false );
BOMB_IF( ! pNodeLeaf, "MOUNT_WALK_SPEED not found", return );
if(pNodeLeaf)
{
ICDBNode::CTextId textId;
pNodeLeaf->addObserver(this, textId);
_WalkSpeed = ((float)pNodeLeaf->getValue32()) / 1000.0f; // may have been received before
}
pNodeLeaf = IM->getDbProp( "SERVER:USER:MOUNT_RUN_SPEED", false );
BOMB_IF( ! pNodeLeaf, "MOUNT_RUN_SPEED not found", return );
if(pNodeLeaf)
{
ICDBNode::CTextId textId;
pNodeLeaf->addObserver(this, textId);
_RunSpeed = ((float)pNodeLeaf->getValue32()) / 1000.0f; // may have been received before
}
}
//---------------------------------------------------
void CUserEntity::CSpeedFactor::release()
{
CInterfaceManager *IM = CInterfaceManager::getInstance ();
CCDBNodeLeaf *pNodeLeaf = IM->getDbProp("SERVER:USER:SPEED_FACTOR", false);
if(pNodeLeaf)
{
/* if(!pNodeLeaf->addToLeaves(this))
nlwarning("UE:SP:init: cannot add the observer");*/
ICDBNode::CTextId textId;
pNodeLeaf->removeObserver(this, textId);
}
else
nlwarning("UE:SP:init: 'SERVER:USER:SPEED_FACTOR' does not exist.");
}// CSpeedFactor::init //
void CUserEntity::CMountHunger::release()
{}
void CUserEntity::CMountSpeeds::release()
{
CInterfaceManager *IM = CInterfaceManager::getInstance ();
CCDBNodeLeaf *pNodeLeaf = IM->getDbProp( "SERVER:USER:MOUNT_WALK_SPEED", false );
BOMB_IF( ! pNodeLeaf, "MOUNT_WALK_SPEED not found", return );
if(pNodeLeaf)
{
ICDBNode::CTextId textId;
pNodeLeaf->removeObserver(this, textId);
}
pNodeLeaf = IM->getDbProp( "SERVER:USER:MOUNT_RUN_SPEED", false );
BOMB_IF( ! pNodeLeaf, "MOUNT_RUN_SPEED not found", return );
if(pNodeLeaf)
{
ICDBNode::CTextId textId;
pNodeLeaf->removeObserver(this, textId);
}
}
//---------------------------------------------------
// CSpeedFactor::update :
// Callback called to update the speed factor.
//---------------------------------------------------
void CUserEntity::CSpeedFactor::update(ICDBNode *node) // virtual
{
CCDBNodeLeaf *leaf = safe_cast<CCDBNodeLeaf *>(node);
_Value = ((float)leaf->getValue64())/100.0f;
//nlinfo("SpeedFactor changed to %f / %"NL_I64"u", _Value, leaf->getValue64());
// clamp the value (2.0 is the egg item or the level 6 speed up power up, nothing should be faster)
if(_Value > 2.0f)
{
//nlwarning("HACK: you try to change the speed factor to %f", _Value);
nlstop;
_Value = 2.0f;
}
}// CSpeedFactor::update //
/*
* Return true if the mount can run. Precondition: UserEntity->isRiding().
*/
bool CUserEntity::CMountHunger::canRun() const
{
CEntityCL *mountEntity = UserEntity->getMountEntity();
if ( ! mountEntity )
return false;
// Find the mount's db leaf and check hunger
CInterfaceManager *pIM = CInterfaceManager::getInstance();
CCDBNodeBranch *animalsNode = safe_cast<CCDBNodeBranch*>(pIM->getDB()->getNode( ICDBNode::CTextId( "SERVER:PACK_ANIMAL" ), false ));
BOMB_IF( ! animalsNode, "! animalsNode", return false; );
uint nbAnimals = (uint)animalsNode->getNbNodes();
for ( uint i=0; i!=nbAnimals; ++i )
{
ICDBNode *beastNode = animalsNode->getNode( i );
CCDBNodeLeaf *uidLeaf = safe_cast<CCDBNodeLeaf*>(beastNode->getNode( ICDBNode::CTextId( "UID" ) ));
if ( ((CLFECOMMON::TClientDataSetIndex)uidLeaf->getValue32()) == mountEntity->dataSetId() )
{
CCDBNodeLeaf *hungerLeaf = safe_cast<CCDBNodeLeaf*>(beastNode->getNode( ICDBNode::CTextId( "HUNGER" ) ));
return (hungerLeaf->getValue32() != (sint)ANIMAL_TYPE::DbHungryValue);
}
}
return false;
}
//---------------------------------------------------
// CMountSpeeds::update :
// Callback called to update the mount speed.
//---------------------------------------------------
void CUserEntity::CMountSpeeds::update(ICDBNode * /* node */) // virtual
{
CInterfaceManager *pIM = CInterfaceManager::getInstance();
_WalkSpeed = ((float)(pIM->getDbProp("SERVER:USER:MOUNT_WALK_SPEED")->getValue32())) / 1000.0f;
_RunSpeed = ((float)(pIM->getDbProp("SERVER:USER:MOUNT_RUN_SPEED")->getValue32())) / 1000.0f;
}
/*
* Return the mount entity if the user is riding, otherwise NULL
*/
CEntityCL* CUserEntity::getMountEntity()
{
if ( _Mount < EntitiesMngr.entities().size() )
{
return EntitiesMngr.entities()[_Mount];
}
return NULL;
}
/*
* Return the DB entry for the specified user's animal (NULL if not found)
*/
CCDBNodeBranch *CUserEntity::getBeastDBEntry( CLFECOMMON::TClientDataSetIndex uid )
{
// Find animal entry corresponding to datasetId
CInterfaceManager *pIM = CInterfaceManager::getInstance();
CCDBNodeBranch *animalsNode = safe_cast<CCDBNodeBranch*>(pIM->getDB()->getNode( ICDBNode::CTextId( "SERVER:PACK_ANIMAL" ), false ));
BOMB_IF( ! animalsNode, "! animalsNode", return NULL );
uint nbAnimals = (uint)animalsNode->getNbNodes();
for ( uint i=0; i!=nbAnimals; ++i )
{
ICDBNode *beastNode = animalsNode->getNode( i );
CCDBNodeLeaf *pNodeLeaf = safe_cast<CCDBNodeLeaf*>(beastNode->getNode( ICDBNode::CTextId( "UID" ) ));
if ( pNodeLeaf && (pNodeLeaf->getValue32() == (sint32)uid) )
return (CCDBNodeBranch*)beastNode;
}
return NULL;
}
//---------------------------------------------------
// displayDebug :
// Display Debug Information.
//---------------------------------------------------
void CUserEntity::displayDebug(float x, float &y, float lineStep) // virtual
{
CPlayerCL::displayDebug(x, y, lineStep);
}// displayDebug //
//---------------------------------------------------
// displayModifiers :
// Display dmg/heal numbers above the head.
//---------------------------------------------------
void CUserEntity::displayModifiers() // virtual
{
if(!UserControls.isInternalView())
CPlayerCL::displayModifiers();
}// displayModifiers //
//---------------------------------------------------
// isVisible :
// Return 'true' is the entity is displayed.
//---------------------------------------------------
bool CUserEntity::isVisible() const // virtual
{
return !UserControls.isInternalView();
}// isVisible //
//---------------------------------------------------
// readWrite :
// Read/Write Variables from/to the stream.
//---------------------------------------------------
void CUserEntity::readWrite(class NLMISC::IStream &f) throw(NLMISC::EStream)
{
CPlayerCL::readWrite(f);
// PROTECTED
f.serial(_SpeedFactor);
f.serial(_FrontVelocity);
f.serial(_LateralVelocity);
CVector dummyHead;
f.serial(dummyHead);
f.serial(_HeadPitch);
f.serial(_EyesHeight);
f.serial(_Run);
f.serial(_WalkVelocity);
f.serial(_RunVelocity);
f.serial(_CurrentVelocity);
f.serial(_Selection);
f.serial(_Trader);
f.serial(_Interlocutor);
f.serial(_Selectable);
// PRIVATE
f.serial(_OnMount);
f.serial(_AnimAttackOn);
// f.serialEnum(_ViewMode);
}// readWrite //
//---------------------------------------------------
// load :
// To call after a read from a stream to re-initialize the entity.
//---------------------------------------------------
void CUserEntity::load() // virtual
{
CInterfaceManager *IM = CInterfaceManager::getInstance ();
// Insert the user into PACS at his saved position
pacsPos(pos());
// update
if(!_WaitForAppearance)
{
// Visual properties A
sint64 prop = IM->getDbProp("SERVER:Entities:E"+toString("%d", _Slot)+":P"+toString("%d", CLFECOMMON::PROPERTY_VPA))->getValue64();
updateVisualPropertyVpa(0, prop); // Vpa udapte vpb and vpc too.
}
}// load //
//---------------------------------------------------
void CUserEntity::CInvisibleObserver::update(ICDBNode* node)
{
UserEntity->buildInSceneInterface();
}
//---------------------------------------------------
void CUserEntity::CSkillPointsObserver::update(ICDBNode* node )
{
if (FarTP.isFarTPInProgress() || IngameDbMngr.initInProgress()) // prevent from displaying at the beginning of a FarTP (due to RESET_BANK or CDB resetData())
return;
CInterfaceManager *pIM = CInterfaceManager::getInstance ();
CCDBNodeLeaf *leaf = dynamic_cast<CCDBNodeLeaf*>(node);
if (leaf)
{
sint32 oldValue = leaf->getOldValue32();
if (oldValue != 0)
{
sint delta = leaf->getValue32()-oldValue;
string deltaStr = toString("%+d", delta);
// get the sp title
ucstring spTitle;
spTitle= CI18N::get(toString("uiSkillPointsBold%d",SpType));
// run the popup
pIM->runActionHandler("message_popup", NULL, "text1="+deltaStr+"|text0="+spTitle.toUtf8());
// Context help
contextHelp ("skill_point");
}
}
}
//---------------------------------------------------
// CFameObserver::update
//---------------------------------------------------
void CUserEntity::CFameObserver::update(ICDBNode* node )
{
CSkillManager *pSM = CSkillManager::getInstance();
CCDBNodeLeaf *leaf = dynamic_cast<CCDBNodeLeaf*>(node);
if (leaf)
{
sint32 fameValue = leaf->getValue32();
pSM->tryToUnblockTitleFromMinFames( FactionIndex, fameValue );
pSM->tryToUnblockTitleFromMaxFames( FactionIndex, fameValue );
}
}
//---------------------------------------------------
void CUserEntity::makeTransparent(bool t)
{
CPlayerCL::makeTransparent(t);
uint32 opaMin= getOpacityMin();
uint8 opacity = (uint8)(opaMin + (255-opaMin) * (1.0 - _TranspFactor));
getFace()->makeInstanceTransparent(opacity, (uint8)opaMin);
}// makeTransparent //
//---------------------------------------------------
void CUserEntity::setDiffuse(bool onOff, NLMISC::CRGBA diffuse)
{
CPlayerCL::setDiffuse(onOff, diffuse);
getFace()->setDiffuse(onOff, diffuse);
}
// Helper for CUserEntity::extractRM()
bool findExtractionActionInMemory( CSPhraseManager *pm, CSBrickManager *bm, uint memoryLine, uint& index )
{
uint x;
for ( x=0; x!=PHRASE_MAX_MEMORY_SLOT; ++x )
{
uint32 phraseSlot = pm->getMemorizedPhrase( memoryLine, x );
const CSPhraseCom& phraseCom = pm->getPhrase( phraseSlot );
if ( ! phraseCom.empty() )
{
CSBrickSheet *brickSheet = bm->getBrick( phraseCom.Bricks[0] );
if ( brickSheet->isForageExtraction() && (!brickSheet->MandatoryFamilies.empty()) ) // assumes care root bricks have not mandatories
{
index = x;
return true;
}
}
}
return false;
}
//---------------------------------------------------
void CUserEntity::extractRM()
{
CSPhraseManager *pm = CSPhraseManager::getInstance();
uint index;
uint memoryLine;
bool autoFindPhrase = (_MoveToPhraseMemoryLine == std::numeric_limits<uint>::max());
if ( ! autoFindPhrase )
{
// Use clicked phrase
memoryLine = _MoveToPhraseMemoryLine;
index = _MoveToPhraseMemorySlot;
}
else
{
// Find the first extraction phrase in the memory bar
CSBrickManager *bm = CSBrickManager::getInstance();
memoryLine = pm->getSelectedMemoryLineDB();
if ( ! findExtractionActionInMemory( pm, bm, memoryLine, index ) )
{
// Search in other memory bar lines (because the auto-equip does not set the current line at once)
memoryLine = std::numeric_limits<uint>::max();
uint nbLines = pm->getNbMemoryLines();
for ( uint j=0; j!=nbLines; ++j )
{
if ( j == memoryLine )
continue;
if ( findExtractionActionInMemory( pm, bm, j, index ) )
{
memoryLine = j;
break;
}
}
}
}
if ( memoryLine != std::numeric_limits<uint>::max() )
{
// Open the forage (but not for care actions). Necessary for the case of redoing an extraction after a Drop All on the same source.
uint32 phraseId = pm->getMemorizedPhrase( memoryLine, index );
if ( phraseId != 0 )
{
const CSPhraseCom& phraseCom = pm->getPhrase( phraseId );
if ( ! phraseCom.empty() )
{
CSBrickSheet *rootBrick = CSBrickManager::getInstance()->getBrick( phraseCom.Bricks[0] );
if ( rootBrick )
{
if ( rootBrick->IndexInFamily == 1 ) // only extracting actions
CTempInvManager::getInstance()->open( TEMP_INV_MODE::Forage );
}
}
}
// Cast the extraction. if autoFindPhrase, clientExecute() not already called.
if ( autoFindPhrase )
{
// decide now if cyclic or not
_MoveToPhraseCyclic= true;
if(pm->avoidCyclicForPhrase(pm->getMemorizedPhrase(memoryLine, index)))
_MoveToPhraseCyclic= false;
// execute on client now
pm->clientExecute( memoryLine, index, _MoveToPhraseCyclic);
}
// execute on server
pm->sendExecuteToServer( memoryLine, index, _MoveToPhraseCyclic );
// Because sendExecuteToServer() has been called, must NOT cancelClientExecute() at resetAnyMoveTo()
_MoveToAction= CUserEntity::None;
}
else
{
CInterfaceManager::getInstance()->displaySystemInfo( CI18N::get("uiExtractionPhraseMissing"), "CHK" );
return;
}
}
// ***************************************************************************
bool CUserEntity::canCastShadowMap() const
{
if(!CCharacterCL::canCastShadowMap())
return false;
// don't cast shadow in first person, but in death mode (third person actually...)
return viewMode() != FirstPV || UserControls.mode() == CUserControls::DeathMode;
}
// ***************************************************************************
void CUserEntity::forceLookEntity(const NLMISC::CVectorD &dir2targIn, bool updateHeadPitch, bool /* start */)
{
CVectorD dir2targ= dir2targIn;
float frontYawBefore = 0.f;
float frontYawAfter;
// Third person: bkup current yaw
if(viewMode()==ThirdPV)
{
frontYawBefore = frontYaw();
}
// **** Look at the entity
dir2targ.normalize();
front(dir2targ, false, false);
// **** FirstPerson
if(viewMode() == FirstPV)
{
if(updateHeadPitch && _FollowForceHeadPitch)
{
// rotate the head to the target
CEntityCL *target = EntitiesMngr.entity(targetSlot());
if(target)
{
// Both Z must be correct
snapToGround();
target->snapToGround();
// don't update to the real head position each frame (else jitter too much cause of target anim)
CVector targetPos= target->pos() + CVector(0,0,_FollowHeadOffset);
// then look at this target
CVector dirToTarget = targetPos - (pos()+CVector(0,0, UserEntity->eyesHeight()));
if((dirToTarget.x != 0.0f) || (dirToTarget.y!=0.0f))
{
dirToTarget.normalize();
setHeadPitch(atan2(dirToTarget.z, sqrt(sqr(dirToTarget.x)+sqr(dirToTarget.y))));
}
// TestYoyo
/*if(ClientCfg.Fly!=0.f)
{
nlinfo("Uy: %.3f. Hp: %.3f. UPos:(%.3f,%.3f,%.3f). TPos:(%.3f,%.3f,%.3f)",
UserEntity->frontYaw(), UserEntity->getHeadPitch(), pos().x, pos().y, pos().z,
targetPos.x, targetPos.y, targetPos.z);
static float uy=0.f;
static float hp=0.f;
if( fabs(fmod(UserEntity->frontYaw()-uy, 2*Pi))>ClientCfg.Fly ||
fabs(fmod(UserEntity->getHeadPitch()-hp, 2*Pi))>ClientCfg.Fly )
{
nlinfo("YOYOBREAK: ^^^^^^^^^^");
}
uy=UserEntity->frontYaw();
hp=UserEntity->getHeadPitch();
}*/
}
}
}
// **** Third person
else if(viewMode()==ThirdPV)
{
// keep the current effective yaw. ONLY if no SmoothResetCameraYaw forced
if(!UserControls.isResetSmoothCDYForced())
{
frontYawAfter = frontYaw();
float deltaYaw= frontYawAfter - frontYawBefore;
// compensate rotation (NB: it stops also any SmoothReset)
UserControls.appendCameraDeltaYaw(-deltaYaw);
}
}
// when looking to an entity, if automatic camera, center the view on it.
if( ClientCfg.AutomaticCamera /*&& start*/ )
{
UserControls.resetSmoothCameraDeltaYaw();
}
}
// ***************************************************************************
void CUserEntity::startForceLookEntity(CLFECOMMON::TCLEntityId slot)
{
// Start a new follow: force head pitch to follow by default
_FollowForceHeadPitch= true;
// if a follow is started in first person, reset CameraYaw
if(viewMode()==FirstPV)
UserControls.resetCameraDeltaYaw();
// reorient now (important else may have a time shift because of resetCameraDeltaYaw above)
CEntityCL *target = EntitiesMngr.entity(slot);
if(target)
{
/* For complex reason, the target may not be still snapped on the ground. snap it now
- this is because in common case, entities are snap only if they are visible (for speed up)
=> depends on camera
- but in this case, the camera depends on real entity position (headPitch in first person)
*/
target->snapToGround();
// For FirstPerson targeting. Get the current target head offset
_FollowHeadOffset= 0.f;
CVector headPos;
if(target->getHeadPos(headPos))
{
_FollowHeadOffset= headPos.z - float(target->pos().z);
}
// Look at the entity
CVectorD dir2targ = target->pos() - pos();
dir2targ.z = 0.0;
if(dir2targ!=CVectorD::Null)
{
forceLookEntity(dir2targ, true, true);
}
}
}
// ***************************************************************************
void CUserEntity::stopForceHeadPitchInFollow()
{
_FollowForceHeadPitch= false;
}
// ***************************************************************************
void CUserEntity::switchVelocity(bool userRequest)
{
if (ClientCfg.R2EDEnabled && R2::getEditor().getMode() == R2::CEditor::EditionMode)
{
// when in the R2 editor, force to run all the time
_Run = true;
}
else
{
_Run = !_Run;
}
_CurrentVelocity = _Run ? runVelocity() : walkVelocity();
if (userRequest)
{
_RunWhenAble = _Run;
}
// display message : your are running, you are walking
CInterfaceManager *pIM= CInterfaceManager::getInstance();
ucstring msg;
if( _Run )
msg = CI18N::get("msgUserIsRunning");
else
msg = CI18N::get("msgUserIsWalking");
string cat = getStringCategory(msg, msg);
pIM->displaySystemInfo(msg, cat);
// Write to the UI database, to update views
CCDBNodeLeaf *node= pIM->getDbProp("UI:VARIABLES:PLAYER_RUNNING", false);
if(node)
node->setValue32(_Run);
}
//-----------------------------------------------
// autoEquipWithLastUsedWeapons
//
//-----------------------------------------------
void CUserEntity::autoEquipWithLastUsedWeapons()
{
CInventoryManager *inv = CInventoryManager::getInstance();
if ( !inv )
{
return;
}
// Clear hands
inv->unequip( "LOCAL:INVENTORY:HAND:1" );
inv->unequip( "LOCAL:INVENTORY:HAND:0" );
uint ir,il;
// Equip right hand
if( _PreviousRightHandItem.Sheet != 0 )
{
// find item in bag with same properties than last used one in right hand
for ( ir=0; ir<MAX_BAGINV_ENTRIES; ++ir )
{
if( _PreviousRightHandItem.Sheet == inv->getBagItem(ir).getSheetID() &&
_PreviousRightHandItem.Quality == inv->getBagItem(ir).getQuality() &&
_PreviousRightHandItem.Weight == inv->getBagItem(ir).getWeight() &&
_PreviousRightHandItem.NameId == inv->getBagItem(ir).getNameId() )
{
break;
}
}
if ( ir != MAX_BAGINV_ENTRIES )
{
// Equip right hand
string bagPath = toString( "LOCAL:INVENTORY:BAG:%u", ir );
inv->equip( bagPath, "LOCAL:INVENTORY:HAND:0" );
// Equip left hand if needed
if( _PreviousLeftHandItem.Sheet != 0 )
{
for ( il=0; il<MAX_BAGINV_ENTRIES; ++il )
{
if( il != ir &&
_PreviousLeftHandItem.Sheet == inv->getBagItem(il).getSheetID() &&
_PreviousLeftHandItem.Quality == inv->getBagItem(il).getQuality() &&
_PreviousLeftHandItem.Weight == inv->getBagItem(il).getWeight() &&
_PreviousLeftHandItem.NameId == inv->getBagItem(il).getNameId() )
{
break;
}
}
if ( il != MAX_BAGINV_ENTRIES )
{
bagPath = toString( "LOCAL:INVENTORY:BAG:%u", il );
inv->equip( bagPath, "LOCAL:INVENTORY:HAND:1" );
}
}
return;
}
}
// TODO : choose the best one
}
// ***************************************************************************
void CUserEntity::executeCombatWithPhrase(CEntityCL *target, uint32 memoryLine, uint32 memoryIndex, bool cyclic)
{
nlassert(target);
CSPhraseManager *pPM= CSPhraseManager::getInstance();
// is a melee combat?
bool meleeCombat = false;
// empty hand => yes!
meleeCombat= true;
uint32 rightHandSheet = getInventory().getRightHandItemSheet();
if(rightHandSheet)
meleeCombat = getInventory().isMeleeWeaponItem(rightHandSheet);
// If melee combat, and if the user entity is not well placed for fight, or if it has changed his target
if( meleeCombat &&
(
!target->isPlacedToFight(pos(), front(), attackRadius() + ClientCfg.AttackDist) ||
target->slot()!=_LastExecuteCombatSlot
)
)
{
_LastExecuteCombatSlot= target->slot();
// Cancel any follow
disableFollow();
// Launch the moveToCombatPhrase, canceling any old action client execution.
// NB: this will also force him to look at the entity
moveToCombatPhrase(target->slot(), 2.0, memoryLine, memoryIndex, cyclic);
// And after (order is important), start the phrase execution on client
pPM->clientExecute(memoryLine, memoryIndex, cyclic);
}
else
{
// Cancel any moveTo(), because don't want to continue reaching the prec entity
// VERY important if previous MoveTo was a SPhrase MoveTo (because cancelClientExecute() must be called)
resetAnyMoveTo();
// start client execution: NB: start client execution even if it
pPM->clientExecute(memoryLine, memoryIndex, cyclic);
// inform Server of phrase cast
pPM->sendExecuteToServer(memoryLine, memoryIndex, cyclic);
if( !meleeCombat && !cyclic )
{
pPM->executeDefaultAttack();
}
}
}
// ***************************************************************************
void CUserEntity::beginCast(const MBEHAV::CBehaviour &behaviour)
{
if(viewMode()==ThirdPV)
{
// backup front yaw
float frontYawBefore = frontYaw();
// begin cast
CCharacterCL::beginCast( behaviour );
// compensate the front change using a camera move
float frontYawAfter = frontYaw();
float deltaYaw= frontYawAfter - frontYawBefore;
UserControls.appendCameraDeltaYaw(-deltaYaw);
// if automatic camera, center the view behind the user
if( ClientCfg.AutomaticCamera )
{
UserControls.resetSmoothCameraDeltaYaw();
}
}
else
{
// in first person mode, reset the delta yaw
UserControls.resetCameraDeltaYaw();
CCharacterCL::beginCast( behaviour );
}
}
// ***************************************************************************
void CUserEntity::updatePreCollision(const NLMISC::TTime &time, CEntityCL *target)
{
CPlayerCL::updatePreCollision(time, target);
// test each frame if the mode has changed
if(SoundMngr)
{
string deadMusic= "death.ogg";
// Play/stop music if comes from or goes to dead
bool isDead= _Mode==MBEHAV::DEATH || _Mode==MBEHAV::SWIM_DEATH;
// must start music?
if( isDead && SoundMngr->getEventMusicPlayed()!=deadMusic )
{
SoundMngr->playEventMusic(deadMusic, 0, true);
}
// must end music?
if( !isDead && SoundMngr->getEventMusicPlayed()==deadMusic )
{
SoundMngr->stopEventMusic(deadMusic, CSoundManager::LoadingMusicXFade);
}
}
}
// ***************************************************************************
void CUserEntity::buildTotem()
{
const string msgName = "TOTEM:BUILD";
CBitMemStream out;
if( GenericMsgHeaderMngr.pushNameToStream( msgName, out ) )
{
NetMngr.push( out );
nlinfo( "sending TOTEM:build message to server" );
}
}
// ***************************************************************************
void CUserEntity::setR2CharMode(R2::TCharMode mode)
{
if (mode == R2::TCharMode::Editer || mode == R2::TCharMode::Dm)
{
_R2CharMode= mode;
walkVelocity(ClientCfg.DmWalk);
runVelocity(ClientCfg.DmRun);
View.setCameraDistanceMaxForDm();
CEntityCL *user = EntitiesMngr.entity(0);
NLPACS::UMovePrimitive* prim = user?user->getPrimitive():0;
if (prim)
{
prim->setObstacle(false);
}
}
else if (mode == R2::TCharMode::Player || mode == R2::TCharMode::Tester)
{
_R2CharMode= mode;
walkVelocity(ClientCfg.Walk);
runVelocity(ClientCfg.Run);
View.setCameraDistanceMaxForPlayer();
CEntityCL *user = EntitiesMngr.entity(0);
NLPACS::UMovePrimitive* prim = user?user->getPrimitive():0;
if (prim)
{
prim->setObstacle(true);
}
}
else
{
nlwarning("R2Ed: Error Char Mode not handled %u", (uint32)mode.getValue());
}
}
bool CUserEntity::isInNpcControl() const
{
CInterfaceManager* pIM = CInterfaceManager::getInstance();
CCDBNodeLeaf *sheet = pIM->getDbProp("SERVER:USER:NPC_CONTROL:SHEET", false);
return sheet && NLMISC::CSheetId(sheet->getValue32())!=NLMISC::CSheetId::Unknown;
}
void CUserEntity::updateNpcContolSpeed()
{
CInterfaceManager* pIM = CInterfaceManager::getInstance();
CCDBNodeLeaf *sheet = pIM->getDbProp("SERVER:USER:NPC_CONTROL:SHEET", false);
CCDBNodeLeaf *walk = pIM->getDbProp("SERVER:USER:NPC_CONTROL:WALK", false);
CCDBNodeLeaf *run = pIM->getDbProp("SERVER:USER:NPC_CONTROL:RUN", false);
if (!sheet || !walk || !run)
{
return;
}
static NLMISC::CSheetId oldSheet = NLMISC::CSheetId::Unknown;
static float oldRun=0.f;
static float oldWalk=0.f;
NLMISC::CSheetId sheetId(sheet->getValue32());
float newRun = float(run->getValue32()) / 1000.0f;
float newWalk = float(walk->getValue32()) / 1000.0f;
if (sheetId == oldSheet && oldRun == newRun && oldWalk == newWalk )
{
return;
}
oldSheet = sheetId;
oldRun = newRun;
oldWalk = newWalk;
if (sheetId != NLMISC::CSheetId::Unknown)
{
walkVelocity(newWalk);
runVelocity(newRun);
}
else
{
setR2CharMode(_R2CharMode);
}
}
//-----------------------------------------------
// cancelAllPhrases
//-----------------------------------------------
void CUserEntity::cancelAllPhrases()
{
CBitMemStream out;
if(GenericMsgHeaderMngr.pushNameToStream("PHRASE:CANCEL_ALL", out))
{
NetMngr.push(out);
}
else
{
nlwarning("<CUserEntity::cancelAllPhrases> unknown message name '%s'", "PHRASE:CANCEL_ALL");
}
}
//-----------------------------------------------
// canChangeFront
//-----------------------------------------------
bool CUserEntity::canChangeFront()
{
return !(_CurrentBehaviour.Behaviour == MBEHAV::EXTRACTING
|| (_CurrentBehaviour.Behaviour == MBEHAV::RANGE_ATTACK && _Mode==MBEHAV::COMBAT && !UserControls.isMoving())
|| (_CurrentBehaviour.Behaviour >= MBEHAV::MAGIC_CASTING_BEHAVIOUR_BEGIN && _CurrentBehaviour.Behaviour <= MBEHAV::MAGIC_CASTING_BEHAVIOUR_END));
}
//-----------------------------------------------
// rememberWeaponsInHand
//
//-----------------------------------------------
void CUserEntity::rememberWeaponsInHand()
{
CInventoryManager * inv = CInventoryManager::getInstance();
if( !inv )
{
return;
}
// keep right hand item
CItemImage * rightItemImg = inv->getHandItem(0);
if( rightItemImg )
{
if( inv->isMeleeWeaponItem(rightItemImg->getSheetID()) || inv->isRangeWeaponItem(rightItemImg->getSheetID()) )
{
_PreviousRightHandItem = CItemSnapshot(*rightItemImg);
// keep left hand item too (could be ammo, second weapon, etc ..)
CItemImage * leftItemImg = inv->getHandItem(1);
if( leftItemImg )
{
_PreviousLeftHandItem = CItemSnapshot(*leftItemImg);
}
else
{
_PreviousLeftHandItem = CItemSnapshot();
}
}
}
}
//-----------------------------------------------
// snapshot of a CItemImage
//
//-----------------------------------------------
CUserEntity::CItemSnapshot::CItemSnapshot( const CItemImage& i )
{
Sheet = i.getSheetID();
Quality = i.getQuality();
Quantity = i.getQuantity();
UserColor = i.getUserColor();
Price = i.getPrice();
Weight = i.getWeight();
NameId = i.getNameId();
InfoVersion = i.getInfoVersion();
}
sint CUserEntity::getLevel() const
{
CInterfaceManager *pIM = CInterfaceManager::getInstance();
sint level = -1;
for(uint i=0;i<EGSPD::CSPType::EndSPType;i++)
{
CCDBNodeLeaf *node= pIM->getDbProp(toString("SERVER:USER:SKILL_POINTS_%d:VALUE", i), false);
if(node)
{
level = std::max(level, (sint) node->getValue32());
}
}
return level;
}
//-----------------------------------------------
// interlocutor
//-----------------------------------------------
void CUserEntity::interlocutor( const CLFECOMMON::TCLEntityId &slot)
{
CLFECOMMON::TCLEntityId prevInterlocutor = _Interlocutor;
_Interlocutor = slot;
// Refresh (hide or unhide) the icon for the interlocutor NPC
if (prevInterlocutor != CLFECOMMON::INVALID_SLOT)
EntitiesMngr.refreshInsceneInterfaceOfFriendNPC(prevInterlocutor);
if (_Interlocutor != CLFECOMMON::INVALID_SLOT)
EntitiesMngr.refreshInsceneInterfaceOfFriendNPC(_Interlocutor);
}
//-----------------------------------------------
// trader
//-----------------------------------------------
void CUserEntity::trader(const CLFECOMMON::TCLEntityId &slot)
{
CLFECOMMON::TCLEntityId prevTrader = _Trader;
_Trader = slot;
// Refresh (hide or unhide) the icon for the trader NPC
if (prevTrader != CLFECOMMON::INVALID_SLOT)
EntitiesMngr.refreshInsceneInterfaceOfFriendNPC(prevTrader);
if (_Trader != CLFECOMMON::INVALID_SLOT)
EntitiesMngr.refreshInsceneInterfaceOfFriendNPC(_Trader);
}