diff --git a/data/npc/gen-bank.ndb b/data/npc/gen-bank.ndb index 2c5ebb6..3514778 100644 --- a/data/npc/gen-bank.ndb +++ b/data/npc/gen-bank.ndb @@ -82,3 +82,57 @@ Topic=99 -> "Well, can I help you with something else #Topic=99,"yes",Count(3035)>=Price -> "Here you are.", Create(3043), Amount=Price, Delete(3035) #Topic=99,"yes" -> "Sorry, you don't have so many platinum coins." #Topic=99 -> "Well, can I help you with something else?" + +# Bank System +"balance" -> Amount=Balance, "Your account balance is %A gold." +"balance",balance>99999 -> Amount=Balance, "You certainly have made a pretty penny. Your account balance is %A gold." +"balance",balance>999999 -> Amount=Balance, "You certainly have made a pretty penny. Your account balance is %A gold." +"balance",balance>9999999 -> Amount=Balance, "You have made ten millions and it still grows! Your account balance is %A gold." +"balance",balance>99999999 -> Amount=Balance, "I think you must be one of the richest inhabitants in the world! Your account balance is %A gold." + +"deposit" -> "You don't have any gold with you." +"deposit",CountMoney>0 -> "Please tell me how much gold it is you would like to deposit.", Topic=81 +"deposit","all",CountMoney>0 -> Price=CountMoney, "Would you really like to deposit %P gold?", Topic=82 +"deposit",$1,0<$1,CountMoney>=$1 -> Price=$1, "Would you really like to deposit %P gold?", Topic=82 +"deposit","0" -> "You are joking, aren't you??" +"deposit",$1,0<$1,CountMoney<$1 -> "You do not have enough gold." +Topic=81,$1,0<$1,CountMoney>=$1 -> Price=$1, "Would you really like to deposit %P gold?", Topic=82 +Topic=81,"0" -> "You are joking, aren't you?" +Topic=81,$1,0<$1,CountMoney<$1 -> "You do not have enough gold." +Topic=81 -> "Please tell me how much gold it is you would like to deposit.", Topic=81 +Topic=82,"yes",CountMoney>=Price -> "Alright, we have added the amount of %P gold to your balance. You can withdraw your money anytime you want to.", DeleteMoney, Deposit(Price) +Topic=82,"yes" -> "I am inconsolable, but it seems you have lost your gold. I hope you get it back." +Topic=82 -> "As you wish. Is there something else I can do for you?" + +"withdraw" -> "Please tell me how much gold you would like to withdraw.", Topic=83 +"withdraw",$1,0<$1,Balance>=$1 -> Price=$1, "Are you sure you wish to withdraw %P gold from your bank account?", Topic=84 +"withdraw","0" -> "Sure, you want nothing you get nothing!" +"withdraw",$1,0<$1,Balance<$1 -> "There is not enough gold on your account." +Topic=83,$1,0<$1,Balance>=$1 -> Price=$1, "Are you sure you wish to withdraw %P gold from your bank account?", Topic=84 +Topic=83,"0" -> "Sure, you want nothing you get nothing!" +Topic=83,$1,0<$1,Balance<$1 -> "There is not enough gold on your account." +Topic=83 -> "Please tell me how much gold you would like to withdraw.", Topic=83 +Topic=84,"yes",Balance>=Price -> "Here you are, %P gold. Please let me know if there is something else I can do for you.", CreateMoney, Withdraw(Price) +Topic=84,"yes" -> "I am inconsolable, but it seems you don't have that many gold in your bank account." +Topic=84 -> "The customer is king! Come back anytime you want to if you wish to withdraw your money." + +"transfer" -> "Please tell me the amount of gold you would like to transfer.", Topic=85 +"transfer","0","to" -> "Please think about it. Okay?" +"transfer",$1,0<$1,"to",Balance<$1 -> "There is not enough gold on your account." +"transfer",$1,0<$1,"to",Balance>=$1,TransferToPlayerNameState=2 -> Price=$1, "Would you really like to transfer %P gold to %S?", Topic=88 +"transfer",$1,0<$1,"to",Balance>=$1,TransferToPlayerNameState=1 -> "I'm afraid this character only holds a junior account at our bank. Do not worry, though. Once he has chosen his vocation or is no longer on Rookgaard, his account will be upgraded." +"transfer",$1,0<$1,"to",Balance>=$1,TransferToPlayerNameState=0 -> "This player does not exist." +Topic=85,$1,0<$1,Balance>=$1 -> Price=$1, "Who would you like transfer %P gold to?", Topic=86 +Topic=85,"0" -> "Please think about it. Okay?" +Topic=85,$1,0<$1,Balance<$1 -> "There is not enough gold on your account." +Topic=86,Balance>=Price,TransferToPlayerNameState=2 -> "Would you really like to transfer %P gold to %S?", Topic=87 +Topic=86,Balance>=Price,TransferToPlayerNameState=1 -> "I'm afraid this character only holds a junior account at our bank. Do not worry, though. Once he has chosen his vocation or is no longer on Rookgaard, his account will be upgraded." +Topic=86,Balance>=Price,TransferToPlayerNameState=0 -> "This player does not exist." +Topic=87,"yes",Balance>=Price -> "You have transferred %P gold to %S.", Transfer(Price) +Topic=87,"yes" -> "I am inconsolable, but it seems you don't have that many gold in your bank account." +Topic=87 -> "Ok. What is next?" +Topic=88,"yes",Balance>=Price -> "Very well. You have transferred %P gold to %S.", Transfer(Price) +Topic=88,"yes" -> "I am inconsolable, but it seems you don't have that many gold in your bank account." +Topic=88 -> "Alright, is there something else I can do for you?" + + diff --git a/src/behaviourdatabase.cpp b/src/behaviourdatabase.cpp index 416559a..49acc94 100644 --- a/src/behaviourdatabase.cpp +++ b/src/behaviourdatabase.cpp @@ -19,6 +19,7 @@ #include "otpch.h" +#include "iologindata.h" #include "behaviourdatabase.h" #include "npc.h" #include "player.h" @@ -174,6 +175,9 @@ bool BehaviourDatabase::loadConditions(ScriptReader& script, NpcBehaviour* behav } else if (script.getSpecial() == '%') { condition->setCondition(BEHAVIOUR_TYPE_MESSAGE_COUNT, script.readNumber(), ""); searchTerm = true; + } else if (script.getSpecial() == '$') { + condition->setCondition(BEHAVIOUR_TYPE_MESSAGE_COUNT_NO_LIMIT, script.readNumber(), ""); + searchTerm = true; } else if (script.getSpecial() == ',') { script.nextToken(); continue; @@ -291,6 +295,9 @@ bool BehaviourDatabase::loadActions(ScriptReader& script, NpcBehaviour* behaviou } else if (identifier == "deposit") { action->type = BEHAVIOUR_TYPE_DEPOSIT; searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "transfer") { + action->type = BEHAVIOUR_TYPE_TRANSFER; + searchType = BEHAVIOUR_PARAMETER_ONE; } else if (identifier == "bless") { action->type = BEHAVIOUR_TYPE_BLESS; searchType = BEHAVIOUR_PARAMETER_ONE; @@ -471,16 +478,24 @@ NpcBehaviourNode* BehaviourDatabase::readValue(ScriptReader& script) } if (script.Token == SPECIAL) { - if (script.getSpecial() != '%') { - script.error("illegal character"); - return nullptr; + if (script.getSpecial() == '%') { + NpcBehaviourNode* node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_MESSAGE_COUNT; + node->number = script.readNumber(); + script.nextToken(); + return node; } - NpcBehaviourNode* node = new NpcBehaviourNode(); - node->type = BEHAVIOUR_TYPE_MESSAGE_COUNT; - node->number = script.readNumber(); - script.nextToken(); - return node; + if (script.getSpecial() == '$') { + NpcBehaviourNode* node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_MESSAGE_COUNT_NO_LIMIT; + node->number = script.readNumber(); + script.nextToken(); + return node; + } + + script.error("illegal character"); + return nullptr; } NpcBehaviourNode* node = nullptr; @@ -526,6 +541,9 @@ NpcBehaviourNode* BehaviourDatabase::readValue(ScriptReader& script) } else if (identifier == "balance") { node = new NpcBehaviourNode(); node->type = BEHAVIOUR_TYPE_BALANCE; + } else if (identifier == "transfertoplayernamestate") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_MESSAGE_TRANSFERTOPLAYERNAME_STATE; } else if (identifier == "spellknown") { node = new NpcBehaviourNode(); node->type = BEHAVIOUR_TYPE_SPELLKNOWN; @@ -709,6 +727,13 @@ bool BehaviourDatabase::checkCondition(const NpcBehaviourCondition* condition, P } break; } + case BEHAVIOUR_TYPE_MESSAGE_COUNT_NO_LIMIT: { + int32_t value = searchDigitNoLimit(message); + if (value < condition->number) { + return false; + } + break; + } case BEHAVIOUR_TYPE_STRING: if (!searchWord(condition->string, message)) { return false; @@ -1011,6 +1036,32 @@ void BehaviourDatabase::checkAction(const NpcBehaviourAction* action, Player* pl player->setBankBalance(player->getBankBalance() + money); break; } + case BEHAVIOUR_TYPE_TRANSFER: { + int32_t money = evaluate(action->expression, player, message); + uint16_t state = 0; + Player* transferToPlayer = g_game.getPlayerByName(string); + if (!transferToPlayer) { + state = IOLoginData::canTransferMoneyToByName(string); + } + else { + state = transferToPlayer->getVocationId() == 0 ? 1 : 2; + } + + if (state != 2) { + break; + } + + player->setBankBalance(player->getBankBalance() - money); + + if (!transferToPlayer) { + IOLoginData::increaseBankBalance(string, money); + } + else { + transferToPlayer->setBankBalance(transferToPlayer->getBankBalance() + money); + } + + break; + } case BEHAVIOUR_TYPE_BLESS: { uint8_t number = static_cast(evaluate(action->expression, player, message)) - 1; @@ -1159,10 +1210,33 @@ int32_t BehaviourDatabase::evaluate(NpcBehaviourNode* node, Player* player, cons } return value; } + case BEHAVIOUR_TYPE_MESSAGE_COUNT_NO_LIMIT: { + int32_t value = searchDigitNoLimit(message); + if (value < node->number) { + return false; + } + return value; + } case BEHAVIOUR_TYPE_OPERATION: return checkOperation(player, node, message); case BEHAVIOUR_TYPE_BALANCE: return player->getBankBalance(); + case BEHAVIOUR_TYPE_MESSAGE_TRANSFERTOPLAYERNAME_STATE: { + std::string lowerMessage = asLowerCaseString(message); + if (lowerMessage.find("to ") != std::string::npos) { + string = asCamelCaseString(message.substr(lowerMessage.find("to ") + 3, message.size())); + } + else { + string = asCamelCaseString(message); + } + + Player* transferToPlayer = g_game.getPlayerByName(string); + if (!transferToPlayer) { + return IOLoginData::canTransferMoneyToByName(string); + } + + return transferToPlayer->getVocationId() == 0 ? 1 : 2; + } case BEHAVIOUR_TYPE_SPELLKNOWN: { if (player->hasLearnedInstantSpell(string)) { return true; @@ -1217,6 +1291,17 @@ int32_t BehaviourDatabase::checkOperation(Player* player, NpcBehaviourNode* node } int32_t BehaviourDatabase::searchDigit(const std::string& message) +{ + int32_t value = searchDigitNoLimit(message); + + if (value > 500) { + value = 500; + } + + return value; +} + +int32_t BehaviourDatabase::searchDigitNoLimit(const std::string& message) { int32_t start = -1; int32_t end = -1; @@ -1244,10 +1329,6 @@ int32_t BehaviourDatabase::searchDigit(const std::string& message) return 0; } - if (value > 500) { - value = 500; - } - return value; } @@ -1299,7 +1380,8 @@ std::string BehaviourDatabase::parseResponse(Player* player, const std::string& replaceString(response, "%D", std::to_string(data)); replaceString(response, "%N", player->getName()); replaceString(response, "%P", std::to_string(price)); - + replaceString(response, "%S", string); + int32_t worldTime = g_game.getLightHour(); int32_t hours = std::floor(worldTime / 60); int32_t minutes = worldTime % 60; diff --git a/src/behaviourdatabase.h b/src/behaviourdatabase.h index 72f61d7..ad525bd 100644 --- a/src/behaviourdatabase.h +++ b/src/behaviourdatabase.h @@ -38,6 +38,8 @@ enum NpcBehaviourType_t BEHAVIOUR_TYPE_NUMBER, // return a number BEHAVIOUR_TYPE_OPERATION, // <, =, >, >=, <=, <> BEHAVIOUR_TYPE_MESSAGE_COUNT, // get quantity in player message + BEHAVIOUR_TYPE_MESSAGE_COUNT_NO_LIMIT, // get quantity in player message without any max value restriction + BEHAVIOUR_TYPE_MESSAGE_TRANSFERTOPLAYERNAME_STATE, // set player name parsed fro message to string object and return state if it is possible to transfer BEHAVIOUR_TYPE_IDLE, // idle npc BEHAVIOUR_TYPE_QUEUE, // queue talking creature BEHAVIOUR_TYPE_TOPIC, // get/set topic @@ -269,6 +271,7 @@ class BehaviourDatabase int32_t checkOperation(Player* player, NpcBehaviourNode* node, const std::string& message); int32_t searchDigit(const std::string& message); + int32_t searchDigitNoLimit(const std::string& message); bool searchWord(const std::string& pattern, const std::string& message); std::string parseResponse(Player* player, const std::string& message); diff --git a/src/iologindata.cpp b/src/iologindata.cpp index 69044ff..61e49ce 100644 --- a/src/iologindata.cpp +++ b/src/iologindata.cpp @@ -824,6 +824,20 @@ uint32_t IOLoginData::getGuidByName(const std::string& name) return result->getNumber("id"); } +// Return 0 means player not found, 1 player is without vocation, 2 player with vocation +uint16_t IOLoginData::canTransferMoneyToByName(const std::string& name) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `vocation` FROM `players` WHERE `name` = " << db->escapeString(name); + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return 0; + } + return result->getNumber("vocation") == 0 ? 1 : 2; +} + bool IOLoginData::getGuidByNameEx(uint32_t& guid, bool& specialVip, std::string& name) { Database* db = Database::getInstance(); @@ -899,6 +913,15 @@ void IOLoginData::increaseBankBalance(uint32_t guid, uint64_t bankBalance) Database::getInstance()->executeQuery(query.str()); } +void IOLoginData::increaseBankBalance(std::string name, uint64_t bankBalance) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "UPDATE `players` SET `balance` = `balance` + " << bankBalance << " WHERE `name` = " << db->escapeString(name); + db->executeQuery(query.str()); +} + bool IOLoginData::hasBiddedOnHouse(uint32_t guid) { Database* db = Database::getInstance(); diff --git a/src/iologindata.h b/src/iologindata.h index 7a1091e..6bfbecd 100644 --- a/src/iologindata.h +++ b/src/iologindata.h @@ -44,11 +44,13 @@ class IOLoginData static bool loadPlayerByName(Player* player, const std::string& name); static bool loadPlayer(Player* player, DBResult_ptr result); static bool savePlayer(Player* player); - static uint32_t getGuidByName(const std::string& name); + static uint32_t getGuidByName(const std::string& name); + static uint16_t canTransferMoneyToByName(const std::string& name); static bool getGuidByNameEx(uint32_t& guid, bool& specialVip, std::string& name); static std::string getNameByGuid(uint32_t guid); static bool formatPlayerName(std::string& name); static void increaseBankBalance(uint32_t guid, uint64_t bankBalance); + static void increaseBankBalance(const std::string name, uint64_t bankBalance); static bool hasBiddedOnHouse(uint32_t guid); static std::forward_list getVIPEntries(uint32_t accountId); diff --git a/src/tools.cpp b/src/tools.cpp index 506f512..dc18231 100644 --- a/src/tools.cpp +++ b/src/tools.cpp @@ -360,6 +360,27 @@ std::string asUpperCaseString(std::string source) return source; } +std::string asCamelCaseString(std::string source) { + bool active = true; + + for (int i = 0; source[i] != '\0'; i++) { + if (std::isalpha(source[i])) { + if (active) { + source[i] = std::toupper(source[i]); + active = false; + } + else { + source[i] = std::tolower(source[i]); + } + } + else if (source[i] == ' ') { + active = true; + } + } + + return source; +} + StringVec explodeString(const std::string& inString, const std::string& separator, int32_t limit/* = -1*/) { StringVec returnVector; diff --git a/src/tools.h b/src/tools.h index 8f05a93..e507430 100644 --- a/src/tools.h +++ b/src/tools.h @@ -41,6 +41,7 @@ void trim_left(std::string& source, char t); void toLowerCaseString(std::string& source); std::string asLowerCaseString(std::string source); std::string asUpperCaseString(std::string source); +std::string asCamelCaseString(std::string source); typedef std::vector StringVec; typedef std::vector IntegerVec;