// 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 "sphrase_manager.h" #include "interface_manager.h" #include "../client_sheets/sphrase_sheet.h" #include "../sheet_manager.h" #include "../string_manager_client.h" #include "sbrick_manager.h" #include "skill_manager.h" #include "inventory_manager.h" #include "../user_entity.h" #include "game_share/memorization_set_types.h" #include "../client_cfg.h" #include "../net_manager.h" #include "nel/misc/algo.h" #include "../client_sheets/success_table_sheet.h" #include "dbctrl_sheet.h" #include "macrocmd_manager.h" #include "game_share/rolemaster_flags.h" #include "game_share/people.h" #include "lua_ihm.h" #include "../time_client.h" using namespace std; using namespace NLMISC; using namespace NLGEORGES; // Context help extern void contextHelp (const std::string &help); // *************************************************************************** // define this for deubg purpose only //#define PHRASE_DEBUG_VERBOSE // *************************************************************************** const std::string PhraseMemoryViewNextAction= "ui:interface:gestionsets:shortcuts:view_next_action"; const std::string PhraseMemoryViewCycleAction= "ui:interface:gestionsets:shortcuts:view_cycle_action"; const std::string PhraseMemoryViewSlotBase= "ui:interface:gestionsets:shortcuts:s"; const std::string PhraseMemoryCtrlBase= "ui:interface:gestionsets:shortcuts:s"; const std::string PhraseMemoryPhraseMenu= "ui:interface:cm_memory_phrase"; const std::string PhraseMemoryPhraseAction= "cast_phrase_or_create_new"; const std::string PhraseMemoryMacroMenu= "ui:interface:cm_memory_macro"; const std::string PhraseMemoryMacroAction= "cast_macro"; // *************************************************************************** CSPhraseManager *CSPhraseManager::_Instance= NULL; // *************************************************************************** SKILLS::ESkills getRightHandItemSkill(); uint32 getRightHandEffectiveLevel(); // *************************************************************************** void CSPhraseManager::releaseInstance() { if( _Instance ) { delete _Instance; _Instance = NULL; } } // *************************************************************************** CSPhraseManager::CSPhraseManager() { reset(); for(uint i=0;igetDbProp(PHRASE_DB_BOOK + ":" + toString(i) + ":PHRASE"); node->setValue32(0); _BookDbLeaves[i]= node; } _MemoryDbLeaves.resize(PHRASE_MAX_MEMORY_SLOT, NULL); for(i=0;igetDbProp(PHRASE_DB_MEMORY + ":" + toString(i) + ":PHRASE"); node->setValue32(0); _MemoryDbLeaves[i]= node; } // Progression Db leaves nlctassert( NumProgressType == sizeof(PHRASE_DB_PROGRESSION)/sizeof(PHRASE_DB_PROGRESSION[0]) ); for(uint j=0;jgetDbProp(PHRASE_DB_PROGRESSION[j] + ":" + toString(i) + ":SHEET"); node->setValue32(0); _ProgressionDbSheets[j][i]= node; // LEVEL node= pIM->getDbProp(PHRASE_DB_PROGRESSION[j] + ":" + toString(i) + ":LEVEL"); node->setValue32(0); _ProgressionDbLevels[j][i]= node; // LOCKED node= pIM->getDbProp(PHRASE_DB_PROGRESSION[j] + ":" + toString(i) + ":LOCKED"); node->setValue32(0); _ProgressionDbLocks[j][i]= node; } } // init the UI Next Execute slot { CCDBNodeLeaf *node= pIM->getDbProp(PHRASE_DB_EXECUTE_NEXT); node->setValue32(0); _NextExecuteLeaf= node; node= pIM->getDbProp(PHRASE_DB_EXECUTE_NEXT_IS_CYCLIC); node->setValue32(0); _NextExecuteIsCyclicLeaf= node; } // Init BotChat leaves _BotChatPhraseSheetLeaves.resize(PHRASE_MAX_BOTCHAT_SLOT, NULL); _BotChatPhrasePriceLeaves.resize(PHRASE_MAX_BOTCHAT_SLOT, NULL); for(i=0;igetDbProp(PHRASE_DB_BOTCHAT+ ":" + toString(i) + ":SHEET"); CCDBNodeLeaf *nodePrice= pIM->getDbProp(PHRASE_DB_BOTCHAT+ ":" + toString(i) + ":PRICE"); _BotChatPhraseSheetLeaves[i]= nodeSheet; _BotChatPhrasePriceLeaves[i]= nodePrice; } // and so update book and memory db updateBookDB(); updateMemoryDBAll(); // Load the success table here loadSuccessTable(); // compute and the progression phrase, and update DB computePhraseProgression(); _EnchantWeaponMainBrick = NLMISC::CSheetId("bsxea10.sbrick"); // build map that gives its description for each built-in phrase // slow test on all sheets here ... const CSheetManager::TEntitySheetMap &sm = SheetMngr.getSheets(); sint32 result= 0; CSPhraseCom tmpPhrase; for(CSheetManager::TEntitySheetMap::const_iterator it = sm.begin(); it != sm.end(); ++it) { if (it->second.EntitySheet && it->second.EntitySheet->Type == CEntitySheet::SPHRASE) { const_cast(this)->buildPhraseFromSheet(tmpPhrase, it->first.asInt()); _PhraseToSheet[tmpPhrase] = it->first.asInt(); } } } // *************************************************************************** void CSPhraseManager::updateMemoryBar() { if(!_InitInGameDone) return; updateMemoryDBAll(); updateAllMemoryCtrlState(); } // *************************************************************************** void CSPhraseManager::setPhraseInternal(uint32 slot, const CSPhraseCom &phrase, bool lock, bool updateDB) { // don't allow slot too big. don't allow set the 0 slot. if(slot>PHRASE_MAX_ID || slot==0) return; // enlargePhraseClient if(slot>=_PhraseClient.size()) _PhraseClient.resize(slot+1); // set the phrase _PhraseMap[slot]= phrase; // increment the phrase version. _PhraseClient[slot].Version++; // BotChat lock? _PhraseClient[slot].Lock= lock; // For Free Slot Mgt. _MaxSlotSet= max(_MaxSlotSet, slot); // update the book, if necessary if( updateDB && !lock && slot >= BookStartSlot ) updateBookDB(); } // *************************************************************************** void CSPhraseManager::setPhrase(uint32 slot, const CSPhraseCom &phrase, bool lock) { // set the phrase and update the DB setPhraseInternal(slot, phrase, lock, true); } // *************************************************************************** void CSPhraseManager::setPhraseNoUpdateDB(uint32 slot, const CSPhraseCom &phrase) { // set the phrase but don't update the DB (NB: no phrase lock) setPhraseInternal(slot, phrase, false, false); } // *************************************************************************** void CSPhraseManager::erasePhrase(uint32 slot) { if(slot>=_PhraseClient.size()) return; _PhraseMap.erase(slot); _PhraseClient[slot].Version= -1; _PhraseClient[slot].Lock= false; // make this slot available for allocation _FreeSlots.push_back(slot); // update the book. updateBookDB(); // if the phrase erased was currently executed, must stop it if(slot==_CurrentExecutePhraseIdCycle || slot==_CurrentExecutePhraseIdNext) { if(slot==_CurrentExecutePhraseIdNext) { _CurrentExecuteLineNext= -1; _CurrentExecuteSlotNext= -1; _CurrentExecutePhraseIdNext= 0; } if(slot==_CurrentExecutePhraseIdCycle) { _CurrentExecuteLineCycle= -1; _CurrentExecuteSlotCycle= -1; _CurrentExecutePhraseIdCycle= 0; } // update execution display updateExecutionDisplay(); } } // *************************************************************************** const CSPhraseCom &CSPhraseManager::getPhrase(uint32 slot) const { TPhraseMap::const_iterator it= _PhraseMap.find(slot); if(it==_PhraseMap.end()) return CSPhraseCom::EmptyPhrase; else return it->second; } // *************************************************************************** sint32 CSPhraseManager::getPhraseVersion(uint32 slot) const { if(slot>=_PhraseClient.size()) return -1; return _PhraseClient[slot].Version; } // *************************************************************************** void CSPhraseManager::unlockPhrase(uint32 slot) { TPhraseMap::const_iterator it= _PhraseMap.find(slot); if(it!=_PhraseMap.end()) { // unlcok the phrase _PhraseClient[slot].Lock= false; // and so update the book. updateBookDB(); } } // *************************************************************************** void CSPhraseManager::receiveBotChatConfirmBuy(uint16 phraseId, bool confirm) { // if confirm => unlock if(confirm) { // first unlock it. unlockPhrase(phraseId); // Then try to auto memorize it. const CSPhraseCom &phrase= getPhrase(phraseId); if(!phrase.empty()) { CSBrickManager *pBM= CSBrickManager::getInstance(); CSBrickSheet *rootBrick= pBM->getBrick(phrase.Bricks[0]); if(rootBrick) { // Must save to the first Memory slot available for(uint dstMemoryLine=0; dstMemoryLine=0) { // memorize memorizePhrase(dstMemoryLine, dstMemorySlot, phraseId); // Send Server Info sendMemorizeToServer(dstMemoryLine, dstMemorySlot, phraseId); // stop! break; } } // Ugly: must update ALL the memoryBar in case of dstMemoryLine is the selected one updateAllMemoryCtrlState(); } } // Context help contextHelp ("action_book"); } // else delete slot else erasePhrase(phraseId); } // *************************************************************************** void CSPhraseManager::forgetPhrase(uint32 memoryLine, uint32 memorySlot) { if(memorySlot>=PHRASE_MAX_MEMORY_SLOT) return; // if the slot eihter don't exist, abort if(memoryLine>=_Memories.size()) return; // if the slot is not a phrase if(!_Memories[memoryLine].Slot[memorySlot].isPhrase()) return; // update memory _Memories[memoryLine].Slot[memorySlot].IsMacro= false; _Memories[memoryLine].Slot[memorySlot].Id= 0; // must update DB? if((sint32)memoryLine==_SelectedMemoryDB) { // update the db updateMemoryDBSlot(memorySlot); // update the ctrl state updateMemoryCtrlState(memorySlot); // If there is an execution running on this slot, no more display it if(_CurrentExecuteSlotNext==(sint32)memorySlot || _CurrentExecuteSlotCycle==(sint32)memorySlot) updateExecutionDisplay(); } } // *************************************************************************** uint32 CSPhraseManager::getMemorizedPhrase(uint32 memoryLine, uint32 memorySlot) const { if(memoryLine>=_Memories.size()) return 0; if(memorySlot>=PHRASE_MAX_MEMORY_SLOT) return 0; if(!_Memories[memoryLine].Slot[memorySlot].isPhrase()) return 0; return _Memories[memoryLine].Slot[memorySlot].Id; } // *************************************************************************** void CSPhraseManager::memorizePhrase(uint32 memoryLine, uint32 memorySlot, uint32 slot) { if(memorySlot>=PHRASE_MAX_MEMORY_SLOT) return; if(slot>=_PhraseClient.size()) return; // Can memorize only a phrase of the book if(slot=_Memories.size()) _Memories.resize(memoryLine+1); // update memory _Memories[memoryLine].Slot[memorySlot].IsMacro= false; _Memories[memoryLine].Slot[memorySlot].Id= slot; // must update DB? if((sint32)memoryLine==_SelectedMemoryDB) { // update the DB updateMemoryDBSlot(memorySlot); // update the ctrl state updateMemoryCtrlState(memorySlot); // If there is an execution running with this action, maybe re-display it if(_CurrentExecutePhraseIdNext==slot || _CurrentExecutePhraseIdCycle==slot) updateExecutionDisplay(); } } // *************************************************************************** void CSPhraseManager::selectMemoryLineDB(sint32 memoryLine) { if(memoryLine<0) memoryLine= -1; if(_SelectedMemoryDB!=memoryLine) { _SelectedMemoryDB= memoryLine; // since memory selection changes then must update all the DB and the Ctrl states updateMemoryDBAll(); updateAllMemoryCtrlState(); updateAllMemoryCtrlRegenTickRange(); // must update also the execution views updateExecutionDisplay(); } } // *************************************************************************** void CSPhraseManager::updateMemoryDBAll() { // If DB not inited, no-op if(!_InitInGameDone) return; if(_SelectedMemoryDB==-1 || _SelectedMemoryDB>=(sint32)_Memories.size()) { for(uint i=0;isetValue32(0); } } else { for(uint i=0;isetValue32(0); else _MemoryDbLeaves[i]->setValue32(slot.Id); } } } // *************************************************************************** void CSPhraseManager::updateMemoryDBSlot(uint32 memorySlot) { // If DB not inited, no-op if(!_InitInGameDone) return; if(_SelectedMemoryDB==-1 || _SelectedMemoryDB>=(sint32)_Memories.size()) return; if(memorySlotsetValue32(0); else _MemoryDbLeaves[memorySlot]->setValue32(slot.Id); } } // *************************************************************************** bool CSPhraseManager::isPhraseCastable(uint32 sheetId) const { return isPhraseCastable(dynamic_cast(SheetMngr.get(NLMISC::CSheetId(sheetId)))); } // *************************************************************************** bool CSPhraseManager::isPhraseCastable(CSPhraseSheet *phraseSheet) const { bool ok = false; if(phraseSheet) { // castable if(phraseSheet->Castable) { // check Charac Buying (compatibility) if(!isPhraseCharacBuying(phraseSheet)) ok= true; } } return ok; } // *************************************************************************** bool CSPhraseManager::isPhraseCharacBuying(uint32 sheetId) const { return isPhraseCharacBuying(dynamic_cast(SheetMngr.get(NLMISC::CSheetId(sheetId)))); } // *************************************************************************** bool CSPhraseManager::isPhraseCharacBuying(class CSPhraseSheet *phraseSheet) const { CSBrickManager *pBM= CSBrickManager::getInstance(); if(!phraseSheet || phraseSheet->Bricks.empty()) return false; // check brick CSBrickSheet *brickSheet= pBM->getBrick(phraseSheet->Bricks[0]); // if not a charac buying phrase if(brickSheet && BRICK_FAMILIES::isCharacBuyFamily(brickSheet->BrickFamily) ) return true; return false; } // *************************************************************************** uint32 CSPhraseManager::allocatePhraseSlot() { if(_FreeSlots.empty()) { // if too big, fail. if(_MaxSlotSet>=PHRASE_MAX_ID) return 0; // get a free slot return _MaxSlotSet+1; } else { uint32 val= _FreeSlots.back(); _FreeSlots.pop_back(); return val; } } // *************************************************************************** bool CSPhraseManager::hasFreeSlot() const { // if no free slot and too big, fail. if(_FreeSlots.empty()) { if(_MaxSlotSet>=PHRASE_MAX_ID) return false; } return true; } // *************************************************************************** void CSPhraseManager::setBookFilter(BRICK_TYPE::EBrickType brickTypeFilter, SKILLS::ESkills skillFilter) { // set the filter _BookBrickTypeFilter = brickTypeFilter; _BookSkillFitler = skillFilter; // the DB book may change updateBookDB(); // the DB progression may change updatePhraseProgressionDB(); } // *************************************************************************** bool CSPhraseManager::matchBookSkillFilter(const CSPhraseCom &phrase) const { // Special: if book Filter is unknown, then always match! if(_BookBrickTypeFilter==BRICK_TYPE::UNKNOWN && _BookSkillFitler==SKILLS::unknown) return true; CSBrickManager *pBM= CSBrickManager::getInstance(); CSkillManager *pSM= CSkillManager::getInstance(); // must have a valid rootBrick. NB: it is an error, but decide to show it (return true) if(phrase.empty()) return true; CSBrickSheet *rootBrick= pBM->getBrick(phrase.Bricks[0]); if(!rootBrick) return true; // If decide to filter by brickType if(_BookBrickTypeFilter!=BRICK_TYPE::UNKNOWN) { if( BRICK_FAMILIES::brickType(rootBrick->BrickFamily) == _BookBrickTypeFilter) return true; } // else filter by Skill else { // Special for powers: always true. \todo yoyo: may change this feature if(rootBrick->isSpecialPower()) { // Skill Filter must be at least combat or magic return pSM->areSkillOnSameBranch(_BookSkillFitler, SKILLS::SM) || pSM->areSkillOnSameBranch(_BookSkillFitler, SKILLS::SF); } // Special for combat else if(rootBrick->isCombat()) { // For combat, it is best to show "All Actions that can be used with the weapon associated to the skill" if(skillCompatibleWithCombatPhrase(_BookSkillFitler, phrase.Bricks)) return true; } // suppose same for magic, craft, and forage else { // run all the phrase Bricks, if only one match (eg important for magic multi spells), it is ok! for(uint i=0;igetBrick(phrase.Bricks[i]); if(brick) { // if the filter is an ancestor, ok! (NB: if brick skill unknown then it will fail!) if(pSM->isSkillAncestor(_BookSkillFitler, brick->getSkill())) return true; } } } } // no match! return false; } // *************************************************************************** void CSPhraseManager::updateBookDB() { // If DB not inited, no-op if(!_InitInGameDone) return; // Fill All the book. TPhraseMap::const_iterator it= _PhraseMap.begin(); sint numBookFill= (sint)_PhraseMap.size(); sint i= 0; while(ifirstfirst].Lock || !matchBookSkillFilter(it->second) ) { numBookFill--; } else { // fill with the phrase id _BookDbLeaves[i]->setValue32(it->first); // next mem fill i++; } // if no more place on book, stop if(i>=PHRASE_MAX_BOOK_SLOT) { numBookFill= PHRASE_MAX_BOOK_SLOT; break; } // next map entry. it++; } // reset old no more used to empty for(i=numBookFill;i<(sint)_LastBookNumDbFill;i++) { _BookDbLeaves[i]->setValue32(0); } // update cache _LastBookNumDbFill= numBookFill; } // *************************************************************************** void CSPhraseManager::buildPhraseFromSheet(CSPhraseCom &phrase, sint32 sheetId) { CSPhraseSheet *phraseSheet= dynamic_cast(SheetMngr.get(CSheetId(sheetId))); if(phraseSheet) { // get localized Name phrase.Name= STRING_MANAGER::CStringManagerClient::getSPhraseLocalizedName(CSheetId(sheetId)); // Build bricks phrase.Bricks.clear(); for(uint i=0;iBricks.size();i++) phrase.Bricks.push_back(phraseSheet->Bricks[i]); } else { phrase= CSPhraseCom::EmptyPhrase; } } // *************************************************************************** bool CSPhraseManager::isPhraseNextExecuteCounterSync() const { CInterfaceManager *pIM= CInterfaceManager::getInstance(); sint32 srvVal= pIM->getDbProp(PHRASE_DB_COUNTER_NEXT)->getValue32(); return srvVal==(sint32)_PhraseNextExecuteCounter; } // *************************************************************************** bool CSPhraseManager::isPhraseCycleExecuteCounterSync() const { CInterfaceManager *pIM= CInterfaceManager::getInstance(); sint32 srvVal= pIM->getDbProp(PHRASE_DB_COUNTER_CYCLE)->getValue32(); return srvVal==(sint32)_PhraseCycleExecuteCounter; } // *************************************************************************** void CSPhraseManager::sendMemorizeToServer(uint32 memoryLine, uint32 memorySlot, uint32 phraseId) { // Local simulation if(ClientCfg.Local) { extern void debugUpdateActionBar(); debugUpdateActionBar(); } // Send the memorize to server else { CBitMemStream out; const string sMsg = "PHRASE:MEMORIZE"; if(GenericMsgHeaderMngr.pushNameToStream(sMsg, out)) { CSPhraseCom phrase= getPhrase(phraseId); // free Band; don't send name phrase.Name.clear(); uint8 memoryId= (uint8)memoryLine; uint8 slotId= (uint8)memorySlot; uint16 pid= (uint16)phraseId; out.serial(memoryId); out.serial(slotId); out.serial(pid); out.serial(phrase); NetMngr.push(out); //nlinfo("impulseCallBack : %s %d %d %d (phrase) sent", sMsg.c_str(), memoryId, slotId, pid); } else nlwarning("impulseCallBack : unknown message name : '%s'.", sMsg.c_str()); } } // *************************************************************************** void CSPhraseManager::sendForgetToServer(uint32 memoryLine, uint32 memoryIndex) { // Local simulation if(ClientCfg.Local) { } // Send the forget to server. else { CBitMemStream out; const string sMsg = "PHRASE:FORGET"; if(GenericMsgHeaderMngr.pushNameToStream(sMsg, out)) { //serial the sentence memorized index uint8 memoryId= (uint8)memoryLine; uint8 slotId= (uint8)memoryIndex; out.serial( memoryId ); out.serial( slotId ); NetMngr.push(out); //nlinfo("impulseCallBack : %s %d %d sent", sMsg.c_str(), memoryId, slotId); } else nlwarning("impulseCallBack : unknown message name : '%s'.", sMsg.c_str()); } } // *************************************************************************** void CSPhraseManager::reset() { _PhraseMap.clear(); _PhraseClient.clear(); _FreeSlots.clear(); _Memories.clear(); // Default alloc. _PhraseClient.resize(1024); _InitInGameDone= false; _SelectedMemoryDB= -1; // NB: slot under 2 can't be taken. _MaxSlotSet= BookStartSlot-1; _LastBookNumDbFill= 0; _PhraseNextExecuteCounter= 0; _PhraseCycleExecuteCounter= 0; _CurrentExecuteLineNext= -1; _CurrentExecuteSlotNext= -1; _CurrentExecutePhraseIdNext= 0; _CurrentExecuteLineCycle= -1; _CurrentExecuteSlotCycle= -1; _CurrentExecutePhraseIdCycle= 0; _PhraseDebugEndNextAction= 0; _PhraseDebugEndCyclicAction= 0; _UserIndoor = true; _BookSkillFitler = SKILLS::unknown; _BookBrickTypeFilter = BRICK_TYPE::UNKNOWN; _BookDbLeaves.clear(); for(uint i=0;iremoveBrickLearnedCallback(&_ProgressionUpdate); pSM->removeSkillChangeCallback(&_ProgressionUpdate); } // *************************************************************************** bool CSPhraseManager::isPhraseKnown(const CSPhraseCom &phrase) const { for(TPhraseMap::const_iterator it = _PhraseMap.begin(); it != _PhraseMap.end(); ++it) { if (it->second == phrase) return true; } return false; } // *************************************************************************** ucstring CSPhraseManager::formatMalus(sint base, sint malus) { if(malus) return toString("@{F80F}%d@{FFFF} (%d)", base+malus, base); else return toString(base); } // *************************************************************************** ucstring CSPhraseManager::formatMalus(float base, float malus) { if(malus) return toString("@{F80F}%.1f@{FFFF} (%.1f)", base+malus, base); else return toString("%.1f", base); } // *************************************************************************** string CSPhraseManager::formatBonusMalus(sint32 base, sint32 mod) { string str; if( mod == 0 ) { str = toString("@{FFFF}") + toString(base); } else if( mod > 0 ) // bonus { str = "@{0F0F}" + toString( base + mod ) + "@{FFFF}(" + toString( base ) + "@{0F0F} + " + toString( mod ) + "@{FFFF})"; } else { str = "@{E42F}" + toString( base + mod ) + "@{FFFF}(" + toString( base ) + "@{E42F} - " + toString( mod ) + "@{FFFF})"; } return str; } // *************************************************************************** void CSPhraseManager::buildPhraseDesc(ucstring &text, const CSPhraseCom &phrase, uint32 phraseSheetId, bool wantRequirement, const std::string &specialPhraseFormat) { CSBrickManager *pBM= CSBrickManager::getInstance(); CInterfaceManager *pIM= CInterfaceManager::getInstance(); text.erase(); // castable action? bool castable= phraseSheetId==0 || isPhraseCastable(phraseSheetId); // For true phraseId, or castable .sphrase only if(castable) { // **** Get the format from EnergyType CSBrickSheet *rootBrick= NULL; if(phrase.Bricks.size()) rootBrick= pBM->getBrick(phrase.Bricks[0]); if(rootBrick) { static const string compoId= "composition"; static const ucstring compoTag("%compostart"); bool isComposition= specialPhraseFormat==compoId; // if format not given by user, auto select it. if(specialPhraseFormat.empty() || isComposition) { if(rootBrick->isCombat()) text= CI18N::get("uihelpPhraseCombatFormat"); else if(rootBrick->isMagic()) text= CI18N::get("uihelpPhraseMagicFormat"); else if(rootBrick->isFaber()) text= CI18N::get("uihelpPhraseCraftFormat"); else if(rootBrick->isSpecialPower()) text= CI18N::get("uihelpPhraseSpecialPowerFormat"); else if(rootBrick->isForageExtraction()) text= CI18N::get("uihelpPhraseForageExtractionFormat"); else if(rootBrick->isProcEnchantment()) text= CI18N::get("uihelpPhraseProcEnchantment"); else text= CI18N::get("uihelpPhraseOtherFormat"); // if composition, cut the text before the tag (including) if(isComposition) { ucstring::size_type pos= text.find(compoTag); if(pos!=ucstring::npos) text.erase(0, pos+compoTag.size()); } // else just clear the tag else { strFindReplace(text, compoTag, ucstring() ); } } else text= CI18N::get(specialPhraseFormat); } else return; // **** Phrase info basics // replace name strFindReplace(text, "%name", phrase.Name); // replace Sabrina Cost and credit. uint32 cost, credit; pBM->getSabrinaCom().getPhraseCost(phrase.Bricks, cost, credit); strFindReplace(text, "%cost", toString(cost)); strFindReplace(text, "%credit", toString(credit)); // for combat, fill weapon compatibility ucstring weaponRestriction; bool usableWithMelee; bool usableWithRange; if(rootBrick && rootBrick->isCombat()) { getCombatWeaponRestriction(weaponRestriction, phrase, usableWithMelee, usableWithRange); strFindReplace(text, "%wcomp", weaponRestriction); } // for Magic, fill Spell Level, and Magic Resist Type if(rootBrick && rootBrick->isMagic()) { // Spell Level uint32 spellLevel= getSpellLevel(phrase.Bricks); strFindReplace(text, "%mglvl", toString(spellLevel)); // ResistMagic. May have mutliple because of double spell bool resistMagic[RESISTANCE_TYPE::NB_RESISTANCE_TYPE]; getResistMagic(resistMagic, phrase.Bricks); bool first= true; ucstring resList; for(uint i=0;igetDbProp("UI:VARIABLES:TOTAL_MALUS_EQUIP", false); // root brick must not be Power or aura, because Action malus don't apply to them // (ie leave 0 ActionMalus for Aura or Powers if(actMalus && !rootBrick->isSpecialPower()) totalActionMalus= actMalus->getValue32(); // Get the stats of the phrase, with malus due to item weared sint success= getPhraseSuccessRate(phrase); float castTime= 0, castTimeMalus= 0; sint range= 0, rangeMalus= 0; sint hpCost= 0, hpCostMalus= 0; sint enCost= 0, enCostMalus= 0; getPhraseCastTime(phrase, totalActionMalus, castTime, castTimeMalus); getPhraseMagicRange(phrase, totalActionMalus, range, rangeMalus); getPhraseHpCost(phrase, totalActionMalus, hpCost, hpCostMalus); if(rootBrick->isCombat()) getPhraseStaCost(phrase, totalActionMalus, enCost, enCostMalus); else if(rootBrick->isMagic()) getPhraseSapCost(phrase, totalActionMalus, enCost, enCostMalus); else getPhraseFocusCost(phrase, totalActionMalus, enCost, enCostMalus); sint32 successModifier = 0; CCDBNodeLeaf * nodeSM = NULL; if(rootBrick->isCombat()) { // if phrase can be used with in melee and range we choose which one to display according to hand weapon family if( usableWithMelee && usableWithRange ) { CInventoryManager *inv = CInventoryManager::getInstance(); if(inv) { uint32 rightHandSheet = inv->getRightHandItemSheet(); if( inv->isRangeWeaponItem(rightHandSheet) ) { nodeSM = pIM->getDbProp("SERVER:CHARACTER_INFO:SUCCESS_MODIFIER:RANGE", false); } else { nodeSM = pIM->getDbProp("SERVER:CHARACTER_INFO:SUCCESS_MODIFIER:MELEE", false); } } } else // phrase usable only in melee fight if( usableWithMelee ) { nodeSM = pIM->getDbProp("SERVER:CHARACTER_INFO:SUCCESS_MODIFIER:MELEE", false); } else // phrase usable only in range fight if( usableWithRange ) { nodeSM = pIM->getDbProp("SERVER:CHARACTER_INFO:SUCCESS_MODIFIER:RANGE", false); } } else if(rootBrick->isMagic()) { nodeSM = pIM->getDbProp("SERVER:CHARACTER_INFO:SUCCESS_MODIFIER:MAGIC", false); } if(nodeSM) { successModifier = nodeSM->getValue32(); } string successStr = formatBonusMalus(success, successModifier); // **** assemble in text strFindReplace(text, "%success", toString(successStr)); strFindReplace(text, "%duration", formatMalus(castTime, castTimeMalus) ); strFindReplace(text, "%energy_cost", formatMalus(enCost, enCostMalus) ); strFindReplace(text, "%hp_cost", formatMalus(hpCost, hpCostMalus) ); // special range and "self" if(range==0) strFindReplace(text, "%range", CI18N::get("uihelpPhraseRangeSelf")); else { ucstring fmt= CI18N::get("uihelpPhraseRangeMeters"); strFindReplace(fmt, "%dist", formatMalus(range, rangeMalus)); strFindReplace(text, "%range", fmt); } // **** special Forage extraction if(rootBrick->isForageExtraction()) { // Choose the fmt text ucstring fmt= getForageExtractionPhraseEcotypeFmt(phrase); // Replace forage success rate in any ecotype successModifier = 0; sint32 commonModifier = 0; nodeSM = pIM->getDbProp("SERVER:CHARACTER_INFO:SUCCESS_MODIFIER:ECO:0:FORAGE", false); if(nodeSM) { commonModifier = nodeSM->getValue32(); } //desert success= getForageExtractionPhraseSuccessRate(phrase, SKILLS::SHFDAEM); nodeSM = pIM->getDbProp("SERVER:CHARACTER_INFO:SUCCESS_MODIFIER:ECO:1:FORAGE", false); if(nodeSM) successModifier = nodeSM->getValue32(); if( successModifier == 0 ) successModifier = commonModifier; successStr = formatBonusMalus(success, successModifier); successModifier = 0; strFindReplace(fmt, "%suc_desert", toString(successStr)); //forest success= getForageExtractionPhraseSuccessRate(phrase, SKILLS::SHFFAEM); nodeSM = pIM->getDbProp("SERVER:CHARACTER_INFO:SUCCESS_MODIFIER:ECO:2:FORAGE", false); if(nodeSM) successModifier = nodeSM->getValue32(); if( successModifier == 0 ) successModifier = commonModifier; successStr = formatBonusMalus(success, successModifier); successModifier = 0; strFindReplace(fmt, "%suc_forest", successStr); // lake success= getForageExtractionPhraseSuccessRate(phrase, SKILLS::SHFLAEM); nodeSM = pIM->getDbProp("SERVER:CHARACTER_INFO:SUCCESS_MODIFIER:ECO:3:FORAGE", false); if(nodeSM) successModifier = nodeSM->getValue32(); if( successModifier == 0 ) successModifier = commonModifier; successStr = formatBonusMalus(success, successModifier); successModifier = 0; strFindReplace(fmt, "%suc_lake", successStr); // jungle success= getForageExtractionPhraseSuccessRate(phrase, SKILLS::SHFJAEM); nodeSM = pIM->getDbProp("SERVER:CHARACTER_INFO:SUCCESS_MODIFIER:ECO:4:FORAGE", false); if(nodeSM) successModifier = nodeSM->getValue32(); if( successModifier == 0 ) successModifier = commonModifier; successStr = formatBonusMalus(success, successModifier); successModifier = 0; strFindReplace(fmt, "%suc_jungle", successStr); //prime roots success= getForageExtractionPhraseSuccessRate(phrase, SKILLS::SHFPAEM); nodeSM = pIM->getDbProp("SERVER:CHARACTER_INFO:SUCCESS_MODIFIER:ECO:6:FORAGE", false); if(nodeSM) successModifier = nodeSM->getValue32(); if( successModifier == 0 ) successModifier = commonModifier; successStr = formatBonusMalus(success, successModifier); successModifier = 0; strFindReplace(fmt, "%suc_prime", successStr); // replace the forage succes strFindReplace(text, "%suc_forage", fmt); } } // **** not castable case else { // special for charac buying if(isPhraseCharacBuying(phraseSheetId)) text= CI18N::get("uihelpPhraseCharacteristic"); else text= CI18N::get("uihelpPhraseNotCastableFormat"); } // **** Special .sphrase description if(phraseSheetId) { // get the text ucstring desc(STRING_MANAGER::CStringManagerClient::getSPhraseLocalizedDescription(CSheetId(phraseSheetId))); if(desc.empty()) strFindReplace(text, "%desc", ucstring()); else { // append an \n before, for clearness desc= ucstring("\n") + desc; // append \n at end if not done if(desc[desc.size()-1]!='\n') desc+= '\n'; // replace in format strFindReplace(text, "%desc", desc); } } else { strFindReplace(text, "%desc", ucstring()); } // **** Special .sphrase requirement if(phraseSheetId && wantRequirement) { ucstring reqText; reqText= CI18N::get("uihelpPhraseRequirementHeader"); // replace the skill point cost strFindReplace(reqText, "%sp", toString(getCurrentPhraseSheetPrice(phraseSheetId))); strFindReplace(reqText, "%basesp", toString(getBasePhraseSheetPrice(phraseSheetId))); // build the phrase requirement from this sheetId TPhraseReqSkillMap::const_iterator it= _PhraseReqSkillMap.find(phraseSheetId); if(it!=_PhraseReqSkillMap.end()) { const CReqSkillFormula &formula= it->second; // from this formula, build the requirement tex ucstring textForm; formula.getInfoText(textForm); reqText+= textForm; } // replace in text strFindReplace(text, "%req", reqText); } else strFindReplace(text, "%req", ""); } // *************************************************************************** sint CSPhraseManager::getPhraseSuccessRate(const CSPhraseCom &phrase) { CSBrickManager *pBM= CSBrickManager::getInstance(); CSkillManager *pSM= CSkillManager::getInstance(); if(phrase.Bricks.size()==0) return 0; CSBrickSheet *rootBrick= pBM->getBrick(phrase.Bricks[0]); if(!rootBrick) return 0; // get the Skill value (according to phrase type etc...) TSuccessTable sTable; sint skillValue= 0; if(rootBrick->isCombat()) { // retrieve the current Skill of the Item in RightHand SKILLS::ESkills skill= getRightHandItemSkill(); // Get its value skillValue= pSM->getSkillValueMaxBranch(skill); // use the combat table sTable= STCombat; } else if(rootBrick->isMagic()) { uint32 numSkill= 0; skillValue= 0; // Must take a mean of all skills for(uint i=0;igetBrick(phrase.Bricks[i]); if(brick) { SKILLS::ESkills skill= brick->getSkill(); if(skill!=SKILLS::unknown) { skillValue+= pSM->getSkillValueMaxBranch(skill); numSkill++; } } } // The mean! if(numSkill) skillValue/= numSkill; // use one of the magic table if(rootBrick->ActionNature==ACTNATURE::CURATIVE_MAGIC) sTable= STCurativeMagic; else // should be ACTNATURE::OFFENSIVE_MAGIC !! sTable= STOffensiveMagic; } // Craft: false, but return value not used else if(rootBrick->isFaber()) { skillValue= pSM->getSkillValueMaxBranch(rootBrick->getSkill()); sTable= STCraft; } // Forage Prospect: always success else if(rootBrick->isForageProspection()) { return 100; } // Forage Extract: false, but return value not used else if(rootBrick->isForageExtraction()) { skillValue= pSM->getSkillValueMaxBranch(rootBrick->getSkill()); sTable= STExtract; } // Default else { // Default: take skill value of the root skillValue= pSM->getSkillValueMaxBranch(rootBrick->getSkill()); // well, it's bug time! sTable= STCombat; } // return the success rate with this skillValue return getPhraseSuccessRate(sTable, phrase, skillValue, INT_MAX); } // *************************************************************************** sint CSPhraseManager::getCraftPhraseSuccessRate(const CSPhraseCom &phrase, SKILLS::ESkills skill, uint minMpLevel) { CSkillManager *pSM= CSkillManager::getInstance(); if(phrase.Bricks.size()==0) return 0; // take skill value of the skill sint skillValue= pSM->getBestSkillValue(skill); // return the sr according to this skill return getPhraseSuccessRate(STCraft, phrase, skillValue, minMpLevel); } // *************************************************************************** sint CSPhraseManager::getForageExtractionPhraseSuccessRate(const CSPhraseCom &phrase, SKILLS::ESkills skill) { CSkillManager *pSM= CSkillManager::getInstance(); if(phrase.Bricks.size()==0) return 0; // take skill value of the skill sint skillValue= pSM->getSkillValueMaxBranch(skill); // return the sr according to this skill return getPhraseSuccessRate(STExtract, phrase, skillValue, INT_MAX); } // *************************************************************************** void CSPhraseManager::getPhraseSapCost(const CSPhraseCom &phrase, uint32 totalActionMalus, sint &cost, sint &costMalus) { CSBrickManager *pBM= CSBrickManager::getInstance(); cost= (sint)getPhraseSumBrickProp(phrase, pBM->SapPropId, true); // compute malus (positive) costMalus= (cost * (totalActionMalus))/100; } // *************************************************************************** void CSPhraseManager::getPhraseStaCost(const CSPhraseCom &phrase, uint32 totalActionMalus, sint &cost, sint &costMalus) { CSBrickManager *pBM= CSBrickManager::getInstance(); cost= (sint)getPhraseSumBrickProp(phrase, pBM->StaPropId, true); // TODO: combat special case // compute malus (positive) costMalus= (cost * (totalActionMalus))/100; } // *************************************************************************** void CSPhraseManager::getPhraseFocusCost(const CSPhraseCom &phrase, uint32 totalActionMalus, sint &cost, sint &costMalus) { CSBrickManager *pBM= CSBrickManager::getInstance(); cost= (sint)getPhraseSumBrickProp(phrase, pBM->FocusPropId, true); // compute malus (positive) costMalus= (cost * (totalActionMalus))/100; } // *************************************************************************** void CSPhraseManager::getPhraseHpCost(const CSPhraseCom &phrase, uint32 totalActionMalus, sint &cost, sint &costMalus) { CSBrickManager *pBM= CSBrickManager::getInstance(); cost= (sint)getPhraseSumBrickProp(phrase, pBM->HpPropId, true); // compute malus (positive) costMalus= (cost * (totalActionMalus))/100; } // *************************************************************************** void CSPhraseManager::getPhraseCastTime(const CSPhraseCom &phrase, uint32 totalActionMalus, float &castTime, float &castTimeMalus) { CSBrickManager *pBM= CSBrickManager::getInstance(); castTime= 0; castTimeMalus= 0; if(phrase.Bricks.size()==0) return; CSBrickSheet *rootBrick= pBM->getBrick(phrase.Bricks[0]); if(!rootBrick) return; // for all bricks get the max "min and Max" cast time, and get the sum of time credit uint sumCastTimeCredit= 0; uint sabrinaCost= 0; float sabrinaRelativeCost = 1.0f; uint minCastTime= 0, maxCastTime= 0; uint i; for(i=0;igetBrick(phrase.Bricks[i]); if(brick) { // For cast time, take the max minCastTime= max(minCastTime, (uint)brick->MinCastTime); maxCastTime= max(maxCastTime, (uint)brick->MaxCastTime); if(brick->SabrinaCost>0) sabrinaCost+= brick->SabrinaCost; if( brick->SabrinaRelativeCost>0.f) sabrinaRelativeCost+= brick->SabrinaRelativeCost; for(uint j=0;jProperties.size();j++) { if(brick->Properties[j].PropId == pBM->CastTimePropId) { sumCastTimeCredit+= abs(brick->SabrinaCost); } } } } // TODO yoyo: may change in future? if(rootBrick->isFaber()) minCastTime= maxCastTime= 20; // Code from Server!! float castingTimeFactor; if(sabrinaCost) castingTimeFactor = (float)sumCastTimeCredit/(float)(sabrinaCost*sabrinaRelativeCost); else castingTimeFactor = (float)sumCastTimeCredit; clamp(castingTimeFactor, 0.f, 1.f); // Check Min >= Max if(maxCastTime < minCastTime) maxCastTime = minCastTime; // Compute the casting time. castTime= (float)(maxCastTime-minCastTime)*castingTimeFactor+(float)minCastTime; // Compute the malus (positive value) castTimeMalus= (float)(maxCastTime-minCastTime)*castingTimeFactor*totalActionMalus/100; // In bricks, save in (1/10sec) castTime/= 10; castTimeMalus/= 10; } // *************************************************************************** void CSPhraseManager::getPhraseMagicRange(const CSPhraseCom &phrase, uint32 totalActionMalus, sint &range, sint &rangeMalus) { CSBrickManager *pBM= CSBrickManager::getInstance(); range= 0; rangeMalus= 0; if(phrase.Bricks.size()==0) return; CSBrickSheet *rootBrick= pBM->getBrick(phrase.Bricks[0]); if(!rootBrick) return; // for all bricks get the min "min and Max" range, and get the sum of range credit uint sumRangeCredit= 0; uint sabrinaCost= 0; float sabrinaRelativeCost= 1.f; uint minRange= 255, maxRange= 255; uint i; for(i=0;igetBrick(phrase.Bricks[i]); if(brick) { // For range, take the min minRange= min(minRange, (uint)brick->MinRange); maxRange= min(maxRange, (uint)brick->MaxRange); if(brick->SabrinaCost>0) sabrinaCost+= brick->SabrinaCost; if(brick->SabrinaRelativeCost>0.f) sabrinaRelativeCost+= brick->SabrinaRelativeCost; for(uint j=0;jProperties.size();j++) { if(brick->Properties[j].PropId == pBM->RangePropId) { // For Range, must add the Creidt Value of the prop sumRangeCredit+= (uint)fabs(brick->Properties[j].Value); } } } } // Code from Server!! float rangeFactor; if(sabrinaCost) rangeFactor = (float)sumRangeCredit/(float)(sabrinaCost*sabrinaRelativeCost); else rangeFactor = (float)sumRangeCredit; clamp(rangeFactor, 0.f, 1.f); // Check Min >= Max if(maxRange < minRange) maxRange = minRange; // Compute the range. the more the credit, the lesser the range float fRange = (float)(maxRange-minRange)*(1-rangeFactor)+(float)minRange; // Compute the range, with malus float fRangeWithMalus = (float)(maxRange-minRange)*(1-rangeFactor)*100/(100+totalActionMalus)+(float)minRange; // In bricks, save in meters range= (sint)fRange; rangeMalus= (sint)fRangeWithMalus - range; } // *************************************************************************** float CSPhraseManager::getPhraseSumBrickProp(const CSPhraseCom &phrase, uint propId, bool doAbs) { CSBrickManager *pBM= CSBrickManager::getInstance(); float sum= 0.f; for(uint i=0;igetBrick(phrase.Bricks[i]); if(brick) { // test all properties for(uint j=0;jProperties.size();j++) { if(brick->Properties[j].PropId==propId) { if(doAbs) sum+= (float)fabs(brick->Properties[j].Value); else sum+= brick->Properties[j].Value; } else if(propId==CSBrickManager::getInstance()->StaPropId && brick->Properties[j].PropId==CSBrickManager::getInstance()->StaWeightFactorId) { CInterfaceManager *im = CInterfaceManager::getInstance(); uint32 weight = (uint32) im->getDbProp("SERVER:USER:DEFAULT_WEIGHT_HANDS")->getValue32() / 10; // weight must be in dg here CDBCtrlSheet *ctrlSheet = dynamic_cast(im->getElementFromId("ui:interface:gestionsets:hands:handr")); if (ctrlSheet) { const CItemSheet *itemSheet = ctrlSheet->asItemSheet(); if (itemSheet && (itemSheet->Family == ITEMFAMILY::MELEE_WEAPON || itemSheet->Family == ITEMFAMILY::RANGE_WEAPON)) { weight = (uint32) ctrlSheet->getItemWeight(); } } ctrlSheet = dynamic_cast(im->getElementFromId("ui:interface:gestionsets:hands:handl")); if (ctrlSheet) { const CItemSheet *itemSheet = ctrlSheet->asItemSheet(); if (itemSheet && (itemSheet->Family == ITEMFAMILY::MELEE_WEAPON /*|| itemSheet->Family == ITEMFAMILY::AMMO*/)) { weight = (weight + (uint32) ctrlSheet->getItemWeight()) /*/ 2*/; } } // float fweight = (float)weight / DB_WEIGHT_SCALE; float value = brick->Properties[j].Value; sint32 value2 = sint32(brick->Properties[j].Value2); if(doAbs) sum+= (float)fabs(value * weight / DB_WEIGHT_SCALE + value2); else sum+= brick->Properties[j].Value * weight / DB_WEIGHT_SCALE + brick->Properties[j].Value2; } } } } return sum; } // *************************************************************************** bool CSPhraseManager::isExecutionInvalidCauseTempInv() const { CTempInvManager *pTIM = CTempInvManager::getInstance(); // Never invalidate action if we are in forage mode if (pTIM->getMode() == TEMP_INV_MODE::Forage) return false; // Invalid action if the temp inv is opened return CTempInvManager::getInstance()->isOpened(); } // *************************************************************************** sint CSPhraseManager::getSuccessRate(TSuccessTable st, sint level, bool partialSuccess) { nlassert(st>=0 && stSuccessTable.size();i++) { sint levelDiff= level - _SuccessTableSheet[st]->SuccessTable[i].RelativeLevel; levelDiff= abs(levelDiff); if(levelDiffSuccessTable[i].PartialSuccessProbability; else bestProba= _SuccessTableSheet[st]->SuccessTable[i].SuccessProbability; } } return bestProba; } else return 0; } // *************************************************************************** void CSPhraseManager::loadSuccessTable() { // load all the table from packed sheet _SuccessTableSheet[STCombat]= dynamic_cast(SheetMngr.get(CSheetId("fight_phrase.succes_chances_table"))); _SuccessTableSheet[STOffensiveMagic]= dynamic_cast(SheetMngr.get(CSheetId("offensive_magic.succes_chances_table"))); _SuccessTableSheet[STCurativeMagic]= dynamic_cast(SheetMngr.get(CSheetId("curative_magic.succes_chances_table"))); _SuccessTableSheet[STCraft]= dynamic_cast(SheetMngr.get(CSheetId("craft.succes_chances_table"))); _SuccessTableSheet[STExtract]= dynamic_cast(SheetMngr.get(CSheetId("extracting.succes_chances_table"))); _SuccessTableSheet[STResistMagic]= dynamic_cast(SheetMngr.get(CSheetId("magic_resist.succes_chances_table"))); _SuccessTableSheet[STResistMagicLink]= dynamic_cast(SheetMngr.get(CSheetId("magic_resist_link.succes_chances_table"))); _SuccessTableSheet[STDodgeParry]= dynamic_cast(SheetMngr.get(CSheetId("dodge_parry.succes_chances_table"))); } // *************************************************************************** void CSPhraseManager::update() { // Update brick state ? if (UserEntity) { bool userIndoor = UserEntity->indoor(); if (_UserIndoor != userIndoor) { _UserIndoor = userIndoor; updateAllMemoryCtrlState(); } } } // *************************************************************************** sint CSPhraseManager::getPhraseSuccessRate(TSuccessTable st, const CSPhraseCom &phrase, sint skillValue, uint minMpLevel) { if(phrase.empty()) return 0; CSBrickManager *pBM= CSBrickManager::getInstance(); // get the Sabrina cost uint32 costSum, creditSum; pBM->getSabrinaCom().getPhraseCost(phrase.Bricks, costSum, creditSum); // get the max cost of all brick uint32 costMax= pBM->getSabrinaCom().getPhraseMaxBrickCost(phrase.Bricks); // Special For combat: maybe replace the costSum CSBrickSheet *rootBrick= pBM->getBrick(phrase.Bricks[0]); if(rootBrick && rootBrick->isCombat()) { // this to avoid problem with the default attack wich has a 0 cost... costSum= max(costSum, getRightHandEffectiveLevel()); } // New Formula to get the Entry in Difficulty table sint deltaLevel; // special for craft (credit is actualy the level of item crafted) if(st==STCraft) deltaLevel= skillValue - min((sint)creditSum,(sint)minMpLevel); // general case else deltaLevel= skillValue + (sint)creditSum - (sint)costSum - (sint)costMax; return getSuccessRate(st, deltaLevel); } // *************************************************************************** void CSPhraseManager::updateExecutionDisplay() { CInterfaceManager *pIM= CInterfaceManager::getInstance(); // **** test if have to draws the icons, and try to recover if sphrase forgeted or removed. // do we have to display icons? bool displayCycle= _CurrentExecuteSlotCycle>=0 && _SelectedMemoryDB==_CurrentExecuteLineCycle; bool displayNext= _CurrentExecuteSlotNext>=0 && _SelectedMemoryDB==_CurrentExecuteLineNext; // if yes, verify that the actions under the icons are relevant if(displayCycle) { // if the memory slot is different from the action executed if(_Memories[_CurrentExecuteLineCycle].Slot[_CurrentExecuteSlotCycle].Id != _CurrentExecutePhraseIdCycle) { // fails! displayCycle= false; // try to find in the action bar, this action (in case of DragAndDrop for instance) for(uint i=0;igetElementFromId(PhraseMemoryViewCycleAction); if(viewCycle) { CInterfaceElement *ctrl= NULL; if(displayCycle) ctrl= pIM->getElementFromId(PhraseMemoryViewSlotBase + toString(_CurrentExecuteSlotCycle)); if(displayCycle && ctrl) { viewCycle->setParentPos(ctrl); viewCycle->setActive(true); viewCycle->invalidateCoords(); } else { viewCycle->setActive(false); } } // DisplayNextSelectionOnActionBar CInterfaceElement *viewNext= pIM->getElementFromId(PhraseMemoryViewNextAction); if(viewNext) { CInterfaceElement *ctrl= NULL; if(displayNext) ctrl= pIM->getElementFromId(PhraseMemoryViewSlotBase + toString(_CurrentExecuteSlotNext)); if(displayNext && ctrl) { viewNext->setParentPos(ctrl); viewNext->setActive(true); viewNext->invalidateCoords(); } else { viewNext->setActive(false); } } // **** Display Next Action on Slot in PLAYER window , whatever the selected memory line is if(_CurrentExecutePhraseIdCycle>0 || _CurrentExecutePhraseIdNext>0) { // get the next phrase uint nextPhrase; if(_CurrentExecutePhraseIdNext>0) nextPhrase= _CurrentExecutePhraseIdNext; else nextPhrase= _CurrentExecutePhraseIdCycle; // display this slot _NextExecuteLeaf->setValue32(nextPhrase); // display or not the widget "this is a cyclic action" _NextExecuteIsCyclicLeaf->setValue32(_CurrentExecutePhraseIdNext==0 || _CurrentExecutePhraseIdNext==_CurrentExecutePhraseIdCycle); } else { _NextExecuteLeaf->setValue32(0); _NextExecuteIsCyclicLeaf->setValue32(0); } } // *************************************************************************** void CSPhraseManager::clientExecute(uint32 memoryLine, uint32 memorySlot, bool cyclic) { if(cyclic) { // Inc Local Counter For Server/Client synchronisation _PhraseCycleExecuteCounter++; _PhraseCycleExecuteCounter&=PHRASE_EXECUTE_COUNTER_MASK; // what slot set? _CurrentExecuteLineCycle= memoryLine; _CurrentExecuteSlotCycle= memorySlot; _CurrentExecutePhraseIdCycle= getMemorizedPhrase(memoryLine, memorySlot); } else { // Inc Local Counter For Server/Client synchronisation _PhraseNextExecuteCounter++; _PhraseNextExecuteCounter&=PHRASE_EXECUTE_COUNTER_MASK; // what slot set? _CurrentExecuteLineNext= memoryLine; _CurrentExecuteSlotNext= memorySlot; _CurrentExecutePhraseIdNext= getMemorizedPhrase(memoryLine, memorySlot); } // update the Action Execution view updateExecutionDisplay(); // Local simulation if(ClientCfg.Local) { if(cyclic) _PhraseDebugEndCyclicAction= T1 + 8000; else _PhraseDebugEndNextAction= T1 + 2000; } } // *************************************************************************** void CSPhraseManager::cancelClientExecute(bool cyclic) { if(cyclic) { // Dec Local Counter For Server/Client synchronisation _PhraseCycleExecuteCounter--; _PhraseCycleExecuteCounter&=PHRASE_EXECUTE_COUNTER_MASK; // reset _CurrentExecuteLineCycle= -1; _CurrentExecuteSlotCycle= -1; _CurrentExecutePhraseIdCycle= 0; } else { // Dec Local Counter For Server/Client synchronisation _PhraseNextExecuteCounter--; _PhraseNextExecuteCounter&=PHRASE_EXECUTE_COUNTER_MASK; // what slot set? _CurrentExecuteLineNext= -1; _CurrentExecuteSlotNext= -1; _CurrentExecutePhraseIdNext= 0; } // update the Action Execution view updateExecutionDisplay(); } // *************************************************************************** void CSPhraseManager::sendExecuteToServer(uint32 memoryLine, uint32 memorySlot, bool cyclic) { CInterfaceManager *pIM= CInterfaceManager::getInstance(); // Local simulation if(ClientCfg.Local) { if(cyclic) pIM->displaySystemInfo("PhraseCycleCasted:"+toString(memoryLine)+"_"+toString(memorySlot), "CHK"); else pIM->displaySystemInfo("PhraseNextCasted:"+toString(memoryLine)+"_"+toString(memorySlot), "CHK"); extern void debugUpdateActionBar(); debugUpdateActionBar(); } // Send the execute to server. else { // before, append the execution counter to the list of ACK to wait appendCurrentToAckExecute(cyclic); // send msg CBitMemStream out; const char *msg; if(cyclic) msg= "PHRASE:EXECUTE_CYCLIC"; else msg= "PHRASE:EXECUTE"; if(GenericMsgHeaderMngr.pushNameToStream(msg, out)) { //serial the sentence memorized index uint8 memoryId= (uint8)memoryLine; uint8 slotId= (uint8)memorySlot; out.serial( memoryId ); out.serial( slotId ); NetMngr.push(out); } else nlinfo("unknown message %s", msg); } } // *************************************************************************** void CSPhraseManager::executeCraft(uint32 memoryLine, uint32 memorySlot, uint32 planSheetId, std::vector &mpItemPartList, std::vector specificItemList) { CInterfaceManager *pIM= CInterfaceManager::getInstance(); // **** Do also a clientExecute to display the correct craft action (NB: not cyclic!) clientExecute(memoryLine, memorySlot, false); // **** Then do a special sendExecuteToServer // Local simulation if(ClientCfg.Local) { pIM->displaySystemInfo("PhraseCraftCasted:"+toString(memoryLine)+"_"+toString(memorySlot), "CHK"); extern void debugUpdateActionBar(); debugUpdateActionBar(); } // Send the execute to the server else { // before, append the execution counter to the list of ACK to wait appendCurrentToAckExecute(false); // send msg CBitMemStream out; const char *msg; msg= "PHRASE:EXECUTE_FABER"; if(GenericMsgHeaderMngr.pushNameToStream(msg, out)) { // Serialize the faberPlan first. out.serial( planSheetId ); // Serial the sentence memorized index uint8 memoryId= (uint8)memoryLine; uint8 slotId= (uint8)memorySlot; out.serial( memoryId ); out.serial( slotId ); // Then serial the list of Mp index out.serialCont( mpItemPartList ); out.serialCont( specificItemList ); NetMngr.push(out); } else nlinfo("unknown message %s", msg); } } // *************************************************************************** void CSPhraseManager::executeCristalize(uint32 memoryLine, uint32 memorySlot) { CInterfaceManager *pIM= CInterfaceManager::getInstance(); // **** Increment counter client side (like an execution) (no cylic) clientExecute(memoryLine, memorySlot, false); // **** special sendExecuteToServer if(ClientCfg.Local) { pIM->displaySystemInfo("PhraseCristalize:"+toString(memoryLine)+"_"+toString(memorySlot), "CHK"); extern void debugUpdateActionBar(); debugUpdateActionBar(); } else { // before, append the execution counter to the list of ACK to wait appendCurrentToAckExecute(false); // send msg CBitMemStream out; const string sMsg = "PHRASE:CRISTALIZE"; if(GenericMsgHeaderMngr.pushNameToStream(sMsg, out)) { //serial the sentence memorized index uint8 memoryId= (uint8)memoryLine; uint8 slotId= (uint8)memorySlot; out.serial( memoryId ); out.serial( slotId ); NetMngr.push(out); } else nlinfo("unknown message %s", sMsg.c_str()); } } // *************************************************************************** void CSPhraseManager::executeDefaultAttack() { // Try to find a "default attack" in the memories, and prefer launch it instead uint32 memoryLine, memorySlot; if(findDefaultAttack(memoryLine, memorySlot)) { clientExecute(memoryLine, memorySlot, true); sendExecuteToServer(memoryLine, memorySlot, true); } else { // Set the database in local. if(ClientCfg.Local) { IngameDbMngr.setProp("Entities:E0:P" + toString(CLFECOMMON::PROPERTY_MODE), MBEHAV::COMBAT); // Mode UserEntity->updateVisualProperty(0, CLFECOMMON::PROPERTY_MODE); } else { // Attack MSG. const string msgName = "COMBAT:DEFAULT_ATTACK"; CBitMemStream out; if(GenericMsgHeaderMngr.pushNameToStream(msgName, out)) NetMngr.push(out); else nlwarning("UE::attack: unknown message named '%s'.", msgName.c_str()); } } } // *************************************************************************** void CSPhraseManager::appendCurrentToAckExecute(bool cyclic) { CAckExecuteEntry entry; // wait an ack from server entry.State= CAckExecuteEntry::WaitAck; if(cyclic) { entry.Counter= _PhraseCycleExecuteCounter; entry.MemoryLine= _CurrentExecuteLineCycle; entry.MemorySlot= _CurrentExecuteSlotCycle; entry.PhraseId= _CurrentExecutePhraseIdCycle; _AckExecuteCycleList.push_back(entry); #ifdef PHRASE_DEBUG_VERBOSE nlinfo("ACK-ADD: CYCLIC. counter=%d. size=%d.", entry.Counter, _AckExecuteCycleList.size()); #endif } else { entry.Counter= _PhraseNextExecuteCounter; entry.MemoryLine= _CurrentExecuteLineNext; entry.MemorySlot= _CurrentExecuteSlotNext; entry.PhraseId= _CurrentExecutePhraseIdNext; _AckExecuteNextList.push_back(entry); #ifdef PHRASE_DEBUG_VERBOSE nlinfo("ACK-ADD: NEXT. counter=%d. size=%d.", entry.Counter, _AckExecuteNextList.size()); #endif } // avoid any problems if server is too laggy. flush the list while(_AckExecuteCycleList.size()>PHRASE_EXECUTE_COUNTER_MASK+1) _AckExecuteCycleList.pop_front(); while(_AckExecuteNextList.size()>PHRASE_EXECUTE_COUNTER_MASK+1) _AckExecuteNextList.pop_front(); } // *************************************************************************** void CSPhraseManager::resetAckExecuteList(bool cyclic) { if(cyclic) { _AckExecuteCycleList.clear(); #ifdef PHRASE_DEBUG_VERBOSE nlinfo("ACK-RESET: CYCLIC."); #endif } else { _AckExecuteNextList.clear(); #ifdef PHRASE_DEBUG_VERBOSE nlinfo("ACK-RESET: NEXT."); #endif } } // *************************************************************************** void CSPhraseManager::receiveAckExecuteFromServer(bool cyclic, uint counterValue, bool ok) { // get the approriated list TAckExecuteList &ackList= cyclic?_AckExecuteCycleList:_AckExecuteNextList; // **** if it's a validation if(ok) { // try to find the element in list TAckExecuteList::iterator it= ackList.begin(); for(;it!=ackList.end();it++) { if(it->Counter==counterValue) { // found, mark it->State= CAckExecuteEntry::OK; // erase all elements before it ackList.erase(ackList.begin(), it); break; } } // NB: if not found, noop (NB: possible for instance if msg arrives after DB update, or if order // of ACK is not good) #ifdef PHRASE_DEBUG_VERBOSE nlinfo("ACK-RECEIVE-OK: %s. counter=%d. size=%d", cyclic?"CYCLIC":"NEXT", counterValue, ackList.size()); #endif } // **** server cancelation else { // try to find the element in list bool found= false; TAckExecuteList::iterator it= ackList.begin(); for(;it!=ackList.end();it++) { if(it->Counter==counterValue) { // found, mark as KO it->State= CAckExecuteEntry::KO; found= true; break; } } // NB: if not found, noop (NB: possible for instance if msg arrives after DB update, or if order // of ACK is not good) // if element succesfuly marked, must find which action we have to fall back if(found) { // find the first element in list (start from end) that is not in KO state (ie WaitAck or OK) bool foundFallBack= false; CAckExecuteEntry fallBack; TAckExecuteList::reverse_iterator it= ackList.rbegin(); for(;it!=ackList.rend();it++) { if(it->State!=CAckExecuteEntry::KO) { foundFallBack= true; fallBack= *it; break; } } // if no fallback found (the list contains only KO) if(!foundFallBack) { // setup an empty slot fallBack.MemoryLine= -1; fallBack.MemorySlot= -1; fallBack.PhraseId= 0; } // replace the Next or Cycle action view, with this fallback slot. if(cyclic) { _CurrentExecuteLineCycle= fallBack.MemoryLine; _CurrentExecuteSlotCycle= fallBack.MemorySlot; _CurrentExecutePhraseIdCycle= fallBack.PhraseId; } else { _CurrentExecuteLineNext= fallBack.MemoryLine; _CurrentExecuteSlotNext= fallBack.MemorySlot; _CurrentExecutePhraseIdNext= fallBack.PhraseId; } // update views updateExecutionDisplay(); } #ifdef PHRASE_DEBUG_VERBOSE nlinfo("ACK-RECEIVE-KO: %s. counter=%d. size=%d", cyclic?"CYCLIC":"NEXT", counterValue, ackList.size()); #endif } } // *************************************************************************** /** Called when the database counter change */ class CHandlerPhraseCounterUpdate : public IActionHandler { public: virtual void execute (CCtrlBase * /* pCaller */, const string &Params) { CSPhraseManager *pPM = CSPhraseManager::getInstance(); // Cyclic Cast? bool cyclic; cyclic= getParam(Params, "cyclic") == "1" ; if(cyclic) { // if synchronized if(pPM->isPhraseCycleExecuteCounterSync()) { // reset the CycleAckList pPM->resetAckExecuteList(true); // reset slot played. pPM->_CurrentExecuteLineCycle= -1; pPM->_CurrentExecuteSlotCycle= -1; pPM->_CurrentExecutePhraseIdCycle= 0; // update views pPM->updateExecutionDisplay(); } } else { // if synchronized if(pPM->isPhraseNextExecuteCounterSync()) { // reset the NextAckList pPM->resetAckExecuteList(false); // reset slot played. pPM->_CurrentExecuteLineNext= -1; pPM->_CurrentExecuteSlotNext= -1; pPM->_CurrentExecutePhraseIdNext= 0; // update views pPM->updateExecutionDisplay(); } } } }; REGISTER_ACTION_HANDLER( CHandlerPhraseCounterUpdate, "phrase_counter_update"); // *************************************************************************** class CHandlerPhraseDebugClient : public IActionHandler { public: virtual void execute (CCtrlBase * /* pCaller */, const string &/* Params */) { if(ClientCfg.Local) { CInterfaceManager *pIM= CInterfaceManager::getInstance(); CSPhraseManager *pPM = CSPhraseManager::getInstance(); // simulate server response for Action if(T1>pPM->_PhraseDebugEndNextAction) { // copy counter pIM->getDbProp(PHRASE_DB_COUNTER_NEXT)->setValue32(pPM->_PhraseNextExecuteCounter); } if(T1>pPM->_PhraseDebugEndCyclicAction) { // copy counter pIM->getDbProp(PHRASE_DB_COUNTER_CYCLE)->setValue32(pPM->_PhraseCycleExecuteCounter); } sint64 st= pIM->getDbProp("UI:VARIABLES:CURRENT_SERVER_TICK")->getValue64(); sint64 stEnd= pIM->getDbProp("SERVER:USER:ACT_TEND")->getValue64(); if(stEnd && st>stEnd+20) pIM->getDbProp("SERVER:USER:ACT_TEND")->setValue64(0); } } }; REGISTER_ACTION_HANDLER( CHandlerPhraseDebugClient, "phrase_debug_client"); // *************************************************************************** void CSPhraseManager::setEquipInvalidation(sint64 serverTick, sint invalidTickTime) { // if the item takes no time to invalidate, no-op! if(invalidTickTime<0) { return; } sint64 tempTick = serverTick + invalidTickTime; if(tempTick==0) { _EquipInvalidationEnd = 0; } else { _EquipInvalidationEnd= max(_EquipInvalidationEnd, tempTick); } } // *************************************************************************** void CSPhraseManager::updateEquipInvalidation(sint64 serverTick) { bool precState= isExecutionInvalidCauseEquip(); // set new server tick _CurrentServerTick= serverTick; // if the state has changed, must update the ctrl states! if(precState != isExecutionInvalidCauseEquip()) { updateAllMemoryCtrlState(); } } // *************************************************************************** void CSPhraseManager::updateAllActionRegen() { if (_RegenTickRangeTouched) { TTicks startTime = CTime::getPerformanceTime(); updateAllMemoryCtrlRegenTickRange(); _RegenTickRangeTouched = false; TTicks endTime = CTime::getPerformanceTime(); //nldebug("***** %d ms for CSPhraseManager::updateAllActionRegen", (int) (1000 * CTime::ticksToSecond(endTime - startTime))); } } // *************************************************************************** bool CSPhraseManager::isExecutionInvalidCauseEquip() const { return _EquipInvalidationEnd > _CurrentServerTick; } // *************************************************************************** void CSPhraseManager::sendDeleteToServer(uint32 slot) { if(!ClientCfg.Local) { CBitMemStream out; if(GenericMsgHeaderMngr.pushNameToStream("PHRASE:DELETE", out)) { uint16 slotId= (uint16)slot; out.serial(slotId); NetMngr.push(out); } else nlwarning(" unknown message name 'PHRASE:DELETE"); } } // *************************************************************************** void CSPhraseManager::sendLearnToServer(uint32 slot) { CSPhraseCom phrase= getPhrase(slot); if(phrase.empty()) return; if(!ClientCfg.Local) { CBitMemStream out; if(GenericMsgHeaderMngr.pushNameToStream("PHRASE:LEARN", out)) { uint16 slotId= (uint16)slot; out.serial(slotId); out.serial(phrase); NetMngr.push(out); } else nlwarning(" unknown message name 'PHRASE:LEARN"); } } // *************************************************************************** void CSPhraseManager::fullDelete(uint32 slot) { // auto-forget all before. forgetAllThatUsePhrase(slot); // erase it. erasePhrase(slot); // send the erase to server sendDeleteToServer(slot); } // *************************************************************************** uint CSPhraseManager::countAllThatUsePhrase(uint32 slot) { if(slot==0) return 0; // for all memory slot uint count= 0; for(uint i=0;i<_Memories.size();i++) { for(uint j=0;jgetDbProp("LOCAL:INVENTORY:HAND:0:INDEX_IN_BAG")->getValue32(); // if something in hand if(itemSlot>0) { CCDBNodeLeaf *node= pIM->getDbProp("LOCAL:INVENTORY:BAG:"+toString(itemSlot-1) +":SHEET", false); if(node) item= node->getValue32(); } return item; } // *************************************************************************** // retrieve the current Skill of the Item in RightHand SKILLS::ESkills getRightHandItemSkill() { SKILLS::ESkills itemSkill= SKILLS::unknown; CSheetId item= getRightHandItem(); if(item.asInt()!=0) { // get the ItemSheet. CItemSheet *itemSheet= dynamic_cast(SheetMngr.get(item)); if(itemSheet) itemSkill= itemSheet->getRequiredSkill(); } else { // Empty Hand => Hand skill itemSkill= SKILLS::SFMCAH; } return itemSkill; } // *************************************************************************** // retrieve the current CraftToolType in RightHand TOOL_TYPE::TCraftingToolType getRightHandCraftToolType() { TOOL_TYPE::TCraftingToolType toolType= TOOL_TYPE::Unknown; CSheetId item= getRightHandItem(); if(item.asInt()!=0) { // get the ItemSheet. CItemSheet *itemSheet= dynamic_cast(SheetMngr.get(item)); if(itemSheet && itemSheet->Family == ITEMFAMILY::CRAFTING_TOOL) toolType= itemSheet->Tool.CraftingToolType; } return toolType; } // *************************************************************************** uint32 getRightHandEffectiveLevel() { CInterfaceManager *pIM= CInterfaceManager::getInstance(); CSkillManager *pSM= CSkillManager::getInstance(); uint32 effectiveLevel= 0; // **** get the right hand item skill value SKILLS::ESkills rhSkill= getRightHandItemSkill(); effectiveLevel= pSM->getSkillValueMaxBranch(rhSkill); // **** get the right hand item 'required skill' // get the RightHand bag index sint32 itemSlot= pIM->getDbProp("LOCAL:INVENTORY:HAND:0:INDEX_IN_BAG")->getValue32(); // if something in hand if(itemSlot>0) { CCDBNodeLeaf *node= pIM->getDbProp("LOCAL:INVENTORY:BAG:"+toString(itemSlot-1) +":QUALITY", false); if(node) // if the right hand item quality is less than our skill value, take it.... effectiveLevel= min(effectiveLevel, (uint32)node->getValue32()); } // else awlays take the bare hand skill level (above) return effectiveLevel; } // *************************************************************************** static sint getRightHandEnchantValue() { CInterfaceManager *pIM= CInterfaceManager::getInstance(); sint ret= 0; // get the RightHand bag index sint32 itemSlot= pIM->getDbProp("LOCAL:INVENTORY:HAND:0:INDEX_IN_BAG")->getValue32(); // if something in hand if(itemSlot>0) { CCDBNodeLeaf *node= pIM->getDbProp("LOCAL:INVENTORY:BAG:"+toString(itemSlot-1) +":ENCHANT", false); if(node) ret= node->getValue32(); } return ret; } // *************************************************************************** void CSPhraseManager::updateMemoryCtrlRegenTickRange(uint memorySlot, CDBCtrlSheet *ctrl) { // uint memoryLine= getSelectedMemoryLineDB(); sint32 phraseId= getMemorizedPhrase(memoryLine, memorySlot); // if(phraseId) { const CSPhraseCom &phrase = getPhrase(phraseId); CTickRange rtr = getRegenTickRange(phrase); if ((_RegenPhrases.find(phraseId) != _RegenPhrases.end()) && rtr.isEmpty() && !ctrl->getRegenTickRange().isEmpty()) { ctrl->startNotifyAnim(); // anim to state that the phraseis available again _RegenPhrases.erase(_RegenPhrases.find(phraseId)); } if (!rtr.isEmpty()) _RegenPhrases.insert(phraseId); ctrl->setRegenTickRange(rtr); } } // *************************************************************************** uint64 CSPhraseManager::getPhraseRequiredFlags(const CSPhraseCom &phrase) { CSBrickManager *pBM= CSBrickManager::getInstance(); uint64 phraseFlags = 0; for(uint i=0;igetBrick(phrase.Bricks[i]); if(brick) phraseFlags|= brick->BrickRequiredFlags; } return phraseFlags; } // *************************************************************************** CTickRange CSPhraseManager::getRegenTickRange(const CSPhraseCom &phrase) const { CTickRange tickRange; if(!phrase.empty()) { CSBrickManager *pBM= CSBrickManager::getInstance(); // Get the RootBrick of the Phrase CSBrickSheet *brick= pBM->getBrick(phrase.Bricks[0]); if(brick) { if (brick->isSpecialPower()) { uint64 phraseFlags = getPhraseRequiredFlags(phrase); for(uint b = 0; b < 64; ++b) { if (phraseFlags & (uint64(1) << b)) { CTickRange newTR = getRegenTickRange(b); if (!newTR.isEmpty()) { if (tickRange.StartTick == tickRange.EndTick) { tickRange = newTR; } else { tickRange.extend(newTR); } } } } } } } return tickRange; } // *************************************************************************** NLMISC::TGameCycle CSPhraseManager::getPowerDisableTime(const CSPhraseCom &phrase) const { float regenTime = 0; if(!phrase.empty()) { CSBrickManager *pBM= CSBrickManager::getInstance(); for (uint brickIndex = 0; brickIndex < phrase.Bricks.size(); ++brickIndex) { // Get the RootBrick of the Phrase CSBrickSheet *brick= pBM->getBrick(phrase.Bricks[brickIndex]); if(brick) { for (uint prop = 0; prop < brick->Properties.size(); ++prop) { std::string::size_type endPos = brick->Properties[prop].Text.find(":"); if (endPos != std::string::npos) { std::string propName = brick->Properties[prop].Text.substr(0, endPos); if (nlstricmp(propName, "SP_RECAST_TIME") == 0) { regenTime = std::max(regenTime, brick->Properties[prop].Value); } } } } } } return (NLMISC::TGameCycle) (regenTime * 10.f); } // *************************************************************************** NLMISC::TGameCycle CSPhraseManager::getRegenTime(const CSPhraseCom &phrase) const { CTickRange tickRange = getRegenTickRange(phrase); sint left = LastGameCycle - tickRange.StartTick; return (uint) std::max(0, left); } // *************************************************************************** NLMISC::TGameCycle CSPhraseManager::getTotalRegenTime(const CSPhraseCom &phrase) const { CTickRange tickRange = getRegenTickRange(phrase); return (tickRange.EndTick - tickRange.StartTick); } // *************************************************************************** void CSPhraseManager::updateMemoryCtrlState(uint memorySlot, CDBCtrlSheet *ctrl, SKILLS::ESkills itemSkill) { /* Update Gray State of Memory Ctrls. Can Execute only if skill of the phrase and skill of the selected Weapon are compatible. Eg: can execute a "FyrosAxe" phrase on a FyrosAxe Weapon (skills on same branch) can execute a "Combat" phrase on a FyrosAxe Weapon (skills on same branch) can't execute a "MatisAxe" phrase on a FyrosAxe Weapon (skills not on same branch) Additionaly, a Magic Phrase is always executable */ CInterfaceManager *pIM = CInterfaceManager::getInstance(); CSBrickManager *pBM = CSBrickManager::getInstance(); CSkillManager *pSM = CSkillManager::getInstance(); // get the slot info uint memoryLine= getSelectedMemoryLineDB(); bool newIsMacro= isMemorizedMacro(memoryLine, memorySlot); sint32 macroId= getMemorizedMacro(memoryLine, memorySlot); sint32 phraseId= getMemorizedPhrase(memoryLine, memorySlot); CMemorySlot *memSlot= NULL; if(memoryLine<_Memories.size() && memorySlotisSPhraseId() || ctrl->isMacro()) ) { // if the new ctrl is a phrase if(!newIsMacro) { // **** if ctrl is macro, change type if(ctrl->isMacro()) { ctrl->setType(CCtrlSheetInfo::SheetType_SPhraseId); ctrl->setListMenuRight(PhraseMemoryPhraseMenu); ctrl->setActionOnLeftClick(PhraseMemoryPhraseAction); } // **** get the related ctrl skill SKILLS::ESkills phraseRootSkill= SKILLS::unknown; bool isGlobalMemPhrase= false; bool isFaberPhrase= false; bool isHarvestPhrase= false; bool isCombatPhrase= false; bool isProcEnchantmentPhrase= false; bool isSpecialPowerPhrase= false; if(phraseId) { const CSPhraseCom &phrase= getPhrase(phraseId); phraseRootSkill= getPhraseRootSkill(phrase.Bricks); if(!phrase.empty()) { // Get the RootBrick of the Phrase CSBrickSheet *brick= pBM->getBrick(phrase.Bricks[0]); if(brick) { // Magic or SpecialPower Root? isGlobalMemPhrase= brick->isMagic() || brick->isSpecialPower(); isSpecialPowerPhrase= brick->isSpecialPower(); isFaberPhrase= brick->isFaber(); isHarvestPhrase= brick->isHarvest(); isCombatPhrase= brick->isCombat(); isProcEnchantmentPhrase= brick->isProcEnchantment(); } } } // **** gray ctrl if not compatibles bool valid= false; if( isGlobalMemPhrase || pSM->areSkillOnSameBranch(phraseRootSkill, itemSkill) ) valid= true; // *** Combat: the item must be compatible with the phrase if( valid && isCombatPhrase && !skillCompatibleWithCombatPhrase(itemSkill, getPhrase(phraseId).Bricks) ) valid= false; // *** Power: item enchant. if( valid && isSpecialPowerPhrase && !skillCompatibleWithSpecialPowerPhrase(itemSkill, getPhrase(phraseId).Bricks) ) valid= false; // *** Harvet : not in indoor if (isHarvestPhrase && UserEntity->indoor()) valid= false; // **** Faber special: valid only if good ToolType if(valid && itemSkill == SKILLS::SC && isFaberPhrase) { valid= false; TOOL_TYPE::TCraftingToolType cttHand= getRightHandCraftToolType(); // Get The FaberPlan const CSPhraseCom &phrase= getPhrase(phraseId); if(!phrase.empty()) { // Ok if the item built can be build with the tool in hand valid= pBM->getSabrinaCom().getPhraseFaberPlanToolType(phrase.Bricks) == cttHand; } } // **** Proc Enchantment: valid only if the item in right hand is enchanted if( isProcEnchantmentPhrase ) { valid= false; sint enchantValue= getRightHandEnchantValue(); // dec because 0 == not enchanted, and 1 == enchanted but no more charge enchantValue--; // if some charge left if(enchantValue>=1) valid= true; } // *** check if need an item in hands if( valid && isCombatPhrase && !phraseUsableWithEmptyHands( getPhrase(phraseId).Bricks) ) { if( getRightHandItem() == CSheetId::Unknown ) valid= false; } // **** special: valid only according to BRICK_TICK_RANGE dbProp. if(valid) { uint64 currentFlags; if (isSpecialPowerPhrase) { currentFlags = ~(uint64(0)); //Power Flag for (uint powerIndex = 32; powerIndex < 64; ++powerIndex) { if (!getRegenTickRange(powerIndex).isEmpty()) { currentFlags &= ~(uint64(1) << powerIndex); } } } else { currentFlags = uint64(0); //CombatEvent Flag for (uint powerIndex = 0; powerIndex < 32; ++powerIndex) { if (!getRegenTickRange(powerIndex).isEmpty()) { currentFlags |= uint64(1) << powerIndex; } } } // Build the phrase required bricks flags const CSPhraseCom &phrase= getPhrase(phraseId); uint64 phraseFlags = getPhraseRequiredFlags(phrase); // if required flags are not present in database, invalid if( (currentFlags & phraseFlags) != phraseFlags ) { valid= false; } } // **** valid only if the user is not equiping himself if(isExecutionInvalidCauseEquip()) { valid= false; } // Check if the temporary inventory is opened if(isExecutionInvalidCauseTempInv()) { valid= false; } // **** update state ctrl->setGrayed(!valid); } else { bool neadReadMacro= false; nlassert(memSlot); // **** if ctrl is macro, change type if(ctrl->isSPhraseId()) { ctrl->setType(CCtrlSheetInfo::SheetType_Macro); ctrl->setListMenuRight(PhraseMemoryMacroMenu); ctrl->setActionOnLeftClick(PhraseMemoryMacroAction); neadReadMacro= true; } // macro changed? if(ctrl->getMacroId() != macroId) neadReadMacro= true; // slot dirty? if(memSlot->IsMacroVisualDirty) { neadReadMacro= true; // clean memSlot->IsMacroVisualDirty= false; } // change macro if(neadReadMacro) { CMacroCmdManager *pMM= CMacroCmdManager::getInstance(); const CMacroCmd *macroCmd= pMM->getMacroFromMacroID(macroId); if(macroCmd) ctrl->readFromMacro(*macroCmd); } // **** set gray state ctrl->setGrayed(false); } } _RegenTickRangeTouched = true; } // *************************************************************************** void CSPhraseManager::updateAllMemoryCtrlState() { TTicks startTime = CTime::getPerformanceTime(); // get the item skill SKILLS::ESkills itemSkill= getRightHandItemSkill(); // Update All Memory Ctrls CInterfaceManager *pIM = CInterfaceManager::getInstance(); // For All the selected Memory Ctrls. for(uint i=0;i(pIM->getElementFromId(PhraseMemoryCtrlBase + toString(i)) ); if(ctrl) { // update the valid state. updateMemoryCtrlState(i, ctrl, itemSkill); } } TTicks endTime = CTime::getPerformanceTime(); //nldebug("***** %d ms for CSPhraseManager::updateAllMemoryCtrlState", (int) (1000 * CTime::ticksToSecond(endTime - startTime))); } // *************************************************************************** void CSPhraseManager::updateAllMemoryCtrlRegenTickRange() { for(uint i=0;i=PHRASE_MAX_MEMORY_SLOT) return NULL; // Get the ctrl CInterfaceManager *pIM= CInterfaceManager::getInstance(); return dynamic_cast(pIM->getElementFromId(PhraseMemoryCtrlBase + toString(memorySlot))); } // *************************************************************************** void CSPhraseManager::updateMemoryCtrlState(uint memorySlot) { CDBCtrlSheet *ctrl= getMemorySlotCtrl(memorySlot); if(ctrl) { // update the valid state. updateMemoryCtrlState(memorySlot, ctrl, getRightHandItemSkill()); } } // *************************************************************************** void CSPhraseManager::updateMemoryCtrlRegenTickRange(uint memorySlot) { CDBCtrlSheet *ctrl= getMemorySlotCtrl(memorySlot); if(ctrl) { // update the valid state. updateMemoryCtrlRegenTickRange(memorySlot, ctrl); } } // *************************************************************************** SKILLS::ESkills CSPhraseManager::getPhraseRootSkill(const std::vector &phraseBricks) const { if(phraseBricks.empty()) return SKILLS::unknown; CSBrickManager *pBM= CSBrickManager::getInstance(); CSBrickSheet *rootBrick= pBM->getBrick(phraseBricks[0]); if(!rootBrick) return SKILLS::unknown; else return rootBrick->getSkill(); } // *************************************************************************** bool CSPhraseManager::skillCompatibleWithCombatPhrase(SKILLS::ESkills compareSkill, const std::vector &phraseBricks) const { // special case for hand to hand due to possible brick restriction if( compareSkill == SKILLS::SFMCAHM ) { if( !phraseUsableWithEmptyHands(phraseBricks) ) { return false; } } CSkillManager *pSM= CSkillManager::getInstance(); // **** Build the "phrase skill compatible" formula. CReqSkillFormula phraseFormula= buildCombatPhraseSkillFormula(phraseBricks); // **** test if one of the ored skill match std::list::iterator it(phraseFormula.OrSkills.begin()), end(phraseFormula.OrSkills.end()); for(;it!=end;it++) { CReqSkillFormula::CSkillValueAnd skillAnd= *it; // if the and is not a single value, then no chance to match (eg: SFR&SFM won't match any compareSkill) if(skillAnd.AndSkills.size()==1) { // ok if the skill to compare is more restrictive (or equal) to this skill if(pSM->isSkillAncestor(skillAnd.AndSkills.begin()->Skill, compareSkill)) return true; } } return false; } // *************************************************************************** bool CSPhraseManager::skillCompatibleWithSpecialPowerPhrase(SKILLS::ESkills compareSkill, const std::vector &phraseBricks) const { CSkillManager *pSM= CSkillManager::getInstance(); bool hasEnchantWeapon = false; vector::const_iterator itBrick, itBrickEnd = phraseBricks.end(); for (itBrick=phraseBricks.begin(); itBrick!=itBrickEnd; ++itBrick) { NLMISC::CSheetId const& brick = *itBrick; if (brick==_EnchantWeaponMainBrick) hasEnchantWeapon = true; } // If phrase enchants a weapon if (hasEnchantWeapon) { // check we have a melee weapon in hand if (pSM->isSkillAncestor(SKILLS::SFM, compareSkill) && compareSkill!=SKILLS::SFMCAH) return true; else return false; } return true; } // *************************************************************************** bool CSPhraseManager::phraseUsableWithEmptyHands(const std::vector &phraseBricks) const { vector::const_iterator itBrick, itBrickEnd = phraseBricks.end(); for (itBrick=phraseBricks.begin(); itBrick!=itBrickEnd; ++itBrick) { CSBrickSheet *brick= CSBrickManager::getInstance()->getBrick(*itBrick); if(brick) { if( !brick->UsableWithEmptyHands ) return false; } } return true; } // *************************************************************************** // *************************************************************************** // MACROS // *************************************************************************** // *************************************************************************** // *************************************************************************** void CSPhraseManager::memorizeMacro(uint32 memoryLine, uint32 memorySlot, uint32 macroId) { if(memorySlot>=PHRASE_MAX_MEMORY_SLOT) return; // enlarge memory if needed if(memoryLine>=_Memories.size()) _Memories.resize(memoryLine+1); // if the slot is a phrase if(_Memories[memoryLine].Slot[memorySlot].isPhrase()) { // forget old phrase slot, both client and server forgetPhrase(memoryLine, memorySlot); sendForgetToServer(memoryLine, memorySlot); } // update memory _Memories[memoryLine].Slot[memorySlot].IsMacro= true; _Memories[memoryLine].Slot[memorySlot].Id= macroId; // must update DB? if((sint32)memoryLine==_SelectedMemoryDB) { // update the DB updateMemoryDBSlot(memorySlot); // update the ctrl state updateMemoryCtrlState(memorySlot); } } // *************************************************************************** void CSPhraseManager::forgetMacro(uint32 memoryLine, uint32 memorySlot) { if(memorySlot>=PHRASE_MAX_MEMORY_SLOT) return; // if the slot eihter don't exist, abort if(memoryLine>=_Memories.size()) return; // if the slot is not a macro if(!_Memories[memoryLine].Slot[memorySlot].isMacro()) return; // update memory. NB: revert to Phrase mode is important _Memories[memoryLine].Slot[memorySlot].IsMacro= false; _Memories[memoryLine].Slot[memorySlot].Id= 0; // must update DB? if((sint32)memoryLine==_SelectedMemoryDB) { // update the db updateMemoryDBSlot(memorySlot); // update the ctrl state updateMemoryCtrlState(memorySlot); } } // *************************************************************************** uint32 CSPhraseManager::getMemorizedMacro(uint32 memoryLine, uint32 memorySlot) const { if(memoryLine>=_Memories.size()) return 0; if(memorySlot>=PHRASE_MAX_MEMORY_SLOT) return 0; if(!_Memories[memoryLine].Slot[memorySlot].isMacro()) return 0; return _Memories[memoryLine].Slot[memorySlot].Id; } // *************************************************************************** bool CSPhraseManager::isMemorizedMacro(uint32 memoryLine, uint32 memorySlot) const { if(memoryLine>=_Memories.size()) return 0; if(memorySlot>=PHRASE_MAX_MEMORY_SLOT) return 0; return _Memories[memoryLine].Slot[memorySlot].isMacro(); } // *************************************************************************** void CSPhraseManager::updateMacroShortcuts(sint32 macroId) { // dirt any macro visual for(uint i=0;i<_Memories.size();i++) { for(uint j=0;j macroList; f.serialCont(macroList); // for each one, test if the macro really exist, then set for(uint i=0;igetMacroFromMacroID(slot.MacroId)) memorizeMacro(slot.MemoryLine, slot.MemorySlot, slot.MacroId); } } // write else { // create the list of macros that resides in the memory vector macroList; // for all mems, test if macro, then append for(uint i=0;i<_Memories.size();i++) { for(uint j=0;j(SheetMngr.get(CSheetId(phraseSheetId))); if(phraseSheet) { // Append the price of all bricks for(uint j=0;jBricks.size();j++) { // add the price only if the brick is not already learned if(!pBM->isBrickKnown(phraseSheet->Bricks[j])) { CSBrickSheet *brick= pBM->getBrick(phraseSheet->Bricks[j]); if(brick) { price+= brick->SPCost; } } } } return price; } // *************************************************************************** uint32 CSPhraseManager::getBasePhraseSheetPrice(uint32 phraseSheetId) { CSBrickManager *pBM= CSBrickManager::getInstance(); uint price= 0; // get the SPhraseSheet CSPhraseSheet *phraseSheet= dynamic_cast(SheetMngr.get(CSheetId(phraseSheetId))); if(phraseSheet) { // Append the price of all bricks for(uint j=0;jBricks.size();j++) { // add the price either if the brick is not already learned CSBrickSheet *brick= pBM->getBrick(phraseSheet->Bricks[j]); if(brick) { price+= brick->SPCost; } } } return price; } // *************************************************************************** void CSPhraseManager::updateBotChatPhrasePrice(uint start, uint end) { end= min(end, (uint)PHRASE_MAX_BOTCHAT_SLOT); start= min(start, end); // For all phrase to test for(uint i=start;igetValue32()); // set the price _BotChatPhrasePriceLeaves[i]->setValue32(price); } } } // *************************************************************************** // *************************************************************************** // PROGRESSION // *************************************************************************** // *************************************************************************** // *************************************************************************** void CSPhraseManager::updatePhraseProgressionDB() { // If DB not inited, no-op if(!_InitInGameDone) return; CSBrickManager *pBM= CSBrickManager::getInstance(); // Fill All the progression db. uint numProgressFill= 0; if(_BookSkillFitler!=SKILLS::unknown) numProgressFill= (uint)_ProgressionPhrases[_BookSkillFitler].Phrases.size(); uint i; uint progressIndex[NumProgressType]; for(i=0;i=PHRASE_MAX_PROGRESSION_SLOT) { continue; } // is this phrase known? bool known= true; CSPhraseSheet *phrase= dynamic_cast(SheetMngr.get(NLMISC::CSheetId(ppi.SheetId))); if(phrase) { // if only one brick is not known, the phrase is not known for(uint j=0;jBricks.size();j++) { if(!pBM->isBrickKnown(phrase->Bricks[j])) { known= false; break; } } } // if show, but only if full learnt, skip it if not fully learnt if(phrase->ShowInAPOnlyIfLearnt && !known) { continue; } // fill with the phrase id _ProgressionDbSheets[progressType][dbIndex]->setValue32(ppi.SheetId); // fill the level info _ProgressionDbLevels[progressType][dbIndex]->setValue32(ppi.Level); // if known, ungray if(known) _ProgressionDbLocks[progressType][dbIndex]->setValue32(0); else { // just gray if all requirement are met in order to learn it if(phraseRequirementOk(ppi.SheetId)) _ProgressionDbLocks[progressType][dbIndex]->setValue32(1); // else redify else _ProgressionDbLocks[progressType][dbIndex]->setValue32(2); } // increment the db index dbIndex++; } // for each progression type for(uint pt= 0;ptsetValue32(0); } // update cache _LastProgressionNumDbFill[pt]= progressIndex[pt]; } } // *************************************************************************** class CPhraseSortEntry { public: CSPhraseManager::CPhraseProgressInfo ProgressInfo; // Key sort uint32 Section; bool Castable; uint32 Type; uint32 Icon; ucstring Text; bool operator<(const CPhraseSortEntry &pse) const { // sort by Section if(Section!=pse.Section) return Sectionpse.Castable; // sort by type if(Type!=pse.Type) return Type skillsToInsert; skillsToInsert.reserve(SKILLS::NUM_SKILLS); memset(skillReqLevel, 0xFF, SKILLS::NUM_SKILLS*sizeof(sint)); // **** For each Phrase Sheet, compute its related progression CSheetManager::TEntitySheetMap::const_iterator it; for(it=SheetMngr.getSheets().begin();it!=SheetMngr.getSheets().end();it++) { if(it->second.EntitySheet->Type==CEntitySheet::SPHRASE) { const CSPhraseSheet *phrase= safe_cast(it->second.EntitySheet); #ifdef PHRASE_DEBUG_VERBOSE static uint pscount= 0; nlinfo("**** PROG PHRASE: %4d, %s", pscount++, phrase->Id.toString().c_str()); #endif // if not shown in ActionProgression window, skip it if(!phrase->ShowInActionProgression) continue; // if not valid, skip it if(!phrase->isValid()) continue; // **** for this phrase, build the required formula, with and without credits CReqSkillFormula skillFormulaProg; // For progression: No Credits CReqSkillFormula skillFormulaFull; // For info: full, With Credits // For all bricks uint i; for(i=0;iBricks.size();i++) { CSBrickSheet *brick= pBM->getBrick(phrase->Bricks[i]); if(brick) { // the brick formula is an OR of all skill required CReqSkillFormula brickFormula; // get all its required Skill for(uint j=0;jRequiredSkills.size();j++) { CSkillValue sv; sv.Skill= brick->RequiredSkills[j].Skill; sv.Value= brick->RequiredSkills[j].Value; brickFormula.orV(sv); } // if not empty if(!brickFormula.empty()) { // AND with phrase requirement formula skillFormulaFull.andV(brickFormula); // if the brick is not a credit if(!brick->isCredit()) skillFormulaProg.andV(brickFormula); } } } /* if the skillFormulaProg is empty(), and if the full formula is not, it is because this phrase is a Credit upgrade!!! we DO WANT see it in the skill tree Additionnaly, for special case of "after critical hit 3" for instance, where the credit is more restrictive than the options, take the full formula instead. Detect this case simply if the full formula is made of only ONE SkillValue */ if(skillFormulaProg.empty() || skillFormulaFull.singleValue()) skillFormulaProg= skillFormulaFull; #ifdef PHRASE_DEBUG_VERBOSE skillFormulaFull.log("FULL"); skillFormulaProg.log("PROG"); #endif // **** For phrase info, store the Full formula _PhraseReqSkillMap[phrase->Id.asInt()]= skillFormulaFull; // **** For phrase progression filter skillsToInsert.clear(); // parse all the required skill, both AND and OR std::list::const_iterator itSVA; for(itSVA= skillFormulaProg.OrSkills.begin();itSVA!=skillFormulaProg.OrSkills.end();itSVA++) { std::list::const_iterator itSV; for(itSV= itSVA->AndSkills.begin();itSV!=itSVA->AndSkills.end();itSV++) { const CSkillValue &sv= *itSV; if(sv.Skill!=SKILLS::unknown) { // get the actual Skill associated to the skillValue (with no sheet data error, // it should be == sv.Skill). SKILLS::ESkills skill= sv.Skill; uint32 value= sv.Value; // eg (SME,10) is incorrect (SME range is [20,50]) => take instead (SM,10) while(valuegetMinSkillValue(skill)) { SKILLS::ESkills sp= pSM->getParent(skill); if(sp==SKILLS::unknown) break; else skill= sp; } /* in case of (SM,25) (eg arise with auras), it means that for example, (SMA,25), or (SMB,25) or (SMC,25) are required. Then we have to add this progression in all those skills! => insert in those skills only! */ insertProgressionSkillRecurs(skill, value, skillReqLevel, skillsToInsert); } } } // then parse the skill to insert list for(i=0;iId.asInt(); ppi.Level= skillReqLevel[skill]; _ProgressionPhrases[skill].Phrases.push_back(ppi); // clean the level, for next phrase skillReqLevel[skill]= -1; } } } // **** Sort each phrase sheet by type/cost // For all skills std::vector phraseSortEntry; for(uint i=0;i(SheetMngr.get(CSheetId(pse.ProgressInfo.SheetId))); NLMISC::CSheetId brickSheet; if(phraseSheet) brickSheet= pBM->getSabrinaCom().getPhraseBestDisplayBrick(phraseSheet->Bricks); // take first the castable phrase pse.Castable= isPhraseCastable(phraseSheet); // sort by section pse.Section= getPhraseSectionFromLevel(pse.ProgressInfo.Level); // get the family of this brick as second sort key CSBrickSheet *brick= pBM->getBrick(brickSheet); if(brick) pse.Type= brick->BrickFamily; else pse.Type= 0; // get the icon of this brick as third sort key if(brick) pse.Icon= (uint32)brick->IdIcon; else pse.Icon= 0; // get the phrase Text as 4th sort key pse.Text= STRING_MANAGER::CStringManagerClient::getSPhraseLocalizedName(CSheetId(pse.ProgressInfo.SheetId)); // avoid mutliple sapce problem strFindReplace(pse.Text, " ", ""); // replace each number with 001 format. strlwr for(uint k=0;kappendBrickLearnedCallback(&_ProgressionUpdate); pSM->appendSkillChangeCallback(&_ProgressionUpdate); } // *************************************************************************** enum {MaxSkillValue= 250}; void CSPhraseManager::insertProgressionSkillRecurs(SKILLS::ESkills skill, uint32 value, sint *skillReqLevel, std::vector &skillsToInsert) { CSkillManager *pSM= CSkillManager::getInstance(); const std::vector &children= pSM->getChildren(skill); // Yoyo: special case for Skill value >250 (disabled bricks).... // Don't insert them if(value>MaxSkillValue) return; // if the value is under the max value of this skill, it's ok // Or special case for the MAX 250 value of skills, ie if the skill has no more children if(value < pSM->getMaxSkillValue(skill) || children.empty()) { // if not used, insert this skill in the "skill used in this phrase progression" if(skillReqLevel[skill]==-1) { skillReqLevel[skill]= value; skillsToInsert.push_back(skill); } // else maximize the level required for this phrase else { skillReqLevel[skill]= max(skillReqLevel[skill], (sint)value); } } // else recurs to children else { for(uint i=0;i &bricks) { CSBrickManager *pBM= CSBrickManager::getInstance(); bricks.clear(); CSPhraseSheet *phraseSheet= dynamic_cast(SheetMngr.get(CSheetId(phraseSheetId))); if(phraseSheet) { uint i; // first get all bricks contained in this phrase set brickSet; for(i=0;iBricks.size();i++) { CSBrickSheet *brick= pBM->getBrick(phraseSheet->Bricks[i]); if(brick) brickSet.insert(brick->Id); } // For all bricks of the phrase. for(i=0;iBricks.size();i++) { CSBrickSheet *brick= pBM->getBrick(phraseSheet->Bricks[i]); if(brick) { // Special case: For "Use Item enchantment" phrase, don't describe requirement if(brick->isProcEnchantment()) { bricks.clear(); return; } // For all its required bricks for(uint j=0;jRequiredBricks.size();j++) { /* if the required brick is not in the current phrase, then it is required E.g: if a phrase has brickA and brickB, and if brickB require brickA then learning the phrase finally don't require brickA! (else won't be possible to learn it if this is the only phrase that offers brickA for instance) */ if( brickSet.find(brick->RequiredBricks[j])==brickSet.end() ) { bricks.push_back(brick->RequiredBricks[j]); } } } } } } // *************************************************************************** bool CSPhraseManager::fameRequirementOk(uint32 phraseSheetId) { CSBrickManager *pBM= CSBrickManager::getInstance(); CSPhraseSheet *phraseSheet= dynamic_cast(SheetMngr.get(CSheetId(phraseSheetId))); if(phraseSheet) { uint i; // first get all bricks contained in this phrase set brickSet; for(i=0;iBricks.size();i++) { CSBrickSheet *brick= pBM->getBrick(phraseSheet->Bricks[i]); if(brick) brickSet.insert(brick->Id); } // For all bricks of the phrase. for(i=0;iBricks.size();i++) { CSBrickSheet *brick= pBM->getBrick(phraseSheet->Bricks[i]); if(brick) { // get player fame for this faction and check with min value needed if( brick->FactionIndex != CStaticFames::INVALID_FACTION_INDEX ) { //TODO } } } return true; } return false; } // *************************************************************************** bool CSPhraseManager::phraseRequirementOk(uint32 phraseSheetId) { CSBrickManager *pBM= CSBrickManager::getInstance(); // **** check skill requirement for this phrase TPhraseReqSkillMap::const_iterator it= _PhraseReqSkillMap.find(phraseSheetId); if(it!=_PhraseReqSkillMap.end()) { const CReqSkillFormula &formula= it->second; if(!formula.evaluate()) return false; } // **** check fame requirement if( !fameRequirementOk(phraseSheetId) ) { return false; } // **** check brick requirement std::vector bricks; buildPhraseBrickRequirement(phraseSheetId, bricks); // For all required bricks for(uint i=0;iisBrickKnown(bricks[i]) ) return false; } // ok all requirement met return true; } // *************************************************************************** bool CSPhraseManager::allowListBrickInHelp(const CSPhraseCom &phrase) const { CSBrickManager *pBM= CSBrickManager::getInstance(); CSBrickSheet *rootBrick= pBM->getBrick(phrase.Bricks[0]); // broken phrase if(!rootBrick) return false; // don't list bricks for Charac upgrades if(BRICK_FAMILIES::isCharacBuyFamily(rootBrick->BrickFamily)) return false; // don't list bricks for "use item enchant" phrase if(rootBrick->isProcEnchantment()) return false; // common case return true; } // *************************************************************************** void CSPhraseManager::getCombatWeaponRestriction(ucstring &text, const CSPhraseCom &phrase, bool& usableWithMelee, bool& usableWithRange) { text.clear(); if(phrase.empty()) return; CSBrickManager *pBM= CSBrickManager::getInstance(); CSBrickSheet *rootBrick= pBM->getBrick(phrase.Bricks[0]); if(rootBrick && rootBrick->isCombat()) { // **** Build the "phrase skill compatible" formula. CReqSkillFormula phraseFormula= buildCombatPhraseSkillFormula(phrase.Bricks); // **** build the dorted array of ored skills std::vector skills; std::list::iterator it(phraseFormula.OrSkills.begin()), end(phraseFormula.OrSkills.end()); for(;it!=end;it++) { CReqSkillFormula::CSkillValueAnd skillAnd= *it; // if the and is not a single value, then no chance to match (eg: SFR&SFM won't match any compareSkill) if(skillAnd.AndSkills.size()==1) { SKILLS::ESkills skill= skillAnd.AndSkills.begin()->Skill; if(skill!=SKILLS::unknown) skills.push_back(skill); } } // sort by skill index. this is the convention, to be sure that we don't search "uiawrSFM_SFR" // while "uiawrSFR_SFM" is defined in en.uxt.... std::sort(skills.begin(), skills.end()); // used to know what weapon's family can be used with this phrase usableWithMelee = false; usableWithRange = false; // **** build the CI18N name std::string idName= "uiawr"; for(uint i=0;i &phraseBricks) const { CSBrickManager *pBM= CSBrickManager::getInstance(); CReqSkillFormula phraseFormula; // default: work with any Skill Fight weapon (ie all... :) ) phraseFormula.andV(CSkillValue(SKILLS::SF)); // for all bricks not Unknown for(uint i=0;igetBrick(phraseBricks[i]); if(brick && brick->getSkill()!=SKILLS::unknown) { CReqSkillFormula brickFormula; for(uint j=0;jUsedSkills.size();j++) { brickFormula.orV(CSkillValue(brick->UsedSkills[j])); } // and with the phraseFormula phraseFormula.andV(brickFormula); } } return phraseFormula; } // *************************************************************************** uint32 CSPhraseManager::getSpellLevel(const std::vector &phraseBricks) const { CSBrickManager *pBM= CSBrickManager::getInstance(); sint32 maxCost= 0; for(uint i=0;igetBrick(phraseBricks[i]); if(brick && !brick->isCredit()) { maxCost= max(maxCost, brick->SabrinaCost); } } return maxCost; } // *************************************************************************** void CSPhraseManager::getResistMagic(bool resistMagic[RESISTANCE_TYPE::NB_RESISTANCE_TYPE], const std::vector &phraseBricks) { // reset for(uint i=0;igetBrick(phraseBricks[i]); if(brick && brick->MagicResistType!=RESISTANCE_TYPE::None) { resistMagic[brick->MagicResistType]= true; } } } // *************************************************************************** uint32 CSPhraseManager::getMemorizedPhraseIfLastOrNewSlot(uint32 memoryLine, uint32 memoryIndex) { // \toto yoyo: There is a Network BUG which prevents us from simply doing {Forget(),Delete()} Old, // then {Learn(),Memorize()} new (Messages are shuffled). // If the memory slot has a Phrase uint32 oldPhraseId= getMemorizedPhrase(memoryLine, memoryIndex); if(oldPhraseId) { // if this is the last referenced, then return this phrase id if(countAllThatUsePhrase(oldPhraseId)==1) { return oldPhraseId; } } // else get New Free Slot return allocatePhraseSlot(); } // *************************************************************************** void CSPhraseManager::fullDeletePhraseIfLast(uint32 memoryLine, uint32 memorySlot) { uint32 phraseId= getMemorizedPhrase(memoryLine, memorySlot); // not a phrase? no-op if(!phraseId) return; // more than 1 use this slot? => no problem (will be forgeted/replaced) if(countAllThatUsePhrase(phraseId)>1) return; // else delete this phrase (server etc....) fullDelete(phraseId); } // *************************************************************************** ucstring CSPhraseManager::getForageExtractionPhraseEcotypeFmt(const CSPhraseCom &phrase) { CSBrickManager *pBM= CSBrickManager::getInstance(); static string tspecString= "FG_ECT_SPC: "; // Test all brick, if find a ecotype specialisation, return fmt for(uint i=0;igetBrick(phrase.Bricks[i]); // if a forage extraction brick if(brick) { // Parse all properties of the brick, if FG_ECT_SPC, this is a terrain speciliasing for(uint j=0;jProperties.size();j++) { const string &text= brick->Properties[j].Text; if(text.compare(0, tspecString.size(), tspecString)==0) { // extract the associated terrain string terran= text.substr(tspecString.size(), string::npos); terran= string("uihelpPhraseForageSuccess")+terran; // try to get the associated I18N if(CI18N::hasTranslation(terran)) return CI18N::get(terran); } } } } // Failure, display all return CI18N::get("uihelpPhraseForageSuccessAll"); } // *************************************************************************** bool CSPhraseManager::findDefaultAttack(uint32 &memoryLine, uint32 &memorySlot) { CSBrickManager *pBM= CSBrickManager::getInstance(); if(_Memories.empty()) return false; // **** Search in all memory lines, but try the currently selected one std::deque memoryLineSort; memoryLineSort.resize(_Memories.size()); uint32 i; for(i=0;i=0 && _SelectedMemoryDB<(sint)memoryLineSort.size()) { memoryLineSort.erase(memoryLineSort.begin()+_SelectedMemoryDB); memoryLineSort.push_front(_SelectedMemoryDB); } // **** Parse all memories for(i=0;igetBrick(phrase.Bricks[0]); // combat root brick? if( brick && brick->isRoot() && brick->isCombat() ) { // default attack! memoryLine= memoryLineSort[i]; memorySlot= j; return true; } } } } } return false; } // *************************************************************************** bool CSPhraseManager::avoidCyclicForPhrase(const CSPhraseCom &phrase) const { CSBrickManager *pBM= CSBrickManager::getInstance(); for(uint i=0;igetBrick(phrase.Bricks[i]); if(brick && brick->AvoidCyclic) return true; } return false; } // *************************************************************************** bool CSPhraseManager::avoidCyclicForPhrase(sint32 phraseSheetId) const { return avoidCyclicForPhrase(getPhrase(phraseSheetId)); } // *************************************************************************** uint CSPhraseManager::fillRoleMasterGenericPhrase(uint32 rmFlags, uint32 rmRace) { CSBrickManager *pBM= CSBrickManager::getInstance(); uint destIndex= 0; uint numFill= 0; // not initialized => abort if(_BotChatPhraseSheetLeaves.empty()) return 0; // *** For all phrase sheets CSheetManager::TEntitySheetMap::const_iterator it; for(it=SheetMngr.getSheets().begin();it!=SheetMngr.getSheets().end();it++) { if(it->second.EntitySheet->Type==CEntitySheet::SPHRASE) { // *** Filter RoleMaster and basics const CSPhraseSheet *phrase= safe_cast(it->second.EntitySheet); // Not valid phrase are not sellable (some brick no more exist) if(!phrase->isValid()) continue; // ShowInAPOnlyIfLearnt are typically special mission phrase, skip them if(phrase->ShowInAPOnlyIfLearnt) continue; // test rmFlags against phrase name if( !ROLEMASTER_FLAGS::canSellPhrase(rmFlags, phrase->Id.toString()) ) continue; // test race (test all bricks) bool raceOk= true; uint i; for(i=0;iBricks.size();i++) { CSBrickSheet *brick= pBM->getBrick(phrase->Bricks[i]); // if the brick exist, not a Common (no restriction), and not equal to rolemaster race, // then can't sell this phrase if( brick && brick->CivRestriction!=EGSPD::CPeople::Common && brick->CivRestriction!=(EGSPD::CPeople::TPeople)rmRace ) { raceOk= false; break; } } // if some brick failed, skip if(!raceOk) continue; // *** Filter not fully known phrases bool fullKnown= true; for(i=0;iBricks.size();i++) { CSBrickSheet *brick= pBM->getBrick(phrase->Bricks[i]); // if the brick exist, and not known then OK, all are not known if(brick && !pBM->isBrickKnown(phrase->Bricks[i])) { fullKnown= false; break; } } // if all bricks of this phrase are known, then don't need to sell! if(fullKnown) continue; // *** Filter phrases that are ok with requirement if(!phraseRequirementOk(phrase->Id.asInt())) continue; // *** All filter pass, add this phrase if(_BotChatPhraseSheetLeaves[destIndex]) _BotChatPhraseSheetLeaves[destIndex]->setValue32(phrase->Id.asInt()); // next destIndex++; // full DB filled? if(destIndex==_BotChatPhraseSheetLeaves.size()) break; } } // nb really filled numFill= destIndex; // *** Fill others with 0! for(;destIndex<_BotChatPhraseSheetLeaves.size();destIndex++) { if(_BotChatPhraseSheetLeaves[destIndex]) _BotChatPhraseSheetLeaves[destIndex]->setValue32(0); } return numFill; } // *************************************************************************** void CSPhraseManager::getPhraseSectionBoundFromSkillFilter(sint &minSectionId, sint &maxSectionId) { CSkillManager *pSM= CSkillManager::getInstance(); SKILLS::ESkills filter= getBookSkillFilter(); // get its skill bound (eg: 20-50) sint minSkillValue= pSM->getMinSkillValue(filter); sint maxSkillValue= pSM->getMaxSkillValue(filter); minSkillValue= max(minSkillValue, 0); maxSkillValue= max(maxSkillValue, 0); // get its related section id bound (eg: 3-10) minSectionId= minSkillValue/5 -1; // NB: 50/5==10 is already exclusive (I mean all actions of DefensiveMagic Skill are 49 max) maxSectionId= maxSkillValue/5; } // *************************************************************************** sint32 CSPhraseManager::getSheetFromPhrase(const CSPhraseCom &phrase) const { TPhraseToSheet::const_iterator it = _PhraseToSheet.find(phrase); if (it == _PhraseToSheet.end()) return 0; return it->second; } // *************************************************************************** uint32 CSPhraseManager::getTotalActionMalus(const CSPhraseCom &phrase) const { CInterfaceManager *pIM = CInterfaceManager::getInstance(); CSBrickManager *pBM= CSBrickManager::getInstance(); uint32 totalActionMalus= 0; CCDBNodeLeaf *actMalus= pIM->getDbProp("UI:VARIABLES:TOTAL_MALUS_EQUIP", false); // root brick must not be Power or aura, because Action malus don't apply to them // (ie leave 0 ActionMalus for Aura or Powers if (!phrase.Bricks.empty()) { CSBrickSheet *rootBrick= pBM->getBrick(phrase.Bricks[0]); if(actMalus && !rootBrick->isSpecialPower()) totalActionMalus= actMalus->getValue32(); } return totalActionMalus; } // *************************************************************************** CCDBNodeLeaf *CSPhraseManager::getRegenTickRangeDbLeaf(uint powerIndex) const { CInterfaceManager *pIM = CInterfaceManager::getInstance(); CCDBNodeLeaf *dbLeaf = pIM->getDbProp(toString("SERVER:FLAGS:BRICK_TICK_RANGE:%d:TICK_RANGE", (int) powerIndex), false); return dbLeaf; } // *************************************************************************** CTickRange CSPhraseManager::getRegenTickRange(uint powerIndex) const { CTickRange tickRange; CCDBNodeLeaf *dbLeaf = getRegenTickRangeDbLeaf(powerIndex); if (dbLeaf) { tickRange.StartTick = (NLMISC::TGameCycle) dbLeaf->getValue32(); tickRange.EndTick = (NLMISC::TGameCycle) (((uint64) dbLeaf->getValue64()) >> 32); } return tickRange; } // *************************************************************************** void CSPhraseManager::setRegenTickRange(uint powerIndex, const CTickRange &tickRange) { CCDBNodeLeaf *dbLeaf = getRegenTickRangeDbLeaf(powerIndex); if (dbLeaf) { uint64 value = uint64(tickRange.StartTick) | (uint64(tickRange.EndTick) << 32); dbLeaf->setValue64(value); } } // *************************************************************************** int CSPhraseComAdpater::luaGetCastTime(CLuaState &ls) { if (Phrase.Bricks.empty()) { ls.push((double) 0); return 1; } CSPhraseManager *pPM = CSPhraseManager::getInstance(); float castTime; float castTimeMalus; pPM->getPhraseCastTime(Phrase, pPM->getTotalActionMalus(Phrase), castTime, castTimeMalus); ls.push((double) (castTime + castTimeMalus)); return 1; } // *************************************************************************** int CSPhraseComAdpater::luaGetCastRange(CLuaState &ls) { if (Phrase.Bricks.empty()) { ls.push((double) 0); return 1; } CSPhraseManager *pPM = CSPhraseManager::getInstance(); sint range; sint rangeMalus; pPM->getPhraseMagicRange(this->Phrase, pPM->getTotalActionMalus(Phrase), range, rangeMalus); ls.push((double) (range + rangeMalus)); return 1; } // *************************************************************************** int CSPhraseComAdpater::luaGetHpCost(CLuaState &ls) { if (Phrase.Bricks.empty()) { ls.push((double) 0); return 1; } CSPhraseManager *pPM = CSPhraseManager::getInstance(); sint hpCost; sint hpCostMalus; pPM->getPhraseHpCost(this->Phrase, pPM->getTotalActionMalus(Phrase), hpCost, hpCostMalus); ls.push((double) (hpCost + hpCostMalus)); return 1; } // *************************************************************************** int CSPhraseComAdpater::luaGetSapCost(CLuaState &ls) { if (Phrase.Bricks.empty()) { ls.push((double) 0); return 1; } CSPhraseManager *pPM = CSPhraseManager::getInstance(); sint sapCost; sint sapCostMalus; pPM->getPhraseSapCost(this->Phrase, pPM->getTotalActionMalus(Phrase), sapCost, sapCostMalus); ls.push((double) (sapCost + sapCostMalus)); return 1; } // *************************************************************************** int CSPhraseComAdpater::luaGetSuccessRate(CLuaState &ls) { if (Phrase.Bricks.empty()) { ls.push((double) 0); return 1; } CSPhraseManager *pPM = CSPhraseManager::getInstance(); ; ls.push((double) pPM->getPhraseSuccessRate(this->Phrase)); return 1; } // *************************************************************************** int CSPhraseComAdpater::luaGetFocusCost(CLuaState &ls) { if (Phrase.Bricks.empty()) { ls.push((double) 0); return 1; } CSPhraseManager *pPM = CSPhraseManager::getInstance(); sint focusCost; sint focusCostMalus; pPM->getPhraseFocusCost(this->Phrase, pPM->getTotalActionMalus(Phrase), focusCost, focusCostMalus); ls.push((double) (focusCost + focusCostMalus)); return 1; } // *************************************************************************** int CSPhraseComAdpater::luaGetStaCost(CLuaState &ls) { if (Phrase.Bricks.empty()) { ls.push((double) 0); return 1; } CSPhraseManager *pPM = CSPhraseManager::getInstance(); sint staCost; sint staCostMalus; pPM->getPhraseStaCost(this->Phrase, pPM->getTotalActionMalus(Phrase), staCost, staCostMalus); ls.push((double) (staCost + staCostMalus)); return 1; } // *************************************************************************** int CSPhraseComAdpater::luaGetName(CLuaState &ls) { CLuaIHM::push(ls, this->Phrase.Name); return 1; } // *************************************************************************** int CSPhraseComAdpater::luaGetDesc(CLuaState &ls) { CSPhraseManager *pPM = CSPhraseManager::getInstance(); sint32 phraseSheetID = pPM->getSheetFromPhrase(Phrase); if (phraseSheetID != 0) { // is it a built-in phrase? ucstring desc(STRING_MANAGER::CStringManagerClient::getSPhraseLocalizedDescription(NLMISC::CSheetId(phraseSheetID))); if (!desc.empty()) { CLuaIHM::push(ls, desc); return 1; } } return 0; } // *************************************************************************** int CSPhraseComAdpater::luaIsCombatPhrase(CLuaState &ls) { if(Phrase.empty()) { ls.push(false); return 1; } CSBrickManager *pBM= CSBrickManager::getInstance(); CSBrickSheet *rootBrick= pBM->getBrick(Phrase.Bricks[0]); if(!rootBrick) { ls.push(false); return 1; } ls.push(rootBrick->isCombat()); return 1; } // *************************************************************************** int CSPhraseComAdpater::luaIsMagicPhrase(CLuaState &ls) { if(Phrase.empty()) { ls.push(false); return 1; } CSBrickManager *pBM= CSBrickManager::getInstance(); CSBrickSheet *rootBrick= pBM->getBrick(Phrase.Bricks[0]); if(!rootBrick) { ls.push(false); return 1; } ls.push(rootBrick->isMagic()); return 1; } // *************************************************************************** int CSPhraseComAdpater::luaIsPowerPhrase(CLuaState &ls) { if(Phrase.empty()) { ls.push(false); return 1; } CSBrickManager *pBM= CSBrickManager::getInstance(); CSBrickSheet *rootBrick= pBM->getBrick(Phrase.Bricks[0]); if(!rootBrick) { ls.push(false); return 1; } ls.push(rootBrick->isSpecialPower()); return 1; } // *************************************************************************** int CSPhraseComAdpater::luaGetRegenTime(CLuaState &ls) { CSPhraseManager *pPM = CSPhraseManager::getInstance(); ls.push((double) pPM->getRegenTime(Phrase)); return 1; } // *************************************************************************** int CSPhraseComAdpater::luaGetTotalRegenTime(CLuaState &ls) { CSPhraseManager *pPM = CSPhraseManager::getInstance(); ls.push((double) pPM->getTotalRegenTime(Phrase)); return 1; } // *************************************************************************** int CSPhraseComAdpater::luaGetPowerDisableTime(CLuaState &ls) { CSPhraseManager *pPM = CSPhraseManager::getInstance(); ls.push((double) pPM->getPowerDisableTime(Phrase)); return 1; } ////////////// // COMMANDS // ////////////// #ifdef NL_DEBUG NLMISC_COMMAND(regenTickRange, "Emulate regen tick range locally for a memory slot)", "") { if (args.size() != 1 && args.size() != 2) return false; CSPhraseManager *pPM = CSPhraseManager::getInstance(); const CSPhraseCom &phrase = pPM->getPhrase(atoi(args[0].c_str())); uint64 powerBitField = CSPhraseManager::getPhraseRequiredFlags(phrase); TGameCycle duration; if (args.size() >= 2) { duration = (TGameCycle) atoi(args[1].c_str()); } else { duration = pPM->getPowerDisableTime(phrase); } for (uint b = 0; b < 64; ++b) { if (powerBitField & (uint64(1) << b)) { pPM->setRegenTickRange(b, CTickRange(LastGameCycle, LastGameCycle + duration)); } } pPM->touchRegenTickRangeFlag(); return true; } NLMISC_COMMAND(forceUpdatePhrases, "Force to update the display of phrases", "") { if (!args.empty()) return false; CSPhraseManager *pPM = CSPhraseManager::getInstance(); pPM->touchRegenTickRangeFlag(); pPM->updateAllMemoryCtrlState(); pPM->updateAllActionRegen(); return true; } #endif