tibia-rme/source/action.cpp
Eduardo Dantas e09f3e94c8
Decouple npcs from the creature class (it's create npc.xml file in the map folder) (#3)
Created the complete organization of npcs, that way, it is possible to create own files for npcs, no longer depending on being together with the monsters
this pull request was made to be worked together with the canary project: https://github.com/opentibiabr/canary

The spawns npcs is "yellow" and spawns monsters is "red" (the old)

With this change, the map will automatically create an "npc.xml" the first time it is saved.
It is possible to edit only the xml, it is not necessary to add or modify the npc by opening the editor map

Thus, there is a script for npcs, similar to how it works with old spawns
2021-05-25 00:29:12 -03:00

730 lines
16 KiB
C++

//////////////////////////////////////////////////////////////////////
// This file is part of Remere's Map Editor
//////////////////////////////////////////////////////////////////////
// Remere's Map Editor 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 3 of the License, or
// (at your option) any later version.
//
// Remere's Map Editor 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, see <http://www.gnu.org/licenses/>.
//////////////////////////////////////////////////////////////////////
#include "main.h"
#include "action.h"
#include "settings.h"
#include "map.h"
#include "editor.h"
#include "gui.h"
Change::Change() : type(CHANGE_NONE), data(nullptr)
{
////
}
Change::Change(Tile* t) : type(CHANGE_TILE)
{
ASSERT(t);
data = t;
}
Change* Change::Create(House* house, const Position& where)
{
Change* c = newd Change();
c->type = CHANGE_MOVE_HOUSE_EXIT;
std::pair<uint32_t, Position>* p = newd std::pair<uint32_t, Position>;
p->first = house->id;
p->second = where;
c->data = p;
return c;
}
Change* Change::Create(Waypoint* wp, const Position& where)
{
Change* c = newd Change();
c->type = CHANGE_MOVE_WAYPOINT;
std::pair<std::string, Position>* p = newd std::pair<std::string, Position>;
p->first = wp->name;
p->second = where;
c->data = p;
return c;
}
Change::~Change()
{
clear();
}
void Change::clear()
{
switch(type) {
case CHANGE_TILE:
ASSERT(data);
delete reinterpret_cast<Tile*>(data);
break;
case CHANGE_MOVE_HOUSE_EXIT:
ASSERT(data);
delete reinterpret_cast<std::pair<uint32_t, Position>* >(data);
break;
case CHANGE_MOVE_WAYPOINT:
ASSERT(data);
delete reinterpret_cast<std::pair<std::string, Position>* >(data);
break;
case CHANGE_NONE:
break;
default:
#ifdef __DEBUG_MODE__
if(data)
printf("UNHANDLED CHANGE TYPE! Leak!");
#endif
break;
}
type = CHANGE_NONE;
data = nullptr;
}
uint32_t Change::memsize() const
{
uint32_t mem = sizeof(*this);
switch(type) {
case CHANGE_TILE:
ASSERT(data);
mem += reinterpret_cast<Tile*>(data)->memsize();
break;
default:
break;
}
return mem;
}
Action::Action(Editor& editor, ActionIdentifier ident) :
commited(false),
editor(editor),
type(ident)
{
}
Action::~Action()
{
ChangeList::const_reverse_iterator it = changes.rbegin();
while(it != changes.rend()) {
delete *it;
++it;
}
}
size_t Action::approx_memsize() const
{
uint32_t mem = sizeof(*this);
mem += changes.size() * (sizeof(Change) + sizeof(Tile) + sizeof(Item) + 6/* approx overhead*/);
return mem;
}
size_t Action::memsize() const
{
uint32_t mem = sizeof(*this);
mem += sizeof(Change*) * 3 * changes.size();
ChangeList::const_iterator it = changes.begin();
while(it != changes.end()) {
Change* c = *it;
switch(c->type) {
case CHANGE_TILE:
{
ASSERT(c->data);
mem += reinterpret_cast<Tile*>(c->data)->memsize();
break;
}
default:
break;
}
++it;
}
return mem;
}
void Action::commit(DirtyList* dirty_list)
{
editor.selection.start(Selection::INTERNAL);
ChangeList::const_iterator it = changes.begin();
while(it != changes.end()) {
Change* c = *it;
switch(c->type) {
case CHANGE_TILE: {
void** data = &c->data;
Tile* newtile = reinterpret_cast<Tile*>(*data);
ASSERT(newtile);
Position pos = newtile->getPosition();
if(editor.IsLiveClient()) {
QTreeNode* nd = editor.map.getLeaf(pos.x, pos.y);
if(!nd || !nd->isVisible(pos.z > GROUND_LAYER)) {
// Delete all changes that affect tiles outside our view
c->clear();
++it;
continue;
}
}
Tile* oldtile = editor.map.swapTile(pos, newtile);
TileLocation* location = newtile->getLocation();
// Update other nodes in the network
if(editor.IsLiveServer() && dirty_list)
dirty_list->AddPosition(pos.x, pos.y, pos.z);
newtile->update();
//std::cout << "\tSwitched tile at " << pos.x << ";" << pos.y << ";" << pos.z << " from " << (void*)oldtile << " to " << *data << std::endl;
if(newtile->isSelected())
editor.selection.addInternal(newtile);
if(oldtile) {
if(newtile->getHouseID() != oldtile->getHouseID()) {
// oooooomggzzz we need to add it to the appropriate house!
House* house = editor.map.houses.getHouse(oldtile->getHouseID());
if(house)
house->removeTile(oldtile);
house = editor.map.houses.getHouse(newtile->getHouseID());
if(house)
house->addTile(newtile);
}
if(oldtile->spawnMonster) {
if(newtile->spawnMonster) {
if(*oldtile->spawnMonster != *newtile->spawnMonster) {
editor.map.removeSpawnMonster(oldtile);
editor.map.addSpawnMonster(newtile);
}
} else {
// Monster spawn has been removed
editor.map.removeSpawnMonster(oldtile);
}
} else if(newtile->spawnMonster) {
editor.map.addSpawnMonster(newtile);
}
if(oldtile->spawnNpc) {
if(newtile->spawnNpc) {
if(*oldtile->spawnNpc != *newtile->spawnNpc) {
editor.map.removeSpawnNpc(oldtile);
editor.map.addSpawnNpc(newtile);
}
} else {
// SpawnMonster has been removed
editor.map.removeSpawnNpc(oldtile);
}
} else if(newtile->spawnNpc) {
editor.map.addSpawnNpc(newtile);
}
//oldtile->update();
if(oldtile->isSelected())
editor.selection.removeInternal(oldtile);
*data = oldtile;
} else {
*data = editor.map.allocator(location);
if(newtile->getHouseID() != 0) {
// oooooomggzzz we need to add it to the appropriate house!
House* house = editor.map.houses.getHouse(newtile->getHouseID());
if(house) {
house->addTile(newtile);
}
}
if(newtile->spawnMonster)
editor.map.addSpawnMonster(newtile);
if(newtile->spawnNpc)
editor.map.addSpawnNpc(newtile);
}
// Mark the tile as modified
newtile->modify();
// Update client dirty list
if(editor.IsLiveClient() && dirty_list && type != ACTION_REMOTE) {
// Local action, assemble changes
dirty_list->AddChange(c);
}
break;
}
case CHANGE_MOVE_HOUSE_EXIT: {
std::pair<uint32_t, Position>* p = reinterpret_cast<std::pair<uint32_t, Position>* >(c->data);
ASSERT(p);
House* whathouse = editor.map.houses.getHouse(p->first);
if(whathouse) {
Position oldpos = whathouse->getExit();
whathouse->setExit(p->second);
p->second = oldpos;
}
break;
}
case CHANGE_MOVE_WAYPOINT: {
std::pair<std::string, Position>* p = reinterpret_cast<std::pair<std::string, Position>* >(c->data);
ASSERT(p);
Waypoint* wp = editor.map.waypoints.getWaypoint(p->first);
if(wp) {
// Change the tiles
TileLocation* oldtile = editor.map.getTileL(wp->pos);
TileLocation* newtile = editor.map.getTileL(p->second);
// Only need to remove from old if it actually exists
if(p->second != Position())
if(oldtile && oldtile->getWaypointCount() > 0)
oldtile->decreaseWaypointCount();
newtile->increaseWaypointCount();
// Update shit
Position oldpos = wp->pos;
wp->pos = p->second;
p->second = oldpos;
}
break;
}
default:
break;
}
++it;
}
editor.selection.finish(Selection::INTERNAL);
commited = true;
}
void Action::undo(DirtyList* dirty_list)
{
if(changes.empty())
return;
editor.selection.start(Selection::INTERNAL);
ChangeList::reverse_iterator it = changes.rbegin();
while(it != changes.rend()) {
Change* c = *it;
switch(c->type) {
case CHANGE_TILE: {
void** data = &c->data;
Tile* oldtile = reinterpret_cast<Tile*>(*data);
ASSERT(oldtile);
Position pos = oldtile->getPosition();
if(editor.IsLiveClient()) {
QTreeNode* nd = editor.map.getLeaf(pos.x, pos.y);
if(!nd || !nd->isVisible(pos.z > GROUND_LAYER)) {
// Delete all changes that affect tiles outside our view
c->clear();
++it;
continue;
}
}
Tile* newtile = editor.map.swapTile(pos, oldtile);
// Update server side change list (for broadcast)
if(editor.IsLiveServer() && dirty_list)
dirty_list->AddPosition(pos.x, pos.y, pos.z);
if(oldtile->isSelected())
editor.selection.addInternal(oldtile);
if(newtile->isSelected())
editor.selection.removeInternal(newtile);
if(newtile->getHouseID() != oldtile->getHouseID()) {
// oooooomggzzz we need to remove it from the appropriate house!
House* house = editor.map.houses.getHouse(newtile->getHouseID());
if(house) {
house->removeTile(newtile);
} else {
// Set tile house to 0, house has been removed
newtile->setHouse(nullptr);
}
house = editor.map.houses.getHouse(oldtile->getHouseID());
if(house) {
house->addTile(oldtile);
}
}
if(oldtile->spawnMonster) {
if(newtile->spawnMonster) {
if(*oldtile->spawnMonster != *newtile->spawnMonster) {
editor.map.removeSpawnMonster(newtile);
editor.map.addSpawnMonster(oldtile);
}
} else {
editor.map.addSpawnMonster(oldtile);
}
} else if(newtile->spawnMonster) {
editor.map.removeSpawnMonster(newtile);
}
if(oldtile->spawnNpc) {
if(newtile->spawnNpc) {
if(*oldtile->spawnNpc != *newtile->spawnNpc) {
editor.map.removeSpawnNpc(newtile);
editor.map.addSpawnNpc(oldtile);
}
} else {
editor.map.addSpawnNpc(oldtile);
}
} else if(newtile->spawnNpc) {
editor.map.removeSpawnNpc(newtile);
}
*data = newtile;
// Update client dirty list
if(editor.IsLiveClient() && dirty_list && type != ACTION_REMOTE) {
// Local action, assemble changes
dirty_list->AddChange(c);
}
break;
}
case CHANGE_MOVE_HOUSE_EXIT: {
std::pair<uint32_t, Position>* p = reinterpret_cast<std::pair<uint32_t, Position>* >(c->data);
ASSERT(p);
House* whathouse = editor.map.houses.getHouse(p->first);
if(whathouse) {
Position oldpos = whathouse->getExit();
whathouse->setExit(p->second);
p->second = oldpos;
}
break;
}
case CHANGE_MOVE_WAYPOINT: {
std::pair<std::string, Position>* p = reinterpret_cast<std::pair<std::string, Position>* >(c->data);
ASSERT(p);
Waypoint* wp = editor.map.waypoints.getWaypoint(p->first);
if(wp) {
// Change the tiles
TileLocation* oldtile = editor.map.getTileL(wp->pos);
TileLocation* newtile = editor.map.getTileL(p->second);
// Only need to remove from old if it actually exists
if(p->second != Position())
if(oldtile && oldtile->getWaypointCount() > 0)
oldtile->decreaseWaypointCount();
newtile->increaseWaypointCount();
// Update shit
Position oldpos = wp->pos;
wp->pos = p->second;
p->second = oldpos;
}
break;
}
default:
break;
}
++it;
}
editor.selection.finish(Selection::INTERNAL);
commited = false;
}
BatchAction::BatchAction(Editor& editor, ActionIdentifier ident) :
editor(editor),
timestamp(0),
memory_size(0),
type(ident)
{
////
}
BatchAction::~BatchAction()
{
for(Action* action : batch) {
delete action;
}
batch.clear();
}
size_t BatchAction::memsize(bool recalc) const
{
// Expensive operation, only evaluate once (won't change anyways)
if(!recalc && memory_size > 0) {
return memory_size;
}
uint32_t mem = sizeof(*this);
mem += sizeof(Action*) * 3 * batch.size();
for(Action* action : batch) {
#ifdef __USE_EXACT_MEMSIZE__
mem += action->memsize();
#else
// Less exact but MUCH faster
mem += action->approx_memsize();
#endif
}
const_cast<BatchAction*>(this)->memory_size = mem;
return mem;
}
void BatchAction::addAction(Action* action)
{
// If empty, do nothing.
if(action->size() == 0) {
delete action;
return;
}
ASSERT(action->getType() == type);
if(!editor.CanEdit()) {
delete action;
return;
}
// Add it!
batch.push_back(action);
timestamp = time(nullptr);
}
void BatchAction::addAndCommitAction(Action* action)
{
// If empty, do nothing.
if(action->size() == 0) {
delete action;
return;
}
if(!editor.CanEdit()) {
delete action;
return;
}
// Add it!
action->commit(nullptr);
batch.push_back(action);
timestamp = time(nullptr);
}
void BatchAction::commit()
{
for(Action* action : batch) {
if(!action->isCommited()) {
action->commit(nullptr);
}
}
}
void BatchAction::undo()
{
for(Action* action : boost::adaptors::reverse(batch)) {
action->undo(nullptr);
}
}
void BatchAction::redo()
{
for(Action* action : batch) {
action->redo(nullptr);
}
}
void BatchAction::merge(BatchAction* other)
{
batch.insert(batch.end(), other->batch.begin(), other->batch.end());
other->batch.clear();
}
ActionQueue::ActionQueue(Editor& editor) :
current(0), memory_size(0), editor(editor)
{
////
}
ActionQueue::~ActionQueue()
{
for(auto it = actions.begin(); it != actions.end(); it = actions.erase(it)) {
delete *it;
}
}
Action* ActionQueue::createAction(ActionIdentifier ident)
{
return newd Action(editor, ident);
}
Action* ActionQueue::createAction(BatchAction* batch)
{
return newd Action(editor, batch->getType());
}
BatchAction* ActionQueue::createBatch(ActionIdentifier ident)
{
return newd BatchAction(editor, ident);
}
void ActionQueue::resetTimer()
{
if(!actions.empty())
actions.back()->resetTimer();
}
void ActionQueue::addBatch(BatchAction* batch, int stacking_delay)
{
ASSERT(batch);
ASSERT(current <= actions.size());
if(batch->size() == 0) {
delete batch;
return;
}
// Commit any uncommited actions...
batch->commit();
// Update title
if(editor.map.doChange())
g_gui.UpdateTitle();
if(batch->type == ACTION_REMOTE) {
delete batch;
return;
}
while(current != actions.size()) {
memory_size -= actions.back()->memsize();
BatchAction* todelete = actions.back();
actions.pop_back();
delete todelete;
}
while(memory_size > size_t(1024 * 1024 * g_settings.getInteger(Config::UNDO_MEM_SIZE)) && !actions.empty()) {
memory_size -= actions.front()->memsize();
delete actions.front();
actions.pop_front();
current--;
}
if(actions.size() > size_t(g_settings.getInteger(Config::UNDO_SIZE)) && !actions.empty()) {
memory_size -= actions.front()->memsize();
BatchAction* todelete = actions.front();
actions.pop_front();
delete todelete;
current--;
}
do {
if(!actions.empty()) {
BatchAction* lastAction = actions.back();
if(lastAction->type == batch->type && g_settings.getInteger(Config::GROUP_ACTIONS) && time(nullptr) - stacking_delay < lastAction->timestamp) {
lastAction->merge(batch);
lastAction->timestamp = time(nullptr);
memory_size -= lastAction->memsize();
memory_size += lastAction->memsize(true);
delete batch;
break;
}
}
memory_size += batch->memsize();
actions.push_back(batch);
batch->timestamp = time(nullptr);
current++;
} while(false);
}
void ActionQueue::addAction(Action* action, int stacking_delay)
{
BatchAction* batch = createBatch(action->getType());
batch->addAndCommitAction(action);
if(batch->size() == 0) {
delete batch;
return;
}
addBatch(batch, stacking_delay);
}
void ActionQueue::undo()
{
if(current > 0) {
current--;
BatchAction* batch = actions[current];
batch->undo();
}
}
void ActionQueue::redo()
{
if(current < actions.size()) {
BatchAction* batch = actions[current];
batch->redo();
current++;
}
}
void ActionQueue::clear()
{
for(ActionList::iterator it = actions.begin(); it != actions.end();) {
delete *it;
it = actions.erase(it);
}
current = 0;
}
DirtyList::DirtyList() :
owner(0)
{
;
}
DirtyList::~DirtyList()
{
;
}
void DirtyList::AddPosition(int x, int y, int z)
{
uint32_t m = ((x >> 2) << 18) | ((y >> 2) << 4);
ValueType fi = {m, 0};
SetType::iterator s = iset.find(fi);
if(s != iset.end()) {
ValueType v = *s;
iset.erase(s);
v.floors = (1 << z) | v.floors;
iset.insert(v);
} else {
ValueType v = {m, (uint32_t)(1 << z)};
iset.insert(v);
}
}
void DirtyList::AddChange(Change* c)
{
ichanges.push_back(c);
}
DirtyList::SetType& DirtyList::GetPosList()
{
return iset;
}
ChangeList& DirtyList::GetChanges()
{
return ichanges;
}