tibia-rme/source/doodad_brush.cpp
2024-06-07 18:16:45 -03:00

427 lines
12 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 "doodad_brush.h"
#include "basemap.h"
//=============================================================================
// Doodad brush
DoodadBrush::DoodadBrush() :
look_id(0),
thickness(0),
thickness_ceiling(0),
draggable(false),
on_blocking(false),
one_size(false),
do_new_borders(false),
on_duplicate(false),
clear_mapflags(0),
clear_statflags(0) {
////
}
DoodadBrush::~DoodadBrush() {
for (std::vector<AlternativeBlock*>::iterator alt_iter = alternatives.begin(); alt_iter != alternatives.end(); ++alt_iter) {
delete *alt_iter;
}
}
DoodadBrush::AlternativeBlock::AlternativeBlock() :
composite_chance(0),
single_chance(0) {
////
}
DoodadBrush::AlternativeBlock::~AlternativeBlock() {
for (std::vector<CompositeBlock>::iterator composite_iter = composite_items.begin(); composite_iter != composite_items.end(); ++composite_iter) {
CompositeTileList &tv = composite_iter->items;
for (CompositeTileList::iterator compt_iter = tv.begin(); compt_iter != tv.end(); ++compt_iter) {
ItemVector &items = compt_iter->second;
for (ItemVector::iterator iiter = items.begin(); iiter != items.end(); ++iiter) {
delete *iiter;
}
}
}
for (std::vector<SingleBlock>::iterator single_iter = single_items.begin(); single_iter != single_items.end(); ++single_iter) {
delete single_iter->item;
}
}
bool DoodadBrush::loadAlternative(pugi::xml_node node, wxArrayString &warnings, AlternativeBlock* which) {
AlternativeBlock* alternativeBlock;
if (which) {
alternativeBlock = which;
} else {
alternativeBlock = newd AlternativeBlock();
}
pugi::xml_attribute attribute;
for (pugi::xml_node childNode = node.first_child(); childNode; childNode = childNode.next_sibling()) {
const std::string &childName = as_lower_str(childNode.name());
if (childName == "item") {
if (!(attribute = childNode.attribute("chance"))) {
warnings.push_back("Can't read chance tag of doodad item node.");
continue;
}
Item* item = Item::Create(childNode);
if (!item) {
warnings.push_back("Can't create item from doodad item node.");
continue;
}
auto type = g_items.getRawItemType(item->getID());
if (type) {
type->doodad_brush = this;
}
SingleBlock sb { attribute.as_int(), item };
alternativeBlock->single_items.push_back(sb);
alternativeBlock->single_chance += sb.chance;
} else if (childName == "composite") {
if (!(attribute = childNode.attribute("chance"))) {
warnings.push_back("Can't read chance tag of doodad item node.");
continue;
}
alternativeBlock->composite_chance += attribute.as_int();
CompositeBlock cb;
cb.chance = alternativeBlock->composite_chance;
for (pugi::xml_node compositeNode = childNode.first_child(); compositeNode; compositeNode = compositeNode.next_sibling()) {
if (as_lower_str(compositeNode.name()) != "tile") {
continue;
}
if (!(attribute = compositeNode.attribute("x"))) {
warnings.push_back("Couldn't read positionX values of composite tile node.");
continue;
}
int32_t x = attribute.as_int();
if (!(attribute = compositeNode.attribute("y"))) {
warnings.push_back("Couldn't read positionY values of composite tile node.");
continue;
}
int32_t y = attribute.as_int();
int32_t z = compositeNode.attribute("z").as_int();
if (x < -0x7FFF || x > 0x7FFF) {
warnings.push_back("Invalid range of x value on composite tile node.");
continue;
} else if (y < -0x7FFF || y > 0x7FFF) {
warnings.push_back("Invalid range of y value on composite tile node.");
continue;
} else if (z < -0x7 || z > 0x7) {
warnings.push_back("Invalid range of z value on composite tile node.");
continue;
}
ItemVector items;
for (pugi::xml_node itemNode = compositeNode.first_child(); itemNode; itemNode = itemNode.next_sibling()) {
if (as_lower_str(itemNode.name()) != "item") {
continue;
}
Item* item = Item::Create(itemNode);
if (item) {
items.push_back(item);
auto type = g_items.getRawItemType(item->getID());
if (type) {
type->doodad_brush = this;
}
}
}
if (!items.empty()) {
cb.items.push_back(std::make_pair(Position(x, y, z), items));
}
}
alternativeBlock->composite_items.push_back(cb);
}
}
if (!which) {
alternatives.push_back(alternativeBlock);
}
return true;
}
bool DoodadBrush::load(pugi::xml_node node, wxArrayString &warnings) {
pugi::xml_attribute attribute;
if ((attribute = node.attribute("lookid"))) {
look_id = attribute.as_uint();
}
if ((attribute = node.attribute("server_lookid"))) {
look_id = g_items.getItemType(attribute.as_uint()).clientID;
}
if ((attribute = node.attribute("on_blocking"))) {
on_blocking = attribute.as_bool();
}
if ((attribute = node.attribute("on_duplicate"))) {
on_duplicate = attribute.as_bool();
}
if ((attribute = node.attribute("redo_borders")) || (attribute = node.attribute("reborder"))) {
do_new_borders = attribute.as_bool();
}
if ((attribute = node.attribute("one_size"))) {
one_size = attribute.as_bool();
}
if ((attribute = node.attribute("draggable"))) {
draggable = attribute.as_bool();
}
if (node.attribute("remove_optional_border").as_bool()) {
if (!do_new_borders) {
warnings.push_back("remove_optional_border will not work without redo_borders\n");
}
clear_statflags |= TILESTATE_OP_BORDER;
}
const std::string &thicknessString = node.attribute("thickness").as_string();
if (!thicknessString.empty()) {
size_t slash = thicknessString.find('/');
if (slash != std::string::npos) {
try {
thickness = std::stoi(thicknessString.substr(0, slash));
} catch (const std::invalid_argument &) {
thickness = 0;
} catch (const std::out_of_range &) {
thickness = 0;
}
try {
thickness_ceiling = std::stoi(thicknessString.substr(slash + 1));
} catch (const std::invalid_argument &) {
thickness_ceiling = 0;
} catch (const std::out_of_range &) {
thickness_ceiling = 0;
}
}
}
for (pugi::xml_node childNode = node.first_child(); childNode; childNode = childNode.next_sibling()) {
if (as_lower_str(childNode.name()) != "alternate") {
continue;
}
if (!loadAlternative(childNode, warnings)) {
return false;
}
}
loadAlternative(node, warnings, alternatives.empty() ? nullptr : alternatives.back());
return true;
}
bool DoodadBrush::AlternativeBlock::ownsItem(uint16_t id) const {
for (std::vector<SingleBlock>::const_iterator single_iter = single_items.begin(); single_iter != single_items.end(); ++single_iter) {
if (single_iter->item->getID() == id) {
return true;
}
}
for (std::vector<CompositeBlock>::const_iterator composite_iter = composite_items.begin(); composite_iter != composite_items.end(); ++composite_iter) {
const CompositeTileList &ctl = composite_iter->items;
for (CompositeTileList::const_iterator comp_iter = ctl.begin(); comp_iter != ctl.end(); ++comp_iter) {
const ItemVector &items = comp_iter->second;
for (ItemVector::const_iterator item_iter = items.begin(), item_end = items.end(); item_iter != item_end; ++item_iter) {
if ((*item_iter)->getID() == id) {
return true;
}
}
}
}
return false;
}
bool DoodadBrush::ownsItem(Item* item) const {
if (item->getDoodadBrush() == this) {
return true;
}
uint16_t id = item->getID();
for (std::vector<AlternativeBlock*>::const_iterator alt_iter = alternatives.begin(); alt_iter != alternatives.end(); ++alt_iter) {
if ((*alt_iter)->ownsItem(id)) {
return true;
}
}
return false;
}
void DoodadBrush::undraw(BaseMap* map, Tile* tile) {
// Remove all doodad-related
for (ItemVector::iterator item_iter = tile->items.begin(); item_iter != tile->items.end();) {
Item* item = *item_iter;
if (item->getDoodadBrush() != nullptr) {
if (item->isComplex() && g_settings.getInteger(Config::ERASER_LEAVE_UNIQUE)) {
++item_iter;
} else if (g_settings.getInteger(Config::DOODAD_BRUSH_ERASE_LIKE)) {
// Only delete items of the same doodad brush
if (ownsItem(item)) {
delete item;
item_iter = tile->items.erase(item_iter);
} else {
++item_iter;
}
} else {
delete item;
item_iter = tile->items.erase(item_iter);
}
} else {
++item_iter;
}
}
if (tile->ground && tile->ground->getDoodadBrush() != nullptr) {
if (g_settings.getInteger(Config::DOODAD_BRUSH_ERASE_LIKE)) {
// Only delete items of the same doodad brush
if (ownsItem(tile->ground)) {
delete tile->ground;
tile->ground = nullptr;
}
} else {
delete tile->ground;
tile->ground = nullptr;
}
}
}
void DoodadBrush::draw(BaseMap* map, Tile* tile, void* parameter) {
int variation = 0;
if (parameter) {
variation = *reinterpret_cast<int*>(parameter);
}
if (alternatives.empty()) {
return;
}
variation %= alternatives.size();
const AlternativeBlock* ab_ptr = alternatives[variation];
ASSERT(ab_ptr);
int roll = random(1, ab_ptr->single_chance);
for (std::vector<SingleBlock>::const_iterator block_iter = ab_ptr->single_items.begin(); block_iter != ab_ptr->single_items.end(); ++block_iter) {
const SingleBlock &sb = *block_iter;
if (roll <= sb.chance) {
// Use this!
tile->addItem(sb.item->deepCopy());
break;
}
roll -= sb.chance;
}
if (clear_mapflags || clear_statflags) {
tile->setMapFlags(tile->getMapFlags() & (~clear_mapflags));
tile->setMapFlags(tile->getStatFlags() & (~clear_statflags));
}
}
const CompositeTileList &DoodadBrush::getComposite(int variation) const {
static CompositeTileList empty;
if (alternatives.empty()) {
return empty;
}
variation %= alternatives.size();
const AlternativeBlock* ab_ptr = alternatives[variation];
ASSERT(ab_ptr);
int roll = random(1, ab_ptr->composite_chance);
for (std::vector<CompositeBlock>::const_iterator block_iter = ab_ptr->composite_items.begin(); block_iter != ab_ptr->composite_items.end(); ++block_iter) {
const CompositeBlock &cb = *block_iter;
if (roll <= cb.chance) {
return cb.items;
}
}
return empty;
}
bool DoodadBrush::isEmpty(int variation) const {
if (hasCompositeObjects(variation)) {
return false;
}
if (hasSingleObjects(variation)) {
return false;
}
if (thickness <= 0) {
return false;
}
return true;
}
int DoodadBrush::getCompositeChance(int ab) const {
if (alternatives.empty()) {
return 0;
}
ab %= alternatives.size();
const AlternativeBlock* ab_ptr = alternatives[ab];
ASSERT(ab_ptr);
return ab_ptr->composite_chance;
}
int DoodadBrush::getSingleChance(int ab) const {
if (alternatives.empty()) {
return 0;
}
ab %= alternatives.size();
const AlternativeBlock* ab_ptr = alternatives[ab];
ASSERT(ab_ptr);
return ab_ptr->single_chance;
}
int DoodadBrush::getTotalChance(int ab) const {
if (alternatives.empty()) {
return 0;
}
ab %= alternatives.size();
const AlternativeBlock* ab_ptr = alternatives[ab];
ASSERT(ab_ptr);
return ab_ptr->composite_chance + ab_ptr->single_chance;
}
bool DoodadBrush::hasSingleObjects(int ab) const {
if (alternatives.empty()) {
return false;
}
ab %= alternatives.size();
AlternativeBlock* ab_ptr = alternatives[ab];
ASSERT(ab_ptr);
return ab_ptr->single_chance > 0;
}
bool DoodadBrush::hasCompositeObjects(int ab) const {
if (alternatives.empty()) {
return false;
}
ab %= alternatives.size();
AlternativeBlock* ab_ptr = alternatives[ab];
ASSERT(ab_ptr);
return ab_ptr->composite_chance > 0;
}