SabrehavenServer/src/behaviourdatabase.cpp
2019-09-16 20:38:16 +03:00

1298 lines
35 KiB
C++

/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2019 Sabrehaven and Mark Samman <mark.samman@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "otpch.h"
#include "behaviourdatabase.h"
#include "npc.h"
#include "player.h"
#include "game.h"
#include "spells.h"
#include "monster.h"
#include "scheduler.h"
extern Game g_game;
extern Monsters g_monsters;
extern Spells* g_spells;
BehaviourDatabase::BehaviourDatabase(Npc * _npc) : npc(_npc) {
topic = 0;
data = -1;
type = 0;
price = 0;
amount = 0;
delay = 1000;
}
BehaviourDatabase::~BehaviourDatabase() {
for (NpcBehaviour* behaviour : behaviourEntries) {
delete behaviour;
}
}
bool BehaviourDatabase::loadDatabase(ScriptReader& script)
{
script.readSymbol('{');
script.nextToken();
while (true) {
if (script.Token == ENDOFFILE) {
break;
}
if (script.Token == SPECIAL && script.getSpecial() == '}') {
break;
}
if (!loadBehaviour(script)) {
return false;
}
}
return true;
}
bool BehaviourDatabase::loadBehaviour(ScriptReader& script)
{
NpcBehaviour* behaviour = new NpcBehaviour();
if (!loadConditions(script, behaviour)) {
return false;
}
if (script.Token != SPECIAL || script.getSpecial() != 'I') {
script.error("'->' expected");
delete behaviour;
return false;
}
script.nextToken();
if (!loadActions(script, behaviour)) {
delete behaviour;
return false;
}
// set this behaviour priority to condition size
behaviour->priority += behaviour->conditions.size();
if (priorityBehaviour) {
priorityBehaviour->priority += behaviour->priority + 1;
priorityBehaviour = nullptr;
}
// order it correctly
auto it = std::lower_bound(behaviourEntries.begin(), behaviourEntries.end(), behaviour, compareBehaviour);
behaviourEntries.insert(it, behaviour);
// set previous behaviour (*) functionality
previousBehaviour = behaviour;
return true;
}
bool BehaviourDatabase::loadConditions(ScriptReader& script, NpcBehaviour* behaviour)
{
while (true) {
std::unique_ptr<NpcBehaviourCondition> condition(new NpcBehaviourCondition);
bool searchTerm = false;
if (script.Token == IDENTIFIER) {
std::string identifier = script.getIdentifier();
if (identifier == "address") {
condition->situation = SITUATION_ADDRESS;
behaviour->situation = SITUATION_ADDRESS;
searchTerm = true;
} else if (identifier == "busy") {
condition->situation = SITUATION_BUSY;
behaviour->situation = SITUATION_BUSY;
searchTerm = true;
} else if (identifier == "vanish") {
condition->situation = SITUATION_VANISH;
behaviour->situation = SITUATION_VANISH;
searchTerm = true;
} else if (identifier == "sorcerer") {
condition->type = BEHAVIOUR_TYPE_SORCERER;
searchTerm = true;
} else if (identifier == "knight") {
condition->type = BEHAVIOUR_TYPE_KNIGHT;
searchTerm = true;
} else if (identifier == "paladin") {
condition->type = BEHAVIOUR_TYPE_PALADIN;
searchTerm = true;
} else if (identifier == "druid") {
condition->type = BEHAVIOUR_TYPE_DRUID;
searchTerm = true;
} else if (identifier == "premium") {
condition->type = BEHAVIOUR_TYPE_ISPREMIUM;
searchTerm = true;
} else if (identifier == "pvpenforced") {
condition->type = BEHAVIOUR_TYPE_PVPENFORCED;
searchTerm = true;
} else if (identifier == "female") {
condition->type = BEHAVIOUR_TYPE_FEMALE;
searchTerm = true;
} else if (identifier == "male") {
condition->type = BEHAVIOUR_TYPE_MALE;
searchTerm = true;
} else if (identifier == "pzblock") {
condition->type = BEHAVIOUR_TYPE_PZLOCKED;
searchTerm = true;
} else if (identifier == "promoted") {
condition->type = BEHAVIOUR_TYPE_PROMOTED;
searchTerm = true;
}
} else if (script.Token == STRING) {
const std::string keyString = asLowerCaseString(script.getString());
condition->setCondition(BEHAVIOUR_TYPE_STRING, 0, keyString);
behaviour->priority += keyString.length();
searchTerm = true;
} else if (script.Token == SPECIAL) {
if (script.getSpecial() == '!') {
condition->setCondition(BEHAVIOUR_TYPE_NOP, 0, "");
searchTerm = true;
// set this one for behaviour
priorityBehaviour = behaviour;
} else if (script.getSpecial() == '%') {
condition->setCondition(BEHAVIOUR_TYPE_MESSAGE_COUNT, script.readNumber(), "");
searchTerm = true;
} else if (script.getSpecial() == ',') {
script.nextToken();
continue;
} else {
break;
}
}
// relational operation search
if (!searchTerm) {
condition->type = BEHAVIOUR_TYPE_OPERATION;
NpcBehaviourNode* headNode = readValue(script);
NpcBehaviourNode* nextNode = readFactor(script, headNode);
// relational operators
if (script.Token != SPECIAL) {
script.error("relational operator expected");
delete nextNode;
return false;
}
NpcBehaviourOperator_t operatorType;
switch (script.getSpecial()) {
case '<':
operatorType = BEHAVIOUR_OPERATOR_LESSER_THAN;
break;
case '=':
operatorType = BEHAVIOUR_OPERATOR_EQUALS;
break;
case '>':
operatorType = BEHAVIOUR_OPERATOR_GREATER_THAN;
break;
case 'G':
operatorType = BEHAVIOUR_OPERATOR_GREATER_OR_EQUALS;
break;
case 'N':
operatorType = BEHAVIOUR_OPERATOR_NOT_EQUALS;
break;
case 'L':
operatorType = BEHAVIOUR_OPERATOR_LESSER_OR_EQUALS;
break;
default:
script.error("relational operator expected");
delete nextNode;
return false;
}
script.nextToken();
headNode = new NpcBehaviourNode();
headNode->type = BEHAVIOUR_TYPE_OPERATION;
headNode->number = operatorType;
headNode->left = nextNode;
nextNode = readValue(script);
nextNode = readFactor(script, nextNode);
headNode->right = nextNode;
condition->expression = headNode;
} else {
script.nextToken();
}
behaviour->conditions.push_back(condition.release());
}
return true;
}
bool BehaviourDatabase::loadActions(ScriptReader& script, NpcBehaviour* behaviour)
{
while (true) {
std::unique_ptr<NpcBehaviourAction> action(new NpcBehaviourAction);
NpcBehaviourParameterSearch_t searchType = BEHAVIOUR_PARAMETER_NONE;
if (script.Token == STRING) {
action->type = BEHAVIOUR_TYPE_STRING;
action->string = script.getString();
} else if (script.Token == IDENTIFIER) {
std::string identifier = script.getIdentifier();
if (identifier == "idle") {
action->type = BEHAVIOUR_TYPE_IDLE;
} else if (identifier == "nop") {
action->type = BEHAVIOUR_TYPE_NOP;
} else if (identifier == "queue") {
action->type = BEHAVIOUR_TYPE_QUEUE;
} else if (identifier == "createmoney") {
action->type = BEHAVIOUR_TYPE_CREATEMONEY;
} else if (identifier == "deletemoney") {
action->type = BEHAVIOUR_TYPE_DELETEMONEY;
} else if (identifier == "promote") {
action->type = BEHAVIOUR_TYPE_PROMOTE;
} else if (identifier == "topic") {
action->type = BEHAVIOUR_TYPE_TOPIC;
searchType = BEHAVIOUR_PARAMETER_ASSIGN;
} else if (identifier == "price") {
action->type = BEHAVIOUR_TYPE_PRICE;
searchType = BEHAVIOUR_PARAMETER_ASSIGN;
} else if (identifier == "amount") {
action->type = BEHAVIOUR_TYPE_AMOUNT;
searchType = BEHAVIOUR_PARAMETER_ASSIGN;
} else if (identifier == "data") {
action->type = BEHAVIOUR_TYPE_DATA;
searchType = BEHAVIOUR_PARAMETER_ASSIGN;
} else if (identifier == "type") {
action->type = BEHAVIOUR_TYPE_ITEM;
searchType = BEHAVIOUR_PARAMETER_ASSIGN;
} else if (identifier == "string") {
action->type = BEHAVIOUR_TYPE_TEXT;
searchType = BEHAVIOUR_PARAMETER_ASSIGN;
} else if (identifier == "hp") {
action->type = BEHAVIOUR_TYPE_HEALTH;
searchType = BEHAVIOUR_PARAMETER_ASSIGN;
} else if (identifier == "withdraw") {
action->type = BEHAVIOUR_TYPE_WITHDRAW;
searchType = BEHAVIOUR_PARAMETER_ONE;
} else if (identifier == "deposit") {
action->type = BEHAVIOUR_TYPE_DEPOSIT;
searchType = BEHAVIOUR_PARAMETER_ONE;
} else if (identifier == "bless") {
action->type = BEHAVIOUR_TYPE_BLESS;
searchType = BEHAVIOUR_PARAMETER_ONE;
} else if (identifier == "effectme") {
action->type = BEHAVIOUR_TYPE_EFFECTME;
searchType = BEHAVIOUR_PARAMETER_ONE;
} else if (identifier == "effectopp") {
action->type = BEHAVIOUR_TYPE_EFFECTOPP;
searchType = BEHAVIOUR_PARAMETER_ONE;
} else if (identifier == "create") {
action->type = BEHAVIOUR_TYPE_CREATE;
searchType = BEHAVIOUR_PARAMETER_ONE;
} else if (identifier == "delete") {
action->type = BEHAVIOUR_TYPE_DELETE;
searchType = BEHAVIOUR_PARAMETER_ONE;
} else if (identifier == "teachspell") {
action->type = BEHAVIOUR_TYPE_TEACHSPELL;
searchType = BEHAVIOUR_PARAMETER_ONE;
} else if (identifier == "town") {
action->type = BEHAVIOUR_TYPE_TOWN;
searchType = BEHAVIOUR_PARAMETER_ONE;
} else if (identifier == "profession") {
action->type = BEHAVIOUR_TYPE_PROFESSION;
searchType = BEHAVIOUR_PARAMETER_ONE;
} else if (identifier == "experience") {
action->type = BEHAVIOUR_TYPE_EXPERIENCE;
searchType = BEHAVIOUR_PARAMETER_ONE;
} else if (identifier == "summon") {
action->type = BEHAVIOUR_TYPE_SUMMON;
searchType = BEHAVIOUR_PARAMETER_ONE;
} else if (identifier == "burning") {
action->type = BEHAVIOUR_TYPE_BURNING;
searchType = BEHAVIOUR_PARAMETER_TWO;
} else if (identifier == "setquestvalue") {
action->type = BEHAVIOUR_TYPE_QUESTVALUE;
searchType = BEHAVIOUR_PARAMETER_TWO;
} else if (identifier == "poison") {
action->type = BEHAVIOUR_TYPE_POISON;
searchType = BEHAVIOUR_PARAMETER_TWO;
} else if (identifier == "teleport") {
action->type = BEHAVIOUR_TYPE_TELEPORT;
searchType = BEHAVIOUR_PARAMETER_THREE;
} else if (identifier == "createcontainer") {
action->type = BEHAVIOUR_TYPE_CREATECONTAINER;
searchType = BEHAVIOUR_PARAMETER_THREE;
} else {
script.error("illegal action term");
return false;
}
} else if (script.Token == SPECIAL) {
if (script.getSpecial() == '*') {
if (previousBehaviour == nullptr) {
script.error("no previous pattern");
return false;
}
for (NpcBehaviourAction* actionCopy : previousBehaviour->actions) {
behaviour->actions.push_back(actionCopy->clone());
}
script.nextToken();
return true;
}
}
if (searchType == BEHAVIOUR_PARAMETER_ASSIGN) {
script.readSymbol('=');
script.nextToken();
NpcBehaviourNode* headNode = readValue(script);
NpcBehaviourNode* nextNode = readFactor(script, headNode);
action->expression = nextNode;
} else if (searchType == BEHAVIOUR_PARAMETER_ONE) {
script.readSymbol('(');
script.nextToken();
NpcBehaviourNode* headNode = readValue(script);
NpcBehaviourNode* nextNode = readFactor(script, headNode);
action->expression = nextNode;
if (script.Token != SPECIAL || script.getSpecial() != ')') {
script.error("')' expected");
return false;
}
script.nextToken();
} else if (searchType == BEHAVIOUR_PARAMETER_TWO) {
script.readSymbol('(');
script.nextToken();
NpcBehaviourNode* headNode = readValue(script);
NpcBehaviourNode* nextNode = readFactor(script, headNode);
action->expression = nextNode;
if (script.Token != SPECIAL || script.getSpecial() != ',') {
script.error("',' expected");
return false;
}
script.nextToken();
headNode = readValue(script);
nextNode = readFactor(script, headNode);
action->expression2 = nextNode;
if (script.Token != SPECIAL || script.getSpecial() != ')') {
script.error("')' expected");
return false;
}
script.nextToken();
} else if (searchType == BEHAVIOUR_PARAMETER_THREE) {
script.readSymbol('(');
script.nextToken();
NpcBehaviourNode* headNode = readValue(script);
NpcBehaviourNode* nextNode = readFactor(script, headNode);
action->expression = nextNode;
if (script.Token != SPECIAL || script.getSpecial() != ',') {
script.error("',' expected");
return false;
}
script.nextToken();
headNode = readValue(script);
nextNode = readFactor(script, headNode);
action->expression2 = nextNode;
if (script.Token != SPECIAL || script.getSpecial() != ',') {
script.error("',' expected");
return false;
}
script.nextToken();
headNode = readValue(script);
nextNode = readFactor(script, headNode);
action->expression3 = nextNode;
if (script.Token != SPECIAL || script.getSpecial() != ')') {
script.error("')' expected");
return false;
}
script.nextToken();
} else {
script.nextToken();
}
behaviour->actions.push_back(action.release());
if (script.Token == SPECIAL) {
if (script.getSpecial() == ',') {
script.nextToken();
continue;
}
}
break;
}
return true;
}
NpcBehaviourNode* BehaviourDatabase::readValue(ScriptReader& script)
{
if (script.Token == NUMBER) {
NpcBehaviourNode* node = new NpcBehaviourNode();
node->type = BEHAVIOUR_TYPE_NUMBER;
node->number = script.getNumber();
script.nextToken();
return node;
}
if (script.Token == STRING) {
NpcBehaviourNode* node = new NpcBehaviourNode();
node->type = BEHAVIOUR_TYPE_STRING;
node->string = asLowerCaseString(script.getString());
script.nextToken();
return node;
}
if (script.Token == SPECIAL) {
if (script.getSpecial() != '%') {
script.error("illegal character");
return nullptr;
}
NpcBehaviourNode* node = new NpcBehaviourNode();
node->type = BEHAVIOUR_TYPE_MESSAGE_COUNT;
node->number = script.readNumber();
script.nextToken();
return node;
}
NpcBehaviourNode* node = nullptr;
NpcBehaviourParameterSearch_t searchType = BEHAVIOUR_PARAMETER_NONE;
std::string identifier = script.getIdentifier();
if (identifier == "topic") {
node = new NpcBehaviourNode();
node->type = BEHAVIOUR_TYPE_TOPIC;
} else if (identifier == "price") {
node = new NpcBehaviourNode();
node->type = BEHAVIOUR_TYPE_PRICE;
} else if (identifier == "type") {
node = new NpcBehaviourNode();
node->type = BEHAVIOUR_TYPE_ITEM;
} else if (identifier == "string") {
node = new NpcBehaviourNode();
node->type = BEHAVIOUR_TYPE_TEXT;
} else if (identifier == "data") {
node = new NpcBehaviourNode();
node->type = BEHAVIOUR_TYPE_DATA;
} else if (identifier == "amount") {
node = new NpcBehaviourNode();
node->type = BEHAVIOUR_TYPE_AMOUNT;
} else if (identifier == "countmoney") {
node = new NpcBehaviourNode();
node->type = BEHAVIOUR_TYPE_COUNTMONEY;
} else if (identifier == "hp") {
node = new NpcBehaviourNode();
node->type = BEHAVIOUR_TYPE_HEALTH;
} else if (identifier == "burning") {
node = new NpcBehaviourNode();
node->type = BEHAVIOUR_TYPE_BURNING;
} else if (identifier == "level") {
node = new NpcBehaviourNode();
node->type = BEHAVIOUR_TYPE_LEVEL;
} else if (identifier == "poison") {
node = new NpcBehaviourNode();
node->type = BEHAVIOUR_TYPE_POISON;
} else if (identifier == "balance") {
node = new NpcBehaviourNode();
node->type = BEHAVIOUR_TYPE_BALANCE;
} else if (identifier == "spellknown") {
node = new NpcBehaviourNode();
node->type = BEHAVIOUR_TYPE_SPELLKNOWN;
searchType = BEHAVIOUR_PARAMETER_ONE;
} else if (identifier == "spelllevel") {
node = new NpcBehaviourNode();
node->type = BEHAVIOUR_TYPE_SPELLLEVEL;
searchType = BEHAVIOUR_PARAMETER_ONE;
} else if (identifier == "questvalue") {
node = new NpcBehaviourNode();
node->type = BEHAVIOUR_TYPE_QUESTVALUE;
searchType = BEHAVIOUR_PARAMETER_ONE;
} else if (identifier == "count") {
node = new NpcBehaviourNode();
node->type = BEHAVIOUR_TYPE_COUNT;
searchType = BEHAVIOUR_PARAMETER_ONE;
} else if (identifier == "random") {
node = new NpcBehaviourNode();
node->type = BEHAVIOUR_TYPE_RANDOM;
searchType = BEHAVIOUR_PARAMETER_TWO;
}
if (searchType == BEHAVIOUR_PARAMETER_ONE) {
script.readSymbol('(');
script.nextToken();
NpcBehaviourNode* nextNode = readValue(script);
nextNode = readFactor(script, nextNode);
node->left = nextNode;
if (script.Token != SPECIAL || script.getSpecial() != ')') {
script.error("')' expected");
}
} else if (searchType == BEHAVIOUR_PARAMETER_TWO) {
script.readSymbol('(');
script.nextToken();
NpcBehaviourNode* nextNode = readValue(script);
nextNode = readFactor(script, nextNode);
node->left = nextNode;
if (script.Token != SPECIAL || script.getSpecial() != ',') {
script.error("',' expected");
}
script.nextToken();
nextNode = readValue(script);
nextNode = readFactor(script, nextNode);
node->right = nextNode;
if (script.Token != SPECIAL || script.getSpecial() != ')') {
script.error("')' expected");
}
}
if (!node) {
script.error("unknown value");
}
script.nextToken();
return node;
}
NpcBehaviourNode* BehaviourDatabase::readFactor(ScriptReader& script, NpcBehaviourNode* nextNode)
{
// * operator
while (true) {
if (script.Token != SPECIAL) {
break;
}
if (script.getSpecial() != '*') {
break;
}
NpcBehaviourNode* headNode = new NpcBehaviourNode();
headNode->type = BEHAVIOUR_TYPE_OPERATION;
headNode->number = BEHAVIOUR_OPERATOR_MULTIPLY;
headNode->left = nextNode;
script.nextToken();
nextNode = readValue(script);
headNode->right = nextNode;
nextNode = headNode;
}
// + - operators
while (true) {
if (script.Token != SPECIAL) {
break;
}
if (script.getSpecial() != '+' && script.getSpecial() != '-') {
break;
}
NpcBehaviourNode* headNode = new NpcBehaviourNode();
headNode->type = BEHAVIOUR_TYPE_OPERATION;
headNode->number = BEHAVIOUR_OPERATOR_SUM;
if (script.getSpecial() == '-') {
headNode->number = BEHAVIOUR_OPERATOR_RES;
}
headNode->left = nextNode;
script.nextToken();
nextNode = readValue(script);
headNode->right = nextNode;
nextNode = headNode;
}
return nextNode;
}
void BehaviourDatabase::react(BehaviourSituation_t situation, Player* player, const std::string& message)
{
for (NpcBehaviour* behaviour : behaviourEntries) {
bool fulfilled = true;
if (situation == SITUATION_ADDRESS && behaviour->situation != SITUATION_ADDRESS) {
continue;
}
if (situation == SITUATION_BUSY && behaviour->situation != SITUATION_BUSY) {
continue;
}
if (situation == SITUATION_VANISH && behaviour->situation != SITUATION_VANISH) {
continue;
}
if (situation == SITUATION_NONE && behaviour->situation != SITUATION_NONE) {
continue;
}
for (const NpcBehaviourCondition* condition : behaviour->conditions) {
if (!checkCondition(condition, player, message)) {
fulfilled = false;
break;
}
}
if (!fulfilled) {
continue;
}
if (player->getID() == npc->focusCreature) {
topic = 0;
}
reset();
if (situation == SITUATION_ADDRESS || npc->focusCreature == player->getID()) {
attendCustomer(player->getID());
}
if (situation == SITUATION_VANISH) {
npc->conversationEndTime = 0;
idle();
}
for (const NpcBehaviourAction* action : behaviour->actions) {
checkAction(action, player, message);
}
break;
}
}
bool BehaviourDatabase::checkCondition(const NpcBehaviourCondition* condition, Player* player, const std::string& message)
{
switch (condition->type) {
case BEHAVIOUR_TYPE_NOP: break;
case BEHAVIOUR_TYPE_MESSAGE_COUNT: {
int32_t value = searchDigit(message);
if (value < condition->number) {
return false;
}
break;
}
case BEHAVIOUR_TYPE_STRING:
if (!searchWord(condition->string, message)) {
return false;
}
break;
case BEHAVIOUR_TYPE_SORCERER:
if (player->getVocationId() != 1 && player->getVocationId() != 5) {
return false;
}
break;
case BEHAVIOUR_TYPE_DRUID:
if (player->getVocationId() != 2 && player->getVocationId() != 6) {
return false;
}
break;
case BEHAVIOUR_TYPE_PALADIN:
if (player->getVocationId() != 3 && player->getVocationId() != 7) {
return false;
}
break;
case BEHAVIOUR_TYPE_KNIGHT:
if (player->getVocationId() != 4 && player->getVocationId() != 8) {
return false;
}
break;
case BEHAVIOUR_TYPE_ISPREMIUM:
if (!player->isPremium()) {
return false;
}
break;
case BEHAVIOUR_TYPE_PVPENFORCED:
if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) {
return false;
}
break;
case BEHAVIOUR_TYPE_FEMALE:
if (player->getSex() != PLAYERSEX_FEMALE) {
return false;
}
break;
case BEHAVIOUR_TYPE_MALE:
if (player->getSex() != PLAYERSEX_MALE) {
return false;
}
break;
case BEHAVIOUR_TYPE_PZLOCKED:
if (!player->isPzLocked()) {
return false;
}
break;
case BEHAVIOUR_TYPE_PROMOTED: {
int32_t value = 0;
player->getStorageValue(30018, value);
if (value != 1) {
return false;
}
break;
}
case BEHAVIOUR_TYPE_OPERATION:
return checkOperation(player, condition->expression, message) > 0;
case BEHAVIOUR_TYPE_SPELLKNOWN: {
if (!player->hasLearnedInstantSpell(string)) {
return false;
}
break;
}
default:
std::cout << "[Warning - BehaviourDatabase::react]: Unhandled node type " << condition->type << std::endl;
return false;
}
return true;
}
void BehaviourDatabase::checkAction(const NpcBehaviourAction* action, Player* player, const std::string& message)
{
switch (action->type) {
case BEHAVIOUR_TYPE_NOP: break;
case BEHAVIOUR_TYPE_STRING: {
delayedEvents.push_back(g_scheduler.addEvent(createSchedulerTask(delay, std::bind(&Npc::doSay, npc, parseResponse(player, action->string)))));
delay += 100 * (message.length() / 5) + 10000;
break;
}
case BEHAVIOUR_TYPE_IDLE:
idle();
break;
case BEHAVIOUR_TYPE_QUEUE:
queueCustomer(player->getID(), message);
break;
case BEHAVIOUR_TYPE_TOPIC:
topic = evaluate(action->expression, player, message);
break;
case BEHAVIOUR_TYPE_PRICE:
price = evaluate(action->expression, player, message);
break;
case BEHAVIOUR_TYPE_DATA:
data = evaluate(action->expression, player, message);
break;
case BEHAVIOUR_TYPE_ITEM:
type = evaluate(action->expression, player, message);
break;
case BEHAVIOUR_TYPE_AMOUNT:
amount = evaluate(action->expression, player, message);
break;
case BEHAVIOUR_TYPE_TEXT:
string = action->expression->string;
break;
case BEHAVIOUR_TYPE_HEALTH: {
int32_t newHealth = evaluate(action->expression, player, message);
player->changeHealth(-player->getHealth() + newHealth);
break;
}
case BEHAVIOUR_TYPE_CREATEMONEY:
g_game.addMoney(player, price);
break;
case BEHAVIOUR_TYPE_DELETEMONEY:
g_game.removeMoney(player, price);
break;
case BEHAVIOUR_TYPE_CREATE: {
int32_t itemId = evaluate(action->expression, player, message);
const ItemType& it = Item::items[itemId];
if (it.stackable) {
do {
int32_t count = std::min<int32_t>(100, amount);
amount -= count;
Item* item = Item::CreateItem(itemId, count);
if (!item) {
break;
}
ReturnValue ret = g_game.internalPlayerAddItem(player, item);
if (ret != RETURNVALUE_NOERROR) {
delete item;
break;
}
} while (amount);
} else {
for (int32_t i = 0; i < std::max<int32_t>(1, amount); i++) {
Item* item = Item::CreateItem(itemId, data);
if (!item) {
break;
}
ReturnValue ret = g_game.internalPlayerAddItem(player, item);
if (ret != RETURNVALUE_NOERROR) {
delete item;
break;
}
}
}
break;
}
case BEHAVIOUR_TYPE_DELETE: {
type = evaluate(action->expression, player, message);
const ItemType& itemType = Item::items[type];
if (itemType.stackable || !itemType.hasSubType()) {
data = -1;
}
if (!player->removeItemOfType(type, amount, data, true)) {
player->removeItemOfType(type, amount, data, false);
}
break;
}
case BEHAVIOUR_TYPE_EFFECTME:
g_game.addMagicEffect(npc->getPosition(), evaluate(action->expression, player, message));
break;
case BEHAVIOUR_TYPE_EFFECTOPP:
g_game.addMagicEffect(player->getPosition(), evaluate(action->expression, player, message));
break;
case BEHAVIOUR_TYPE_BURNING: {
const int32_t cycles = evaluate(action->expression, player, message);
const int32_t count = evaluate(action->expression2, player, message);
if (cycles == 0) {
player->removeCondition(CONDITION_FIRE, true);
break;
}
ConditionDamage* conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_FIRE);
conditionDamage->setParam(CONDITION_PARAM_CYCLE, cycles);
conditionDamage->setParam(CONDITION_PARAM_COUNT, count);
conditionDamage->setParam(CONDITION_PARAM_MAX_COUNT, count);
player->addCondition(conditionDamage);
break;
}
case BEHAVIOUR_TYPE_POISON: {
const int32_t cycles = evaluate(action->expression, player, message);
const int32_t count = evaluate(action->expression2, player, message);
if (cycles == 0) {
player->removeCondition(CONDITION_POISON, true);
break;
}
ConditionDamage* conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_POISON);
conditionDamage->setParam(CONDITION_PARAM_CYCLE, cycles);
conditionDamage->setParam(CONDITION_PARAM_COUNT, count);
conditionDamage->setParam(CONDITION_PARAM_MAX_COUNT, count);
player->addCondition(conditionDamage);
break;
}
case BEHAVIOUR_TYPE_TOWN:
player->setTown(g_game.map.towns.getTown(evaluate(action->expression, player, message)));
break;
case BEHAVIOUR_TYPE_TEACHSPELL:
player->learnInstantSpell(string);
break;
case BEHAVIOUR_TYPE_QUESTVALUE: {
int32_t questNumber = evaluate(action->expression, player, message);
int32_t questValue = evaluate(action->expression2, player, message);
player->addStorageValue(questNumber, questValue);
break;
}
case BEHAVIOUR_TYPE_TELEPORT: {
Position pos;
pos.x = evaluate(action->expression, player, message);
pos.y = evaluate(action->expression2, player, message);
pos.z = evaluate(action->expression3, player, message);
g_game.internalTeleport(player, pos);
break;
}
case BEHAVIOUR_TYPE_PROFESSION: {
int32_t newVocation = evaluate(action->expression, player, message);
player->setVocation(newVocation);
break;
}
case BEHAVIOUR_TYPE_PROMOTE: {
int32_t newVocation = player->getVocationId() + 4;
player->setVocation(newVocation);
player->addStorageValue(30018, 1);
break;
}
case BEHAVIOUR_TYPE_SUMMON: {
std::string name = action->expression->string;
Monster* monster = Monster::createMonster(name);
if (!monster) {
break;
}
if (!g_game.placeCreature(monster, npc->getPosition(), true, true)) {
delete monster;
}
break;
}
case BEHAVIOUR_TYPE_EXPERIENCE: {
int32_t experience = evaluate(action->expression, player, message);
player->addExperience(nullptr, experience, false);
break;
}
case BEHAVIOUR_TYPE_WITHDRAW: {
int32_t money = evaluate(action->expression, player, message);
player->setBankBalance(player->getBankBalance() - money);
break;
}
case BEHAVIOUR_TYPE_DEPOSIT: {
int32_t money = evaluate(action->expression, player, message);
player->setBankBalance(player->getBankBalance() + money);
break;
}
case BEHAVIOUR_TYPE_BLESS: {
uint8_t number = static_cast<uint8_t>(evaluate(action->expression, player, message)) - 1;
if (!player->hasBlessing(number)) {
player->addBlessing(1 << number);
}
break;
}
case BEHAVIOUR_TYPE_CREATECONTAINER: {
int32_t containerId = evaluate(action->expression, player, message);
int32_t itemId = evaluate(action->expression2, player, message);
int32_t data = evaluate(action->expression3, player, message);
for (int32_t i = 0; i < std::max<int32_t>(1, amount); i++) {
Item* container = Item::CreateItem(containerId);
if (!container) {
std::cout << "[Error - BehaviourDatabase::checkAction]: CreateContainer - failed to create container item" << std::endl;
break;
}
Container* realContainer = container->getContainer();
for (int32_t c = 0; c < std::max<int32_t>(1, realContainer->capacity()); c++) {
Item* item = Item::CreateItem(itemId, data);
if (!item) {
std::cout << "[Error - BehaviourDatabase::checkAction]: CreateContainer - failed to create item" << std::endl;
break;
}
realContainer->internalAddThing(item);
}
ReturnValue ret = g_game.internalPlayerAddItem(player, container);
if (ret != RETURNVALUE_NOERROR) {
delete container;
break;
}
}
break;
}
default:
std::cout << "[Warning - BehaviourDatabase::checkAction]: Unhandled node type " << action->type << std::endl;
break;
}
}
int32_t BehaviourDatabase::evaluate(NpcBehaviourNode* node, Player* player, const std::string& message)
{
switch (node->type) {
case BEHAVIOUR_TYPE_NUMBER:
return node->number;
case BEHAVIOUR_TYPE_TOPIC:
return topic;
case BEHAVIOUR_TYPE_PRICE:
return price;
case BEHAVIOUR_TYPE_DATA:
return data;
case BEHAVIOUR_TYPE_ITEM:
return type;
case BEHAVIOUR_TYPE_AMOUNT:
return amount;
case BEHAVIOUR_TYPE_HEALTH:
return player->getHealth();
case BEHAVIOUR_TYPE_COUNT: {
int32_t itemId = evaluate(node->left, player, message);
const ItemType& itemType = Item::items[itemId];
if (itemType.stackable || !itemType.hasSubType()) {
data = -1;
}
return player->getItemTypeCount(itemId, data);
}
case BEHAVIOUR_TYPE_COUNTMONEY:
return player->getMoney();
case BEHAVIOUR_TYPE_BURNING: {
Condition* condition = player->getCondition(CONDITION_FIRE);
if (!condition) {
return false;
}
ConditionDamage* damage = static_cast<ConditionDamage*>(condition);
if (damage == nullptr) {
return false;
}
return damage->getTotalDamage();
}
case BEHAVIOUR_TYPE_POISON: {
Condition* condition = player->getCondition(CONDITION_POISON);
if (!condition) {
return false;
}
ConditionDamage* damage = static_cast<ConditionDamage*>(condition);
if (damage == nullptr) {
return false;
}
return damage->getTotalDamage();
}
case BEHAVIOUR_TYPE_LEVEL:
return player->getLevel();
case BEHAVIOUR_TYPE_RANDOM: {
int32_t min = evaluate(node->left, player, message);
int32_t max = evaluate(node->right, player, message);
return normal_random(min, max);
}
case BEHAVIOUR_TYPE_QUESTVALUE: {
int32_t questNumber = evaluate(node->left, player, message);
int32_t questValue;
player->getStorageValue(questNumber, questValue);
return questValue;
}
case BEHAVIOUR_TYPE_MESSAGE_COUNT: {
int32_t value = searchDigit(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_SPELLKNOWN: {
if (player->hasLearnedInstantSpell(string)) {
return true;
}
break;
}
case BEHAVIOUR_TYPE_SPELLLEVEL: {
InstantSpell* spell = g_spells->getInstantSpellByName(string);
if (!spell) {
std::cout << "[Warning - BehaviourDatabase::evaluate]: SpellLevel unknown spell " << node->string << std::endl;
return std::numeric_limits<int32_t>::max();
}
return spell->getLevel();
}
default:
std::cout << "[Warning - BehaviourDatabase::evaluate]: Unhandled node type " << node->type << std::endl;
break;
}
return false;
}
int32_t BehaviourDatabase::checkOperation(Player* player, NpcBehaviourNode* node, const std::string& message)
{
int32_t leftResult = evaluate(node->left, player, message);
int32_t rightResult = evaluate(node->right, player, message);
switch (node->number) {
case BEHAVIOUR_OPERATOR_LESSER_THAN:
return leftResult < rightResult;
case BEHAVIOUR_OPERATOR_EQUALS:
return leftResult == rightResult;
case BEHAVIOUR_OPERATOR_GREATER_THAN:
return leftResult > rightResult;
case BEHAVIOUR_OPERATOR_GREATER_OR_EQUALS:
return leftResult >= rightResult;
case BEHAVIOUR_OPERATOR_LESSER_OR_EQUALS:
return leftResult <= rightResult;
case BEHAVIOUR_OPERATOR_NOT_EQUALS:
return leftResult != rightResult;
case BEHAVIOUR_OPERATOR_MULTIPLY:
return leftResult * rightResult;
case BEHAVIOUR_OPERATOR_SUM:
return leftResult + rightResult;
case BEHAVIOUR_OPERATOR_RES:
return leftResult - rightResult;
default:
break;
}
return false;
}
int32_t BehaviourDatabase::searchDigit(const std::string& message)
{
int32_t start = -1;
int32_t end = -1;
int32_t value = 0;
int32_t i = -1;
for (char c : message) {
i++;
if (start == -1 && IsDigit(c)) {
start = i;
}
else if (start != -1 && !IsDigit(c)) {
end = i;
break;
}
}
try {
value = std::stoi(message.substr(start, end).c_str());
}
catch (std::invalid_argument) {
return 0;
}
catch (std::out_of_range) {
return 0;
}
if (value > 500) {
value = 500;
}
return value;
}
bool BehaviourDatabase::searchWord(const std::string& pattern, const std::string& message)
{
if (pattern.empty() || message.empty()) {
return false;
}
size_t len = pattern.length();
bool wholeWord = false;
if (pattern[len - 1] == '$') {
len--;
wholeWord = true;
}
std::string newPattern = pattern.substr(0, len);
std::string actualMessage = asLowerCaseString(message);
if (actualMessage.find(newPattern) == std::string::npos) {
return false;
}
if (wholeWord) {
size_t wordPos = actualMessage.find(newPattern);
size_t wordEnd = wordPos + newPattern.length() - 1;
if (wordEnd + 1 > actualMessage.length()) {
return false;
}
if (wordEnd + 1 == actualMessage.length()) {
return true;
}
if (!isspace(actualMessage[wordEnd + 1])) {
return false;
}
}
return true;
}
std::string BehaviourDatabase::parseResponse(Player* player, const std::string& message)
{
std::string response = message;
replaceString(response, "%A", std::to_string(amount));
replaceString(response, "%D", std::to_string(data));
replaceString(response, "%N", player->getName());
replaceString(response, "%P", std::to_string(price));
int32_t worldTime = g_game.getLightHour();
int32_t hours = std::floor<int32_t>(worldTime / 60);
int32_t minutes = worldTime % 60;
std::stringstream ss;
ss << hours << ":";
if (minutes < 10) {
ss << '0' << minutes;
} else {
ss << minutes;
}
replaceString(response, "%T", ss.str());
return response;
}
void BehaviourDatabase::attendCustomer(uint32_t playerId)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
reset();
npc->conversationStartTime = OTSYS_TIME();
npc->conversationEndTime = OTSYS_TIME() + 60000;
npc->focusCreature = playerId;
}
void BehaviourDatabase::queueCustomer(uint32_t playerId, const std::string& message)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
for (NpcQueueEntry entry : queueList) {
if (entry.playerId == playerId) {
return;
}
}
NpcQueueEntry customer;
customer.playerId = playerId;
customer.text = message;
queueList.push_back(customer);
}
void BehaviourDatabase::idle()
{
std::lock_guard<std::recursive_mutex> lock(mutex);
if (queueList.empty()) {
if (OTSYS_TIME() - npc->conversationStartTime <= 3000) {
npc->staticMovementTime = OTSYS_TIME() + 5000;
}
npc->focusCreature = 0;
} else {
while (!queueList.empty()) {
NpcQueueEntry nextCustomer = queueList.front();
queueList.pop_front();
Player* player = g_game.getPlayerByID(nextCustomer.playerId);
if (!player) {
continue;
} else {
if (!Position::areInRange<3, 3>(player->getPosition(), npc->getPosition())) {
continue;
}
react(SITUATION_ADDRESS, player, nextCustomer.text);
return;
}
}
npc->focusCreature = 0;
}
}
void BehaviourDatabase::reset()
{
delay = 1000;
for (uint32_t eventId : delayedEvents) {
g_scheduler.stopEvent(eventId);
}
delayedEvents.clear();
}
bool NpcBehaviourCondition::setCondition(NpcBehaviourType_t _type, int32_t _number, const std::string & _string)
{
type = _type;
number = _number;
string = _string;
return false;
}