// 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 "magic_phrase.h"
#include "game_share/brick_families.h"
#include "s_phrase_factory.h"
#include "entity_manager.h"
#include "phrase_manager.h"
#include "player_manager.h"
#include "game_share/entity_structure/statistic.h"
#include "s_effect.h"
#include "character.h"
#include "phrase_utilities_functions.h"
//////////////
// USING //
//////////////
using namespace RY_GAME_SHARE;
using namespace std;
using namespace NLMISC;
//////////////
// EXTERN //
//////////////
extern NLMISC::CRandom RandomGenerator;
extern CPlayerManager PlayerManager;
DEFAULT_SPHRASE_FACTORY( CMagicPhrase, BRICK_TYPE::MAGIC );
////////////
// STATIC //
////////////
float CMagicPhrase::_DefaultCastingTime = 1.0f;
//-----------------------------------------------
// CMagicPhrase dtor
//-----------------------------------------------
CMagicPhrase::~CMagicPhrase()
{
for (uint i = 0; i < _Actions.size(); i++ )
{
delete _Actions[i];
}
}
//-----------------------------------------------
// CMagicPhrase applyBrickParam
//-----------------------------------------------
void CMagicPhrase::applyBrickParam( TBrickParam::IId * param )
{
nlassert(param);
switch(param->id())
{
case TBrickParam::MA_CASTING_TIME:
INFOLOG("MA_CASTING_TIME: %f",((CSBrickParamCastingTime *)param)->CastingTime);
_CastingTime += NLMISC::TGameCycle( ((CSBrickParamCastingTime *)param)->CastingTime / CTickEventHandler::getGameTimeStep() );
break;
case TBrickParam::ChaScore1:
INFOLOG("ChaScore1: %i",((CSBrickParamChaScore1 *)param)->ChaScore1);
_ChaScore1Cost += ((CSBrickParamChaScore1 *)param)->ChaScore1;
break;
case TBrickParam::ChaScore3:
INFOLOG("ChaScore3: %i",((CSBrickParamChaScore3 *)param)->ChaScore3);
_ChaScore3 += ((CSBrickParamChaScore3 *)param)->ChaScore3;
break;
case TBrickParam::MA_RANGES:
INFOLOG("MA_RANGES: %u",((CSBrickParamMagicRanges *)param)->RangeIndex);
_RangeIndex += ((CSBrickParamMagicRanges *)param)->RangeIndex;
break;
case TBrickParam::MA_BREAK_RES:
INFOLOG("MA_BREAK_RES: %u",((CSBrickParamMagicBreakResist *)param)->BreakResist);
_BreakResist = ((CSBrickParamMagicBreakResist*)param)->BreakResist;
break;
case TBrickParam::MA_ARMOR_COMP:
INFOLOG("MA_ARMOR_COMP: %u",((CSBrickParamMagicArmorComp *)param)->ArmorComp);
_ArmorCompensation = ((CSBrickParamMagicArmorComp*)param)->ArmorComp;
break;
}
}// CMagicPhrase applyBrickParam
//-----------------------------------------------
// CMagicPhrase build
//-----------------------------------------------
bool CMagicPhrase::build( const TDataSetRow & actorRowId, const std::vector< const CStaticBrick* >& bricks )
{
_IsStatic = true;
//init with default values
_RangeIndex = MagicDefaultRangeIndex;
_CastingTime = NLMISC::TGameCycle( CMagicPhrase::defaultCastingTime() / CTickEventHandler::getGameTimeStep() );
// we are sure there is at least one brick and that there are non NULL;
nlassert( !bricks.empty() );
_ActorRowId = actorRowId;
CEntityBase * caster = CEntityBaseManager::getEntityBasePtr( actorRowId );
if ( !caster )
{
nlwarning(" invalid caster %u", actorRowId.getIndex() );
return false;
}
// compute cost, credit and aggro
for ( uint i = 0; i < bricks.size(); ++i )
{
if ( bricks[i]->SabrinaValue < 0 )
_SabrinaCredit -= bricks[i]->SabrinaValue;
else
_SabrinaCost += bricks[i]->SabrinaValue;
}
// Parse other params
std::vector ranges;
for ( uint i = 0; i < bricks.size(); )
{
nlassert ( bricks[i] );
const CStaticBrick & brick = *bricks[i];
INFOLOG("Build brick % u. Name : %s",i, brick.SheetId.toString().c_str() );
if ( brick.Skill != SKILLS::unknown )
_Skills.push_back(brick.Skill);
// if we are on an effect brick, treat the effect
if ( !brick.Params.empty() && brick.Params[0]->id() == TBrickParam::MA )
{
INFOLOG("brick %u. Name : %s : first param is an effect type",i, brick.SheetId.toString().c_str() );
// get the action range table
if ( brick.RangeTable != CSheetId::Unknown )
ranges.push_back(brick.RangeTable);
// determine the execution behaviour of the phrase through the effect
// if different nature are found, choose the neutral one
if ( _Nature == ACTNATURE::UNKNOWN )
_Nature = brick.Nature;
else if ( brick.Nature != _Nature )
_Nature = ACTNATURE::NEUTRAL;
// build the action
IMagicAction * action = IMagicActionFactory::buildAction(actorRowId,bricks,i,this);
if ( !action )
{
nlwarning( " could not build action in brick %s position in phrase %u", brick.SheetId.toString().c_str(),i );
return false;
}
_Actions.push_back(action);
}
// if we are on a sentence global params
else
{
INFOLOG("pos in phrase %u brick name : %s : first param is a global sentence param or is empty",i, brick.SheetId.toString().c_str() );
for ( uint j=0 ; j < brick.Params.size() ; ++j)
{
applyBrickParam( brick.Params[j] );
}
++i;
}
INFOLOG("pos in phrase %u Brick name : %s : all param parsed",i, brick.SheetId.toString().c_str() );
}
if ( _RangeIndex < 0 )
{
nlwarning( " range index %d is < 0", _RangeIndex );
_RangeIndex = 0;
}
// compute final range : it is the shortst action range
if ( ranges.empty() )
{
nlwarning("using default range value");
ranges.push_back( CSheetId( "default.sbrick_magic_range" ) );
}
for (uint i = 0; i < ranges.size(); i++ )
{
const CStaticMagicRange * sheet = CSheets::getMagicRangeTable(ranges[i]);
if ( sheet )
{
if ( _RangeIndex > (sint)sheet->Ranges.size() )
{
nlwarning( " range index %d too high: max is %u", _RangeIndex, sheet->Ranges.size() );
return false;
}
if ( _Range < sheet->Ranges[_RangeIndex])
_Range = sheet->Ranges[_RangeIndex];
}
else
{
nlwarning( " invalid range in effect %u", i);
}
}
if ( _Range == 0 )
{
nlwarning( " no valid range found setting it to 100");
_Range = 100;
}
_Range = _Range * 1000;
INFOLOG("Phrase built");
return true;
}// CMagicPhrase build
//-----------------------------------------------
// CMagicPhrase evaluate
//-----------------------------------------------
bool CMagicPhrase::evaluate( CEvalReturnInfos *msg )
{
// update state
_State = CSPhrase::Evaluated;
return true;
}// CMagicPhrase evaluate
//-----------------------------------------------
// CMagicPhrase validate
//-----------------------------------------------
bool CMagicPhrase::validate()
{
CEntityBase * entity = CEntityBaseManager::getEntityBasePtr( _ActorRowId );
CEntityBase * target = CEntityBaseManager::getEntityBasePtr( _Targets[0] );
if ( !entity )
{
nlwarning(" Invalid caster %u",_ActorRowId.getIndex() );
return false;
}
if ( !target )
{
nlwarning(" Invalid target %u",_Targets[0].getIndex() );
return false;
}
// test caster scores
const sint32 ChaScore1 = entity->getScores()._PhysicalScores[ SCORES::cha_score1 ].Current;
if (ChaScore1 <= 0 || entity->getMode()==MBEHAV::DEATH)
{
return false;
}
if ( ChaScore1 < ChaScore1 )
{
if ( entity->getId().getType() == RYZOMID::player )
CCharacter::sendMessageToClient( entity->getId(),"MAGIC_LACK_ChaScore1" );
return false;
}
const sint32 ChaScore3 = entity->getScores()._PhysicalScores[ SCORES::ChaScore3 ].Current;
if ( ChaScore3 < _ChaScore3Cost )
{
if ( entity->getId().getType() == RYZOMID::player )
CCharacter::sendMessageToClient( entity->getId(),"MAGIC_LACK_ChaScore3" );
return false;
}
// test range
const double dx = entity->getState().X - target->getState().X;
const double dy = entity->getState().Y - target->getState().Y;
// get range debuff
sint32 range = _Range;
const CSEffect * debuff = entity->lookForSEffect( EFFECT_FAMILIES::RangeCap, false );
if ( debuff && debuff->getParamValue() < range )
range = debuff->getParamValue();
if( dx* dx + dy*dy > (double)range* (double) range )
{
if ( entity->getId().getType() == RYZOMID::player )
CCharacter::sendMessageToClient( entity->getId(),"MAGIC_TARGET_OUT_OF_RANGE" );
return false;
}
/// todo nico : test if on mount
/// todo nico : test specific effects preventing spell cast
// at least one action must work on the main target
uint i = 0;
for ( ; i < _Actions.size(); i++ )
{
if ( _Actions[i]->validate(this) )
break;
}
if ( i == _Actions.size() )
{
if ( entity->getId().getType() == RYZOMID::player )
CCharacter::sendMessageToClient( entity->getId(),"MAGIC_BAD_TARGET" );
return false;
}
// update state
if (_State == Evaluated)
_State = Validated;
else if (_State == ExecutionInProgress)
_State = SecondValidated;
return true;
}// CMagicPhrase validate
//-----------------------------------------------
// CMagicPhrase update
//-----------------------------------------------
bool CMagicPhrase::update()
{
const NLMISC::TGameCycle time = CTickEventHandler::getGameCycle();
// if the sentence execution delay time has ended, apply sentence effects
if ( _State == SecondValidated && _ExecutionEndDate <= time && _NbWaitingRequests == 0 )
{
apply();
_State = LatencyEnded;
}
return true;
}// CMagicPhrase update
//-----------------------------------------------
// CMagicPhrase execute
//-----------------------------------------------
void CMagicPhrase::execute()
{
if( _NbWaitingRequests != 0)
return;
TDataSetRow mainTarget = _ActorRowId;
bool self = true;
if ( !_Targets.empty() && _Targets[0] != _ActorRowId)
{
mainTarget = _Targets[0];
self = false;
}
// determine the final behaviour
MBEHAV::CBehaviour behav;
switch (_Nature)
{
case ACTNATURE::NEUTRAL:
behav = MBEHAV::CAST_MIX;
break;
case ACTNATURE::DEFENSIVE:
behav = MBEHAV::CAST_CUR;
break;
case ACTNATURE::OFFENSIVE:
behav = MBEHAV::CAST_OFF;
break;
}
behav.Data = 0;
if ( behav.Behaviour != MBEHAV::UNKNOWN_BEHAVIOUR )
PHRASE_UTILITIES::sendUpdateBehaviour( _ActorRowId, behav );
else
nlwarning(" Invalid behaviour");
// determine the end of the cast
const NLMISC::TGameCycle time = CTickEventHandler::getGameCycle();
_State = CSPhrase::ExecutionInProgress;
CEntityBase * caster = CEntityBaseManager::getEntityBasePtr( _ActorRowId );
if ( !caster )
{
nlwarning(" Invalid entity %u",_ActorRowId.getIndex());
}
NLMISC::TGameCycle castingTime = _CastingTime ;
const CSEffect * slow = caster->lookForSEffect( EFFECT_FAMILIES::SlowMagic );
if ( slow )
{
castingTime = NLMISC::TGameCycle ( castingTime * (slow->getParamValue() / 100.0f ) );
}
_ExecutionEndDate = time + castingTime;
if ( caster->getId().getType() == RYZOMID::player )
PHRASE_UTILITIES::sendSpellBeginCastMessages(_ActorRowId, mainTarget, _Nature);
}// CMagicPhrase execute
//-----------------------------------------------
// CMagicPhrase apply
//-----------------------------------------------
void CMagicPhrase::apply()
{
// spend ChaScore3, ChaScore1
CEntityBase* entity = PHRASE_UTILITIES::entityPtrFromId( _ActorRowId );
if (entity == NULL)
{
nlwarning(" Invalid entity Id %s", TheDataset.getEntityId(_ActorRowId).toString().c_str() );
return;
}
RY_GAME_SHARE::SCharacteristicsAndScores &ChaScore3 = entity->getScores()._PhysicalScores[SCORES::cha_score3];
if ( _ChaScore3Cost )
{
ChaScore3.Current = ChaScore3.Current - _ChaScore3;
if (ChaScore3.Current < 0)
ChaScore3.Current = 0;
}
RY_GAME_SHARE::SCharacteristicsAndScores &ChaScore1 = entity->getScores()._PhysicalScores[SCORES::cha_score1];
if ( _ChaScore1Cost != 0)
{
entity->changeCurrentChaScore1(-_ChaScore1Cost);
}
// compute average skill value
sint skillValue = 0;
for ( uint i = 0; i < _Skills.size(); i++ )
{
SSkill * skill = entity->getSkills().getSkillStruct( _Skills[i] );
if ( skill )
{
skillValue+= skill->Current;
}
else
{
nlwarning(" invalid skill %d",_Skills[i]);
return;
}
}
skillValue /= _Skills.size();
const CSEffect * debuff = entity->lookForSEffect( EFFECT_FAMILIES::DebuffSkillMagic );
if ( debuff)
skillValue -= debuff->getParamValue();
sint32 armorMalus = (sint32) ( entity->getArmorCastingMalus() * (float)skillValue );
armorMalus -= _ArmorCompensation;
if ( armorMalus <0 )
armorMalus = 0;
skillValue -= armorMalus;
// check for magic madness effect
bool isMad = false;
const CSEffect * madness = entity->lookForSEffect(EFFECT_FAMILIES::MadnessMagic);
if ( madness )
{
const uint8 roll = (uint8) RandomGenerator.rand(99);
if ( roll < madness->getParamValue() )
{
_Targets.resize(1);
_Targets[0] = _ActorRowId;
isMad = true;
}
}
// get the success factor (divide delta level by 10 because a level is 10 skill points
sint deltaLvl = ( skillValue + (sint32)_SabrinaCredit - (sint32)(_SabrinaCost<<1) )/10;
const uint8 chances = PHRASE_UTILITIES::getSuccessChance( deltaLvl );
const uint8 roll = (uint8) RandomGenerator.rand(99);
float successFactor = PHRASE_UTILITIES::getSucessFactor(chances, roll);
/// compute XP gain
if ( entity->getId().getType() == RYZOMID::player && successFactor > 0.0f )
{
for (uint i = 0; i < _Skills.size(); i++ )
{
///\todo nico successFactor is the quality factor of the action for xp
///\todo nico multi target XP
CEntityBase * mainTarget = CEntityBaseManager::getEntityBasePtr( _Targets[0] );
((CCharacter*) entity)->actionReport( mainTarget,deltaLvl, ACTNATURE::OFFENSIVE, SKILLS::toString( _Skills[i] ) );
}
}
// send behaviour
MBEHAV::CBehaviour behav;
///\todo links
/*
CAST_OFF_LINK,
CAST_CUR_LINK,
CAST_MIX_LINK, */
TDataSetRow mainTarget = _ActorRowId;
if ( !_Targets.empty() && _Targets[0] != _ActorRowId)
{
mainTarget = _Targets[0];
}
if ( successFactor < 0.0f )
{
PHRASE_UTILITIES::sendSpellFumbleMessages(_ActorRowId, mainTarget);
switch (_Nature)
{
case ACTNATURE::NEUTRAL:
behav = MBEHAV::CAST_MIX_FUMBLE;
break;
case ACTNATURE::DEFENSIVE:
behav = MBEHAV::CAST_CUR_FUMBLE;
break;
case ACTNATURE::OFFENSIVE:
behav = MBEHAV::CAST_OFF_FUMBLE;
break;
}
}
else if ( successFactor > 0.0f )
{
PHRASE_UTILITIES::sendSpellSuccessMessages(_ActorRowId, mainTarget);
switch (_Nature)
{
case ACTNATURE::NEUTRAL:
behav = MBEHAV::CAST_MIX_SUCCESS;
break;
case ACTNATURE::DEFENSIVE:
behav = MBEHAV::CAST_CUR_SUCCESS;
break;
case ACTNATURE::OFFENSIVE:
behav = MBEHAV::CAST_OFF_SUCCESS;
break;
}
}
else
{
PHRASE_UTILITIES::sendSpellFailedMessages(_ActorRowId, mainTarget);
switch (_Nature)
{
case ACTNATURE::NEUTRAL:
behav = MBEHAV::CAST_MIX_FAIL;
break;
case ACTNATURE::DEFENSIVE:
behav = MBEHAV::CAST_CUR_FAIL;
break;
case ACTNATURE::OFFENSIVE:
behav = MBEHAV::CAST_OFF_FAIL;
break;
}
}
// apply each effect of the spell
for ( uint i = 0; i < _Actions.size(); i++ )
{
_Actions[i]->apply(this,successFactor,behav, isMad );
}
behav.Spell.Time = CTickEventHandler::getGameCycle();
if ( behav.Behaviour != MBEHAV::UNKNOWN_BEHAVIOUR )
PHRASE_UTILITIES::sendUpdateBehaviour( _ActorRowId, behav );
else
nlwarning(" Invalid behaviour");
// clear temporary execution data
_Targets.reserve(1);
_Targets.resize(1);
}//CMagicPhrase apply
//-----------------------------------------------
// CMagicPhrase stop
//-----------------------------------------------
void CMagicPhrase::stop()
{
if ( _State >= CSPhrase::ExecutionInProgress )
{
CCharacter* player = PlayerManager.getChar(_ActorRowId);
if (player)
{
player->clearCurrentAction();
PHRASE_UTILITIES::sendSimpleMessage( _ActorRowId, "EGS_ACTOR_CASTING_INTERUPT");
}
// send behaviour
MBEHAV::CBehaviour behav;
switch (_Nature)
{
case ACTNATURE::NEUTRAL:
behav = MBEHAV::CAST_MIX_FAIL;
break;
case ACTNATURE::DEFENSIVE:
behav = MBEHAV::CAST_CUR_FAIL;
break;
case ACTNATURE::OFFENSIVE:
behav = MBEHAV::CAST_OFF_FAIL;
break;
}
// set behaviour
PHRASE_UTILITIES::sendUpdateBehaviour( _ActorRowId, behav );
}
} // stop //