// 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
/////////////
#include "stdpch.h"
#include "phrase_manager.h"
#include "phrase_manager_callbacks.h"
#include "combat_phrase.h"
#include "s_phrase_factory.h"
#include "player_manager.h"
#include "character.h"
#include "phrase_utilities_functions.h"
#include "magic_phrase.h"
#include "harvest_phrase.h"
#include "game_share/entity_structure/statistic.h"
/////////////
// USING
/////////////
using namespace std;
using namespace NLMISC;
using namespace NLNET;
using namespace RY_GAME_SHARE;
/////////////
// GLOBALS
/////////////
CPhraseManager *CPhraseManager::_Instance = NULL;
uint8 CEntityPhrases::_QueueMaxSize = 2;
bool FIFOFullReplaceOrDiscard = false;
/////////////
// EXTERN
/////////////
extern CPhraseManager *PhraseManager;
// FIFOFullReplaceOrDiscard
NLMISC_COMMAND(FIFOFullReplaceOrDiscard,"toggle the behaviour when phrase fifo is full( replace old one or discrad new one)","")
{
FIFOFullReplaceOrDiscard = !FIFOFullReplaceOrDiscard;
log.displayNL("FIFOFullReplaceOrDiscard is %s",FIFOFullReplaceOrDiscard?"replace":"discard");
return true;
}
//--------------------------------------------------------------
// CEntityPhrases : clear
//--------------------------------------------------------------
void CEntityPhrases::clear()
{
if (CyclicAction != NULL)
{
delete CyclicAction;
CyclicAction = NULL;
}
const TPhraseList::iterator itEnd = Fifo.end();
for (TPhraseList::iterator it = Fifo.begin() ; it != itEnd ; ++it)
{
if (*it != NULL)
{
delete (*it);
(*it) = NULL;
}
}
Fifo.clear();
CyclicInProgress = false;
DefaultAttackUsed = false;
} // clear //
//--------------------------------------------------------------
// CEntityPhrases::setCyclicAction()
//--------------------------------------------------------------
void CEntityPhrases::setCyclicAction( CSPhrase *phrase)
{
if (!phrase)
return;
// test new sentence validity (if not valid, keep old sentence)
string errorCode;
if (phrase->evaluate(NULL) == false || phrase->validate() == false)
{
DEBUGLOG(" Invalid sentence tested, error code = ");
/*// if error code begins with '(' do not send it !
if ( !errorCode.empty() && errorCode[0] != '(' )
{
// inform player
PHRASE_UTILITIES::sendSimpleMessage( sentence->getPlayerId(), errorCode );
}
*/
// delete the sentence
delete phrase;
return;
}
if ( CyclicAction != NULL)
{
// if in progress, set repeat mode to false and insert this sentence in front of the sentence fifo
if ( CyclicInProgress )
{
Fifo.push_front(CyclicAction);
CyclicInProgress = false;
}
// not in progress, simply delete it
else
{
delete CyclicAction;
}
}
CyclicAction = phrase;
//const CBrick *rootBrick = sentence->getRootBrick();
//if ( rootBrick != NULL && rootBrick->name() == "Default attack")
// DefaultAttackUsed = true;
//else
DefaultAttackUsed = false;
} // setCyclicAction //
//--------------------------------------------------------------
// CEntityPhrases::stopCyclicAction()
//--------------------------------------------------------------
void CEntityPhrases::stopCyclicAction(const TDataSetRow &entityRowId)
{
if ( CyclicAction != NULL)
{
CCharacter *character = PlayerManager.getChar(entityRowId);
// if in progress, set repeat mode to false and insert this sentence in front of the sentence fifo
if ( CyclicInProgress )
{
Fifo.push_front(CyclicAction);
CyclicInProgress = false;
DefaultAttackUsed = false;
}
// not in progress, simply delete it
else
{
delete CyclicAction;
}
CyclicAction = NULL;
if (character)
{
character->writeCycleCounterInDB();
}
}
} // stopCyclicAction //
//--------------------------------------------------------------
// CEntityPhrases::createDefaultAttackIfCombat()
//--------------------------------------------------------------
void CEntityPhrases::createDefaultAttackIfCombat( const TDataSetRow &actingEntityRowId )
{
const CEntityId &actingEntityId = TheDataset.getEntityId(actingEntityRowId);
if (CyclicAction != NULL)
return;
if (DefaultAttackUsed)
return;
// check entity has engaged a combat
TDataSetRow targetRowId = PhraseManager->getEntityEngagedMeleeBy(actingEntityRowId);
if ( !targetRowId.isValid() || !TheDataset.isDataSetRowStillValid(targetRowId) )
{
targetRowId = PhraseManager->getEntityEngagedRangeBy(actingEntityRowId);
}
if ( !targetRowId.isValid() || !TheDataset.isDataSetRowStillValid(targetRowId))
return;
// create new sentence
DEBUGLOG("Create default attack for entity %s", actingEntityId.toString().c_str() );
vector bricks;
bricks.push_back( CSheetId("test_default_attack.sbrick") );
CSPhrase *phrase = PhraseManager->buildSabrinaPhrase( actingEntityRowId, targetRowId, bricks );
if (phrase)
{
CyclicAction = phrase;
}
else
nlwarning(" Failed to create default attack phrase for entity %s",actingEntityId.toString().c_str() );
DefaultAttackUsed = true;
} // createDefaultAttackIfCombat //
//--------------------------------------------------------------
// CEntityPhrases::cancelAllPhrasesButFirstOne()
//--------------------------------------------------------------
void CEntityPhrases::cancelAllPhrasesButFirstOne()
{
if (CyclicInProgress)
{
TPhraseList::iterator it;
for (it = Fifo.begin() ; it != Fifo.end() ; ++it)
{
// delete the sentence
if ( (*it) != NULL )
delete (*it);
}
// clear every sentences
Fifo.clear();
}
else
{
if (CyclicAction)
{
delete CyclicAction;
CyclicAction = NULL;
}
// skip the first sentence if any
TPhraseList::iterator it = Fifo.begin();
TPhraseList::iterator itSec;
if (it != Fifo.end())
itSec = ++it;
else
return;
// clear every other sentences
for ( ; it != Fifo.end() ; ++it)
{
// remove the sentence
if ( (*it) != NULL )
delete (*it);
}
// clear every sentences
Fifo.erase(itSec, Fifo.end());
}
} // CEntityPhrases::cancelAllPhrasesButFirstOne //
//--------------------------------------------------------------
// CEntityPhrases::cancelTopSentence()
//--------------------------------------------------------------
void CEntityPhrases::cancelTopSentence(bool staticOnly)
{
if (CyclicInProgress && CyclicAction && (!staticOnly || CyclicAction->isStatic()) )
{
CyclicAction->stop();
delete CyclicAction;
CyclicAction = NULL;
}
else
{
TPhraseList::iterator it = Fifo.begin();
if (it != Fifo.end())
{
if ( ((*it) != NULL) && (!staticOnly || (*it)->isStatic()))
{
(*it)->stop();
delete (*it);
Fifo.pop_front();
}
}
}
} // CEntityPhrases::cancelTopSentence //
//--------------------------------------------------------------
// CEntityPhrases::cancelTopSentence()
//--------------------------------------------------------------
bool CEntityPhrases::addPhraseFifo( CSPhrase *phrase)
{
if (!phrase)
return false;
// check the fifo queue isn't already at max size
if ( Fifo.size() >= _QueueMaxSize )
{
if ( FIFOFullReplaceOrDiscard == true)
{
if ( (*Fifo.begin()) != 0)
{
DEBUGLOG(" FIFO is full (contains %u elts), delete 1st phrase and push new one",_QueueMaxSize );
delete (*Fifo.begin());
}
else
{
DEBUGLOG(" FIFO is full (contains %u elts), 1st phrase is NULL push new one",_QueueMaxSize );
}
Fifo.pop_front();
}
else
{
DEBUGLOG(" FIFO is full (contains %u elts), do not add new phrase",_QueueMaxSize );
delete phrase;
return false;
}
}
Fifo.push_back(phrase);
return true;
} // CEntityPhrases::cancelTopSentence //
//--------------------------------------------------------------
// CPhraseManager()
//--------------------------------------------------------------
CPhraseManager::CPhraseManager()
{
} // CPhraseManager
//--------------------------------------------------------------
// updatePhrases()
//--------------------------------------------------------------
void CPhraseManager::updatePhrases()
{
string errorString;
// update first sentence in each player sentences Fifo
TMapIdToPhraseStruc::iterator it;
for (it = _Phrases.begin() ; it != _Phrases.end() ; )
{
bool deletePhrase = false; // true if an error occurs
bool executionEnd = false; // true if sentence execution has ended
CSPhrase *phrase = NULL;
CEntityPhrases &entityPhrases = (*it).second;
if ( entityPhrases.CyclicAction == NULL )
{
// entityPhrases.createDefaultAttackIfCombat( (*it).first );
}
if ( entityPhrases.CyclicAction != NULL && (entityPhrases.CyclicInProgress == true || entityPhrases.Fifo.empty()) )
{
phrase = entityPhrases.CyclicAction;
if (entityPhrases.DefaultAttackUsed == true && entityPhrases.CyclicInProgress == false)
{
// send message to client
PHRASE_UTILITIES::sendSimpleMessage( (*it).first, "EGS_START_DEFAULT_ATTACK" );
}
entityPhrases.CyclicInProgress = true;
}
else
{
// get the first sentence if any
if ( ! entityPhrases.Fifo.empty() )
phrase = * entityPhrases.Fifo.begin();
}
if (phrase == NULL)
{
TMapIdToPhraseStruc::iterator itDel = it;
++it;
_Phrases.erase(itDel);
continue;
}
// if the phrase is being executed
if ( phrase->state() == CSPhrase::SecondValidated || phrase->state() == CSPhrase::ExecutionInProgress || phrase->state() == CSPhrase::Latent || phrase->state() == CSPhrase::WaitNextCycle )
{
// if phrase execution time ended, make a second validation test
// if the phrase execution delay time has ended, apply phrase effects
const NLMISC::TGameCycle time = CTickEventHandler::getGameCycle();
if ( phrase->executionEndDate() <= time && phrase->state() == CSPhrase::ExecutionInProgress && phrase->getNbWaitingRequests() == 0 && !phrase->idle())
{
// second validation of phrase if not already done
//nlwarning("Second Validate of phrase for entity %s",(*it).first.toString().c_str() );
if ( ! phrase->validate(/* errorString*/) )
{
// DEBUGLOG(" For entity %s error validating a phrase during second validation, error code = %s",phrase->getPlayerId().toString().c_str(), errorString.c_str() );
// set player behaviour to failed (if he was casting for exemple)
/* PHRASE_UTILITIES::sendUpdateBehaviour( phrase->getPlayerId(), phrase->failureBehaviour() );
// inform the player of the problem
// if error code begins with '(' do not send it !
if ( !errorString.empty() && errorString[0] != '(' )
{
PHRASE_UTILITIES::sendSimpleMessage( phrase->getPlayerId(), errorString );
}
*/
deletePhrase = true;
}
}
if ( !deletePhrase)
{
if ( ! phrase->update(/*errorString*/) )
{
/* DEBUGLOG(" For entity %s, error updating a phrase, error code = %s", phrase->getPlayerId().toString().c_str(),errorString.c_str() );
// inform the player of the problem
// if error code begins with '(' do not send it !
if ( !errorString.empty() && errorString[0] != '(' )
{
PHRASE_UTILITIES::sendSimpleMessage( phrase->getPlayerId(), errorString );
}
*/
phrase->stop();
deletePhrase = true;
}
// test the end of the execution
//else if ( phrase->executionHasEnded() )
else if ( phrase->state() == CSPhrase::LatencyEnded )
{
executionEnd = true;
}
}
}
else if ( phrase->idle() )
{
// phrase is idle, update and execute if no longer idle
if ( ! phrase->update(/*errorString*/) )
{
/* DEBUGLOG(" For entity %s, error updating a phrase, error code = %s", phrase->getPlayerId().toString().c_str(),errorString.c_str() );
// inform the player of the problem
// if error code begins with '(' do not send it !
if ( !errorString.empty() && errorString[0] != '(' )
{
PHRASE_UTILITIES::sendSimpleMessage( phrase->getPlayerId(), errorString );
}
*/
phrase->stop();
deletePhrase = true;
}
if (!phrase->idle() && phrase->state() == CSPhrase::Validated )
{
phrase->execute();
// if entity is a player, update DB
CCharacter *character = PlayerManager.getChar((*it).first);
if (character)
{
character->writeExecPhraseInDB(phrase->phraseBookIndex(), phrase->nextCounter());
}
}
}
// the phrase is updated for the first time validate and execute it
else
{
// if phrase has never been evaluated, evaluate it
if ( phrase->state() == CSPhrase::New )
{
bool eval;
/* if (phrase->getId() != PhraseNullId)
{
CEvalReturnInfos infos;
eval = phrase->evaluate(&infos);
CCharacter *player = PlayerManager.getChar( phrase->getPlayerId() );
if (player != NULL)
{
player->sentenceEvaluationResult( infos.Valid, infos.Appraisal, infos.Cost, phrase->sentenceId().first, phrase->sentenceId().second);
}
}
else
*/
eval = phrase->evaluate();
if (!eval)
{
//nlwarning("For player %s", phrase->getPlayerId().toString().c_str() );
nlwarning(" Invalid phrase tested - should NEVER happens !!!!!!!!");
deletePhrase = true;
}
}
// validate phrase
if ( ! phrase->validate(/*errorString*/) )
{
/* DEBUGLOG(" For entity %s error validating a phrase, error code = %s",phrase->getPlayerId().toString().c_str(), errorString.c_str() );
// inform the player of the problem
// if error code begins with '(' do not send it !
if ( !errorString.empty() && errorString[0] != '(' )
{
PHRASE_UTILITIES::sendSimpleMessage( phrase->getPlayerId(), errorString );
}
*/
deletePhrase = true;
}
//if sentense isn't idle, execute it
else if ( ! phrase->idle() )
{
// determine success
// phrase->determineSuccess( NULL );
// test dodge and resists
// if (phrase->success())
// phrase->testDodgeAndResist( NULL );
// if all target have not dodge, we consider the phrase as a success for the mission system
/// TODO : there could be a better way to determine mission success
/* CCharacter *player = PlayerManager.getChar( phrase->getPlayerId() );
if (player != NULL)
{
list::const_iterator itEl = phrase->getElements().begin();
for (;itEl != phrase->getElements().end(); ++itEl)
{
const CBrick *brick = dynamic_cast (*itEl);
if (brick != NULL)
{
if ( !phrase->targetDodgeFlags(0) )
{
CMissionEventUseBrick event( phrase->getPlayerId(),phrase->getTarget(),brick->getSheetId(),brick->getCareer(),brick->getJob() );
player->processMissionEvent(event);
}
const std::vector targets = phrase->getSecondaryTargets();
for ( uint i = 0; i < phrase->getSecondaryTargets().size(); ++i )
{
if ( !phrase->targetDodgeFlags(i+1) == 0)
{
CMissionEventUseBrick event( phrase->getPlayerId(),phrase->getSecondaryTargets()[i],brick->getSheetId(),brick->getCareer(),brick->getJob() );
player->processMissionEvent(event);
}
}
}
}
}
*/ // execute phrase
phrase->execute();
// if entity is a player, update DB
CCharacter *character = PlayerManager.getChar((*it).first);
if (character)
{
character->writeExecPhraseInDB(phrase->phraseBookIndex(), phrase->nextCounter());
}
}
}
if (deletePhrase == true)
{
/*CCharacter *character = PlayerManager.getChar((*it).first);
if (character)
{
character->writeExecPhraseInDB(0, phrase->nextCounter());
}
*/
phrase->stop();
if (entityPhrases.CyclicInProgress == true)
{
entityPhrases.CyclicInProgress = false;
delete entityPhrases.CyclicAction;
entityPhrases.CyclicAction = NULL;
// if the deleted phrase wasn't already the default attack, create a default attack as the cyclic phrase
if ( !entityPhrases.DefaultAttackUsed)
{
// entityPhrases.createDefaultAttackIfCombat( (*it).first );
}
// if the deleted phrase was the default attack -> disengage from combat
else
{
//DEBUGLOG("Disengage entity %s as the default attack validation has failed", ((*it).first).toString().c_str() );
disengage((*it).first, true, true);
}
}
else
{
delete phrase;
entityPhrases.Fifo.pop_front();
}
}
else if(executionEnd == true)
{
// if entity is a player, update DB
/* CCharacter *character = PlayerManager.getChar((*it).first);
if (character)
{
character->writeExecPhraseInDB(0, phrase->nextCounter());
}
*/
if (entityPhrases.CyclicInProgress == true )
{
entityPhrases.CyclicAction->evaluate();
if ( !entityPhrases.Fifo.empty() )
entityPhrases.CyclicInProgress = false;
}
else
{
delete phrase;
entityPhrases.Fifo.pop_front();
}
}
// get next entity sentences
++it;
}
// send all the waiting event reports
sendEventReports();
//
sendAIEvents();
} // updatePhrases //
//-----------------------------------------------
// sendEventReports()
//-----------------------------------------------
void CPhraseManager::sendEventReports()
{
if ( _EventReports.empty() && _AIEventReports.empty())
return;
if ( !_RegisteredServices.empty() )
{
// send to registered services
CMessage msgReport("EVENT_REPORTS");
msgReport.serialCont(_EventReports);
set::iterator it;
for (it = _RegisteredServices.begin() ; it != _RegisteredServices.end() ; ++it)
{
sendMessageViaMirror (*it, msgReport);
//
INFOLOG("Send EVENT_REPORTS to service %s", (*it).c_str() );
}
}
if ( !_AIEventReports.empty() && !_AIRegisteredServices.empty() )
{
// send to registered services for AI
CBSAIEventReportMsg msgAI;
const uint nbAiReports = _AIEventReports.size();
for (uint i = 0 ; i < nbAiReports ; ++i )
{
msgAI.pushBack( _AIEventReports[i] );
}
set::iterator it;
for (it = _AIRegisteredServices.begin() ; it != _AIRegisteredServices.end() ; ++it)
{
msgAI.send (*it );
INFOLOG("Send EVENT_REPORTS to AI service %s", (*it).c_str() );
}
}
_EventReports.clear();
_AIEventReports.clear();
// _AggroWeights.clear();
} // sendEventReports //
//-----------------------------------------------
// sendAIEvents()
//-----------------------------------------------
void CPhraseManager::sendAIEvents()
{
if (_AIEvents.empty())
return;
// send to registered services
CMessage msgai("AI_EVENTS");
uint16 size = _AIEvents.size();
msgai.serial( size );
TAIEventList::iterator it;
const TAIEventList::iterator itEnd = _AIEvents.end();
for (it = _AIEvents.begin() ; it != itEnd ; ++it)
{
msgai.serial( *(*it) );
delete (*it);
*it = NULL;
}
_AIEvents.clear();
set::iterator itservice;
for (itservice = _AIRegisteredServices.begin() ; itservice != _AIRegisteredServices.end() ; ++itservice)
{
sendMessageViaMirror (*itservice, msgai);
INFOLOG("Send AI_EVENTS to AI service %s", (*itservice).c_str() );
}
} // sendAIEvents //
//-----------------------------------------------
// executePhrase()
//-----------------------------------------------
void CPhraseManager::executePhrase( const TDataSetRow &actorRowId, const TDataSetRow &targetRowId, const std::vector &brickIds, bool cyclic, uint16 phraseId, uint8 nextCounter )
{
CSPhrase *phrase = buildSabrinaPhrase(actorRowId, targetRowId, brickIds, phraseId, nextCounter);
if (!phrase)
{
nlwarning(" Failed to build sabrina phrase for actor %u", actorRowId.getIndex());
return;
}
phrase->setPrimaryTarget( targetRowId );
TMapIdToPhraseStruc::iterator it = _Phrases.find( actorRowId );
// actor doesn't already have phrases
if (it == _Phrases.end() )
{
// new entry
CEntityPhrases entityPhrases;
if (cyclic)
{
entityPhrases.setCyclicAction(phrase);
entityPhrases.CyclicInProgress = true;
}
else
{
entityPhrases.addPhraseFifo(phrase);
}
_Phrases.insert( make_pair(actorRowId, entityPhrases) );
}
// actor already have phrases in the manager, just add the new one
else
{
CEntityPhrases &entityPhrases = (*it).second;
if (cyclic)
{
entityPhrases.setCyclicAction(phrase);
}
else
{
entityPhrases.addPhraseFifo(phrase);
}
}
} // executePhrase //
//-----------------------------------------------
// init()
//-----------------------------------------------
void CPhraseManager::init()
{
addCallbacks();
PHRASE_UTILITIES::loadLocalisationTable( CPath::lookup("localisation.localisation_table" ) );
} // init //
//-----------------------------------------------
// addCallbacks()
//-----------------------------------------------
void CPhraseManager::addCallbacks()
{
static bool added = false;
if (added)
return;
added = true;
//array of callback items
NLNET::TUnifiedCallbackItem _cbArray[] =
{
{ "REGISTER_EVENT_REPORTS", cbRegisterService },
{ "REGISTER_AI_EVENT_REPORTS", cbRegisterServiceAI },
{ "UNREGISTER_EVENT_REPORTS", cbUnregisterService },
{ "UNREGISTER_AI_EVENT_REPORTS", cbUnregisterServiceAI },
{ "DISENGAGE_NOTIFICATION", cbDisengageNotification },
{ "DISENGAGE", cbDisengage },
};
CUnifiedNetwork::getInstance()->addCallbackArray( _cbArray, sizeof(_cbArray) / sizeof(_cbArray[0]) );
} // addCallbacks //
//-----------------------------------------------
// clearMeleeEngagedEntities()
//-----------------------------------------------
void CPhraseManager::clearMeleeEngagedEntities()
{
TRowRowMap::const_iterator it;
const TRowRowMap::const_iterator itEnd = _MapEntityToEngagedEntityInMeleeCombat.end();
for (it = _MapEntityToEngagedEntityInMeleeCombat.begin() ; it != itEnd ; ++it)
{
// cancel all combat sentences for that entity
cancelAllCombatSentences( (*it).first, false );
}
_MapEntityToMeleeAggressors.clear();
_MapEntityToEngagedEntityInMeleeCombat.clear();
} // clearMeleeEngagedEntities //
//-----------------------------------------------
// checkPhraseValidity()
//-----------------------------------------------
bool CPhraseManager::checkPhraseValidity( const std::vector &brickIds ) const
{
/*
vector::const_iterator it;
vector::const_iterator itEnd = brickIds.end();
set mandatoryFamilies;
set allowedFamilies;
set forbiddenFamilies;
set foundFamilies;
sint16 totalCost = 0;
for (it = brickIds.begin() ; it != itEnd ; ++it)
{
const CStaticBrick *brick = CSheets::getSBrickForm(*it);
if (!brick)
{
nlwarning(" Cannot find brick object for brick sheet %s", (*it).toString().c_str() );
return false;
}
mandatoryFamilies.insert( brick->MandatoryFamilies.begin(), brick->MandatoryFamilies.end() );
allowedFamilies.insert( brick->OptionalFamilies.begin(), brick->OptionalFamilies.end() );
allowedFamilies.insert( brick->CreditFamilies.begin(), brick->CreditFamilies.end() );
forbiddenFamilies.insert( brick->ForbiddenFamilies.begin(), brick->ForbiddenFamilies.end() );
if ( foundFamilies.insert( brick->Family ).second == false)
{
nlwarning(" The family %s were already found in phrase, error", BRICK_FAMILIES::toString(brick->Family).c_str() );
return false;
}
totalCost += brick->SabrinaValue;
}
// check cost
if (totalCost > 0)
{
DEBUGLOG(" Creadit must be > to cost for a phrase to be valid, cancel return false");
return false;
}
// check all mandatory are present
set::const_iterator itb;
set::const_iterator itbEnd = mandatoryFamilies.end();
for (itb = mandatoryFamilies.begin() ; itb != itbEnd ; ++itb)
{
set::iterator itbf = foundFamilies.find(*itb);
if ( itbf == foundFamilies.end() )
{
DEBUGLOG(" The family %s is mandatory but isn't in the phrase, cancel return false", BRICK_FAMILIES::toString(*itb).c_str() );
return false;
}
else
foundFamilies.erase(itbf);
}
// check no forbidden is present
itbEnd = forbiddenFamilies.end();
for (itb = forbiddenFamilies.begin() ; itb != itbEnd ; ++itb)
{
if (foundFamilies.find(*itb) != foundFamilies.end() )
{
DEBUGLOG(" The family %s is forbidden but is in the phrase, cancel return false", BRICK_FAMILIES::toString(*itb).c_str() );
return false;
}
}
// check all the other families are allowed
itbEnd = foundFamilies.end();
for (itb = foundFamilies.begin() ; itb != itbEnd ; ++itb)
{
if (allowedFamilies.find(*itb) == allowedFamilies.end() )
{
DEBUGLOG(" The family %s isn't a valid optional or creadit one, cancel return false", BRICK_FAMILIES::toString(*itb).c_str() );
return false;
}
}
*/
return true;
} // checkPhraseValidity //
//-----------------------------------------------
// checkPhraseValidity()
//-----------------------------------------------
CSPhrase *CPhraseManager::buildSabrinaPhrase( const TDataSetRow &actorRowId, const TDataSetRow &targetRowId, const vector &brickIds, uint16 phraseId, uint8 nextCounter )
{
if (brickIds.empty())
return NULL;
if (!checkPhraseValidity(brickIds))
return NULL;
CSPhrase *phrase = ISPhraseFactory::buildPhrase(actorRowId,brickIds);
if (!phrase)
{
nlwarning(" For entity %u, factory returns a NULL pointer instead of phrase!. First brick is %s", actorRowId.getIndex(),brickIds[0].toString().c_str());
return NULL;
}
phrase->nextCounter(nextCounter);
phrase->phraseBookIndex(phraseId);
return phrase;
} // buildSabrinaPhrase //
//-----------------------------------------------
// defaultAttackSabrina()
//-----------------------------------------------
void CPhraseManager::defaultAttackSabrina( const TDataSetRow &attackerRowId, const TDataSetRow &targetRowId )
{
CEntityId attackerId = TheDataset.getEntityId(attackerRowId);
CEntityId targetId = TheDataset.getEntityId(targetRowId);
DEBUGLOG(" entity %d attacks entity %d", attackerId.toString().c_str(), targetId.toString().c_str() );
CEntityBase* entity = CEntityBaseManager::getEntityBasePtr( attackerId );
if (entity == NULL)
{
nlwarning(" Invalid entity Id %s", attackerId.toString().c_str() );
return;
}
// cancel entity static action
if ( entity->getId().getType() == RYZOMID::player )
{
( (CCharacter*)entity)->cancelStaticActionInProgress();
}
entity->cancelStaticEffects();
// check if attacker is already in combat
CEntityId id ;// = getEntityEngagedMeleeBy( attackerId );
if (id == CEntityId::Unknown )
{
//id = getEntityEngagedRangeBy( attackerId );
}
if (id != CEntityId::Unknown )
{
TMapIdToPhraseStruc::iterator it = _Phrases.find( attackerRowId );
if (it == _Phrases.end() )
{
// error should not happens
nlwarning(" ERROR Cannot find entity sentences for entity Id %s but entity is engaged !, should never occurs (serious)", attackerId.toString().c_str() );
return;
}
CEntityPhrases &entityPhrases = (*it).second;
// check if the engaged target and the new one are different
if (id != targetId)
{
// close first combat, program the action for disengage on end //
entityPhrases.cancelAllPhrasesButFirstOne();
if (entityPhrases.CyclicInProgress)
{
// entityPhrases.CyclicAction->disengageOnEnd(true);
}
else
{
TPhraseList::iterator it = entityPhrases.Fifo.begin();
if (it != entityPhrases.Fifo.end() && (*it) != NULL);
{
// (*it)->disengageOnEnd(true);
}
}
}
vector bricks;
bricks.push_back( CSheetId("test_default_attack.sbrick") );
CSPhrase *phrase = CPhraseManager::buildSabrinaPhrase( attackerRowId, targetRowId, bricks );
if (phrase)
{
(*it).second.setCyclicAction( phrase );
}
else
{
nlwarning(" ERROR while creating default attack sentence for entity Id %s", attackerId.toString().c_str() );
return;
}
}
else
{
// attacks
vector bricks;
bricks.push_back( CSheetId("test_default_attack.sbrick") );
//executePhrase(attackerId, targetId, bricks);
CSPhrase *phrase = CPhraseManager::buildSabrinaPhrase( attackerRowId, targetRowId, bricks );
if (!phrase)
{
nlwarning("Error when creating default sabrina attack, cancel");
return;
}
TMapIdToPhraseStruc::iterator it = _Phrases.find( attackerRowId );
if (it == _Phrases.end() )
{
// new entry
CEntityPhrases entityPhrases;
entityPhrases.setCyclicAction(phrase);
entityPhrases.CyclicInProgress = true;
_Phrases.insert( make_pair(attackerRowId, entityPhrases) );
}
else
{
CEntityPhrases &entityPhrases = (*it).second;
entityPhrases.setCyclicAction(phrase);
}
}
} // defaultAttackSabrina //
//--------------------------------------------------------------
// removeEntity()
//
// Precondition: the entity is in mirror
//--------------------------------------------------------------
void CPhraseManager::removeEntity( const TDataSetRow &entityRowId)
{
// disengage entity if he was engaged in combat
disengage( entityRowId, false, true );
//disengage all the entities which were in melee combat with the removed entity
TRowSetRowMap::iterator it = _MapEntityToMeleeAggressors.find( entityRowId );
if (it != _MapEntityToMeleeAggressors.end() )
{
set ids = (*it).second;
set::iterator itId;
for ( itId = ids.begin() ; itId != ids.end() ; ++itId )
{
if (cancelAllCombatSentences( *itId, true))
disengage( *itId, false, true);
}
//_MapEntityToMeleeAggressors.erase( it ); // automatically done by disengaging all aggressors
}
//disengage all the entities which were in range combat with the removed entity
it = _MapEntityToRangeAggressors.find( entityRowId );
if (it != _MapEntityToRangeAggressors.end() )
{
set ids = (*it).second;
set::iterator itId;
for ( itId = ids.begin() ; itId != ids.end() ; ++itId )
{
if (cancelAllCombatSentences( *itId, true))
disengage( *itId, false, true );
}
//_MapEntityToRangeAggressors.erase( it ); // automatically done by disengaging all aggressors
}
// find the entity phrases execution list if any
TMapIdToPhraseStruc::iterator itEntityPhrase = _Phrases.find( entityRowId );
if ( itEntityPhrase != _Phrases.end() )
{
// remove the entry
(*itEntityPhrase).second.clear();
_Phrases.erase( itEntityPhrase );
INFOLOG(" Removed entity (row %u)", entityRowId.getIndex() );
}
} // removeEntity //
//-----------------------------------------------
// engageMelee()
//-----------------------------------------------
void CPhraseManager::engageMelee( const TDataSetRow &entity1, const TDataSetRow &entity2 )
{
//disengage from precedent combat if any, without deleting combat phrase
disengage(entity1, true, true, false);
_MapEntityToInitiatedCombat.insert( make_pair(entity1, CCombat(entity1, entity2, true) ) );
_MapEntityToEngagedEntityInMeleeCombat.insert( make_pair(entity1,entity2) );
TRowSetRowMap::iterator it = _MapEntityToMeleeAggressors.find( entity2 );
// add the aggressor to target aggressors
CEntityBase* targetEntity = PHRASE_UTILITIES::entityPtrFromId( entity2 );
if ( targetEntity )
{
// targetEntity->addAgressor( entity1 );
}
if (it != _MapEntityToMeleeAggressors.end() )
{
(*it).second.insert( entity1 );
}
else
{
set agg;
agg.insert( entity1 );
_MapEntityToMeleeAggressors.insert( make_pair( entity2, agg) );
}
// check mode if player
if (TheDataset.getEntityId(entity1).getType() == RYZOMID::player)
{
CCharacter *character = PlayerManager.getChar(entity1);
if ( character && character->getMode() != MBEHAV::COMBAT )
character->setMode(MBEHAV::COMBAT);
}
// send message to clients to indicate the new combat
PHRASE_UTILITIES::sendEngageMessages( TheDataset.getEntityId(entity1), TheDataset.getEntityId(entity2) );
} // engageMelee //
//-----------------------------------------------
// engageMelee()
//-----------------------------------------------
void CPhraseManager::engageRange( const TDataSetRow &entity1, const TDataSetRow &entity2 )
{
//disengage from precedent combat if any
disengage(entity1, true, true, false);
_MapEntityToInitiatedCombat.insert( make_pair(entity1, CCombat(entity1, entity2, false) ) );
_MapEntityToEngagedEntityInRangeCombat.insert( make_pair(entity1,entity2) );
// add the aggressor to target aggressors
CEntityBase* targetEntity = PHRASE_UTILITIES::entityPtrFromId( entity2 );
if ( targetEntity )
{
// targetEntity->addAgressor( entity1 );
}
TRowSetRowMap::iterator it = _MapEntityToRangeAggressors.find( entity2 );
if (it != _MapEntityToRangeAggressors.end() )
{
(*it).second.insert( entity1 );
}
else
{
set agg;
agg.insert( entity1 );
_MapEntityToRangeAggressors.insert( make_pair( entity2, agg) );
}
// change entity mode for COMBAT
CEntityBase* entity = PHRASE_UTILITIES::entityPtrFromId( entity1 );
if (entity == NULL)
{
nlwarning(" Invalid entity rowId %u", entity1.getIndex() );
return;
}
// check mode if player
if (TheDataset.getEntityId(entity1).getType() == RYZOMID::player)
{
CCharacter *character = PlayerManager.getChar(entity1);
if ( character && character->getMode() != MBEHAV::COMBAT )
character->setMode(MBEHAV::COMBAT);
}
//entity->setMode( MBEHAV::COMBAT );
// send message to clients to indicate the new combat
PHRASE_UTILITIES::sendEngageMessages( TheDataset.getEntityId(entity1), TheDataset.getEntityId(entity2) );
} // engageRange //
//-----------------------------------------------
// disengage()
//-----------------------------------------------
void CPhraseManager::disengage( const TDataSetRow &entityRowId, bool sendChatMsg, bool disengageCreature, bool cancelCombatSentence)
{
CEntityId entityId = TheDataset.getEntityId(entityRowId);
// only disengage players unless specified
if (entityId.getType() != RYZOMID::player && !disengageCreature )
{
nlwarning(" Tried to disengage bot %s, cancel",entityId.toString().c_str() );
return;
}
//CEntityId entityTarget;
TDataSetRow entityTargetRowId;
CEntityBase* entityPtr = PHRASE_UTILITIES::entityPtrFromId( entityId );
if (!entityPtr)
{
//nlwarning (" WARNING invalid entityId %s",entityId.toString().c_str() );
return;
}
// if player and in mode combat, change mode to normal
if (entityId.getType() == RYZOMID::player && entityPtr->getMode() == MBEHAV::COMBAT)
{
entityPtr->setMode( MBEHAV::NORMAL, false, false );
}
_MapEntityToInitiatedCombat.erase(entityRowId);
TRowRowMap::iterator it = _MapEntityToEngagedEntityInMeleeCombat.find( entityRowId );
// was in melee combat
if (it != _MapEntityToEngagedEntityInMeleeCombat.end() )
{
entityTargetRowId = (*it).second;
_MapEntityToEngagedEntityInMeleeCombat.erase( entityRowId );
DEBUGLOG(" Disengage entity Id %s from MELEE combat", entityId.toString().c_str() );
// remove this entity from the aggressors of its previous target entity
TRowSetRowMap::iterator itAgg = _MapEntityToMeleeAggressors.find( entityTargetRowId );
if (itAgg != _MapEntityToMeleeAggressors.end() )
{
(*itAgg).second.erase( entityRowId );
// if last aggressor, remove entry
if ((*itAgg).second.empty() )
{
_MapEntityToMeleeAggressors.erase( itAgg );
}
}
else
nlwarning(" Error in _MapEntityToMeleeAggressors, should have found aggressor for entity %s",TheDataset.getEntityId(entityTargetRowId).toString().c_str() );
// remove the aggressor from target aggressors
CEntityBase* targetEntity = PHRASE_UTILITIES::entityPtrFromId( entityTargetRowId );
if ( targetEntity )
{
//targetEntity->removeAgressor( entityId );
}
}
// was in range combat
else
{
it = _MapEntityToEngagedEntityInRangeCombat.find( entityRowId );
if (it != _MapEntityToEngagedEntityInRangeCombat.end() )
{
entityTargetRowId = (*it).second;
_MapEntityToEngagedEntityInRangeCombat.erase( entityRowId );
DEBUGLOG(" Disengage entity Id %s from RANGE combat", entityId.toString().c_str() );
CEntityBase* entity = PHRASE_UTILITIES::entityPtrFromId( entityRowId );
if (entity == NULL)
{
nlwarning(" Invalid entity Id %s", entityId.toString().c_str() );
}
else
{
// change entity mode for Normal mode
entity->setMode( MBEHAV::NORMAL, true );
}
}
else
return; // not engaged in combat
// remove this entity from the aggressors of its previous target entity
TRowSetRowMap::iterator itAgg = _MapEntityToRangeAggressors.find( entityTargetRowId );
if (itAgg != _MapEntityToRangeAggressors.end() )
{
(*itAgg).second.erase( entityRowId );
// if last aggressor, remove entry
if ((*itAgg).second.empty() )
_MapEntityToRangeAggressors.erase( itAgg );
}
else
nlwarning(" Error in _MapEntityToRangeAggressors, should have found aggressor for entity %s",TheDataset.getEntityId(entityTargetRowId).toString().c_str() );
// remove the aggressor from target aggressors
CEntityBase* targetEntity = PHRASE_UTILITIES::entityPtrFromId( entityTargetRowId );
if ( targetEntity )
{
// targetEntity->removeAgressor( entityId );
}
}
INFOLOG(" Disengaging entity %s, was in combat with %s", entityId.toString().c_str(), TheDataset.getEntityId(entityTargetRowId).toString().c_str());
if (cancelCombatSentence)
{
// cancel all combat sentences for that entity
cancelAllCombatSentences( entityRowId, false);
}
// send message to players
if (sendChatMsg)
PHRASE_UTILITIES::sendDisengageMessages( entityId, TheDataset.getEntityId(entityTargetRowId));
} // disengage //
//--------------------------------------------------------------
// cancelAllCombatSentences()
//--------------------------------------------------------------
bool CPhraseManager::cancelAllCombatSentences( const TDataSetRow &entityRowId, bool disengageOnEndOnly)
{
bool returnValue = true;
// find the player execution list if any
TMapIdToPhraseStruc::iterator itEntityPhrase = _Phrases.find( entityRowId );
if ( itEntityPhrase != _Phrases.end() )
{
CEntityPhrases &entityPhrases = (*itEntityPhrase).second;
// manage cyclic sentence
if ( entityPhrases.CyclicAction != NULL /*&& entityPhrases.CyclicAction->getType() == BRICK_TYPE::COMBAT*/)
{
//if (entityPhrases.CyclicInProgress)
{
entityPhrases.stopCyclicAction(entityRowId);
}
/*else
{
entityPhrases.CyclicAction->stop();
delete entityPhrases.CyclicAction;
entityPhrases.CyclicAction = NULL;
entityPhrases.CyclicInProgress = false;
// if entity is a player, write the cyclic counter in DB
CCharacter *character = PlayerManager.getChar(entityRowId);
if (character)
{
character-writeCycleCounterInDB();
}
}
*/
}
// non-cyclic sentence
vector delVect;
// get the first sentence if any
TPhraseList &phrases = entityPhrases.Fifo;
TPhraseList::iterator it = phrases.begin();
if (it != phrases.end() /*&& (*it)->getType() == BRICK_TYPE::COMBAT*/)
{
CCombatPhrase *combatPhrase = dynamic_cast (*it);
if (combatPhrase != 0)
{
// if the sentence is currently executed, do nothing, else, erase it like all the others
if ( combatPhrase->beingProcessed() == false && !combatPhrase->disengageOnEnd() )
{
combatPhrase->stop();
// remove the sentence
delVect.push_back(it);
if ( combatPhrase != NULL )
delete combatPhrase;
}
else if (disengageOnEndOnly)
{
combatPhrase->disengageOnEnd( true );
returnValue = false;
}
if ( it == phrases.begin() )
{
CCharacter *character = PlayerManager.getChar(entityRowId);
if (character)
{
character->writeExecPhraseInDB(0, combatPhrase->nextCounter());
}
}
++it;
}
}
for ( ; it != phrases.end() ; ++it)
{
if ( dynamic_cast (*it) != 0)
{
// remove the sentence
delVect.push_back(it);
if ( (*it) != NULL )
delete (*it);
}
}
// clear every sentences
vector::iterator itdel;
for ( itdel = delVect.begin() ; itdel != delVect.end() ; ++itdel)
{
phrases.erase( *itdel );
}
}
// CODE
/////////------------------------------------- TEMP PATCH
// find the entity phrases execution list if any
/*TMapIdToPhraseStruc::iterator itEntityPhrase = _Phrases.find( entityRowId );
if ( itEntityPhrase != _Phrases.end() )
{
// remove the entry
(*itEntityPhrase).second.clear();
_Phrases.erase( itEntityPhrase );
}
/////////------------------------------------- TEMP PATCH
*/
return returnValue;
} // cancelAllCombatSentences //
//--------------------------------------------------------------
// breakCast()
//--------------------------------------------------------------
void CPhraseManager::breakCast( sint32 attackSkillValue, CEntityBase * entity, CEntityBase * defender)
{
nlassert(entity);
nlassert(defender);
// try to get a magic phrase being cast (it the phrase at the bginning of the queue
TMapIdToPhraseStruc::iterator it = _Phrases.find( defender->getEntityRowId() );
if ( it != _Phrases.end() )
{
if ( (*it).second.Fifo.begin() != (*it).second.Fifo.end() )
{
CMagicPhrase * magicPhrase = dynamic_cast< CMagicPhrase * > ( *( (*it).second.Fifo.begin() ) );
if ( magicPhrase )
{
// compute average skill value of the phrase
sint skillValue = 0;
for ( uint i = 0; i < magicPhrase->getSkills().size(); i++ )
{
SSkill * skill = entity->getSkills().getSkillStruct( magicPhrase->getSkills()[i] );
if ( skill )
{
skillValue+= skill->Current;
}
else
{
nlwarning(" invalid skill %d",magicPhrase->getSkills()[i] );
return;
}
}
//test if the spell is broken
const uint8 chances = PHRASE_UTILITIES::getSuccessChance( (attackSkillValue - skillValue - magicPhrase->getBreakResist() )/10 );
const uint8 roll = (uint8) RandomGenerator.rand(99);
float successFactor = PHRASE_UTILITIES::getSucessFactor(chances, roll);
if ( successFactor >= 1 )
(*it).second.cancelTopSentence();
}
}
}
}// breakCast
//--------------------------------------------------------------
// breakCast()
//--------------------------------------------------------------
bool CPhraseManager::harvestDefault(const TDataSetRow &actorRowId, const CSheetId &rawMaterialSheet, uint16 minQuality, uint16 maxQuality, uint16 quantity, bool deposit )
{
vector bricks;
const CSheetId quarteringBrick("root_harvest_default.sbrick");
const CSheetId foragingBrick("root_harvest_default.sbrick");
if (quarteringBrick == CSheetId::Unknown)
{
nlwarning("ERROR : cannot find quartering brick : root_harvest_default.sbrick.");
return false;
}
if (foragingBrick == CSheetId::Unknown)
{
nlwarning("ERROR : cannot find foraging brick : root_harvest_default.sbrick.");
return false;
}
if (deposit)
bricks.push_back( foragingBrick );
else
bricks.push_back( quarteringBrick );
TDataSetRow nullId;
CSPhrase *phrase = buildSabrinaPhrase(actorRowId, nullId, bricks);
CHarvestPhrase *harvestPhrase = dynamic_cast (phrase);
if (!phrase)
{
return false;
}
if (!harvestPhrase)
{
delete phrase;
return false;
}
harvestPhrase->minQuality(minQuality);
harvestPhrase->maxQuality(maxQuality);
harvestPhrase->quantity(quantity);
harvestPhrase->setRawMaterial(rawMaterialSheet);
harvestPhrase->deposit(deposit);
TMapIdToPhraseStruc::iterator it = _Phrases.find( actorRowId );
// actor doesn't already have phrases
if (it == _Phrases.end() )
{
// new entry
CEntityPhrases entityPhrases;
entityPhrases.addPhraseFifo(phrase);
_Phrases.insert( make_pair(actorRowId, entityPhrases) );
}
// actor already have phrases in the manager, just add the new one
else
{
CEntityPhrases &entityPhrases = (*it).second;
entityPhrases.addPhraseFifo(phrase);
}
return true;
} // harvestDefault //
//--------------------------------------------------------------
// cancelStaticActionInProgress()
//--------------------------------------------------------------
void CPhraseManager::cancelStaticActionInProgress(const TDataSetRow &actorRowId)
{
TMapIdToPhraseStruc::iterator it = _Phrases.find( actorRowId );
if (it != _Phrases.end() )
{
(*it).second.cancelTopSentence(true);
}
} // cancelStaticActionInProgress //