mirror of
https://github.com/ErikasKontenis/SabrehavenServer.git
synced 2025-05-03 02:59:21 +02:00
487 lines
9.8 KiB
C++
487 lines
9.8 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 "npc.h"
|
|
#include "game.h"
|
|
#include "tools.h"
|
|
#include "position.h"
|
|
#include "player.h"
|
|
#include "spawn.h"
|
|
#include "script.h"
|
|
#include "behaviourdatabase.h"
|
|
|
|
extern Game g_game;
|
|
|
|
uint32_t Npc::npcAutoID = 0x80000000;
|
|
|
|
void Npcs::loadNpcs()
|
|
{
|
|
std::cout << ">> Loading npcs..." << std::endl;
|
|
|
|
std::vector<boost::filesystem::path> files;
|
|
getFilesInDirectory("data/npc/", ".npc", files);
|
|
for (auto file : files)
|
|
{
|
|
std::string npcName = file.filename().string();
|
|
int32_t end = npcName.find_first_of('/');
|
|
npcName = npcName.substr(end + 1, npcName.length() - end);
|
|
end = npcName.find_first_of('.');
|
|
npcName = npcName.substr(0, end);
|
|
|
|
Npc* npc = Npc::createNpc(npcName);
|
|
if (!npc) {
|
|
return;
|
|
}
|
|
|
|
if (npc->getClientVersion() <= g_game.getClientVersion()) {
|
|
g_game.placeCreature(npc, npc->getMasterPos(), false, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Npcs::reload()
|
|
{
|
|
const std::map<uint32_t, Npc*>& npcs = g_game.getNpcs();
|
|
|
|
for (const auto& it : npcs) {
|
|
it.second->reload();
|
|
}
|
|
}
|
|
|
|
Npc* Npc::createNpc(const std::string& name)
|
|
{
|
|
std::unique_ptr<Npc> npc(new Npc(name));
|
|
npc->filename = "data/npc/" + name + ".npc";
|
|
if (!npc->load()) {
|
|
return nullptr;
|
|
}
|
|
|
|
return npc.release();
|
|
}
|
|
|
|
Npc::Npc(const std::string& name) :
|
|
Creature(),
|
|
filename("data/npc/" + name + ".npc"),
|
|
masterRadius(0),
|
|
staticMovementTime(0),
|
|
loaded(false),
|
|
behaviourDatabase(nullptr)
|
|
{
|
|
baseSpeed = 5;
|
|
reset();
|
|
}
|
|
|
|
Npc::~Npc()
|
|
{
|
|
reset();
|
|
}
|
|
|
|
void Npc::addList()
|
|
{
|
|
g_game.addNpc(this);
|
|
}
|
|
|
|
void Npc::removeList()
|
|
{
|
|
g_game.removeNpc(this);
|
|
}
|
|
|
|
bool Npc::load()
|
|
{
|
|
if (loaded) {
|
|
return true;
|
|
}
|
|
|
|
reset();
|
|
|
|
ScriptReader script;
|
|
if (!script.open(filename)) {
|
|
return false;
|
|
}
|
|
|
|
while (true) {
|
|
script.nextToken();
|
|
|
|
if (script.Token == ENDOFFILE) {
|
|
break;
|
|
}
|
|
|
|
if (script.Token != IDENTIFIER) {
|
|
script.error("identifier expected");
|
|
return false;
|
|
}
|
|
|
|
std::string ident = script.getIdentifier();
|
|
script.readSymbol('=');
|
|
|
|
if (ident == "name") {
|
|
name = script.readString();
|
|
} else if (ident == "outfit") {
|
|
script.readSymbol('(');
|
|
uint8_t* c;
|
|
currentOutfit.lookType = script.readNumber();
|
|
script.readSymbol(',');
|
|
if (currentOutfit.lookType > 0) {
|
|
c = script.readBytesequence();
|
|
currentOutfit.lookHead = c[0];
|
|
currentOutfit.lookBody = c[1];
|
|
currentOutfit.lookLegs = c[2];
|
|
currentOutfit.lookFeet = c[3];
|
|
currentOutfit.lookAddons = c[4];
|
|
} else {
|
|
currentOutfit.lookTypeEx = script.readNumber();
|
|
}
|
|
script.readSymbol(')');
|
|
} else if (ident == "home") {
|
|
script.readCoordinate(masterPos.x, masterPos.y, masterPos.z);
|
|
} else if (ident == "radius") {
|
|
masterRadius = script.readNumber();
|
|
} else if (ident == "clientversion") {
|
|
clientVersion = script.readNumber();
|
|
} else if (ident == "behaviour") {
|
|
if (behaviourDatabase) {
|
|
script.error("behaviour database already defined");
|
|
return false;
|
|
}
|
|
|
|
behaviourDatabase = new BehaviourDatabase(this);
|
|
if (!behaviourDatabase->loadDatabase(script)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
script.close();
|
|
return true;
|
|
}
|
|
|
|
void Npc::reset()
|
|
{
|
|
loaded = false;
|
|
focusCreature = 0;
|
|
conversationEndTime = 0;
|
|
|
|
if (behaviourDatabase) {
|
|
delete behaviourDatabase;
|
|
behaviourDatabase = nullptr;
|
|
}
|
|
}
|
|
|
|
void Npc::reload()
|
|
{
|
|
loaded = false;
|
|
|
|
reset();
|
|
load();
|
|
|
|
if (baseSpeed > 0) {
|
|
addEventWalk();
|
|
}
|
|
}
|
|
|
|
bool Npc::canSee(const Position& pos) const
|
|
{
|
|
if (pos.z != getPosition().z) {
|
|
return false;
|
|
}
|
|
return Creature::canSee(getPosition(), pos, 3, 3);
|
|
}
|
|
|
|
std::string Npc::getDescription(int32_t) const
|
|
{
|
|
std::string descr;
|
|
descr.reserve(name.length() + 1);
|
|
descr.assign(name);
|
|
descr.push_back('.');
|
|
return descr;
|
|
}
|
|
|
|
void Npc::onCreatureAppear(Creature* creature, bool isLogin)
|
|
{
|
|
Creature::onCreatureAppear(creature, isLogin);
|
|
|
|
if (creature == this) {
|
|
if (baseSpeed > 0) {
|
|
addEventWalk();
|
|
}
|
|
} else if (Player* player = creature->getPlayer()) {
|
|
spectators.insert(player);
|
|
updateIdleStatus();
|
|
}
|
|
}
|
|
|
|
void Npc::onRemoveCreature(Creature* creature, bool isLogout)
|
|
{
|
|
Creature::onRemoveCreature(creature, isLogout);
|
|
|
|
if (!behaviourDatabase) {
|
|
return;
|
|
}
|
|
|
|
Player* player = creature->getPlayer();
|
|
if (player) {
|
|
if (player->getID() == focusCreature) {
|
|
behaviourDatabase->react(SITUATION_VANISH, player, "");
|
|
}
|
|
|
|
spectators.erase(player);
|
|
updateIdleStatus();
|
|
}
|
|
}
|
|
|
|
void Npc::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos,
|
|
const Tile* oldTile, const Position& oldPos, bool teleport)
|
|
{
|
|
Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport);
|
|
|
|
if (!behaviourDatabase) {
|
|
return;
|
|
}
|
|
|
|
Player* player = creature->getPlayer();
|
|
if (player && player->getID() == focusCreature) {
|
|
if (!Position::areInRange<3, 3, 0>(creature->getPosition(), getPosition())) {
|
|
behaviourDatabase->react(SITUATION_VANISH, player, "");
|
|
}
|
|
}
|
|
|
|
if (creature != this) {
|
|
if (player) {
|
|
if (player->canSee(position)) {
|
|
spectators.insert(player);
|
|
} else {
|
|
spectators.erase(player);
|
|
}
|
|
|
|
updateIdleStatus();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Npc::onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text)
|
|
{
|
|
if (creature->getID() == id || type != TALKTYPE_SAY || !behaviourDatabase) {
|
|
return;
|
|
}
|
|
|
|
Player* player = creature->getPlayer();
|
|
if (player) {
|
|
if (!Position::areInRange<3, 3>(creature->getPosition(), getPosition())) {
|
|
return;
|
|
}
|
|
|
|
lastTalkCreature = creature->getID();
|
|
|
|
if (focusCreature == 0) {
|
|
behaviourDatabase->react(SITUATION_ADDRESS, player, text);
|
|
} else if (focusCreature != player->getID()) {
|
|
behaviourDatabase->react(SITUATION_BUSY, player, text);
|
|
} else if (focusCreature == player->getID()) {
|
|
behaviourDatabase->react(SITUATION_NONE, player, text);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Npc::onThink(uint32_t interval)
|
|
{
|
|
Creature::onThink(interval);
|
|
|
|
if (!isIdle && focusCreature == 0 && baseSpeed > 0 && getTimeSinceLastMove() >= 100 + getStepDuration()) {
|
|
addEventWalk();
|
|
}
|
|
|
|
if (!behaviourDatabase) {
|
|
return;
|
|
}
|
|
|
|
if (focusCreature) {
|
|
Player* player = g_game.getPlayerByID(focusCreature);
|
|
if (player) {
|
|
turnToCreature(player);
|
|
|
|
if (conversationEndTime != 0 && OTSYS_TIME() > conversationEndTime) {
|
|
if (player) {
|
|
behaviourDatabase->react(SITUATION_VANISH, player, "");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Npc::doSay(const std::string& text)
|
|
{
|
|
if (lastTalkCreature == focusCreature) {
|
|
conversationEndTime = OTSYS_TIME() + 60000;
|
|
}
|
|
|
|
g_game.internalCreatureSay(this, TALKTYPE_SAY, text, false);
|
|
}
|
|
|
|
bool Npc::getNextStep(Direction& dir, uint32_t& flags)
|
|
{
|
|
if (Creature::getNextStep(dir, flags)) {
|
|
return true;
|
|
}
|
|
|
|
if (baseSpeed <= 0) {
|
|
return false;
|
|
}
|
|
|
|
if (focusCreature != 0) {
|
|
return false;
|
|
}
|
|
|
|
if (OTSYS_TIME() < staticMovementTime) {
|
|
return false;
|
|
}
|
|
|
|
if (getTimeSinceLastMove() < 100 + getStepDuration() + getStepSpeed()) {
|
|
return false;
|
|
}
|
|
|
|
return getRandomStep(dir);
|
|
}
|
|
|
|
void Npc::setIdle(bool idle)
|
|
{
|
|
if (isRemoved() || getHealth() <= 0) {
|
|
return;
|
|
}
|
|
|
|
isIdle = idle;
|
|
|
|
if (isIdle) {
|
|
onIdleStatus();
|
|
}
|
|
}
|
|
|
|
void Npc::updateIdleStatus()
|
|
{
|
|
bool status = spectators.empty();
|
|
if (status != isIdle) {
|
|
setIdle(status);
|
|
}
|
|
}
|
|
|
|
bool Npc::canWalkTo(const Position& fromPos, Direction dir) const
|
|
{
|
|
if (masterRadius == 0) {
|
|
return false;
|
|
}
|
|
|
|
Position toPos = getNextPosition(dir, fromPos);
|
|
if (!Spawns::isInZone(masterPos, masterRadius, toPos)) {
|
|
return false;
|
|
}
|
|
|
|
Tile* tile = g_game.map.getTile(toPos);
|
|
if (!tile || tile->queryAdd(0, *this, 1, 0) != RETURNVALUE_NOERROR) {
|
|
return false;
|
|
}
|
|
|
|
if (tile->hasFlag(TILESTATE_BLOCKPATH)) {
|
|
return false;
|
|
}
|
|
|
|
if (tile->hasHeight(1)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Npc::getRandomStep(Direction& dir) const
|
|
{
|
|
std::vector<Direction> dirList;
|
|
const Position& creaturePos = getPosition();
|
|
|
|
if (canWalkTo(creaturePos, DIRECTION_NORTH)) {
|
|
dirList.push_back(DIRECTION_NORTH);
|
|
}
|
|
|
|
if (canWalkTo(creaturePos, DIRECTION_SOUTH)) {
|
|
dirList.push_back(DIRECTION_SOUTH);
|
|
}
|
|
|
|
if (canWalkTo(creaturePos, DIRECTION_EAST)) {
|
|
dirList.push_back(DIRECTION_EAST);
|
|
}
|
|
|
|
if (canWalkTo(creaturePos, DIRECTION_WEST)) {
|
|
dirList.push_back(DIRECTION_WEST);
|
|
}
|
|
|
|
if (dirList.empty()) {
|
|
return false;
|
|
}
|
|
|
|
dir = dirList[uniform_random(0, dirList.size() - 1)];
|
|
return true;
|
|
}
|
|
|
|
void Npc::doMoveTo(const Position& target)
|
|
{
|
|
std::forward_list<Direction> listDir;
|
|
if (getPathTo(target, listDir, 1, 1, true, true)) {
|
|
startAutoWalk(listDir);
|
|
}
|
|
}
|
|
|
|
void Npc::turnToCreature(Creature* creature)
|
|
{
|
|
const Position& creaturePos = creature->getPosition();
|
|
const Position& myPos = getPosition();
|
|
const auto dx = Position::getOffsetX(myPos, creaturePos);
|
|
const auto dy = Position::getOffsetY(myPos, creaturePos);
|
|
|
|
float tan;
|
|
if (dx != 0) {
|
|
tan = static_cast<float>(dy) / dx;
|
|
} else {
|
|
tan = 10;
|
|
}
|
|
|
|
Direction dir;
|
|
if (std::abs(tan) < 1) {
|
|
if (dx > 0) {
|
|
dir = DIRECTION_WEST;
|
|
} else {
|
|
dir = DIRECTION_EAST;
|
|
}
|
|
} else {
|
|
if (dy > 0) {
|
|
dir = DIRECTION_NORTH;
|
|
} else {
|
|
dir = DIRECTION_SOUTH;
|
|
}
|
|
}
|
|
g_game.internalCreatureTurn(this, dir);
|
|
}
|
|
|
|
void Npc::setCreatureFocus(Creature* creature)
|
|
{
|
|
if (creature) {
|
|
focusCreature = creature->getID();
|
|
turnToCreature(creature);
|
|
} else {
|
|
focusCreature = 0;
|
|
}
|
|
} |