tibia-rme/source/action.cpp
dalkon 886cbc23a7 Lots of changes
Removed libxml2 to use pugixml.
Fixed a bug with the progress bar.
Changed some things in most xml files that pugixml did not like.
Updated the Visual Studio project.
Any NULL symbol has been changed to nullptr.
This most probably broke the CMake that was recently added.
And probably some other things.
2014-01-22 20:16:17 +01:00

743 lines
16 KiB
C++

//////////////////////////////////////////////////////////////////////
// This file is part of Remere's Map Editor
//////////////////////////////////////////////////////////////////////
// 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 3 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, see <http://www.gnu.org/licenses/>.
//////////////////////////////////////////////////////////////////////
// $URL: http://svn.rebarp.se/svn/RME/trunk/source/action.hpp $
// $Id: action.hpp 311 2010-02-26 18:23:48Z admin $
#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, 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, 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;
}
uint Change::memsize() const
{
uint 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
{
uint mem = sizeof(*this);
mem += changes.size() * (sizeof(Change) + sizeof(Tile) + sizeof(Item) + 6/* approx overhead*/);
return mem;
}
size_t Action::memsize() const
{
uint 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 > 7))
{
// 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->spawn)
{
if(newtile->spawn)
{
if(*oldtile->spawn != *newtile->spawn)
{
editor.map.removeSpawn(oldtile);
editor.map.addSpawn(newtile);
}
}
else
{
// Spawn has been removed
editor.map.removeSpawn(oldtile);
}
}
else if(newtile->spawn)
{
editor.map.addSpawn(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->spawn)
editor.map.addSpawn(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 > 7))
{
// 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->spawn)
{
if(newtile->spawn)
{
if(*oldtile->spawn != *newtile->spawn)
{
editor.map.removeSpawn(newtile);
editor.map.addSpawn(oldtile);
}
}
else
{
editor.map.addSpawn(oldtile);
}
}
else if(newtile->spawn)
{
editor.map.removeSpawn(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(std::vector<Action*>::iterator it = batch.begin(); it != batch.end(); it++)
delete (*it);
}
size_t BatchAction::memsize(bool recalc) const
{
// Expensive operation, only evaluate once (won't change anyways)
if(!recalc && memory_size > 0) return memory_size;
uint mem = sizeof(*this);
mem += sizeof(Action*) * 3 * batch.size();
for(std::vector<Action*>::const_iterator it = batch.begin(); it != batch.end(); it++)
{
#ifdef __USE_EXACT_MEMSIZE__
mem += (*it)->memsize();
#else
// Less exact but MUCH faster
mem += (*it)->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(ActionVector::iterator it = batch.begin(); it != batch.end(); it++)
if(!(*it)->isCommited())
(*it)->commit(nullptr);
}
void BatchAction::undo() {
for(ActionVector::reverse_iterator it = batch.rbegin(); it != batch.rend(); ++it)
(*it)->undo(nullptr);
}
void BatchAction::redo() {
for(ActionVector::iterator it = batch.begin(); it != batch.end(); ++it)
(*it)->redo(nullptr);
}
void BatchAction::merge(BatchAction* other)
{
for(ActionVector::iterator it = other->batch.begin(); it != other->batch.end(); it++)
batch.push_back(*it);
other->batch.clear();
}
ActionQueue::ActionQueue(Editor& editor) : current(0), memory_size(0), editor(editor)
{
}
ActionQueue::~ActionQueue()
{
for(ActionList::iterator it = actions.begin();
it != actions.end();)
{
delete *it;
it = actions.erase(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())
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 * settings.getInteger(Config::UNDO_MEM_SIZE)) && actions.empty() == false)
{
memory_size -= actions.front()->memsize();
delete actions.front();
actions.pop_front();
current--;
}
if(actions.size() > size_t(settings.getInteger(Config::UNDO_SIZE)) && actions.empty() == false)
{
memory_size -= actions.front()->memsize();
BatchAction* todelete = actions.front();
actions.pop_front();
delete todelete;
current--;
}
do {
if(actions.empty() == false)
{
BatchAction* lastAction = actions.back();
if(lastAction->type == batch->type && 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, (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;
}