mirror of
https://github.com/ErikasKontenis/SabrehavenServer.git
synced 2025-04-30 01:29:21 +02:00
1298 lines
35 KiB
C++
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;
|
|
}
|