// Ryzom - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#include "stdpch.h"
#include "ai_player.h"
#include "ai_bot_fauna.h"
#include "ai_bot_npc.h"
using namespace MULTI_LINE_FORMATER;
//////////////////////////////////////////////////////////////////////////////
// CBotPlayer //
//////////////////////////////////////////////////////////////////////////////
CBotPlayer::CBotPlayer(CManagerPlayer* owner, TDataSetRow const& DataSetRow, NLMISC::CEntityId const& id, uint32 level)
: CChild(owner)
, CAIEntityPhysical(static_cast(*this), DataSetRow, id, 0.5f, level, RYAI_MAP_CRUNCH::Nothing)
, _CurrentTeamId(CTEAM::InvalidTeamId)
, _FollowMode(false)
, _Aggroable(true)
{
#ifdef NL_DEBUG
nlassert(owner->playerList().find(dataSetRow())==owner->playerList().end());
#endif
owner->playerList().insert(std::make_pair(dataSetRow(), this));
NLMISC::CSheetId sheetId = CMirrors::sheet(DataSetRow);
_Sheet = AISHEETS::CSheets::getInstance()->lookupRaceStats(sheetId);
}
CBotPlayer::~CBotPlayer()
{
getOwner()->playerList().erase(dataSetRow());
if (isSpawned())
{
despawnBot();
}
}
std::string CBotPlayer::getIndexString() const
{
return getOwner()->getIndexString()+NLMISC::toString(":p%u", getChildIndex());
}
std::string CBotPlayer::getEntityIdString() const
{
return getEntityId().toString() ;
}
std::string CBotPlayer::getOneLineInfoString() const
{
return std::string("Player '") + getEntityId().toString() + "'";
}
std::vector CBotPlayer::getMultiLineInfoString() const
{
std::vector container;
pushTitle(container, "CBotPlayer");
pushEntry(container, "id=" + getIndexString());
container.back() += " eid=" + getEntityIdString();
container.back() += " teamid=" + NLMISC::toString("%u", _CurrentTeamId);
pushFooter(container);
return container;
}
void CBotPlayer::processEvent(CCombatInterface::CEvent const& event)
{
// if heal happends, dispatch aggro on targetters.
if (event._nature==ACTNATURE::CURATIVE_MAGIC && event._weight>=0)
{
float aggro = -event._weight;
if (aggro>-0.5f)
aggro = -0.5f;
else if (aggro<-1.f)
aggro = -1.f;
CAIEntityPhysical const* const targetBot = CAIS::instance().getEntityPhysical(event._targetRow);
if (targetBot)
{
CAIEntityPhysical* targeter = targetBot->firstTargeter();
while (targeter)
{
if (targeter->dataSetRow()!=event._originatorRow)
{
switch (targeter->getRyzomType())
{
case RYZOMID::creature:
{
CSpawnBotFauna* const fauna = NLMISC::safe_cast(targeter);
fauna->addAggroFor(event._originatorRow, aggro, true);
}
break;
case RYZOMID::npc:
{
CSpawnBotNpc* const npc = NLMISC::safe_cast(targeter);
npc->addAggroFor(event._originatorRow, aggro, true);
}
break;
default:
break;
}
}
targeter = targeter->nextTargeter();
}
}
}
}
void CBotPlayer::updatePos()
{
RYAI_MAP_CRUNCH::CWorldPosition wpos;
if (!CWorldContainer::getWorldMap().setWorldPosition(pos().h(), wpos,CAIVector(pos())))
{
_PlayerPosIsInvalid = true;
return;
}
_PlayerPosIsInvalid = false;
setWPos(wpos);
linkEntityToMatrix(this->pos(),getOwner()->getOwner()->playerMatrix());
if (wpos.getFlags()&RYAI_MAP_CRUNCH::Water)
setActionFlags(RYZOMACTIONFLAGS::InWater);
else
removeActionFlags(RYZOMACTIONFLAGS::InWater);
}
CAIPos CBotPlayer::aipos() const
{
if (_PlayerPosIsInvalid)
return CAIPos(wpos().toAIVector(), wpos().h(), 0); // This is last valid position on AI collision map
else
return CAIPos(pos());
}
void CBotPlayer::update()
{
updatePos();
}
CAIInstance* CBotPlayer::getAIInstance() const
{
return getOwner()->getAIInstance();
}
bool CBotPlayer::spawn()
{
setSpawn(this);
return true;
}
void CBotPlayer::despawnBot()
{
setSpawn(NULL);
}
bool CBotPlayer::isUnReachable() const
{
// _PlayerPosIsInvalid does not reflect the fact that the player is unreachable
// _PlayerPosIsInvalid is true when player is in delta between PACS and WorldMap
// collisions, and is reachable in those cases if the bot is near
if (useOldUnreachable)
{
return _PlayerPosIsInvalid;
}
else
{
return false;
}
}
bool CBotPlayer::setPos(CAIPos const& pos)
{
#ifdef NL_DEBUG
nlassert(1==0);
#endif
return true;
}
float CBotPlayer::walkSpeed() const
{
nlerror("Non-virtual overriden function walkSpeed in CBotPlayer");
return 3.f/10.f;
}
float CBotPlayer::runSpeed() const
{
nlerror("Non-virtual overriden function runSpeed in CBotPlayer");
return 6.f/10.f;
}
bool CBotPlayer::isAggressive() const
{
MBEHAV::TMode const mode = getMode();
return mode==MBEHAV::COMBAT_FLOAT || mode==MBEHAV::COMBAT;
}
void CBotPlayer::addAggroer(TDataSetRow const& row)
{
#if !FINAL_VERSION
for (sint32 i=(sint32)_AggroerList.size()-1;i>=0;i--)
nlassert(_AggroerList[i]!=row);
#endif
_AggroerList.push_back(row);
}
void CBotPlayer::removeAggroer(TDataSetRow const& row)
{
for (sint32 i=(sint32)_AggroerList.size()-1;i>=0;i--)
{
if (_AggroerList[i]==row)
{
_AggroerList.at(i)=_AggroerList.back();
_AggroerList.pop_back();
break;
}
}
}
void CBotPlayer::updateInsideTriggerZones(const std::set& newInsideTriggerZone, std::vector& onEnterZone, std::vector& onLeaveZone)
{
std::set::const_iterator firstInside(_InsideTriggerZones.begin()), lastInside( _InsideTriggerZones.end());
std::set::const_iterator firstNewInside(newInsideTriggerZone.begin()), lastNewInside( newInsideTriggerZone.end());
std::set_difference(firstInside, lastInside, firstNewInside, lastNewInside, std::back_inserter(onLeaveZone));
std::set_difference(firstNewInside, lastNewInside, firstInside, lastInside, std::back_inserter(onEnterZone));
_InsideTriggerZones = newInsideTriggerZone;
}
//////////////////////////////////////////////////////////////////////////////
// CManagerPlayer //
//////////////////////////////////////////////////////////////////////////////
CManagerPlayer::~CManagerPlayer()
{
TPlayerMap::iterator it = _spawnedPlayers.begin();
while (it != _spawnedPlayers.end())
{
CBotPlayer* player = (*it).second;
// a CBotPlayer object removes itself from _spawnedPlayers at destruction
// increment the iterator before it becomes invalid
++it;
player->despawnBot();
removeChildByIndex(player->getChildIndex());
// now the player object is destroyed
}
}
void CManagerPlayer::update()
{
FOREACH(it, TPlayerMap, _spawnedPlayers)
{
it->second->CBotPlayer::update();
}
}
void CManagerPlayer::addSpawnedPlayer(TDataSetRow const& dataSetRow, NLMISC::CEntityId const& id)
{
CBotPlayer* player = new CBotPlayer(this,dataSetRow,id,1); // :TODO: default player level calculation (skill & hp ?).
addChild(player);
player->spawn();
player->linkToWorldMap(player, player->pos(), getOwner()->playerMatrix());
player->updatePos();
// update team id and composition
CMirrorPropValueRO value( *CMirrors::DataSet, dataSetRow, DSPropertyTEAM_ID );
player->setCurrentTeamId(value());
if (value() != CTEAM::InvalidTeamId)
{
_teams[value()].insert(dataSetRow);
}
}
void CManagerPlayer::removeDespawnedPlayer(TDataSetRow const& dataSetRow)
{
// Remove player from Manager.
TPlayerMap::iterator it = _spawnedPlayers.find(dataSetRow);
if (it==_spawnedPlayers.end())
{
// need to log some warning
nlwarning("Player Despawn Error");
#ifdef NL_DEBUG
nlerror("Player Despawn Error");
#endif
return;
}
else
{
CBotPlayer* const player = (*it).second;
// update team composition
if (player->getCurrentTeamId() != CTEAM::InvalidTeamId)
{
CHashMap >::iterator it(_teams.find(player->getCurrentTeamId()));
if (it != _teams.end())
{
it->second.erase(dataSetRow);
if (it->second.empty())
_teams.erase(it);
}
}
player->despawnBot();
removeChildByIndex(player->getChildIndex());
}
}
void CManagerPlayer::updatePlayerTeam(TDataSetRow const& dataSetRow)
{
TPlayerMap::iterator it(_spawnedPlayers.find(dataSetRow));
if (it!=_spawnedPlayers.end())
{
uint16 const oldTeam = it->second->getCurrentTeamId();
if (oldTeam!=CTEAM::InvalidTeamId)
{
CHashMap >::iterator it(_teams.find(oldTeam));
if (it != _teams.end())
{
it->second.erase(dataSetRow);
if (it->second.empty())
_teams.erase(it);
}
}
// update team id and composition
CMirrorPropValueRO value(*CMirrors::DataSet, dataSetRow, DSPropertyTEAM_ID);
it->second->setCurrentTeamId(value());
if (value() != CTEAM::InvalidTeamId)
{
_teams[value()].insert(dataSetRow);
}
}
else
{
nlwarning("CManagerPlayer::updatePlayerTeam : dataSetRow %u, can't find spawned player !", dataSetRow.getIndex());
}
}
// This static data is just to have a ref return type anytime, bad habit.
std::set CManagerPlayer::emptySet;
std::set const& CManagerPlayer::getPlayerTeam(TDataSetRow const& playerRow)
{
TPlayerMap::iterator it(_spawnedPlayers.find(playerRow));
if (it != _spawnedPlayers.end())
{
uint16 const teamId = it->second->getCurrentTeamId();
return getPlayerTeam(teamId);
}
else
{
nlwarning("CManagerPlayer::getPlayerTeam can't find player from dataset %u", playerRow.getIndex());
return emptySet;
}
}
std::set const& CManagerPlayer::getPlayerTeam(uint16 teamId)
{
if (teamId == CTEAM::InvalidTeamId)
{
return emptySet;
}
else
{
TTeamMap::iterator itTeam = _teams.find(teamId);
if (itTeam != _teams.end())
{
return itTeam->second;
}
else
{
nlwarning("CManagerPlayer::getPlayerTeam : no player in team %u", teamId);
return emptySet;
}
}
}
void CManagerPlayer::getTeamIds(std::vector& teamIds)
{
FOREACH(itTeam, TTeamMap, _teams)
{
teamIds.push_back(itTeam->first);
}
}
void CBotPlayer::forgotAggroForAggroer()
{
for (sint32 i=(sint32)_AggroerList.size()-1; i>=0; --i)
{
CAIEntityPhysical* const phys = CAIS::instance().getEntityPhysical(_AggroerList[i]);
if (!phys)
continue;
CBotAggroOwner* aggroOwner = NULL;
switch(phys->getRyzomType())
{
case RYZOMID::creature:
aggroOwner = NLMISC::safe_cast(NLMISC::safe_cast(phys));
break;
case RYZOMID::npc:
aggroOwner = NLMISC::safe_cast(NLMISC::safe_cast(phys));
break;
}
if (!aggroOwner)
continue;
aggroOwner->forgetAggroFor(dataSetRow());
}
}
bool CBotPlayer::useOldUnreachable = false;
NLMISC_COMMAND(playerUseOldUnreachable, "Old unreachable state computing is used","")
{
if(args.size()>1) return false;
if(args.size()==1) StrToBool(CBotPlayer::useOldUnreachable, args[0]);
log.displayNL("playerUseOldUnreachable is %s", CBotPlayer::useOldUnreachable?"true":"false");
return true;
}
sint32 CBotPlayer::getFame(std::string const& faction, bool modulated, bool returnUnknownValue) const
{
sint32 fame = CAIEntityPhysical::getFame(faction, modulated, true);
if (fame==NO_FAME)
{
fame = CStaticFames::getInstance().getStaticFame(_Sheet->Race(), faction);
}
if (!returnUnknownValue && fame==NO_FAME)
fame = 0;
return fame;
}
sint32 CBotPlayer::getFameIndexed(uint32 factionIndex, bool modulated, bool returnUnknownValue) const
{
sint32 fame = CAIEntityPhysical::getFameIndexed(factionIndex, modulated, true);
if (fame==NO_FAME)
{
uint32 playerFaction = CStaticFames::getInstance().getFactionIndex(_Sheet->Race());
fame = CStaticFames::getInstance().getStaticFameIndexed(playerFaction, factionIndex);
}
if (!returnUnknownValue && fame==NO_FAME)
fame = 0;
return fame;
}