// 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 //