| @@ -0,0 +1,1427 @@ | |||
| 1 | + | /* | |
| 2 | + | * Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient> | |
| 3 | + | * | |
| 4 | + | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 5 | + | * of this software and associated documentation files (the "Software"), to deal | |
| 6 | + | * in the Software without restriction, including without limitation the rights | |
| 7 | + | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 8 | + | * copies of the Software, and to permit persons to whom the Software is | |
| 9 | + | * furnished to do so, subject to the following conditions: | |
| 10 | + | * | |
| 11 | + | * The above copyright notice and this permission notice shall be included in | |
| 12 | + | * all copies or substantial portions of the Software. | |
| 13 | + | * | |
| 14 | + | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 15 | + | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 16 | + | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 17 | + | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 18 | + | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 19 | + | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| 20 | + | * THE SOFTWARE. | |
| 21 | + | */ | |
| 22 | + | ||
| 23 | + | #include "map.h" | |
| 24 | + | #include "game.h" | |
| 25 | + | #include "item.h" | |
| 26 | + | #include "localplayer.h" | |
| 27 | + | #include "mapview.h" | |
| 28 | + | #include "minimap.h" | |
| 29 | + | #include "missile.h" | |
| 30 | + | #include "statictext.h" | |
| 31 | + | #include "tile.h" | |
| 32 | + | ||
| 33 | + | #include <framework/core/application.h> | |
| 34 | + | #include <framework/core/asyncdispatcher.h> | |
| 35 | + | #include <framework/core/eventdispatcher.h> | |
| 36 | + | #include <framework/util/extras.h> | |
| 37 | + | #include <set> | |
| 38 | + | ||
| 39 | + | Map g_map; | |
| 40 | + | TilePtr Map::m_nulltile = nullptr; | |
| 41 | + | ||
| 42 | + | void Map::init() { | |
| 43 | + | resetAwareRange(); | |
| 44 | + | m_animationFlags |= Animation_Show; | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | void Map::terminate() { clean(); } | |
| 48 | + | ||
| 49 | + | void Map::addMapView(const MapViewPtr &mapView) { | |
| 50 | + | m_mapViews.push_back(mapView); | |
| 51 | + | } | |
| 52 | + | ||
| 53 | + | void Map::removeMapView(const MapViewPtr &mapView) { | |
| 54 | + | auto it = std::find(m_mapViews.begin(), m_mapViews.end(), mapView); | |
| 55 | + | if (it != m_mapViews.end()) | |
| 56 | + | m_mapViews.erase(it); | |
| 57 | + | } | |
| 58 | + | ||
| 59 | + | void Map::notificateTileUpdate(const Position &pos, bool updateMinimap) { | |
| 60 | + | if (!pos.isMapPosition()) | |
| 61 | + | return; | |
| 62 | + | ||
| 63 | + | for (const MapViewPtr &mapView : m_mapViews) | |
| 64 | + | mapView->onTileUpdate(pos); | |
| 65 | + | ||
| 66 | + | if (!updateMinimap) | |
| 67 | + | return; | |
| 68 | + | ||
| 69 | + | if (!g_game.getFeature(Otc::GameMinimapLimitedToSingleFloor) || | |
| 70 | + | (m_centralPosition.z == pos.z)) { | |
| 71 | + | g_minimap.updateTile(pos, getTile(pos)); | |
| 72 | + | } | |
| 73 | + | } | |
| 74 | + | ||
| 75 | + | void Map::requestVisibleTilesCacheUpdate() { | |
| 76 | + | for (const MapViewPtr &mapView : m_mapViews) | |
| 77 | + | mapView->requestVisibleTilesCacheUpdate(); | |
| 78 | + | } | |
| 79 | + | ||
| 80 | + | void Map::clean() { | |
| 81 | + | cleanDynamicThings(); | |
| 82 | + | ||
| 83 | + | for (int i = 0; i <= Otc::MAX_Z; ++i) | |
| 84 | + | m_tileBlocks[i].clear(); | |
| 85 | + | ||
| 86 | + | m_waypoints.clear(); | |
| 87 | + | ||
| 88 | + | g_towns.clear(); | |
| 89 | + | g_houses.clear(); | |
| 90 | + | g_creatures.clearSpawns(); | |
| 91 | + | m_tilesRect = Rect(65534, 65534, 0, 0); | |
| 92 | + | } | |
| 93 | + | ||
| 94 | + | void Map::cleanDynamicThings() { | |
| 95 | + | for (const auto &pair : m_knownCreatures) { | |
| 96 | + | const CreaturePtr &creature = pair.second; | |
| 97 | + | removeThing(creature); | |
| 98 | + | } | |
| 99 | + | m_knownCreatures.clear(); | |
| 100 | + | ||
| 101 | + | for (int i = 0; i <= Otc::MAX_Z; ++i) | |
| 102 | + | m_floorMissiles[i].clear(); | |
| 103 | + | ||
| 104 | + | cleanTexts(); | |
| 105 | + | } | |
| 106 | + | ||
| 107 | + | void Map::cleanTexts() { | |
| 108 | + | m_animatedTexts.clear(); | |
| 109 | + | m_staticTexts.clear(); | |
| 110 | + | } | |
| 111 | + | ||
| 112 | + | void Map::addThing(const ThingPtr &thing, const Position &pos, int stackPos) { | |
| 113 | + | if (!thing) | |
| 114 | + | return; | |
| 115 | + | ||
| 116 | + | if (thing->isItem() || thing->isCreature() || thing->isEffect()) { | |
| 117 | + | const TilePtr &tile = getOrCreateTile(pos); | |
| 118 | + | if (tile) | |
| 119 | + | tile->addThing(thing, stackPos); | |
| 120 | + | } else { | |
| 121 | + | if (thing->isMissile()) { | |
| 122 | + | m_floorMissiles[pos.z].push_back(thing->static_self_cast<Missile>()); | |
| 123 | + | } else if (thing->isAnimatedText()) { | |
| 124 | + | // this code will stack animated texts of the same color | |
| 125 | + | AnimatedTextPtr animatedText = thing->static_self_cast<AnimatedText>(); | |
| 126 | + | ||
| 127 | + | AnimatedTextPtr prevAnimatedText; | |
| 128 | + | bool merged = false; | |
| 129 | + | for (auto other : m_animatedTexts) { | |
| 130 | + | if (other->getPosition() == pos) { | |
| 131 | + | prevAnimatedText = other; | |
| 132 | + | if (!g_game.getFeature(Otc::GameDontMergeAnimatedText)) { | |
| 133 | + | if (other->merge(animatedText)) { | |
| 134 | + | merged = true; | |
| 135 | + | break; | |
| 136 | + | } | |
| 137 | + | } | |
| 138 | + | } | |
| 139 | + | } | |
| 140 | + | ||
| 141 | + | if (!merged) { | |
| 142 | + | if (prevAnimatedText) { | |
| 143 | + | Point offset = prevAnimatedText->getOffset(); | |
| 144 | + | float t = prevAnimatedText->getTimer().ticksElapsed(); | |
| 145 | + | int y = 48 * t / (float)Otc::ANIMATED_TEXT_DURATION; | |
| 146 | + | offset -= Point(0, y); | |
| 147 | + | offset.y = std::min<int>(std::max<int>(0, offset.y + 12), 24); | |
| 148 | + | animatedText->setOffset(offset); | |
| 149 | + | } | |
| 150 | + | m_animatedTexts.push_back(animatedText); | |
| 151 | + | } | |
| 152 | + | } else if (thing->isStaticText()) { | |
| 153 | + | StaticTextPtr staticText = thing->static_self_cast<StaticText>(); | |
| 154 | + | ||
| 155 | + | bool mustAdd = true; | |
| 156 | + | for (auto other : m_staticTexts) { | |
| 157 | + | // try to combine messages | |
| 158 | + | if (other->getPosition() == pos && | |
| 159 | + | other->addColoredMessage(staticText->getName(), | |
| 160 | + | staticText->getMessageMode(), | |
| 161 | + | staticText->getFirstMessage())) { | |
| 162 | + | mustAdd = false; | |
| 163 | + | break; | |
| 164 | + | } | |
| 165 | + | } | |
| 166 | + | ||
| 167 | + | if (mustAdd) | |
| 168 | + | m_staticTexts.push_back(staticText); | |
| 169 | + | else | |
| 170 | + | return; | |
| 171 | + | } | |
| 172 | + | ||
| 173 | + | thing->setPosition(pos); | |
| 174 | + | thing->onAppear(); | |
| 175 | + | ||
| 176 | + | if (thing->isMissile()) { | |
| 177 | + | g_lua.callGlobalField("g_map", "onMissle", thing); | |
| 178 | + | } else if (thing->isAnimatedText()) { | |
| 179 | + | AnimatedTextPtr animatedText = thing->static_self_cast<AnimatedText>(); | |
| 180 | + | g_lua.callGlobalField("g_map", "onAnimatedText", thing, | |
| 181 | + | animatedText->getText()); | |
| 182 | + | } else if (thing->isStaticText()) { | |
| 183 | + | StaticTextPtr staticText = thing->static_self_cast<StaticText>(); | |
| 184 | + | g_lua.callGlobalField("g_map", "onStaticText", thing, | |
| 185 | + | staticText->getText()); | |
| 186 | + | } | |
| 187 | + | } | |
| 188 | + | ||
| 189 | + | notificateTileUpdate(pos, thing->isItem()); | |
| 190 | + | } | |
| 191 | + | ||
| 192 | + | void Map::setTileSpeed(const Position &pos, uint16_t speed, uint8_t blocking) { | |
| 193 | + | const TilePtr &tile = getOrCreateTile(pos); | |
| 194 | + | if (tile) | |
| 195 | + | tile->setSpeed(speed, blocking); | |
| 196 | + | } | |
| 197 | + | ||
| 198 | + | ThingPtr Map::getThing(const Position &pos, int stackPos) { | |
| 199 | + | if (TilePtr tile = getTile(pos)) | |
| 200 | + | return tile->getThing(stackPos); | |
| 201 | + | return nullptr; | |
| 202 | + | } | |
| 203 | + | ||
| 204 | + | bool Map::removeThing(const ThingPtr &thing) { | |
| 205 | + | if (!thing) | |
| 206 | + | return false; | |
| 207 | + | ||
| 208 | + | bool ret = false; | |
| 209 | + | if (thing->isMissile()) { | |
| 210 | + | MissilePtr missile = thing->static_self_cast<Missile>(); | |
| 211 | + | int z = missile->getPosition().z; | |
| 212 | + | auto it = std::find(m_floorMissiles[z].begin(), m_floorMissiles[z].end(), | |
| 213 | + | missile); | |
| 214 | + | if (it != m_floorMissiles[z].end()) { | |
| 215 | + | m_floorMissiles[z].erase(it); | |
| 216 | + | ret = true; | |
| 217 | + | } | |
| 218 | + | } else if (thing->isAnimatedText()) { | |
| 219 | + | AnimatedTextPtr animatedText = thing->static_self_cast<AnimatedText>(); | |
| 220 | + | auto it = | |
| 221 | + | std::find(m_animatedTexts.begin(), m_animatedTexts.end(), animatedText); | |
| 222 | + | if (it != m_animatedTexts.end()) { | |
| 223 | + | m_animatedTexts.erase(it); | |
| 224 | + | ret = true; | |
| 225 | + | } | |
| 226 | + | } else if (thing->isStaticText()) { | |
| 227 | + | StaticTextPtr staticText = thing->static_self_cast<StaticText>(); | |
| 228 | + | auto it = std::find(m_staticTexts.begin(), m_staticTexts.end(), staticText); | |
| 229 | + | if (it != m_staticTexts.end()) { | |
| 230 | + | m_staticTexts.erase(it); | |
| 231 | + | ret = true; | |
| 232 | + | } | |
| 233 | + | } else if (const TilePtr &tile = thing->getTile()) | |
| 234 | + | ret = tile->removeThing(thing); | |
| 235 | + | ||
| 236 | + | notificateTileUpdate(thing->getPosition(), thing->isItem()); | |
| 237 | + | return ret; | |
| 238 | + | } | |
| 239 | + | ||
| 240 | + | bool Map::removeThingByPos(const Position &pos, int stackPos) { | |
| 241 | + | if (TilePtr tile = getTile(pos)) | |
| 242 | + | return removeThing(tile->getThing(stackPos)); | |
| 243 | + | return false; | |
| 244 | + | } | |
| 245 | + | ||
| 246 | + | void Map::colorizeThing(const ThingPtr &thing, const Color &color) { | |
| 247 | + | if (!thing) | |
| 248 | + | return; | |
| 249 | + | ||
| 250 | + | if (thing->isItem()) | |
| 251 | + | thing->static_self_cast<Item>()->setColor(color); | |
| 252 | + | else if (thing->isCreature()) { | |
| 253 | + | const TilePtr &tile = thing->getTile(); | |
| 254 | + | VALIDATE(tile); | |
| 255 | + | ||
| 256 | + | const ThingPtr &topThing = tile->getTopThing(); | |
| 257 | + | VALIDATE(topThing); | |
| 258 | + | ||
| 259 | + | topThing->static_self_cast<Item>()->setColor(color); | |
| 260 | + | } | |
| 261 | + | } | |
| 262 | + | ||
| 263 | + | void Map::removeThingColor(const ThingPtr &thing) { | |
| 264 | + | if (!thing) | |
| 265 | + | return; | |
| 266 | + | ||
| 267 | + | if (thing->isItem()) | |
| 268 | + | thing->static_self_cast<Item>()->setColor(Color::alpha); | |
| 269 | + | else if (thing->isCreature()) { | |
| 270 | + | const TilePtr &tile = thing->getTile(); | |
| 271 | + | VALIDATE(tile); | |
| 272 | + | ||
| 273 | + | const ThingPtr &topThing = tile->getTopThing(); | |
| 274 | + | VALIDATE(topThing); | |
| 275 | + | ||
| 276 | + | topThing->static_self_cast<Item>()->setColor(Color::alpha); | |
| 277 | + | } | |
| 278 | + | } | |
| 279 | + | ||
| 280 | + | StaticTextPtr Map::getStaticText(const Position &pos) { | |
| 281 | + | for (auto staticText : m_staticTexts) { | |
| 282 | + | // try to combine messages | |
| 283 | + | if (staticText->getPosition() == pos) | |
| 284 | + | return staticText; | |
| 285 | + | } | |
| 286 | + | return nullptr; | |
| 287 | + | } | |
| 288 | + | ||
| 289 | + | const TilePtr &Map::createTile(const Position &pos) { | |
| 290 | + | if (!pos.isMapPosition()) | |
| 291 | + | return m_nulltile; | |
| 292 | + | if (pos.x < m_tilesRect.left()) | |
| 293 | + | m_tilesRect.setLeft(pos.x); | |
| 294 | + | if (pos.y < m_tilesRect.top()) | |
| 295 | + | m_tilesRect.setTop(pos.y); | |
| 296 | + | if (pos.x > m_tilesRect.right()) | |
| 297 | + | m_tilesRect.setRight(pos.x); | |
| 298 | + | if (pos.y > m_tilesRect.bottom()) | |
| 299 | + | m_tilesRect.setBottom(pos.y); | |
| 300 | + | TileBlock &block = m_tileBlocks[pos.z][getBlockIndex(pos)]; | |
| 301 | + | return block.create(pos); | |
| 302 | + | } | |
| 303 | + | ||
| 304 | + | template <typename... Items> | |
| 305 | + | const TilePtr &Map::createTileEx(const Position &pos, const Items &...items) { | |
| 306 | + | if (!pos.isValid()) | |
| 307 | + | return m_nulltile; | |
| 308 | + | const TilePtr &tile = getOrCreateTile(pos); | |
| 309 | + | auto vec = {items...}; | |
| 310 | + | for (auto it : vec) | |
| 311 | + | addThing(it, pos); | |
| 312 | + | ||
| 313 | + | return tile; | |
| 314 | + | } | |
| 315 | + | ||
| 316 | + | const TilePtr &Map::getOrCreateTile(const Position &pos) { | |
| 317 | + | if (!pos.isMapPosition()) | |
| 318 | + | return m_nulltile; | |
| 319 | + | if (pos.x < m_tilesRect.left()) | |
| 320 | + | m_tilesRect.setLeft(pos.x); | |
| 321 | + | if (pos.y < m_tilesRect.top()) | |
| 322 | + | m_tilesRect.setTop(pos.y); | |
| 323 | + | if (pos.x > m_tilesRect.right()) | |
| 324 | + | m_tilesRect.setRight(pos.x); | |
| 325 | + | if (pos.y > m_tilesRect.bottom()) | |
| 326 | + | m_tilesRect.setBottom(pos.y); | |
| 327 | + | TileBlock &block = m_tileBlocks[pos.z][getBlockIndex(pos)]; | |
| 328 | + | return block.getOrCreate(pos); | |
| 329 | + | } | |
| 330 | + | ||
| 331 | + | const TilePtr &Map::getTile(const Position &pos) { | |
| 332 | + | if (!pos.isMapPosition()) | |
| 333 | + | return m_nulltile; | |
| 334 | + | auto it = m_tileBlocks[pos.z].find(getBlockIndex(pos)); | |
| 335 | + | if (it != m_tileBlocks[pos.z].end()) | |
| 336 | + | return it->second.get(pos); | |
| 337 | + | return m_nulltile; | |
| 338 | + | } | |
| 339 | + | ||
| 340 | + | const TileList Map::getTiles(int floor /* = -1*/) { | |
| 341 | + | TileList tiles; | |
| 342 | + | if (floor > Otc::MAX_Z) { | |
| 343 | + | return tiles; | |
| 344 | + | } else if (floor < 0) { | |
| 345 | + | // Search all floors | |
| 346 | + | for (uint8_t z = 0; z <= Otc::MAX_Z; ++z) { | |
| 347 | + | for (const auto &pair : m_tileBlocks[z]) { | |
| 348 | + | const TileBlock &block = pair.second; | |
| 349 | + | for (const TilePtr &tile : block.getTiles()) { | |
| 350 | + | if (tile != nullptr) | |
| 351 | + | tiles.push_back(tile); | |
| 352 | + | } | |
| 353 | + | } | |
| 354 | + | } | |
| 355 | + | } else { | |
| 356 | + | for (const auto &pair : m_tileBlocks[floor]) { | |
| 357 | + | const TileBlock &block = pair.second; | |
| 358 | + | for (const TilePtr &tile : block.getTiles()) { | |
| 359 | + | if (tile != nullptr) | |
| 360 | + | tiles.push_back(tile); | |
| 361 | + | } | |
| 362 | + | } | |
| 363 | + | } | |
| 364 | + | return tiles; | |
| 365 | + | } | |
| 366 | + | ||
| 367 | + | void Map::cleanTile(const Position &pos) { | |
| 368 | + | if (!pos.isMapPosition()) | |
| 369 | + | return; | |
| 370 | + | auto it = m_tileBlocks[pos.z].find(getBlockIndex(pos)); | |
| 371 | + | if (it != m_tileBlocks[pos.z].end()) { | |
| 372 | + | TileBlock &block = it->second; | |
| 373 | + | if (const TilePtr &tile = block.get(pos)) { | |
| 374 | + | tile->clean(); | |
| 375 | + | if (tile->canErase()) | |
| 376 | + | block.remove(pos); | |
| 377 | + | ||
| 378 | + | notificateTileUpdate(pos, false); | |
| 379 | + | } | |
| 380 | + | } | |
| 381 | + | for (auto it = m_staticTexts.begin(); it != m_staticTexts.end();) { | |
| 382 | + | const StaticTextPtr &staticText = *it; | |
| 383 | + | if (staticText->getPosition() == pos && | |
| 384 | + | staticText->getMessageMode() == Otc::MessageNone) | |
| 385 | + | it = m_staticTexts.erase(it); | |
| 386 | + | else | |
| 387 | + | ++it; | |
| 388 | + | } | |
| 389 | + | ||
| 390 | + | if (!g_game.getFeature(Otc::GameMinimapLimitedToSingleFloor) || | |
| 391 | + | (m_centralPosition.z == pos.z)) { | |
| 392 | + | g_minimap.updateTile(pos, getTile(pos)); | |
| 393 | + | } | |
| 394 | + | } | |
| 395 | + | ||
| 396 | + | void Map::setShowZone(tileflags_t zone, bool show) { | |
| 397 | + | if (show) | |
| 398 | + | m_zoneFlags |= (uint32)zone; | |
| 399 | + | else | |
| 400 | + | m_zoneFlags &= ~(uint32)zone; | |
| 401 | + | } | |
| 402 | + | ||
| 403 | + | void Map::setShowZones(bool show) { | |
| 404 | + | if (!show) | |
| 405 | + | m_zoneFlags = 0; | |
| 406 | + | else if (m_zoneFlags == 0) | |
| 407 | + | m_zoneFlags = TILESTATE_HOUSE | TILESTATE_PROTECTIONZONE; | |
| 408 | + | } | |
| 409 | + | ||
| 410 | + | void Map::setZoneColor(tileflags_t zone, const Color &color) { | |
| 411 | + | if ((m_zoneFlags & zone) == zone) | |
| 412 | + | m_zoneColors[zone] = color; | |
| 413 | + | } | |
| 414 | + | ||
| 415 | + | Color Map::getZoneColor(tileflags_t flag) { | |
| 416 | + | auto it = m_zoneColors.find(flag); | |
| 417 | + | if (it == m_zoneColors.end()) | |
| 418 | + | return Color::alpha; | |
| 419 | + | return it->second; | |
| 420 | + | } | |
| 421 | + | ||
| 422 | + | void Map::setForceShowAnimations(bool force) { | |
| 423 | + | if (force) { | |
| 424 | + | if (!(m_animationFlags & Animation_Force)) | |
| 425 | + | m_animationFlags |= Animation_Force; | |
| 426 | + | } else | |
| 427 | + | m_animationFlags &= ~Animation_Force; | |
| 428 | + | } | |
| 429 | + | ||
| 430 | + | bool Map::isForcingAnimations() { | |
| 431 | + | return (m_animationFlags & Animation_Force) == Animation_Force; | |
| 432 | + | } | |
| 433 | + | ||
| 434 | + | bool Map::isShowingAnimations() { | |
| 435 | + | return (m_animationFlags & Animation_Show) == Animation_Show; | |
| 436 | + | } | |
| 437 | + | ||
| 438 | + | void Map::setShowAnimations(bool show) { | |
| 439 | + | if (show) { | |
| 440 | + | if (!(m_animationFlags & Animation_Show)) | |
| 441 | + | m_animationFlags |= Animation_Show; | |
| 442 | + | } else | |
| 443 | + | m_animationFlags &= ~Animation_Show; | |
| 444 | + | } | |
| 445 | + | ||
| 446 | + | std::map<Position, ItemPtr> Map::findItemsById(uint16 clientId, uint32 max) { | |
| 447 | + | std::map<Position, ItemPtr> ret; | |
| 448 | + | uint32 count = 0; | |
| 449 | + | for (uint8_t z = 0; z <= Otc::MAX_Z; ++z) { | |
| 450 | + | for (const auto &pair : m_tileBlocks[z]) { | |
| 451 | + | const TileBlock &block = pair.second; | |
| 452 | + | for (const TilePtr &tile : block.getTiles()) { | |
| 453 | + | if (unlikely(!tile || tile->isEmpty())) | |
| 454 | + | continue; | |
| 455 | + | for (const ItemPtr &item : tile->getItems()) { | |
| 456 | + | if (item->getId() == clientId) { | |
| 457 | + | ret.insert(std::make_pair(tile->getPosition(), item)); | |
| 458 | + | if (++count >= max) | |
| 459 | + | break; | |
| 460 | + | } | |
| 461 | + | } | |
| 462 | + | } | |
| 463 | + | } | |
| 464 | + | } | |
| 465 | + | ||
| 466 | + | return ret; | |
| 467 | + | } | |
| 468 | + | ||
| 469 | + | void Map::addCreature(const CreaturePtr &creature) { | |
| 470 | + | m_knownCreatures[creature->getId()] = creature; | |
| 471 | + | } | |
| 472 | + | ||
| 473 | + | CreaturePtr Map::getCreatureById(uint32 id) { | |
| 474 | + | auto it = m_knownCreatures.find(id); | |
| 475 | + | if (it == m_knownCreatures.end()) | |
| 476 | + | return nullptr; | |
| 477 | + | return it->second; | |
| 478 | + | } | |
| 479 | + | ||
| 480 | + | void Map::removeCreatureById(uint32 id) { | |
| 481 | + | if (id == 0) | |
| 482 | + | return; | |
| 483 | + | ||
| 484 | + | auto it = m_knownCreatures.find(id); | |
| 485 | + | if (it != m_knownCreatures.end()) | |
| 486 | + | m_knownCreatures.erase(it); | |
| 487 | + | } | |
| 488 | + | ||
| 489 | + | void Map::removeUnawareThings() { | |
| 490 | + | // remove creatures from tiles that we are not aware of anymore | |
| 491 | + | for (const auto &pair : m_knownCreatures) { | |
| 492 | + | const CreaturePtr &creature = pair.second; | |
| 493 | + | if (!isAwareOfPosition(creature->getPosition())) | |
| 494 | + | removeThing(creature); | |
| 495 | + | } | |
| 496 | + | ||
| 497 | + | // remove static texts from tiles that we are not aware anymore | |
| 498 | + | for (auto it = m_staticTexts.begin(); it != m_staticTexts.end();) { | |
| 499 | + | const StaticTextPtr &staticText = *it; | |
| 500 | + | if (staticText->getMessageMode() == Otc::MessageNone && | |
| 501 | + | !isAwareOfPosition(staticText->getPosition())) | |
| 502 | + | it = m_staticTexts.erase(it); | |
| 503 | + | else | |
| 504 | + | ++it; | |
| 505 | + | } | |
| 506 | + | ||
| 507 | + | bool extended = g_game.getFeature(Otc::GameBiggerMapCache); | |
| 508 | + | if (!g_game.getFeature(Otc::GameKeepUnawareTiles)) { | |
| 509 | + | // remove tiles that we are not aware anymore | |
| 510 | + | for (int z = 0; z <= Otc::MAX_Z; ++z) { | |
| 511 | + | auto &tileBlocks = m_tileBlocks[z]; | |
| 512 | + | for (auto it = tileBlocks.begin(); it != tileBlocks.end();) { | |
| 513 | + | TileBlock &block = (*it).second; | |
| 514 | + | bool blockEmpty = true; | |
| 515 | + | for (const TilePtr &tile : block.getTiles()) { | |
| 516 | + | if (!tile) | |
| 517 | + | continue; | |
| 518 | + | ||
| 519 | + | const Position &pos = tile->getPosition(); | |
| 520 | + | ||
| 521 | + | if (!isAwareOfPositionForClean(pos, extended)) | |
| 522 | + | block.remove(pos); | |
| 523 | + | else | |
| 524 | + | blockEmpty = false; | |
| 525 | + | } | |
| 526 | + | ||
| 527 | + | if (blockEmpty) | |
| 528 | + | it = tileBlocks.erase(it); | |
| 529 | + | else | |
| 530 | + | ++it; | |
| 531 | + | } | |
| 532 | + | } | |
| 533 | + | } | |
| 534 | + | } | |
| 535 | + | ||
| 536 | + | void Map::setCentralPosition(const Position ¢ralPosition) { | |
| 537 | + | if (m_centralPosition == centralPosition) | |
| 538 | + | return; | |
| 539 | + | ||
| 540 | + | m_centralPosition = centralPosition; | |
| 541 | + | ||
| 542 | + | removeUnawareThings(); | |
| 543 | + | ||
| 544 | + | // this fixes local player position when the local player is removed from the | |
| 545 | + | // map, the local player is removed from the map when there are too many | |
| 546 | + | // creatures on his tile, so there is no enough stackpos to the server send | |
| 547 | + | // him | |
| 548 | + | g_dispatcher.addEvent([this] { | |
| 549 | + | LocalPlayerPtr localPlayer = g_game.getLocalPlayer(); | |
| 550 | + | if (!localPlayer || localPlayer->getPosition() == m_centralPosition) | |
| 551 | + | return; | |
| 552 | + | TilePtr tile = localPlayer->getTile(); | |
| 553 | + | if (tile && tile->hasThing(localPlayer)) | |
| 554 | + | return; | |
| 555 | + | ||
| 556 | + | Position oldPos = localPlayer->getPosition(); | |
| 557 | + | Position pos = m_centralPosition; | |
| 558 | + | if (oldPos != pos) { | |
| 559 | + | if (!localPlayer->isRemoved()) | |
| 560 | + | localPlayer->onDisappear(); | |
| 561 | + | localPlayer->setPosition(pos); | |
| 562 | + | localPlayer->onAppear(); | |
| 563 | + | g_logger.debug("forced player position update"); | |
| 564 | + | } | |
| 565 | + | }); | |
| 566 | + | ||
| 567 | + | for (const MapViewPtr &mapView : m_mapViews) | |
| 568 | + | mapView->onMapCenterChange(centralPosition); | |
| 569 | + | } | |
| 570 | + | ||
| 571 | + | std::vector<CreaturePtr> Map::getSightSpectators(const Position ¢erPos, | |
| 572 | + | bool multiFloor) { | |
| 573 | + | return getSpectatorsInRangeEx(centerPos, multiFloor, m_awareRange.left - 1, | |
| 574 | + | m_awareRange.right - 2, m_awareRange.top - 1, | |
| 575 | + | m_awareRange.bottom - 2); | |
| 576 | + | } | |
| 577 | + | ||
| 578 | + | std::vector<CreaturePtr> Map::getSpectators(const Position ¢erPos, | |
| 579 | + | bool multiFloor) { | |
| 580 | + | return getSpectatorsInRangeEx(centerPos, multiFloor, m_awareRange.left, | |
| 581 | + | m_awareRange.right, m_awareRange.top, | |
| 582 | + | m_awareRange.bottom); | |
| 583 | + | } | |
| 584 | + | ||
| 585 | + | std::vector<CreaturePtr> Map::getSpectatorsInRange(const Position ¢erPos, | |
| 586 | + | bool multiFloor, int xRange, | |
| 587 | + | int yRange) { | |
| 588 | + | return getSpectatorsInRangeEx(centerPos, multiFloor, xRange, xRange, yRange, | |
| 589 | + | yRange); | |
| 590 | + | } | |
| 591 | + | ||
| 592 | + | std::vector<CreaturePtr> | |
| 593 | + | Map::getSpectatorsInRangeEx(const Position ¢erPos, bool multiFloor, | |
| 594 | + | int minXRange, int maxXRange, int minYRange, | |
| 595 | + | int maxYRange) { | |
| 596 | + | int minZRange = 0; | |
| 597 | + | int maxZRange = 0; | |
| 598 | + | std::vector<CreaturePtr> creatures; | |
| 599 | + | ||
| 600 | + | if (multiFloor) { | |
| 601 | + | minZRange = centerPos.z - getFirstAwareFloor(); | |
| 602 | + | maxZRange = getLastAwareFloor() - centerPos.z; | |
| 603 | + | } | |
| 604 | + | ||
| 605 | + | // TODO: optimize | |
| 606 | + | // TODO: delivery creatures in distance order | |
| 607 | + | ||
| 608 | + | for (int iz = -minZRange; iz <= maxZRange; ++iz) { | |
| 609 | + | for (int iy = -minYRange; iy <= maxYRange; ++iy) { | |
| 610 | + | for (int ix = -minXRange; ix <= maxXRange; ++ix) { | |
| 611 | + | TilePtr tile = getTile(centerPos.translated(ix, iy, iz)); | |
| 612 | + | if (!tile) | |
| 613 | + | continue; | |
| 614 | + | ||
| 615 | + | auto tileCreatures = tile->getCreatures(); | |
| 616 | + | creatures.insert(creatures.end(), tileCreatures.rbegin(), | |
| 617 | + | tileCreatures.rend()); | |
| 618 | + | } | |
| 619 | + | } | |
| 620 | + | } | |
| 621 | + | ||
| 622 | + | return creatures; | |
| 623 | + | } | |
| 624 | + | ||
| 625 | + | std::vector<CreaturePtr> Map::getSpectatorsByPattern(const Position ¢erPos, | |
| 626 | + | const std::string &pattern, | |
| 627 | + | Otc::Direction direction) { | |
| 628 | + | std::vector<bool> finalPattern(pattern.size(), false); | |
| 629 | + | std::vector<CreaturePtr> creatures; | |
| 630 | + | int width = 0, height = 0, lineLength = 0, p = 0; | |
| 631 | + | for (auto &c : pattern) { | |
| 632 | + | lineLength += 1; | |
| 633 | + | if (c == '0' || c == '-') { | |
| 634 | + | p += 1; | |
| 635 | + | } else if (c == '1' || c == '+') { | |
| 636 | + | finalPattern[p++] = true; | |
| 637 | + | } else if (c == 'N' || c == 'n') { | |
| 638 | + | finalPattern[p++] = direction == Otc::North; | |
| 639 | + | } else if (c == 'E' || c == 'e') { | |
| 640 | + | finalPattern[p++] = direction == Otc::East; | |
| 641 | + | } else if (c == 'W' || c == 'w') { | |
| 642 | + | finalPattern[p++] = direction == Otc::West; | |
| 643 | + | } else if (c == 'S' || c == 's') { | |
| 644 | + | finalPattern[p++] = direction == Otc::South; | |
| 645 | + | } else { | |
| 646 | + | lineLength -= 1; | |
| 647 | + | if (lineLength > 1) { | |
| 648 | + | if (width == 0) | |
| 649 | + | width = lineLength; | |
| 650 | + | if (width != lineLength) { | |
| 651 | + | g_logger.error(stdext::format( | |
| 652 | + | "Invalid pattern for getSpectatorsByPattern: %s", pattern)); | |
| 653 | + | return creatures; | |
| 654 | + | } | |
| 655 | + | height += 1; | |
| 656 | + | lineLength = 0; | |
| 657 | + | } | |
| 658 | + | } | |
| 659 | + | } | |
| 660 | + | if (lineLength > 0) { | |
| 661 | + | if (width == 0) | |
| 662 | + | width = lineLength; | |
| 663 | + | if (width != lineLength) { | |
| 664 | + | g_logger.error(stdext::format( | |
| 665 | + | "Invalid pattern for getSpectatorsByPattern: %s", pattern)); | |
| 666 | + | return creatures; | |
| 667 | + | } | |
| 668 | + | height += 1; | |
| 669 | + | } | |
| 670 | + | if (width % 2 != 1 || height % 2 != 1) { | |
| 671 | + | g_logger.error( | |
| 672 | + | stdext::format("Invalid pattern for getSpectatorsByPattern, width and " | |
| 673 | + | "height should be odd (height: %i width: %i)", | |
| 674 | + | height, width)); | |
| 675 | + | return creatures; | |
| 676 | + | } | |
| 677 | + | ||
| 678 | + | p = 0; | |
| 679 | + | for (int y = centerPos.y - height / 2, endy = centerPos.y + height / 2; | |
| 680 | + | y <= endy; ++y) { | |
| 681 | + | for (int x = centerPos.x - width / 2, endx = centerPos.x + width / 2; | |
| 682 | + | x <= endx; ++x) { | |
| 683 | + | if (!finalPattern[p++]) | |
| 684 | + | continue; | |
| 685 | + | TilePtr tile = getTile(Position(x, y, centerPos.z)); | |
| 686 | + | if (!tile) | |
| 687 | + | continue; | |
| 688 | + | auto tileCreatures = tile->getCreatures(); | |
| 689 | + | creatures.insert(creatures.end(), tileCreatures.rbegin(), | |
| 690 | + | tileCreatures.rend()); | |
| 691 | + | } | |
| 692 | + | } | |
| 693 | + | return creatures; | |
| 694 | + | } | |
| 695 | + | ||
| 696 | + | bool Map::isLookPossible(const Position &pos) { | |
| 697 | + | TilePtr tile = getTile(pos); | |
| 698 | + | return tile && tile->isLookPossible(); | |
| 699 | + | } | |
| 700 | + | ||
| 701 | + | bool Map::isCovered(const Position &pos, int firstFloor) { | |
| 702 | + | // check for tiles on top of the postion | |
| 703 | + | Position tilePos = pos; | |
| 704 | + | while (tilePos.coveredUp() && tilePos.z >= firstFloor) { | |
| 705 | + | TilePtr tile = getTile(tilePos); | |
| 706 | + | // the below tile is covered when the above tile has a full ground | |
| 707 | + | if (tile && tile->isFullGround()) | |
| 708 | + | return true; | |
| 709 | + | } | |
| 710 | + | return false; | |
| 711 | + | } | |
| 712 | + | ||
| 713 | + | bool Map::isCompletelyCovered(const Position &pos, int firstFloor) { | |
| 714 | + | const TilePtr &checkTile = getTile(pos); | |
| 715 | + | Position tilePos = pos; | |
| 716 | + | while (tilePos.coveredUp() && tilePos.z >= firstFloor) { | |
| 717 | + | bool covered = true; | |
| 718 | + | bool done = false; | |
| 719 | + | // check in 2x2 range tiles that has no transparent pixels | |
| 720 | + | for (int x = 0; x < 2 && !done; ++x) { | |
| 721 | + | for (int y = 0; y < 2 && !done; ++y) { | |
| 722 | + | const TilePtr &tile = getTile(tilePos.translated(-x, -y)); | |
| 723 | + | if (!tile || !tile->isFullyOpaque()) { | |
| 724 | + | covered = false; | |
| 725 | + | done = true; | |
| 726 | + | } else if (x == 0 && y == 0 && | |
| 727 | + | (!checkTile || checkTile->isSingleDimension())) { | |
| 728 | + | done = true; | |
| 729 | + | } | |
| 730 | + | } | |
| 731 | + | } | |
| 732 | + | if (covered) | |
| 733 | + | return true; | |
| 734 | + | } | |
| 735 | + | return false; | |
| 736 | + | } | |
| 737 | + | ||
| 738 | + | bool Map::isAwareOfPosition(const Position &pos, bool extended) { | |
| 739 | + | if ((pos.z < getFirstAwareFloor() || pos.z > getLastAwareFloor()) && | |
| 740 | + | !extended) | |
| 741 | + | return false; | |
| 742 | + | ||
| 743 | + | Position groundedPos = pos; | |
| 744 | + | while (groundedPos.z != m_centralPosition.z) { | |
| 745 | + | if (groundedPos.z > m_centralPosition.z) { | |
| 746 | + | if (groundedPos.x == 65535 || | |
| 747 | + | groundedPos.y == 65535) // When pos == 65535,65535,15 we cant go up to | |
| 748 | + | // 65536,65536,14 | |
| 749 | + | break; | |
| 750 | + | groundedPos.coveredUp(); | |
| 751 | + | } else { | |
| 752 | + | if (groundedPos.x == 0 || | |
| 753 | + | groundedPos.y == 0) // When pos == 0,0,0 we cant go down to -1,-1,1 | |
| 754 | + | break; | |
| 755 | + | groundedPos.coveredDown(); | |
| 756 | + | } | |
| 757 | + | } | |
| 758 | + | ||
| 759 | + | if (extended) { | |
| 760 | + | return m_centralPosition.isInRange( | |
| 761 | + | groundedPos, m_awareRange.left * 2, m_awareRange.right * 2, | |
| 762 | + | m_awareRange.top * 2, m_awareRange.bottom * 2); | |
| 763 | + | } | |
| 764 | + | return m_centralPosition.isInRange(groundedPos, m_awareRange.left, | |
| 765 | + | m_awareRange.right, m_awareRange.top, | |
| 766 | + | m_awareRange.bottom); | |
| 767 | + | } | |
| 768 | + | ||
| 769 | + | bool Map::isAwareOfPositionForClean(const Position &pos, bool extended) { | |
| 770 | + | if ((pos.z < getFirstAwareFloor() || pos.z > getLastAwareFloor()) && | |
| 771 | + | !extended) | |
| 772 | + | return false; | |
| 773 | + | ||
| 774 | + | Position groundedPos = pos; | |
| 775 | + | while (groundedPos.z != m_centralPosition.z) { | |
| 776 | + | if (groundedPos.z > m_centralPosition.z) { | |
| 777 | + | if (groundedPos.x == 65535 || | |
| 778 | + | groundedPos.y == 65535) // When pos == 65535,65535,15 we cant go up to | |
| 779 | + | // 65536,65536,14 | |
| 780 | + | break; | |
| 781 | + | groundedPos.coveredUp(); | |
| 782 | + | } else { | |
| 783 | + | if (groundedPos.x == 0 || | |
| 784 | + | groundedPos.y == 0) // When pos == 0,0,0 we cant go down to -1,-1,1 | |
| 785 | + | break; | |
| 786 | + | groundedPos.coveredDown(); | |
| 787 | + | } | |
| 788 | + | } | |
| 789 | + | ||
| 790 | + | if (extended) { | |
| 791 | + | return m_centralPosition.isInRange( | |
| 792 | + | groundedPos, m_awareRange.left * 4, m_awareRange.right * 4, | |
| 793 | + | m_awareRange.top * 4, m_awareRange.bottom * 4); | |
| 794 | + | } | |
| 795 | + | return m_centralPosition.isInRange( | |
| 796 | + | groundedPos, m_awareRange.left + 1, m_awareRange.right + 1, | |
| 797 | + | m_awareRange.top + 1, m_awareRange.bottom + 1); | |
| 798 | + | } | |
| 799 | + | ||
| 800 | + | void Map::setAwareRange(const AwareRange &range) { | |
| 801 | + | m_awareRange = range; | |
| 802 | + | removeUnawareThings(); | |
| 803 | + | } | |
| 804 | + | ||
| 805 | + | void Map::resetAwareRange() { | |
| 806 | + | AwareRange range; | |
| 807 | + | range.left = 8; | |
| 808 | + | range.top = 6; | |
| 809 | + | range.bottom = 7; | |
| 810 | + | range.right = 9; | |
| 811 | + | setAwareRange(range); | |
| 812 | + | } | |
| 813 | + | ||
| 814 | + | int Map::getFirstAwareFloor() { | |
| 815 | + | if (m_centralPosition.z > Otc::SEA_FLOOR) | |
| 816 | + | return m_centralPosition.z - Otc::AWARE_UNDEGROUND_FLOOR_RANGE; | |
| 817 | + | else | |
| 818 | + | return 0; | |
| 819 | + | } | |
| 820 | + | ||
| 821 | + | int Map::getLastAwareFloor() { | |
| 822 | + | if (m_centralPosition.z > Otc::SEA_FLOOR) | |
| 823 | + | return std::min<int>(m_centralPosition.z + | |
| 824 | + | Otc::AWARE_UNDEGROUND_FLOOR_RANGE, | |
| 825 | + | (int)Otc::MAX_Z); | |
| 826 | + | else | |
| 827 | + | // FIX (OTLand/OTCv8): Server sends floors 15->0 for ALL surface players. | |
| 828 | + | // Must keep all floors in the aware range so removeUnawareThings() | |
| 829 | + | // does NOT delete them on every walk step. | |
| 830 | + | return (int)Otc::MAX_Z; // Keep all floors when at surface | |
| 831 | + | } | |
| 832 | + | ||
| 833 | + | std::tuple<std::vector<Otc::Direction>, Otc::PathFindResult> | |
| 834 | + | Map::findPath(const Position &startPos, const Position &goalPos, | |
| 835 | + | int maxComplexity, int flags) { | |
| 836 | + | // pathfinding using A* search algorithm (otclientv8 note: it's dijkstra | |
| 837 | + | // algorithm) as described in http://en.wikipedia.org/wiki/A*_search_algorithm | |
| 838 | + | ||
| 839 | + | struct SNode { | |
| 840 | + | SNode(const Position &pos) | |
| 841 | + | : cost(0), totalCost(0), pos(pos), prev(nullptr), | |
| 842 | + | dir(Otc::InvalidDirection) {} | |
| 843 | + | float cost; | |
| 844 | + | float totalCost; | |
| 845 | + | Position pos; | |
| 846 | + | SNode *prev; | |
| 847 | + | Otc::Direction dir; | |
| 848 | + | }; | |
| 849 | + | ||
| 850 | + | struct LessNode { | |
| 851 | + | bool operator()(std::pair<SNode *, float> a, | |
| 852 | + | std::pair<SNode *, float> b) const { | |
| 853 | + | return b.second < a.second; | |
| 854 | + | } | |
| 855 | + | }; | |
| 856 | + | ||
| 857 | + | std::tuple<std::vector<Otc::Direction>, Otc::PathFindResult> ret; | |
| 858 | + | std::vector<Otc::Direction> &dirs = std::get<0>(ret); | |
| 859 | + | Otc::PathFindResult &result = std::get<1>(ret); | |
| 860 | + | ||
| 861 | + | result = Otc::PathFindResultNoWay; | |
| 862 | + | ||
| 863 | + | if (startPos == goalPos) { | |
| 864 | + | result = Otc::PathFindResultSamePosition; | |
| 865 | + | return ret; | |
| 866 | + | } | |
| 867 | + | ||
| 868 | + | if (startPos.z != goalPos.z) { | |
| 869 | + | result = Otc::PathFindResultImpossible; | |
| 870 | + | return ret; | |
| 871 | + | } | |
| 872 | + | ||
| 873 | + | // check the goal pos is walkable | |
| 874 | + | if (g_map.isAwareOfPosition(goalPos)) { | |
| 875 | + | const TilePtr goalTile = getTile(goalPos); | |
| 876 | + | if (!goalTile || | |
| 877 | + | (!goalTile->isWalkable(flags & Otc::PathFindIgnoreCreatures))) { | |
| 878 | + | return ret; | |
| 879 | + | } | |
| 880 | + | } else { | |
| 881 | + | const MinimapTile &goalTile = g_minimap.getTile(goalPos); | |
| 882 | + | if (goalTile.hasFlag(MinimapTileNotWalkable)) { | |
| 883 | + | return ret; | |
| 884 | + | } | |
| 885 | + | } | |
| 886 | + | ||
| 887 | + | std::unordered_map<Position, SNode *, PositionHasher> nodes; | |
| 888 | + | std::priority_queue<std::pair<SNode *, float>, | |
| 889 | + | std::vector<std::pair<SNode *, float>>, LessNode> | |
| 890 | + | searchList; | |
| 891 | + | ||
| 892 | + | SNode *currentNode = new SNode(startPos); | |
| 893 | + | currentNode->pos = startPos; | |
| 894 | + | nodes[startPos] = currentNode; | |
| 895 | + | SNode *foundNode = nullptr; | |
| 896 | + | while (currentNode) { | |
| 897 | + | if ((int)nodes.size() > maxComplexity) { | |
| 898 | + | result = Otc::PathFindResultTooFar; | |
| 899 | + | break; | |
| 900 | + | } | |
| 901 | + | ||
| 902 | + | // path found | |
| 903 | + | if (currentNode->pos == goalPos && | |
| 904 | + | (!foundNode || currentNode->cost < foundNode->cost)) | |
| 905 | + | foundNode = currentNode; | |
| 906 | + | ||
| 907 | + | // cost too high | |
| 908 | + | if (foundNode && currentNode->totalCost >= foundNode->cost) | |
| 909 | + | break; | |
| 910 | + | ||
| 911 | + | for (int i = -1; i <= 1; ++i) { | |
| 912 | + | for (int j = -1; j <= 1; ++j) { | |
| 913 | + | if (i == 0 && j == 0) | |
| 914 | + | continue; | |
| 915 | + | ||
| 916 | + | bool wasSeen = false; | |
| 917 | + | bool hasCreature = false; | |
| 918 | + | bool isNotWalkable = true; | |
| 919 | + | bool isNotPathable = true; | |
| 920 | + | int speed = 100; | |
| 921 | + | ||
| 922 | + | Position neighborPos = currentNode->pos.translated(i, j); | |
| 923 | + | if (neighborPos.x < 0 || neighborPos.y < 0) | |
| 924 | + | continue; | |
| 925 | + | if (g_map.isAwareOfPosition(neighborPos)) { | |
| 926 | + | wasSeen = true; | |
| 927 | + | if (const TilePtr &tile = getTile(neighborPos)) { | |
| 928 | + | hasCreature = tile->hasCreature() && | |
| 929 | + | (!(flags & Otc::PathFindIgnoreCreatures)); | |
| 930 | + | isNotWalkable = | |
| 931 | + | !tile->isWalkable(flags & Otc::PathFindIgnoreCreatures); | |
| 932 | + | isNotPathable = !tile->isPathable(); | |
| 933 | + | speed = tile->getGroundSpeed(); | |
| 934 | + | } | |
| 935 | + | } else { | |
| 936 | + | const MinimapTile &mtile = g_minimap.getTile(neighborPos); | |
| 937 | + | wasSeen = mtile.hasFlag(MinimapTileWasSeen); | |
| 938 | + | isNotWalkable = mtile.hasFlag(MinimapTileNotWalkable); | |
| 939 | + | isNotPathable = mtile.hasFlag(MinimapTileNotPathable); | |
| 940 | + | if (isNotWalkable || isNotPathable) | |
| 941 | + | wasSeen = true; | |
| 942 | + | speed = mtile.getSpeed(); | |
| 943 | + | } | |
| 944 | + | ||
| 945 | + | float walkFactor = 0; | |
| 946 | + | if (neighborPos != goalPos) { | |
| 947 | + | if (!(flags & Otc::PathFindAllowNotSeenTiles) && !wasSeen) | |
| 948 | + | continue; | |
| 949 | + | if (wasSeen) { | |
| 950 | + | if (!(flags & Otc::PathFindAllowCreatures) && hasCreature) | |
| 951 | + | continue; | |
| 952 | + | if (!(flags & Otc::PathFindAllowNonPathable) && isNotPathable) | |
| 953 | + | continue; | |
| 954 | + | if (!(flags & Otc::PathFindAllowNonWalkable) && isNotWalkable) | |
| 955 | + | continue; | |
| 956 | + | } | |
| 957 | + | } else { | |
| 958 | + | if (!(flags & Otc::PathFindAllowNotSeenTiles) && !wasSeen) | |
| 959 | + | continue; | |
| 960 | + | if (wasSeen) { | |
| 961 | + | if (!(flags & Otc::PathFindAllowNonWalkable) && isNotWalkable) | |
| 962 | + | continue; | |
| 963 | + | } | |
| 964 | + | } | |
| 965 | + | ||
| 966 | + | Otc::Direction walkDir = | |
| 967 | + | currentNode->pos.getDirectionFromPosition(neighborPos); | |
| 968 | + | if (walkDir >= Otc::NorthEast) | |
| 969 | + | walkFactor += 3.0f; | |
| 970 | + | else | |
| 971 | + | walkFactor += 1.0f; | |
| 972 | + | ||
| 973 | + | float cost = currentNode->cost + (speed * walkFactor) / 100.0f; | |
| 974 | + | ||
| 975 | + | SNode *neighborNode; | |
| 976 | + | if (nodes.find(neighborPos) == nodes.end()) { | |
| 977 | + | neighborNode = new SNode(neighborPos); | |
| 978 | + | nodes[neighborPos] = neighborNode; | |
| 979 | + | } else { | |
| 980 | + | neighborNode = nodes[neighborPos]; | |
| 981 | + | if (neighborNode->cost <= cost) | |
| 982 | + | continue; | |
| 983 | + | } | |
| 984 | + | ||
| 985 | + | neighborNode->prev = currentNode; | |
| 986 | + | neighborNode->cost = cost; | |
| 987 | + | neighborNode->totalCost = | |
| 988 | + | neighborNode->cost + neighborPos.distance(goalPos); | |
| 989 | + | neighborNode->dir = walkDir; | |
| 990 | + | searchList.push(std::make_pair(neighborNode, neighborNode->totalCost)); | |
| 991 | + | } | |
| 992 | + | } | |
| 993 | + | ||
| 994 | + | if (!searchList.empty()) { | |
| 995 | + | currentNode = searchList.top().first; | |
| 996 | + | searchList.pop(); | |
| 997 | + | } else | |
| 998 | + | currentNode = nullptr; | |
| 999 | + | } | |
| 1000 | + | ||
| 1001 | + | if (foundNode) { | |
| 1002 | + | currentNode = foundNode; | |
| 1003 | + | while (currentNode) { | |
| 1004 | + | dirs.push_back(currentNode->dir); | |
| 1005 | + | currentNode = currentNode->prev; | |
| 1006 | + | } | |
| 1007 | + | dirs.pop_back(); | |
| 1008 | + | std::reverse(dirs.begin(), dirs.end()); | |
| 1009 | + | result = Otc::PathFindResultOk; | |
| 1010 | + | } | |
| 1011 | + | ||
| 1012 | + | for (auto it : nodes) | |
| 1013 | + | delete it.second; | |
| 1014 | + | ||
| 1015 | + | return ret; | |
| 1016 | + | } | |
| 1017 | + | ||
| 1018 | + | int Map::getMinimapColor(const Position &pos) { | |
| 1019 | + | int color = 0; | |
| 1020 | + | if (const TilePtr &tile = getTile(pos)) { | |
| 1021 | + | color = tile->getMinimapColorByte(); | |
| 1022 | + | } | |
| 1023 | + | if (color == 0) { | |
| 1024 | + | const MinimapTile &mtile = g_minimap.getTile(pos); | |
| 1025 | + | color = mtile.color; | |
| 1026 | + | } | |
| 1027 | + | return color; | |
| 1028 | + | } | |
| 1029 | + | ||
| 1030 | + | bool Map::isPatchable(const Position &pos) { | |
| 1031 | + | if (const TilePtr &tile = getTile(pos)) { | |
| 1032 | + | return tile->isPathable(); | |
| 1033 | + | } | |
| 1034 | + | const MinimapTile &mtile = g_minimap.getTile(pos); | |
| 1035 | + | return !mtile.hasFlag(MinimapTileNotPathable); | |
| 1036 | + | } | |
| 1037 | + | ||
| 1038 | + | bool Map::isWalkable(const Position &pos, bool ignoreCreatures) { | |
| 1039 | + | if (const TilePtr &tile = getTile(pos)) { | |
| 1040 | + | return tile->isWalkable(ignoreCreatures); | |
| 1041 | + | } | |
| 1042 | + | const MinimapTile &mtile = g_minimap.getTile(pos); | |
| 1043 | + | return !mtile.hasFlag(MinimapTileNotPathable); | |
| 1044 | + | } | |
| 1045 | + | ||
| 1046 | + | bool Map::isSightClear(const Position &fromPos, const Position &toPos) { | |
| 1047 | + | if (fromPos == toPos) { | |
| 1048 | + | return true; | |
| 1049 | + | } | |
| 1050 | + | ||
| 1051 | + | Position start(fromPos.z > toPos.z ? toPos : fromPos); | |
| 1052 | + | Position destination(fromPos.z > toPos.z ? fromPos : toPos); | |
| 1053 | + | ||
| 1054 | + | const int8_t mx = start.x < destination.x ? 1 | |
| 1055 | + | : start.x == destination.x ? 0 | |
| 1056 | + | : -1; | |
| 1057 | + | const int8_t my = start.y < destination.y ? 1 | |
| 1058 | + | : start.y == destination.y ? 0 | |
| 1059 | + | : -1; | |
| 1060 | + | ||
| 1061 | + | int32_t A = destination.y - start.y; | |
| 1062 | + | int32_t B = start.x - destination.x; | |
| 1063 | + | int32_t C = -(A * destination.x + B * destination.y); | |
| 1064 | + | ||
| 1065 | + | while (start.x != destination.x || start.y != destination.y) { | |
| 1066 | + | int32_t move_hor = std::abs(A * (start.x + mx) + B * (start.y) + C); | |
| 1067 | + | int32_t move_ver = std::abs(A * (start.x) + B * (start.y + my) + C); | |
| 1068 | + | int32_t move_cross = std::abs(A * (start.x + mx) + B * (start.y + my) + C); | |
| 1069 | + | ||
| 1070 | + | if (start.y != destination.y && | |
| 1071 | + | (start.x == destination.x || move_hor > move_ver || | |
| 1072 | + | move_hor > move_cross)) { | |
| 1073 | + | start.y += my; | |
| 1074 | + | } | |
| 1075 | + | ||
| 1076 | + | if (start.x != destination.x && | |
| 1077 | + | (start.y == destination.y || move_ver > move_hor || | |
| 1078 | + | move_ver > move_cross)) { | |
| 1079 | + | start.x += mx; | |
| 1080 | + | } | |
| 1081 | + | ||
| 1082 | + | auto tile = getTile(Position(start.x, start.y, start.z)); | |
| 1083 | + | if (tile && tile->isBlockingProjectile()) { | |
| 1084 | + | return false; | |
| 1085 | + | } | |
| 1086 | + | } | |
| 1087 | + | ||
| 1088 | + | while (start.z != destination.z) { | |
| 1089 | + | auto tile = getTile(Position(start.x, start.y, start.z)); | |
| 1090 | + | if (tile && tile->getThingCount() > 0) { | |
| 1091 | + | return false; | |
| 1092 | + | } | |
| 1093 | + | start.z++; | |
| 1094 | + | } | |
| 1095 | + | ||
| 1096 | + | return true; | |
| 1097 | + | } | |
| 1098 | + | ||
| 1099 | + | bool Map::checkSightLine(const Position &fromPos, const Position &toPos) { | |
| 1100 | + | if (fromPos.z != toPos.z) | |
| 1101 | + | return false; | |
| 1102 | + | return checkSightLine(fromPos, toPos) || checkSightLine(toPos, fromPos); | |
| 1103 | + | } | |
| 1104 | + | ||
| 1105 | + | PathFindResult_ptr | |
| 1106 | + | Map::newFindPath(const Position &start, const Position &goal, | |
| 1107 | + | std::shared_ptr<std::list<Node *>> visibleNodes) { | |
| 1108 | + | auto ret = std::make_shared<PathFindResult>(); | |
| 1109 | + | ret->start = start; | |
| 1110 | + | ret->destination = goal; | |
| 1111 | + | ||
| 1112 | + | if (start == goal) { | |
| 1113 | + | ret->status = Otc::PathFindResultSamePosition; | |
| 1114 | + | return ret; | |
| 1115 | + | } | |
| 1116 | + | if (goal.z != start.z) { | |
| 1117 | + | return ret; | |
| 1118 | + | } | |
| 1119 | + | ||
| 1120 | + | struct LessNode { | |
| 1121 | + | bool operator()(Node *a, Node *b) const { | |
| 1122 | + | return b->totalCost < a->totalCost; | |
| 1123 | + | } | |
| 1124 | + | }; | |
| 1125 | + | ||
| 1126 | + | std::unordered_map<Position, Node *, PositionHasher> nodes; | |
| 1127 | + | std::priority_queue<Node *, std::vector<Node *>, LessNode> searchList; | |
| 1128 | + | ||
| 1129 | + | if (visibleNodes) { | |
| 1130 | + | for (auto &node : *visibleNodes) | |
| 1131 | + | nodes.emplace(node->pos, node); | |
| 1132 | + | } | |
| 1133 | + | ||
| 1134 | + | Node *initNode = new Node{1, 0, start, nullptr, 0, 0}; | |
| 1135 | + | nodes[start] = initNode; | |
| 1136 | + | searchList.push(initNode); | |
| 1137 | + | ||
| 1138 | + | int limit = 50000; | |
| 1139 | + | float distance = start.distance(goal); | |
| 1140 | + | ||
| 1141 | + | Node *dstNode = nullptr; | |
| 1142 | + | while (!searchList.empty() && --limit) { | |
| 1143 | + | Node *node = searchList.top(); | |
| 1144 | + | searchList.pop(); | |
| 1145 | + | if (node->pos == goal) { | |
| 1146 | + | dstNode = node; | |
| 1147 | + | break; | |
| 1148 | + | } | |
| 1149 | + | if (node->pos.distance(goal) > distance + 10000) | |
| 1150 | + | continue; | |
| 1151 | + | for (int i = -1; i <= 1; ++i) { | |
| 1152 | + | for (int j = -1; j <= 1; ++j) { | |
| 1153 | + | if (i == 0 && j == 0) | |
| 1154 | + | continue; | |
| 1155 | + | Position neighbor = node->pos.translated(i, j); | |
| 1156 | + | if (neighbor.x < 0 || neighbor.y < 0) | |
| 1157 | + | continue; | |
| 1158 | + | auto it = nodes.find(neighbor); | |
| 1159 | + | if (it == nodes.end()) { | |
| 1160 | + | auto blockAndTile = g_minimap.threadGetTile(neighbor); | |
| 1161 | + | bool wasSeen = blockAndTile.second.hasFlag(MinimapTileWasSeen); | |
| 1162 | + | bool isNotWalkable = | |
| 1163 | + | blockAndTile.second.hasFlag(MinimapTileNotWalkable); | |
| 1164 | + | bool isNotPathable = | |
| 1165 | + | blockAndTile.second.hasFlag(MinimapTileNotPathable); | |
| 1166 | + | bool isEmpty = blockAndTile.second.hasFlag(MinimapTileEmpty); | |
| 1167 | + | float speed = blockAndTile.second.getSpeed(); | |
| 1168 | + | if ((isNotWalkable || isNotPathable || isEmpty) && neighbor != goal) { | |
| 1169 | + | it = nodes.emplace(neighbor, nullptr).first; | |
| 1170 | + | } else { | |
| 1171 | + | if (!wasSeen) | |
| 1172 | + | speed = 2000; | |
| 1173 | + | it = nodes | |
| 1174 | + | .emplace(neighbor, | |
| 1175 | + | new Node{speed, 10000000.0f, neighbor, node, | |
| 1176 | + | node->distance + 1, wasSeen ? 0 : 1}) | |
| 1177 | + | .first; | |
| 1178 | + | } | |
| 1179 | + | } | |
| 1180 | + | if (!it->second) // no way | |
| 1181 | + | continue; | |
| 1182 | + | if (it->second->unseen > 50) | |
| 1183 | + | continue; | |
| 1184 | + | ||
| 1185 | + | float diagonal = ((i == 0 || j == 0) ? 1.0f : 3.0f); | |
| 1186 | + | float cost = it->second->cost * diagonal; | |
| 1187 | + | cost += | |
| 1188 | + | diagonal * (50.0f * std::max<float>(5.0f, it->second->pos.distance( | |
| 1189 | + | goal))); // heuristic | |
| 1190 | + | if (node->totalCost + cost + 50 < it->second->totalCost) { | |
| 1191 | + | it->second->totalCost = node->totalCost + cost; | |
| 1192 | + | it->second->prev = node; | |
| 1193 | + | if (it->second->unseen) | |
| 1194 | + | it->second->unseen = node->unseen + 1; | |
| 1195 | + | it->second->distance = node->distance + 1; | |
| 1196 | + | searchList.push(it->second); | |
| 1197 | + | } | |
| 1198 | + | } | |
| 1199 | + | } | |
| 1200 | + | } | |
| 1201 | + | ||
| 1202 | + | if (dstNode) { | |
| 1203 | + | while (dstNode && dstNode->prev) { | |
| 1204 | + | if (dstNode->unseen) { | |
| 1205 | + | ret->path.clear(); | |
| 1206 | + | } else { | |
| 1207 | + | ret->path.push_back( | |
| 1208 | + | dstNode->prev->pos.getDirectionFromPosition(dstNode->pos)); | |
| 1209 | + | } | |
| 1210 | + | dstNode = dstNode->prev; | |
| 1211 | + | } | |
| 1212 | + | std::reverse(ret->path.begin(), ret->path.end()); | |
| 1213 | + | ret->status = Otc::PathFindResultOk; | |
| 1214 | + | } | |
| 1215 | + | ret->complexity = 50000 - limit; | |
| 1216 | + | ||
| 1217 | + | for (auto &node : nodes) { | |
| 1218 | + | if (node.second) | |
| 1219 | + | delete node.second; | |
| 1220 | + | } | |
| 1221 | + | ||
| 1222 | + | return ret; | |
| 1223 | + | } | |
| 1224 | + | ||
| 1225 | + | void Map::findPathAsync(const Position &start, const Position &goal, | |
| 1226 | + | std::function<void(PathFindResult_ptr)> callback) { | |
| 1227 | + | auto visibleNodes = std::make_shared<std::list<Node *>>(); | |
| 1228 | + | for (auto &tile : getTiles(start.z)) { | |
| 1229 | + | if (tile->getPosition() == start) | |
| 1230 | + | continue; | |
| 1231 | + | bool isNotWalkable = !tile->isWalkable(false); | |
| 1232 | + | bool isNotPathable = !tile->isPathable(); | |
| 1233 | + | float speed = tile->getGroundSpeed(); | |
| 1234 | + | if ((isNotWalkable || isNotPathable) && tile->getPosition() != goal) { | |
| 1235 | + | visibleNodes->push_back( | |
| 1236 | + | new Node{speed, 0, tile->getPosition(), nullptr, 0, 0}); | |
| 1237 | + | } else { | |
| 1238 | + | visibleNodes->push_back( | |
| 1239 | + | new Node{speed, 10000000.0f, tile->getPosition(), nullptr, 0, 0}); | |
| 1240 | + | } | |
| 1241 | + | } | |
| 1242 | + | ||
| 1243 | + | g_asyncDispatcher.dispatch([=] { | |
| 1244 | + | auto ret = g_map.newFindPath(start, goal, visibleNodes); | |
| 1245 | + | g_dispatcher.addEvent(std::bind(callback, ret)); | |
| 1246 | + | }); | |
| 1247 | + | } | |
| 1248 | + | ||
| 1249 | + | std::map<std::string, std::tuple<int, int, int, std::string>> | |
| 1250 | + | Map::findEveryPath(const Position &start, int maxDistance, | |
| 1251 | + | const std::map<std::string, std::string> ¶ms) { | |
| 1252 | + | // using Dijkstra's algorithm | |
| 1253 | + | struct LessNode { | |
| 1254 | + | bool operator()(Node *a, Node *b) const { | |
| 1255 | + | return b->totalCost < a->totalCost; | |
| 1256 | + | } | |
| 1257 | + | }; | |
| 1258 | + | ||
| 1259 | + | if (g_extras.debugPathfinding) { | |
| 1260 | + | g_logger.info(stdext::format("findEveryPath: %i %i %i - %i", start.x, | |
| 1261 | + | start.y, start.z, maxDistance)); | |
| 1262 | + | for (auto ¶m : params) { | |
| 1263 | + | g_logger.info(stdext::format("%s - %s", param.first, param.second)); | |
| 1264 | + | } | |
| 1265 | + | } | |
| 1266 | + | ||
| 1267 | + | std::map<std::string, std::string>::const_iterator it; | |
| 1268 | + | it = params.find("ignoreLastCreature"); | |
| 1269 | + | bool ignoreLastCreature = | |
| 1270 | + | it != params.end() && it->second != "0" && it->second != ""; | |
| 1271 | + | it = params.find("ignoreCreatures"); | |
| 1272 | + | bool ignoreCreatures = | |
| 1273 | + | it != params.end() && it->second != "0" && it->second != ""; | |
| 1274 | + | it = params.find("ignoreNonPathable"); | |
| 1275 | + | bool ignoreNonPathable = | |
| 1276 | + | it != params.end() && it->second != "0" && it->second != ""; | |
| 1277 | + | it = params.find("ignoreNonWalkable"); | |
| 1278 | + | bool ignoreNonWalkable = | |
| 1279 | + | it != params.end() && it->second != "0" && it->second != ""; | |
| 1280 | + | it = params.find("ignoreStairs"); | |
| 1281 | + | bool ignoreStairs = | |
| 1282 | + | it != params.end() && it->second != "0" && it->second != ""; | |
| 1283 | + | it = params.find("ignoreCost"); | |
| 1284 | + | bool ignoreCost = it != params.end() && it->second != "0" && it->second != ""; | |
| 1285 | + | it = params.find("allowUnseen"); | |
| 1286 | + | bool allowUnseen = | |
| 1287 | + | it != params.end() && it->second != "0" && it->second != ""; | |
| 1288 | + | it = params.find("allowOnlyVisibleTiles"); | |
| 1289 | + | bool allowOnlyVisibleTiles = | |
| 1290 | + | it != params.end() && it->second != "0" && it->second != ""; | |
| 1291 | + | it = params.find("marginMin"); | |
| 1292 | + | bool hasMargin = it != params.end(); | |
| 1293 | + | it = params.find("marginMax"); | |
| 1294 | + | hasMargin = hasMargin || (it != params.end()); | |
| 1295 | + | ||
| 1296 | + | Position destPos; | |
| 1297 | + | it = params.find("destination"); | |
| 1298 | + | if (it != params.end()) { | |
| 1299 | + | std::vector<int32> pos = stdext::split<int32>(it->second, ","); | |
| 1300 | + | if (pos.size() == 3) { | |
| 1301 | + | destPos = Position(pos[0], pos[1], pos[2]); | |
| 1302 | + | } | |
| 1303 | + | } | |
| 1304 | + | ||
| 1305 | + | Position maxDistanceFromPos; | |
| 1306 | + | int maxDistanceFrom = 0; | |
| 1307 | + | it = params.find("maxDistanceFrom"); | |
| 1308 | + | if (it != params.end()) { | |
| 1309 | + | std::vector<int32> pos = stdext::split<int32>(it->second, ","); | |
| 1310 | + | if (pos.size() == 4) { | |
| 1311 | + | maxDistanceFromPos = Position(pos[0], pos[1], pos[2]); | |
| 1312 | + | maxDistanceFrom = pos[3]; | |
| 1313 | + | } | |
| 1314 | + | } | |
| 1315 | + | ||
| 1316 | + | std::map<std::string, std::tuple<int, int, int, std::string>> ret; | |
| 1317 | + | std::unordered_map<Position, Node *, PositionHasher> nodes; | |
| 1318 | + | std::priority_queue<Node *, std::vector<Node *>, LessNode> searchList; | |
| 1319 | + | ||
| 1320 | + | Node *initNode = new Node{1, 0, start, nullptr, 0, 0}; | |
| 1321 | + | nodes[start] = initNode; | |
| 1322 | + | searchList.push(initNode); | |
| 1323 | + | ||
| 1324 | + | while (!searchList.empty()) { | |
| 1325 | + | Node *node = searchList.top(); | |
| 1326 | + | searchList.pop(); | |
| 1327 | + | ret[node->pos.toString()] = std::make_tuple( | |
| 1328 | + | node->totalCost, node->distance, | |
| 1329 | + | node->prev ? node->prev->pos.getDirectionFromPosition(node->pos) : -1, | |
| 1330 | + | node->prev ? node->prev->pos.toString() : ""); | |
| 1331 | + | if (node->pos == destPos) { | |
| 1332 | + | if (hasMargin) { | |
| 1333 | + | maxDistance = std::min<int>(node->distance + 4, maxDistance); | |
| 1334 | + | } else { | |
| 1335 | + | break; | |
| 1336 | + | } | |
| 1337 | + | } | |
| 1338 | + | if (node->distance >= maxDistance) | |
| 1339 | + | continue; | |
| 1340 | + | for (int i = -1; i <= 1; ++i) { | |
| 1341 | + | for (int j = -1; j <= 1; ++j) { | |
| 1342 | + | if (i == 0 && j == 0) | |
| 1343 | + | continue; | |
| 1344 | + | Position neighbor = node->pos.translated(i, j); | |
| 1345 | + | if (neighbor.x < 0 || neighbor.y < 0) | |
| 1346 | + | continue; | |
| 1347 | + | auto it = nodes.find(neighbor); | |
| 1348 | + | if (it == nodes.end()) { | |
| 1349 | + | bool wasSeen = false; | |
| 1350 | + | bool hasCreature = false; | |
| 1351 | + | bool isNotWalkable = true; | |
| 1352 | + | bool isNotPathable = true; | |
| 1353 | + | int mapColor = 0; | |
| 1354 | + | int speed = 1000; | |
| 1355 | + | if (g_map.isAwareOfPosition(neighbor)) { | |
| 1356 | + | if (const TilePtr &tile = getTile(neighbor)) { | |
| 1357 | + | wasSeen = true; | |
| 1358 | + | hasCreature = tile->hasBlockingCreature(); | |
| 1359 | + | isNotWalkable = !tile->isWalkable(true); | |
| 1360 | + | isNotPathable = !tile->isPathable(); | |
| 1361 | + | mapColor = tile->getMinimapColorByte(); | |
| 1362 | + | speed = tile->getGroundSpeed(); | |
| 1363 | + | } | |
| 1364 | + | } else if (!allowOnlyVisibleTiles) { | |
| 1365 | + | const MinimapTile &mtile = g_minimap.getTile(neighbor); | |
| 1366 | + | wasSeen = mtile.hasFlag(MinimapTileWasSeen); | |
| 1367 | + | isNotWalkable = mtile.hasFlag(MinimapTileNotWalkable); | |
| 1368 | + | isNotPathable = mtile.hasFlag(MinimapTileNotPathable); | |
| 1369 | + | mapColor = mtile.color; | |
| 1370 | + | if (isNotWalkable || isNotPathable) | |
| 1371 | + | wasSeen = true; | |
| 1372 | + | speed = mtile.getSpeed(); | |
| 1373 | + | } | |
| 1374 | + | bool hasStairs = isNotPathable && mapColor >= 210 && mapColor <= 213; | |
| 1375 | + | bool hasReachedMaxDistance = | |
| 1376 | + | maxDistanceFrom && maxDistanceFromPos.isValid() && | |
| 1377 | + | maxDistanceFromPos.distance(neighbor) > maxDistanceFrom; | |
| 1378 | + | if ((!wasSeen && !allowUnseen) || | |
| 1379 | + | (hasStairs && !ignoreStairs && neighbor != destPos) || | |
| 1380 | + | (isNotPathable && !ignoreNonPathable && neighbor != destPos) || | |
| 1381 | + | (isNotWalkable && !ignoreNonWalkable) || hasReachedMaxDistance) { | |
| 1382 | + | it = nodes.emplace(neighbor, nullptr).first; | |
| 1383 | + | } else if ((hasCreature && !ignoreCreatures)) { | |
| 1384 | + | it = nodes.emplace(neighbor, nullptr).first; | |
| 1385 | + | if (ignoreLastCreature) { | |
| 1386 | + | ret[neighbor.toString()] = | |
| 1387 | + | std::make_tuple(node->totalCost + 100, node->distance + 1, | |
| 1388 | + | node->pos.getDirectionFromPosition(neighbor), | |
| 1389 | + | node->pos.toString()); | |
| 1390 | + | } | |
| 1391 | + | } else { | |
| 1392 | + | it = | |
| 1393 | + | nodes | |
| 1394 | + | .emplace(neighbor, | |
| 1395 | + | new Node{(float)speed, 10000000.0f, neighbor, node, | |
| 1396 | + | node->distance + 1, wasSeen ? 0 : 1}) | |
| 1397 | + | .first; | |
| 1398 | + | } | |
| 1399 | + | } | |
| 1400 | + | ||
| 1401 | + | if (!it->second) { | |
| 1402 | + | continue; | |
| 1403 | + | } | |
| 1404 | + | ||
| 1405 | + | float diagonal = ((i == 0 || j == 0) ? 1.0f : 3.0f); | |
| 1406 | + | float cost = it->second->cost * diagonal; | |
| 1407 | + | if (ignoreCost) | |
| 1408 | + | cost = 1; | |
| 1409 | + | if (node->totalCost + cost < it->second->totalCost) { | |
| 1410 | + | it->second->totalCost = node->totalCost + cost; | |
| 1411 | + | it->second->prev = node; | |
| 1412 | + | if (it->second->unseen) | |
| 1413 | + | it->second->unseen = node->unseen + 1; | |
| 1414 | + | it->second->distance = node->distance + 1; | |
| 1415 | + | searchList.push(it->second); | |
| 1416 | + | } | |
| 1417 | + | } | |
| 1418 | + | } | |
| 1419 | + | } | |
| 1420 | + | ||
| 1421 | + | for (auto &node : nodes) { | |
| 1422 | + | if (node.second) | |
| 1423 | + | delete node.second; | |
| 1424 | + | } | |
| 1425 | + | ||
| 1426 | + | return ret; | |
| 1427 | + | } |
| @@ -0,0 +1,307 @@ | |||
| 1 | + | /* | |
| 2 | + | * Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient> | |
| 3 | + | * | |
| 4 | + | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 5 | + | * of this software and associated documentation files (the "Software"), to deal | |
| 6 | + | * in the Software without restriction, including without limitation the rights | |
| 7 | + | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 8 | + | * copies of the Software, and to permit persons to whom the Software is | |
| 9 | + | * furnished to do so, subject to the following conditions: | |
| 10 | + | * | |
| 11 | + | * The above copyright notice and this permission notice shall be included in | |
| 12 | + | * all copies or substantial portions of the Software. | |
| 13 | + | * | |
| 14 | + | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 15 | + | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 16 | + | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 17 | + | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 18 | + | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 19 | + | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| 20 | + | * THE SOFTWARE. | |
| 21 | + | */ | |
| 22 | + | ||
| 23 | + | #ifndef MAP_H | |
| 24 | + | #define MAP_H | |
| 25 | + | ||
| 26 | + | #include "creature.h" | |
| 27 | + | #include "houses.h" | |
| 28 | + | #include "towns.h" | |
| 29 | + | #include "creatures.h" | |
| 30 | + | #include "animatedtext.h" | |
| 31 | + | #include "statictext.h" | |
| 32 | + | #include "tile.h" | |
| 33 | + | ||
| 34 | + | #include <framework/core/clock.h> | |
| 35 | + | ||
| 36 | + | enum OTBM_ItemAttr | |
| 37 | + | { | |
| 38 | + | OTBM_ATTR_DESCRIPTION = 1, | |
| 39 | + | OTBM_ATTR_EXT_FILE = 2, | |
| 40 | + | OTBM_ATTR_TILE_FLAGS = 3, | |
| 41 | + | OTBM_ATTR_ACTION_ID = 4, | |
| 42 | + | OTBM_ATTR_UNIQUE_ID = 5, | |
| 43 | + | OTBM_ATTR_TEXT = 6, | |
| 44 | + | OTBM_ATTR_DESC = 7, | |
| 45 | + | OTBM_ATTR_TELE_DEST = 8, | |
| 46 | + | OTBM_ATTR_ITEM = 9, | |
| 47 | + | OTBM_ATTR_DEPOT_ID = 10, | |
| 48 | + | OTBM_ATTR_SPAWN_FILE = 11, | |
| 49 | + | OTBM_ATTR_RUNE_CHARGES = 12, | |
| 50 | + | OTBM_ATTR_HOUSE_FILE = 13, | |
| 51 | + | OTBM_ATTR_HOUSEDOORID = 14, | |
| 52 | + | OTBM_ATTR_COUNT = 15, | |
| 53 | + | OTBM_ATTR_DURATION = 16, | |
| 54 | + | OTBM_ATTR_DECAYING_STATE = 17, | |
| 55 | + | OTBM_ATTR_WRITTENDATE = 18, | |
| 56 | + | OTBM_ATTR_WRITTENBY = 19, | |
| 57 | + | OTBM_ATTR_SLEEPERGUID = 20, | |
| 58 | + | OTBM_ATTR_SLEEPSTART = 21, | |
| 59 | + | OTBM_ATTR_CHARGES = 22, | |
| 60 | + | OTBM_ATTR_CONTAINER_ITEMS = 23, | |
| 61 | + | OTBM_ATTR_ATTRIBUTE_MAP = 128, | |
| 62 | + | /// just random numbers, they're not actually used by the binary reader... | |
| 63 | + | OTBM_ATTR_WIDTH = 129, | |
| 64 | + | OTBM_ATTR_HEIGHT = 130 | |
| 65 | + | }; | |
| 66 | + | ||
| 67 | + | enum OTBM_NodeTypes_t | |
| 68 | + | { | |
| 69 | + | OTBM_ROOTV2 = 1, | |
| 70 | + | OTBM_MAP_DATA = 2, | |
| 71 | + | OTBM_ITEM_DEF = 3, | |
| 72 | + | OTBM_TILE_AREA = 4, | |
| 73 | + | OTBM_TILE = 5, | |
| 74 | + | OTBM_ITEM = 6, | |
| 75 | + | OTBM_TILE_SQUARE = 7, | |
| 76 | + | OTBM_TILE_REF = 8, | |
| 77 | + | OTBM_SPAWNS = 9, | |
| 78 | + | OTBM_SPAWN_AREA = 10, | |
| 79 | + | OTBM_MONSTER = 11, | |
| 80 | + | OTBM_TOWNS = 12, | |
| 81 | + | OTBM_TOWN = 13, | |
| 82 | + | OTBM_HOUSETILE = 14, | |
| 83 | + | OTBM_WAYPOINTS = 15, | |
| 84 | + | OTBM_WAYPOINT = 16 | |
| 85 | + | }; | |
| 86 | + | ||
| 87 | + | enum { | |
| 88 | + | OTCM_SIGNATURE = 0x4D43544F, | |
| 89 | + | OTCM_VERSION = 1 | |
| 90 | + | }; | |
| 91 | + | ||
| 92 | + | enum { | |
| 93 | + | BLOCK_SIZE = 32 | |
| 94 | + | }; | |
| 95 | + | ||
| 96 | + | enum : uint8 { | |
| 97 | + | Animation_Force, | |
| 98 | + | Animation_Show | |
| 99 | + | }; | |
| 100 | + | ||
| 101 | + | class TileBlock { | |
| 102 | + | public: | |
| 103 | + | TileBlock() { m_tiles.fill(nullptr); } | |
| 104 | + | ||
| 105 | + | const TilePtr& create(const Position& pos) { | |
| 106 | + | TilePtr& tile = m_tiles[getTileIndex(pos)]; | |
| 107 | + | tile = TilePtr(new Tile(pos)); | |
| 108 | + | return tile; | |
| 109 | + | } | |
| 110 | + | const TilePtr& getOrCreate(const Position& pos) { | |
| 111 | + | TilePtr& tile = m_tiles[getTileIndex(pos)]; | |
| 112 | + | if(!tile) | |
| 113 | + | tile = TilePtr(new Tile(pos)); | |
| 114 | + | return tile; | |
| 115 | + | } | |
| 116 | + | const TilePtr& get(const Position& pos) { return m_tiles[getTileIndex(pos)]; } | |
| 117 | + | void remove(const Position& pos) { m_tiles[getTileIndex(pos)] = nullptr; } | |
| 118 | + | ||
| 119 | + | uint getTileIndex(const Position& pos) { return ((pos.y % BLOCK_SIZE) * BLOCK_SIZE) + (pos.x % BLOCK_SIZE); } | |
| 120 | + | ||
| 121 | + | const std::array<TilePtr, BLOCK_SIZE*BLOCK_SIZE>& getTiles() const { return m_tiles; } | |
| 122 | + | ||
| 123 | + | private: | |
| 124 | + | std::array<TilePtr, BLOCK_SIZE*BLOCK_SIZE> m_tiles; | |
| 125 | + | }; | |
| 126 | + | ||
| 127 | + | struct AwareRange | |
| 128 | + | { | |
| 129 | + | int top; | |
| 130 | + | int right; | |
| 131 | + | int bottom; | |
| 132 | + | int left; | |
| 133 | + | ||
| 134 | + | int horizontal() { return left + right + 1; } | |
| 135 | + | int vertical() { return top + bottom + 1; } | |
| 136 | + | }; | |
| 137 | + | ||
| 138 | + | struct PathFindResult | |
| 139 | + | { | |
| 140 | + | Otc::PathFindResult status = Otc::PathFindResultNoWay; | |
| 141 | + | std::vector<Otc::Direction> path; | |
| 142 | + | int complexity = 0; | |
| 143 | + | Position start; | |
| 144 | + | Position destination; | |
| 145 | + | }; | |
| 146 | + | using PathFindResult_ptr = std::shared_ptr<PathFindResult>; | |
| 147 | + | ||
| 148 | + | struct Node { | |
| 149 | + | float cost; | |
| 150 | + | float totalCost; | |
| 151 | + | Position pos; | |
| 152 | + | Node *prev; | |
| 153 | + | int distance; | |
| 154 | + | int unseen; | |
| 155 | + | }; | |
| 156 | + | ||
| 157 | + | //@bindsingleton g_map | |
| 158 | + | class Map | |
| 159 | + | { | |
| 160 | + | public: | |
| 161 | + | void init(); | |
| 162 | + | void terminate(); | |
| 163 | + | ||
| 164 | + | void addMapView(const MapViewPtr& mapView); | |
| 165 | + | void removeMapView(const MapViewPtr& mapView); | |
| 166 | + | void notificateTileUpdate(const Position& pos, bool updateMinimap = false); | |
| 167 | + | ||
| 168 | + | void requestVisibleTilesCacheUpdate(); | |
| 169 | + | ||
| 170 | + | bool loadOtcm(const std::string& fileName); | |
| 171 | + | void saveOtcm(const std::string& fileName); | |
| 172 | + | ||
| 173 | + | void loadOtbm(const std::string& fileName); | |
| 174 | + | void saveOtbm(const std::string& fileName); | |
| 175 | + | ||
| 176 | + | // otbm attributes (description, size, etc.) | |
| 177 | + | void setHouseFile(const std::string& file) { m_attribs.set(OTBM_ATTR_HOUSE_FILE, file); } | |
| 178 | + | void setSpawnFile(const std::string& file) { m_attribs.set(OTBM_ATTR_SPAWN_FILE, file); } | |
| 179 | + | void setDescription(const std::string& desc) { m_attribs.set(OTBM_ATTR_DESCRIPTION, desc); } | |
| 180 | + | ||
| 181 | + | void clearDescriptions() { m_attribs.remove(OTBM_ATTR_DESCRIPTION); } | |
| 182 | + | void setWidth(uint16 w) { m_attribs.set(OTBM_ATTR_WIDTH, w); } | |
| 183 | + | void setHeight(uint16 h) { m_attribs.set(OTBM_ATTR_HEIGHT, h); } | |
| 184 | + | ||
| 185 | + | std::string getHouseFile() { return m_attribs.get<std::string>(OTBM_ATTR_HOUSE_FILE); } | |
| 186 | + | std::string getSpawnFile() { return m_attribs.get<std::string>(OTBM_ATTR_SPAWN_FILE); } | |
| 187 | + | Size getSize() { return Size(m_attribs.get<uint16>(OTBM_ATTR_WIDTH), m_attribs.get<uint16>(OTBM_ATTR_HEIGHT)); } | |
| 188 | + | std::vector<std::string> getDescriptions() { return stdext::split(m_attribs.get<std::string>(OTBM_ATTR_DESCRIPTION), "\n"); } | |
| 189 | + | ||
| 190 | + | void clean(); | |
| 191 | + | void cleanDynamicThings(); | |
| 192 | + | void cleanTexts(); | |
| 193 | + | ||
| 194 | + | // thing related | |
| 195 | + | void addThing(const ThingPtr& thing, const Position& pos, int stackPos = -1); | |
| 196 | + | void setTileSpeed(const Position & pos, uint16_t speed, uint8_t blocking); | |
| 197 | + | ThingPtr getThing(const Position& pos, int stackPos); | |
| 198 | + | bool removeThing(const ThingPtr& thing); | |
| 199 | + | bool removeThingByPos(const Position& pos, int stackPos); | |
| 200 | + | void colorizeThing(const ThingPtr& thing, const Color& color); | |
| 201 | + | void removeThingColor(const ThingPtr& thing); | |
| 202 | + | ||
| 203 | + | StaticTextPtr getStaticText(const Position& pos); | |
| 204 | + | ||
| 205 | + | // tile related | |
| 206 | + | const TilePtr& createTile(const Position& pos); | |
| 207 | + | template <typename... Items> | |
| 208 | + | const TilePtr& createTileEx(const Position& pos, const Items&... items); | |
| 209 | + | const TilePtr& getOrCreateTile(const Position& pos); | |
| 210 | + | const TilePtr& getTile(const Position& pos); | |
| 211 | + | const TileList getTiles(int floor = -1); | |
| 212 | + | void cleanTile(const Position& pos); | |
| 213 | + | ||
| 214 | + | // tile zone related | |
| 215 | + | void setShowZone(tileflags_t zone, bool show); | |
| 216 | + | void setShowZones(bool show); | |
| 217 | + | void setZoneColor(tileflags_t flag, const Color& color); | |
| 218 | + | void setZoneOpacity(float opacity) { m_zoneOpacity = opacity; } | |
| 219 | + | ||
| 220 | + | float getZoneOpacity() { return m_zoneOpacity; } | |
| 221 | + | Color getZoneColor(tileflags_t flag); | |
| 222 | + | tileflags_t getZoneFlags() { return (tileflags_t)m_zoneFlags; } | |
| 223 | + | bool showZones() { return m_zoneFlags != 0; } | |
| 224 | + | bool showZone(tileflags_t zone) { return (m_zoneFlags & zone) == zone; } | |
| 225 | + | ||
| 226 | + | void setForceShowAnimations(bool force); | |
| 227 | + | bool isForcingAnimations(); | |
| 228 | + | bool isShowingAnimations(); | |
| 229 | + | void setShowAnimations(bool show); | |
| 230 | + | ||
| 231 | + | std::map<Position, ItemPtr> findItemsById(uint16 clientId, uint32 max); | |
| 232 | + | ||
| 233 | + | // known creature related | |
| 234 | + | void addCreature(const CreaturePtr& creature); | |
| 235 | + | CreaturePtr getCreatureById(uint32 id); | |
| 236 | + | void removeCreatureById(uint32 id); | |
| 237 | + | std::vector<CreaturePtr> getSightSpectators(const Position& centerPos, bool multiFloor); | |
| 238 | + | std::vector<CreaturePtr> getSpectators(const Position& centerPos, bool multiFloor); | |
| 239 | + | std::vector<CreaturePtr> getSpectatorsInRange(const Position& centerPos, bool multiFloor, int xRange, int yRange); | |
| 240 | + | std::vector<CreaturePtr> getSpectatorsInRangeEx(const Position& centerPos, bool multiFloor, int minXRange, int maxXRange, int minYRange, int maxYRange); | |
| 241 | + | std::vector<CreaturePtr> getSpectatorsByPattern(const Position& centerPos, const std::string& pattern, Otc::Direction direction); | |
| 242 | + | ||
| 243 | + | void setLight(const Light& light) { m_light = light; } | |
| 244 | + | void setCentralPosition(const Position& centralPosition); | |
| 245 | + | ||
| 246 | + | bool isLookPossible(const Position& pos); | |
| 247 | + | bool isCovered(const Position& pos, int firstFloor = 0); | |
| 248 | + | bool isCompletelyCovered(const Position& pos, int firstFloor = 0); | |
| 249 | + | bool isAwareOfPosition(const Position& pos, bool extended = false); | |
| 250 | + | bool isAwareOfPositionForClean(const Position& pos, bool extended = false); | |
| 251 | + | ||
| 252 | + | void setAwareRange(const AwareRange& range); | |
| 253 | + | void resetAwareRange(); | |
| 254 | + | AwareRange getAwareRange() { return m_awareRange; } | |
| 255 | + | Size getAwareRangeAsSize() { return Size(m_awareRange.horizontal(), m_awareRange.vertical()); } | |
| 256 | + | ||
| 257 | + | Light getLight() { return m_light; } | |
| 258 | + | Position getCentralPosition() { return m_centralPosition; } | |
| 259 | + | int getFirstAwareFloor(); | |
| 260 | + | int getLastAwareFloor(); | |
| 261 | + | const std::vector<MissilePtr>& getFloorMissiles(int z) { return m_floorMissiles[z]; } | |
| 262 | + | ||
| 263 | + | std::vector<AnimatedTextPtr> getAnimatedTexts() { return m_animatedTexts; } | |
| 264 | + | std::vector<StaticTextPtr> getStaticTexts() { return m_staticTexts; } | |
| 265 | + | ||
| 266 | + | std::tuple<std::vector<Otc::Direction>, Otc::PathFindResult> findPath(const Position& start, const Position& goal, int maxComplexity, int flags = 0); | |
| 267 | + | PathFindResult_ptr newFindPath(const Position& start, const Position& goal, std::shared_ptr<std::list<Node*>> visibleNodes); | |
| 268 | + | void findPathAsync(const Position & start, const Position & goal, std::function<void(PathFindResult_ptr)> callback); | |
| 269 | + | ||
| 270 | + | // tuple = <cost, distance, prevPos> | |
| 271 | + | std::map<std::string, std::tuple<int, int, int, std::string>> findEveryPath(const Position& start, int maxDistance, const std::map<std::string, std::string>& params); | |
| 272 | + | ||
| 273 | + | int getMinimapColor(const Position& pos); | |
| 274 | + | bool isPatchable(const Position& pos); | |
| 275 | + | bool isWalkable(const Position& pos, bool ignoreCreatures); | |
| 276 | + | bool isSightClear(const Position& fromPos, const Position& toPos); | |
| 277 | + | bool checkSightLine(const Position& fromPos, const Position& toPos); | |
| 278 | + | ||
| 279 | + | private: | |
| 280 | + | void removeUnawareThings(); | |
| 281 | + | uint getBlockIndex(const Position& pos) { return ((pos.y / BLOCK_SIZE) * (65536 / BLOCK_SIZE)) + (pos.x / BLOCK_SIZE); } | |
| 282 | + | ||
| 283 | + | std::map<uint, TileBlock> m_tileBlocks[Otc::MAX_Z+1]; | |
| 284 | + | std::map<uint32, CreaturePtr> m_knownCreatures; | |
| 285 | + | std::array<std::vector<MissilePtr>, Otc::MAX_Z+1> m_floorMissiles; | |
| 286 | + | std::vector<AnimatedTextPtr> m_animatedTexts; | |
| 287 | + | std::vector<StaticTextPtr> m_staticTexts; | |
| 288 | + | std::vector<MapViewPtr> m_mapViews; | |
| 289 | + | std::unordered_map<Position, std::string, PositionHasher> m_waypoints; | |
| 290 | + | ||
| 291 | + | uint8 m_animationFlags; | |
| 292 | + | uint32 m_zoneFlags; | |
| 293 | + | std::map<uint32, Color> m_zoneColors; | |
| 294 | + | float m_zoneOpacity; | |
| 295 | + | ||
| 296 | + | Light m_light; | |
| 297 | + | Position m_centralPosition; | |
| 298 | + | Rect m_tilesRect; | |
| 299 | + | ||
| 300 | + | stdext::packed_storage<uint8> m_attribs; | |
| 301 | + | AwareRange m_awareRange; | |
| 302 | + | static TilePtr m_nulltile; | |
| 303 | + | }; | |
| 304 | + | ||
| 305 | + | extern Map g_map; | |
| 306 | + | ||
| 307 | + | #endif |
| @@ -0,0 +1,538 @@ | |||
| 1 | + | /* | |
| 2 | + | * Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient> | |
| 3 | + | * | |
| 4 | + | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 5 | + | * of this software and associated documentation files (the "Software"), to deal | |
| 6 | + | * in the Software without restriction, including without limitation the rights | |
| 7 | + | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 8 | + | * copies of the Software, and to permit persons to whom the Software is | |
| 9 | + | * furnished to do so, subject to the following conditions: | |
| 10 | + | * | |
| 11 | + | * The above copyright notice and this permission notice shall be included in | |
| 12 | + | * all copies or substantial portions of the Software. | |
| 13 | + | * | |
| 14 | + | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 15 | + | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 16 | + | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 17 | + | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 18 | + | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 19 | + | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| 20 | + | * THE SOFTWARE. | |
| 21 | + | */ | |
| 22 | + | ||
| 23 | + | #include "map.h" | |
| 24 | + | #include "tile.h" | |
| 25 | + | #include "game.h" | |
| 26 | + | ||
| 27 | + | #include <framework/core/application.h> | |
| 28 | + | #include <framework/core/eventdispatcher.h> | |
| 29 | + | #include <framework/core/resourcemanager.h> | |
| 30 | + | #include <framework/core/filestream.h> | |
| 31 | + | #include <framework/core/binarytree.h> | |
| 32 | + | #include <framework/xml/tinyxml.h> | |
| 33 | + | #include <framework/ui/uiwidget.h> | |
| 34 | + | ||
| 35 | + | void Map::loadOtbm(const std::string& fileName) | |
| 36 | + | { | |
| 37 | + | try { | |
| 38 | + | if(!g_things.isOtbLoaded()) | |
| 39 | + | stdext::throw_exception("OTB isn't loaded yet to load a map."); | |
| 40 | + | ||
| 41 | + | FileStreamPtr fin = g_resources.openFile(fileName, g_game.getFeature(Otc::GameDontCacheFiles)); | |
| 42 | + | if(!fin) | |
| 43 | + | stdext::throw_exception(stdext::format("Unable to load map '%s'", fileName)); | |
| 44 | + | ||
| 45 | + | char identifier[4]; | |
| 46 | + | if(fin->read(identifier, 1, 4) < 4) | |
| 47 | + | stdext::throw_exception("Could not read file identifier"); | |
| 48 | + | ||
| 49 | + | if(memcmp(identifier, "OTBM", 4) != 0 && memcmp(identifier, "\0\0\0\0", 4) != 0) | |
| 50 | + | stdext::throw_exception(stdext::format("Invalid file identifier detected: %s", identifier)); | |
| 51 | + | ||
| 52 | + | BinaryTreePtr root = fin->getBinaryTree(); | |
| 53 | + | if(root->getU8()) | |
| 54 | + | stdext::throw_exception("could not read root property!"); | |
| 55 | + | ||
| 56 | + | uint32 headerVersion = root->getU32(); | |
| 57 | + | if(headerVersion > 3) | |
| 58 | + | stdext::throw_exception(stdext::format("Unknown OTBM version detected: %u.", headerVersion)); | |
| 59 | + | ||
| 60 | + | setWidth(root->getU16()); | |
| 61 | + | setHeight(root->getU16()); | |
| 62 | + | ||
| 63 | + | uint32 headerMajorItems = root->getU8(); | |
| 64 | + | if(headerMajorItems > g_things.getOtbMajorVersion()) { | |
| 65 | + | stdext::throw_exception(stdext::format("This map was saved with different OTB version. read %d what it's supposed to be: %d", | |
| 66 | + | headerMajorItems, g_things.getOtbMajorVersion())); | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | root->skip(3); | |
| 70 | + | uint32 headerMinorItems = root->getU32(); | |
| 71 | + | if(headerMinorItems > g_things.getOtbMinorVersion()) { | |
| 72 | + | g_logger.warning(stdext::format("This map needs an updated OTB. read %d what it's supposed to be: %d or less", | |
| 73 | + | headerMinorItems, g_things.getOtbMinorVersion())); | |
| 74 | + | } | |
| 75 | + | ||
| 76 | + | BinaryTreePtr node = root->getChildren()[0]; | |
| 77 | + | if(node->getU8() != OTBM_MAP_DATA) | |
| 78 | + | stdext::throw_exception("Could not read root data node"); | |
| 79 | + | ||
| 80 | + | while(node->canRead()) { | |
| 81 | + | uint8 attribute = node->getU8(); | |
| 82 | + | std::string tmp = node->getString(); | |
| 83 | + | switch (attribute) { | |
| 84 | + | case OTBM_ATTR_DESCRIPTION: | |
| 85 | + | setDescription(tmp); | |
| 86 | + | break; | |
| 87 | + | case OTBM_ATTR_SPAWN_FILE: | |
| 88 | + | setSpawnFile(fileName.substr(0, fileName.rfind('/') + 1) + tmp); | |
| 89 | + | break; | |
| 90 | + | case OTBM_ATTR_HOUSE_FILE: | |
| 91 | + | setHouseFile(fileName.substr(0, fileName.rfind('/') + 1) + tmp); | |
| 92 | + | break; | |
| 93 | + | default: | |
| 94 | + | stdext::throw_exception(stdext::format("Invalid attribute '%d'", (int)attribute)); | |
| 95 | + | } | |
| 96 | + | } | |
| 97 | + | ||
| 98 | + | for(const BinaryTreePtr& nodeMapData : node->getChildren()) { | |
| 99 | + | uint8 mapDataType = nodeMapData->getU8(); | |
| 100 | + | if(mapDataType == OTBM_TILE_AREA) { | |
| 101 | + | Position basePos; | |
| 102 | + | basePos.x = nodeMapData->getU16(); | |
| 103 | + | basePos.y = nodeMapData->getU16(); | |
| 104 | + | basePos.z = nodeMapData->getU8(); | |
| 105 | + | ||
| 106 | + | for(const BinaryTreePtr &nodeTile : nodeMapData->getChildren()) { | |
| 107 | + | uint8 type = nodeTile->getU8(); | |
| 108 | + | if(unlikely(type != OTBM_TILE && type != OTBM_HOUSETILE)) | |
| 109 | + | stdext::throw_exception(stdext::format("invalid node tile type %d", (int)type)); | |
| 110 | + | ||
| 111 | + | HousePtr house = nullptr; | |
| 112 | + | uint32 flags = TILESTATE_NONE; | |
| 113 | + | Position pos = basePos + nodeTile->getPoint(); | |
| 114 | + | ||
| 115 | + | if(type == OTBM_HOUSETILE) { | |
| 116 | + | uint32 hId = nodeTile->getU32(); | |
| 117 | + | TilePtr tile = getOrCreateTile(pos); | |
| 118 | + | if(!(house = g_houses.getHouse(hId))) { | |
| 119 | + | house = HousePtr(new House(hId)); | |
| 120 | + | g_houses.addHouse(house); | |
| 121 | + | } | |
| 122 | + | house->setTile(tile); | |
| 123 | + | } | |
| 124 | + | ||
| 125 | + | while(nodeTile->canRead()) { | |
| 126 | + | uint8 tileAttr = nodeTile->getU8(); | |
| 127 | + | switch(tileAttr) { | |
| 128 | + | case OTBM_ATTR_TILE_FLAGS: { | |
| 129 | + | uint32 _flags = nodeTile->getU32(); | |
| 130 | + | if((_flags & TILESTATE_PROTECTIONZONE) == TILESTATE_PROTECTIONZONE) | |
| 131 | + | flags |= TILESTATE_PROTECTIONZONE; | |
| 132 | + | else if((_flags & TILESTATE_OPTIONALZONE) == TILESTATE_OPTIONALZONE) | |
| 133 | + | flags |= TILESTATE_OPTIONALZONE; | |
| 134 | + | else if((_flags & TILESTATE_HARDCOREZONE) == TILESTATE_HARDCOREZONE) | |
| 135 | + | flags |= TILESTATE_HARDCOREZONE; | |
| 136 | + | ||
| 137 | + | if((_flags & TILESTATE_NOLOGOUT) == TILESTATE_NOLOGOUT) | |
| 138 | + | flags |= TILESTATE_NOLOGOUT; | |
| 139 | + | ||
| 140 | + | if((_flags & TILESTATE_REFRESH) == TILESTATE_REFRESH) | |
| 141 | + | flags |= TILESTATE_REFRESH; | |
| 142 | + | break; | |
| 143 | + | } | |
| 144 | + | case OTBM_ATTR_ITEM: { | |
| 145 | + | addThing(Item::createFromOtb(nodeTile->getU16()), pos); | |
| 146 | + | break; | |
| 147 | + | } | |
| 148 | + | default: { | |
| 149 | + | stdext::throw_exception(stdext::format("invalid tile attribute %d at pos %s", | |
| 150 | + | (int)tileAttr, stdext::to_string(pos))); | |
| 151 | + | } | |
| 152 | + | } | |
| 153 | + | } | |
| 154 | + | ||
| 155 | + | for(const BinaryTreePtr& nodeItem : nodeTile->getChildren()) { | |
| 156 | + | if(unlikely(nodeItem->getU8() != OTBM_ITEM)) | |
| 157 | + | stdext::throw_exception("invalid item node"); | |
| 158 | + | ||
| 159 | + | ItemPtr item = Item::createFromOtb(nodeItem->getU16()); | |
| 160 | + | item->unserializeItem(nodeItem); | |
| 161 | + | ||
| 162 | + | if(item->isContainer()) { | |
| 163 | + | for(const BinaryTreePtr& containerItem : nodeItem->getChildren()) { | |
| 164 | + | if(containerItem->getU8() != OTBM_ITEM) | |
| 165 | + | stdext::throw_exception("invalid container item node"); | |
| 166 | + | ||
| 167 | + | ItemPtr cItem = Item::createFromOtb(containerItem->getU16()); | |
| 168 | + | cItem->unserializeItem(containerItem); | |
| 169 | + | item->addContainerItem(cItem); | |
| 170 | + | } | |
| 171 | + | } | |
| 172 | + | ||
| 173 | + | if(house && item->isMoveable()) { | |
| 174 | + | g_logger.warning(stdext::format("Moveable item found in house: %d at pos %s - escaping...", item->getId(), stdext::to_string(pos))); | |
| 175 | + | item.reset(); | |
| 176 | + | } | |
| 177 | + | ||
| 178 | + | addThing(item, pos); | |
| 179 | + | } | |
| 180 | + | ||
| 181 | + | if(const TilePtr& tile = getTile(pos)) { | |
| 182 | + | if(house) | |
| 183 | + | tile->setFlag(TILESTATE_HOUSE); | |
| 184 | + | tile->setFlag(flags); | |
| 185 | + | } | |
| 186 | + | } | |
| 187 | + | } else if(mapDataType == OTBM_TOWNS) { | |
| 188 | + | TownPtr town = nullptr; | |
| 189 | + | for(const BinaryTreePtr &nodeTown : nodeMapData->getChildren()) { | |
| 190 | + | if(nodeTown->getU8() != OTBM_TOWN) | |
| 191 | + | stdext::throw_exception("invalid town node."); | |
| 192 | + | ||
| 193 | + | uint32 townId = nodeTown->getU32(); | |
| 194 | + | std::string townName = nodeTown->getString(); | |
| 195 | + | ||
| 196 | + | Position townCoords; | |
| 197 | + | townCoords.x = nodeTown->getU16(); | |
| 198 | + | townCoords.y = nodeTown->getU16(); | |
| 199 | + | townCoords.z = nodeTown->getU8(); | |
| 200 | + | ||
| 201 | + | if(!(town = g_towns.getTown(townId))) | |
| 202 | + | g_towns.addTown(TownPtr(new Town(townId, townName, townCoords))); | |
| 203 | + | } | |
| 204 | + | g_towns.sort(); | |
| 205 | + | } else if(mapDataType == OTBM_WAYPOINTS && headerVersion > 1) { | |
| 206 | + | for(const BinaryTreePtr &nodeWaypoint : nodeMapData->getChildren()) { | |
| 207 | + | if(nodeWaypoint->getU8() != OTBM_WAYPOINT) | |
| 208 | + | stdext::throw_exception("invalid waypoint node."); | |
| 209 | + | ||
| 210 | + | std::string name = nodeWaypoint->getString(); | |
| 211 | + | ||
| 212 | + | Position waypointPos; | |
| 213 | + | waypointPos.x = nodeWaypoint->getU16(); | |
| 214 | + | waypointPos.y = nodeWaypoint->getU16(); | |
| 215 | + | waypointPos.z = nodeWaypoint->getU8(); | |
| 216 | + | ||
| 217 | + | if(waypointPos.isValid() && !name.empty() && m_waypoints.find(waypointPos) == m_waypoints.end()) | |
| 218 | + | m_waypoints.insert(std::make_pair(waypointPos, name)); | |
| 219 | + | } | |
| 220 | + | } else | |
| 221 | + | stdext::throw_exception(stdext::format("Unknown map data node %d", (int)mapDataType)); | |
| 222 | + | } | |
| 223 | + | ||
| 224 | + | fin->close(); | |
| 225 | + | } catch(std::exception& e) { | |
| 226 | + | g_logger.error(stdext::format("Failed to load '%s': %s", fileName, e.what())); | |
| 227 | + | } | |
| 228 | + | } | |
| 229 | + | ||
| 230 | + | void Map::saveOtbm(const std::string& fileName) | |
| 231 | + | { | |
| 232 | + | try { | |
| 233 | + | FileStreamPtr fin = g_resources.createFile(fileName); | |
| 234 | + | if(!fin) | |
| 235 | + | stdext::throw_exception(stdext::format("failed to open file '%s' for write", fileName)); | |
| 236 | + | ||
| 237 | + | std::string dir; | |
| 238 | + | if(fileName.find_last_of('/') == std::string::npos) | |
| 239 | + | dir = g_resources.getWorkDir(); | |
| 240 | + | else | |
| 241 | + | dir = fileName.substr(0, fileName.find_last_of('/')); | |
| 242 | + | ||
| 243 | + | uint32 version = 0; | |
| 244 | + | if(g_things.getOtbMajorVersion() < ClientVersion820) | |
| 245 | + | version = 1; | |
| 246 | + | else | |
| 247 | + | version = 2; | |
| 248 | + | ||
| 249 | + | /// Usually when a map has empty house/spawn file it means the map is new. | |
| 250 | + | /// TODO: Ask the user for a map name instead of those ugly uses of substr | |
| 251 | + | std::string::size_type sep_pos; | |
| 252 | + | std::string houseFile = getHouseFile(); | |
| 253 | + | std::string spawnFile = getSpawnFile(); | |
| 254 | + | std::string cpyf; | |
| 255 | + | ||
| 256 | + | if((sep_pos = fileName.rfind('.')) != std::string::npos && stdext::ends_with(fileName, ".otbm")) | |
| 257 | + | cpyf = fileName.substr(0, sep_pos); | |
| 258 | + | ||
| 259 | + | if(houseFile.empty()) | |
| 260 | + | houseFile = cpyf + "-houses.xml"; | |
| 261 | + | ||
| 262 | + | if(spawnFile.empty()) | |
| 263 | + | spawnFile = cpyf + "-spawns.xml"; | |
| 264 | + | ||
| 265 | + | /// we only need the filename to save to, the directory should be resolved by the OTBM loader not here | |
| 266 | + | if((sep_pos = spawnFile.rfind('/')) != std::string::npos) | |
| 267 | + | spawnFile = spawnFile.substr(sep_pos + 1); | |
| 268 | + | ||
| 269 | + | if((sep_pos = houseFile.rfind('/')) != std::string::npos) | |
| 270 | + | houseFile = houseFile.substr(sep_pos + 1); | |
| 271 | + | ||
| 272 | + | fin->addU32(0); // file version | |
| 273 | + | OutputBinaryTreePtr root(new OutputBinaryTree(fin)); | |
| 274 | + | { | |
| 275 | + | root->addU32(version); | |
| 276 | + | ||
| 277 | + | Size mapSize = getSize(); | |
| 278 | + | root->addU16(mapSize.width()); | |
| 279 | + | root->addU16(mapSize.height()); | |
| 280 | + | ||
| 281 | + | root->addU32(g_things.getOtbMajorVersion()); | |
| 282 | + | root->addU32(g_things.getOtbMinorVersion()); | |
| 283 | + | ||
| 284 | + | root->startNode(OTBM_MAP_DATA); | |
| 285 | + | { | |
| 286 | + | root->addU8(OTBM_ATTR_DESCRIPTION); | |
| 287 | + | root->addString(m_attribs.get<std::string>(OTBM_ATTR_DESCRIPTION)); | |
| 288 | + | ||
| 289 | + | root->addU8(OTBM_ATTR_SPAWN_FILE); | |
| 290 | + | root->addString(spawnFile); | |
| 291 | + | ||
| 292 | + | root->addU8(OTBM_ATTR_HOUSE_FILE); | |
| 293 | + | root->addString(houseFile); | |
| 294 | + | ||
| 295 | + | int px = -1, py = -1, pz =-1; | |
| 296 | + | bool firstNode = true; | |
| 297 | + | ||
| 298 | + | for(uint8_t z = 0; z <= Otc::MAX_Z; ++z) { | |
| 299 | + | for(const auto& it : m_tileBlocks[z]) { | |
| 300 | + | const TileBlock& block = it.second; | |
| 301 | + | for(const TilePtr& tile : block.getTiles()) { | |
| 302 | + | if(unlikely(!tile || tile->isEmpty())) | |
| 303 | + | continue; | |
| 304 | + | ||
| 305 | + | const Position& pos = tile->getPosition(); | |
| 306 | + | if(unlikely(!pos.isValid())) | |
| 307 | + | continue; | |
| 308 | + | ||
| 309 | + | if(pos.x < px || pos.x >= px + 256 | |
| 310 | + | || pos.y < py || pos.y >= py + 256 | |
| 311 | + | || pos.z != pz) { | |
| 312 | + | if(!firstNode) | |
| 313 | + | root->endNode(); /// OTBM_TILE_AREA | |
| 314 | + | ||
| 315 | + | firstNode = false; | |
| 316 | + | root->startNode(OTBM_TILE_AREA); | |
| 317 | + | ||
| 318 | + | px = pos.x & 0xFF00; | |
| 319 | + | py = pos.y & 0xFF00; | |
| 320 | + | pz = pos.z; | |
| 321 | + | root->addPos(px, py, pz); | |
| 322 | + | } | |
| 323 | + | ||
| 324 | + | root->startNode(tile->isHouseTile() ? OTBM_HOUSETILE : OTBM_TILE); | |
| 325 | + | root->addPoint(Point(pos.x, pos.y) & 0xFF); | |
| 326 | + | if(tile->isHouseTile()) | |
| 327 | + | root->addU32(tile->getHouseId()); | |
| 328 | + | ||
| 329 | + | if(tile->getFlags()) { | |
| 330 | + | root->addU8(OTBM_ATTR_TILE_FLAGS); | |
| 331 | + | root->addU32(tile->getFlags()); | |
| 332 | + | } | |
| 333 | + | ||
| 334 | + | const auto& itemList = tile->getItems(); | |
| 335 | + | const ItemPtr& ground = tile->getGround(); | |
| 336 | + | if(ground) { | |
| 337 | + | // Those types are called "complex" needs other stuff to be written. | |
| 338 | + | // For containers, there is container items, for depot, depot it and so on. | |
| 339 | + | if(!ground->isContainer() && !ground->isDepot() | |
| 340 | + | && !ground->isDoor() && !ground->isTeleport()) { | |
| 341 | + | root->addU8(OTBM_ATTR_ITEM); | |
| 342 | + | root->addU16(ground->getServerId()); | |
| 343 | + | } else | |
| 344 | + | ground->serializeItem(root); | |
| 345 | + | } | |
| 346 | + | for(const ItemPtr& item : itemList) | |
| 347 | + | if(!item->isGround()) | |
| 348 | + | item->serializeItem(root); | |
| 349 | + | ||
| 350 | + | root->endNode(); // OTBM_TILE | |
| 351 | + | } | |
| 352 | + | } | |
| 353 | + | } | |
| 354 | + | ||
| 355 | + | if(!firstNode) | |
| 356 | + | root->endNode(); // OTBM_TILE_AREA | |
| 357 | + | ||
| 358 | + | root->startNode(OTBM_TOWNS); | |
| 359 | + | for(const TownPtr& town : g_towns.getTowns()) { | |
| 360 | + | root->startNode(OTBM_TOWN); | |
| 361 | + | ||
| 362 | + | root->addU32(town->getId()); | |
| 363 | + | root->addString(town->getName()); | |
| 364 | + | ||
| 365 | + | Position townPos = town->getPos(); | |
| 366 | + | root->addPos(townPos.x, townPos.y, townPos.z); | |
| 367 | + | root->endNode(); | |
| 368 | + | } | |
| 369 | + | root->endNode(); | |
| 370 | + | ||
| 371 | + | if(version > 1) { | |
| 372 | + | root->startNode(OTBM_WAYPOINTS); | |
| 373 | + | for(const auto& it : m_waypoints) { | |
| 374 | + | root->startNode(OTBM_WAYPOINT); | |
| 375 | + | root->addString(it.second); | |
| 376 | + | ||
| 377 | + | Position pos = it.first; | |
| 378 | + | root->addPos(pos.x, pos.y, pos.z); | |
| 379 | + | root->endNode(); | |
| 380 | + | } | |
| 381 | + | root->endNode(); | |
| 382 | + | } | |
| 383 | + | } | |
| 384 | + | root->endNode(); // OTBM_MAP_DATA | |
| 385 | + | } | |
| 386 | + | root->endNode(); | |
| 387 | + | ||
| 388 | + | fin->flush(); | |
| 389 | + | fin->close(); | |
| 390 | + | } catch(std::exception& e) { | |
| 391 | + | g_logger.error(stdext::format("Failed to save '%s': %s", fileName, e.what())); | |
| 392 | + | } | |
| 393 | + | } | |
| 394 | + | ||
| 395 | + | bool Map::loadOtcm(const std::string& fileName) | |
| 396 | + | { | |
| 397 | + | try { | |
| 398 | + | FileStreamPtr fin = g_resources.openFile(fileName, g_game.getFeature(Otc::GameDontCacheFiles)); | |
| 399 | + | if(!fin) | |
| 400 | + | stdext::throw_exception("unable to open file"); | |
| 401 | + | ||
| 402 | + | uint32 signature = fin->getU32(); | |
| 403 | + | if(signature != OTCM_SIGNATURE) | |
| 404 | + | stdext::throw_exception("invalid otcm file"); | |
| 405 | + | ||
| 406 | + | uint16 start = fin->getU16(); | |
| 407 | + | uint16 version = fin->getU16(); | |
| 408 | + | fin->getU32(); // flags | |
| 409 | + | ||
| 410 | + | switch(version) { | |
| 411 | + | case 1: { | |
| 412 | + | fin->getString(); // description | |
| 413 | + | uint32 datSignature = fin->getU32(); | |
| 414 | + | fin->getU16(); // protocol version | |
| 415 | + | fin->getString(); // world name | |
| 416 | + | ||
| 417 | + | if(datSignature != g_things.getDatSignature()) | |
| 418 | + | g_logger.warning("otcm map loaded was created with a different dat signature"); | |
| 419 | + | ||
| 420 | + | break; | |
| 421 | + | } | |
| 422 | + | default: | |
| 423 | + | stdext::throw_exception("otcm version not supported"); | |
| 424 | + | } | |
| 425 | + | ||
| 426 | + | fin->seek(start); | |
| 427 | + | ||
| 428 | + | while(true) { | |
| 429 | + | Position pos; | |
| 430 | + | ||
| 431 | + | pos.x = fin->getU16(); | |
| 432 | + | pos.y = fin->getU16(); | |
| 433 | + | pos.z = fin->getU8(); | |
| 434 | + | ||
| 435 | + | // end of file | |
| 436 | + | if(!pos.isValid()) | |
| 437 | + | break; | |
| 438 | + | ||
| 439 | + | const TilePtr& tile = g_map.createTile(pos); | |
| 440 | + | ||
| 441 | + | int stackPos = 0; | |
| 442 | + | while(true) { | |
| 443 | + | int id = fin->getU16(); | |
| 444 | + | ||
| 445 | + | // end of tile | |
| 446 | + | if(id == 0xFFFF) | |
| 447 | + | break; | |
| 448 | + | ||
| 449 | + | int countOrSubType = fin->getU8(); | |
| 450 | + | ||
| 451 | + | ItemPtr item = Item::create(id); | |
| 452 | + | item->setCountOrSubType(countOrSubType); | |
| 453 | + | ||
| 454 | + | if(item->isValid()) | |
| 455 | + | tile->addThing(item, stackPos++); | |
| 456 | + | } | |
| 457 | + | ||
| 458 | + | g_map.notificateTileUpdate(pos); | |
| 459 | + | } | |
| 460 | + | ||
| 461 | + | fin->close(); | |
| 462 | + | ||
| 463 | + | return true; | |
| 464 | + | } catch(stdext::exception& e) { | |
| 465 | + | g_logger.error(stdext::format("failed to load OTCM map: %s", e.what())); | |
| 466 | + | return false; | |
| 467 | + | } | |
| 468 | + | } | |
| 469 | + | ||
| 470 | + | void Map::saveOtcm(const std::string& fileName) | |
| 471 | + | { | |
| 472 | + | try { | |
| 473 | + | stdext::timer saveTimer; | |
| 474 | + | ||
| 475 | + | FileStreamPtr fin = g_resources.createFile(fileName); | |
| 476 | + | ||
| 477 | + | //TODO: compression flag with zlib | |
| 478 | + | uint32 flags = 0; | |
| 479 | + | ||
| 480 | + | // header | |
| 481 | + | fin->addU32(OTCM_SIGNATURE); | |
| 482 | + | fin->addU16(0); // data start, will be overwritten later | |
| 483 | + | fin->addU16(OTCM_VERSION); | |
| 484 | + | fin->addU32(flags); | |
| 485 | + | ||
| 486 | + | // version 1 header | |
| 487 | + | fin->addString("OTCM 1.0"); // map description | |
| 488 | + | fin->addU32(g_things.getDatSignature()); | |
| 489 | + | fin->addU16(g_game.getClientVersion()); | |
| 490 | + | fin->addString(g_game.getWorldName()); | |
| 491 | + | ||
| 492 | + | // go back and rewrite where the map data starts | |
| 493 | + | uint32 start = fin->tell(); | |
| 494 | + | fin->seek(4); | |
| 495 | + | fin->addU16(start); | |
| 496 | + | fin->seek(start); | |
| 497 | + | ||
| 498 | + | for(uint8_t z = 0; z <= Otc::MAX_Z; ++z) { | |
| 499 | + | for(const auto& it : m_tileBlocks[z]) { | |
| 500 | + | const TileBlock& block = it.second; | |
| 501 | + | for(const TilePtr& tile : block.getTiles()) { | |
| 502 | + | if(!tile || tile->isEmpty()) | |
| 503 | + | continue; | |
| 504 | + | ||
| 505 | + | Position pos = tile->getPosition(); | |
| 506 | + | fin->addU16(pos.x); | |
| 507 | + | fin->addU16(pos.y); | |
| 508 | + | fin->addU8(pos.z); | |
| 509 | + | ||
| 510 | + | for(const ThingPtr& thing : tile->getThings()) { | |
| 511 | + | if(thing->isItem()) { | |
| 512 | + | ItemPtr item = thing->static_self_cast<Item>(); | |
| 513 | + | fin->addU16(item->getId()); | |
| 514 | + | fin->addU8(item->getCountOrSubType()); | |
| 515 | + | } | |
| 516 | + | } | |
| 517 | + | ||
| 518 | + | // end of tile | |
| 519 | + | fin->addU16(0xFFFF); | |
| 520 | + | } | |
| 521 | + | } | |
| 522 | + | } | |
| 523 | + | ||
| 524 | + | // end of file | |
| 525 | + | Position invalidPos; | |
| 526 | + | fin->addU16(invalidPos.x); | |
| 527 | + | fin->addU16(invalidPos.y); | |
| 528 | + | fin->addU8(invalidPos.z); | |
| 529 | + | ||
| 530 | + | fin->flush(); | |
| 531 | + | ||
| 532 | + | fin->close(); | |
| 533 | + | } catch(stdext::exception& e) { | |
| 534 | + | g_logger.error(stdext::format("failed to save OTCM map: %s", e.what())); | |
| 535 | + | } | |
| 536 | + | } | |
| 537 | + | ||
| 538 | + | /* vim: set ts=4 sw=4 et: */ |
| @@ -0,0 +1,716 @@ | |||
| 1 | + | /* | |
| 2 | + | * Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient> | |
| 3 | + | * | |
| 4 | + | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 5 | + | * of this software and associated documentation files (the "Software"), to deal | |
| 6 | + | * in the Software without restriction, including without limitation the rights | |
| 7 | + | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 8 | + | * copies of the Software, and to permit persons to whom the Software is | |
| 9 | + | * furnished to do so, subject to the following conditions: | |
| 10 | + | * | |
| 11 | + | * The above copyright notice and this permission notice shall be included in | |
| 12 | + | * all copies or substantial portions of the Software. | |
| 13 | + | * | |
| 14 | + | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 15 | + | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 16 | + | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 17 | + | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 18 | + | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 19 | + | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| 20 | + | * THE SOFTWARE. | |
| 21 | + | */ | |
| 22 | + | ||
| 23 | + | #include "mapview.h" | |
| 24 | + | ||
| 25 | + | #include "animatedtext.h" | |
| 26 | + | #include "creature.h" | |
| 27 | + | #include "game.h" | |
| 28 | + | #include "lightview.h" | |
| 29 | + | #include "localplayer.h" | |
| 30 | + | #include "map.h" | |
| 31 | + | #include "missile.h" | |
| 32 | + | #include "spritemanager.h" | |
| 33 | + | #include "statictext.h" | |
| 34 | + | #include "tile.h" | |
| 35 | + | ||
| 36 | + | #include <framework/core/application.h> | |
| 37 | + | #include <framework/core/eventdispatcher.h> | |
| 38 | + | #include <framework/core/resourcemanager.h> | |
| 39 | + | #include <framework/graphics/atlas.h> | |
| 40 | + | #include <framework/graphics/framebuffermanager.h> | |
| 41 | + | #include <framework/graphics/graphics.h> | |
| 42 | + | #include <framework/graphics/image.h> | |
| 43 | + | #include <framework/graphics/shadermanager.h> | |
| 44 | + | #include <framework/graphics/texturemanager.h> | |
| 45 | + | ||
| 46 | + | #include <framework/core/adaptiverenderer.h> | |
| 47 | + | #include <framework/util/extras.h> | |
| 48 | + | ||
| 49 | + | MapView::MapView() { | |
| 50 | + | m_lockedFirstVisibleFloor = -1; | |
| 51 | + | m_cachedFirstVisibleFloor = 7; | |
| 52 | + | m_cachedLastVisibleFloor = 7; | |
| 53 | + | m_minimumAmbientLight = 0; | |
| 54 | + | m_optimizedSize = Size(g_map.getAwareRange().horizontal(), | |
| 55 | + | g_map.getAwareRange().vertical()) * | |
| 56 | + | g_sprites.spriteSize(); | |
| 57 | + | ||
| 58 | + | setVisibleDimension(Size(15, 11)); | |
| 59 | + | } | |
| 60 | + | ||
| 61 | + | MapView::~MapView() { VALIDATE(!g_app.isTerminated()); } | |
| 62 | + | ||
| 63 | + | void MapView::drawTileTexts(const Rect &rect, const Rect &srcRect) { | |
| 64 | + | Position cameraPosition = getCameraPosition(); | |
| 65 | + | Point drawOffset = srcRect.topLeft(); | |
| 66 | + | float horizontalStretchFactor = rect.width() / (float)srcRect.width(); | |
| 67 | + | float verticalStretchFactor = rect.height() / (float)srcRect.height(); | |
| 68 | + | ||
| 69 | + | auto player = g_game.getLocalPlayer(); | |
| 70 | + | auto floor = player->getPosition().z; | |
| 71 | + | for (auto &tile : m_cachedVisibleTiles[floor]) { | |
| 72 | + | Position tilePos = tile->getPosition(); | |
| 73 | + | Point p = transformPositionTo2D(tilePos, cameraPosition) - drawOffset; | |
| 74 | + | p.x *= horizontalStretchFactor; | |
| 75 | + | p.y *= verticalStretchFactor; | |
| 76 | + | p += rect.topLeft(); | |
| 77 | + | p.y += 5; | |
| 78 | + | ||
| 79 | + | tile->drawTexts(p); | |
| 80 | + | } | |
| 81 | + | } | |
| 82 | + | ||
| 83 | + | void MapView::drawTileWidget(const Rect &rect, const Rect &srcRect) { | |
| 84 | + | Position cameraPosition = getCameraPosition(); | |
| 85 | + | Point drawOffset = srcRect.topLeft(); | |
| 86 | + | float horizontalStretchFactor = rect.width() / (float)srcRect.width(); | |
| 87 | + | float verticalStretchFactor = rect.height() / (float)srcRect.height(); | |
| 88 | + | ||
| 89 | + | auto player = g_game.getLocalPlayer(); | |
| 90 | + | auto floor = player->getPosition().z; | |
| 91 | + | for (auto &tile : m_cachedVisibleTiles[floor]) { | |
| 92 | + | Position tilePos = tile->getPosition(); | |
| 93 | + | if (tilePos.z != player->getPosition().z) | |
| 94 | + | continue; | |
| 95 | + | ||
| 96 | + | Point p = transformPositionTo2D(tilePos, cameraPosition) - drawOffset; | |
| 97 | + | p.x *= horizontalStretchFactor; | |
| 98 | + | p.y *= verticalStretchFactor; | |
| 99 | + | p += rect.topLeft(); | |
| 100 | + | ||
| 101 | + | size_t drawQueueStart = g_drawQueue->size(); | |
| 102 | + | tile->drawWidget(p); | |
| 103 | + | g_drawQueue->setClip(drawQueueStart, rect); | |
| 104 | + | } | |
| 105 | + | } | |
| 106 | + | ||
| 107 | + | void MapView::drawMapBackground(const Rect &rect, | |
| 108 | + | const TilePtr &crosshairTile) { | |
| 109 | + | Position cameraPosition = getCameraPosition(); | |
| 110 | + | if (m_mustUpdateVisibleTilesCache) { | |
| 111 | + | updateVisibleTilesCache(); | |
| 112 | + | } | |
| 113 | + | ||
| 114 | + | if (g_game.getFeature(Otc::GameForceLight)) { | |
| 115 | + | m_drawLight = true; | |
| 116 | + | m_minimumAmbientLight = 0.05f; | |
| 117 | + | } | |
| 118 | + | ||
| 119 | + | Rect srcRect = calcFramebufferSource(rect.size()); | |
| 120 | + | g_drawQueue->setFrameBuffer(rect, m_optimizedSize, srcRect); | |
| 121 | + | ||
| 122 | + | if (m_drawLight) { | |
| 123 | + | Light ambientLight; | |
| 124 | + | if (cameraPosition.z <= Otc::SEA_FLOOR) | |
| 125 | + | ambientLight = g_map.getLight(); | |
| 126 | + | if (!m_lightTexture || m_lightTexture->getSize() != m_drawDimension) | |
| 127 | + | m_lightTexture = TexturePtr(new Texture(m_drawDimension, false, true)); | |
| 128 | + | m_lightView = std::make_unique<LightView>( | |
| 129 | + | m_lightTexture, m_drawDimension, rect, srcRect, ambientLight.color, | |
| 130 | + | std::max<int>(m_minimumAmbientLight * 255, ambientLight.intensity), | |
| 131 | + | cameraPosition.x - m_virtualCenterOffset.x, | |
| 132 | + | cameraPosition.y - m_virtualCenterOffset.y); | |
| 133 | + | } | |
| 134 | + | ||
| 135 | + | for (int z = m_cachedLastVisibleFloor; z >= m_cachedFirstFadingFloor; --z) { | |
| 136 | + | float fading = 1.0; | |
| 137 | + | if (m_floorFading > 0) { | |
| 138 | + | fading = 0.; | |
| 139 | + | if (m_floorFading > 0) { | |
| 140 | + | fading = stdext::clamp<float>( | |
| 141 | + | (float)m_fadingFloorTimers[z].elapsed_millis() / | |
| 142 | + | (float)m_floorFading, | |
| 143 | + | 0.f, 1.f); | |
| 144 | + | if (z < m_cachedFirstVisibleFloor) | |
| 145 | + | fading = 1.0 - fading; | |
| 146 | + | } | |
| 147 | + | if (fading == 0) | |
| 148 | + | break; | |
| 149 | + | } | |
| 150 | + | ||
| 151 | + | size_t floorStart = g_drawQueue->size(); | |
| 152 | + | drawFloor(z, cameraPosition, crosshairTile); | |
| 153 | + | ||
| 154 | + | if (fading < 0.99) | |
| 155 | + | g_drawQueue->setOpacity(floorStart, fading); | |
| 156 | + | } | |
| 157 | + | } | |
| 158 | + | ||
| 159 | + | void MapView::drawFloor(short floor, const Position &cameraPosition, | |
| 160 | + | const TilePtr &crosshairTile) { | |
| 161 | + | if (floor < 0 || floor > Otc::MAX_Z) | |
| 162 | + | return; | |
| 163 | + | ||
| 164 | + | auto &tiles = m_cachedVisibleTiles[floor]; | |
| 165 | + | size_t lightFloorStart = m_lightView ? m_lightView->size() : 0; | |
| 166 | + | ||
| 167 | + | // light | |
| 168 | + | if (m_lightView) { | |
| 169 | + | for (auto &tile : tiles) { | |
| 170 | + | Point tileDrawPos = | |
| 171 | + | transformPositionTo2D(tile->getPosition(), cameraPosition); | |
| 172 | + | ItemPtr ground = tile->getGround(); | |
| 173 | + | // FIX: Only block light for ground tiles ON the camera floor (z=7). | |
| 174 | + | // For floor 8 tiles rendered under floor 7 (through holes), do NOT | |
| 175 | + | // block their light, otherwise they render completely black. | |
| 176 | + | if (ground && ground->isGround() && !ground->isTranslucent() && | |
| 177 | + | tile->getPosition().z == cameraPosition.z) { | |
| 178 | + | m_lightView->setFieldBrightness(tileDrawPos, lightFloorStart, 0); | |
| 179 | + | } | |
| 180 | + | } | |
| 181 | + | } | |
| 182 | + | ||
| 183 | + | if (g_game.getFeature(Otc::GameMapDrawGroundFirst)) { | |
| 184 | + | // ground | |
| 185 | + | for (auto &tile : tiles) { | |
| 186 | + | Point tileDrawPos = | |
| 187 | + | transformPositionTo2D(tile->getPosition(), cameraPosition); | |
| 188 | + | tile->drawGround(tileDrawPos, m_lightView.get()); | |
| 189 | + | } | |
| 190 | + | // bottom, creatures, top | |
| 191 | + | for (auto &tile : tiles) { | |
| 192 | + | Point tileDrawPos = | |
| 193 | + | transformPositionTo2D(tile->getPosition(), cameraPosition); | |
| 194 | + | ||
| 195 | + | tile->drawBottom(tileDrawPos, m_lightView.get()); | |
| 196 | + | ||
| 197 | + | if (m_crosshair && tile == crosshairTile) { | |
| 198 | + | g_drawQueue->addTexturedRect( | |
| 199 | + | Rect(tileDrawPos, tileDrawPos + g_sprites.spriteSize() - 1), | |
| 200 | + | m_crosshair, Rect(0, 0, m_crosshair->getSize())); | |
| 201 | + | } | |
| 202 | + | ||
| 203 | + | tile->drawCreatures(tileDrawPos, m_lightView.get()); | |
| 204 | + | tile->drawTop(tileDrawPos, m_lightView.get()); | |
| 205 | + | } | |
| 206 | + | } else { | |
| 207 | + | // ground, bottom, creatures, top | |
| 208 | + | for (auto &tile : tiles) { | |
| 209 | + | Point tileDrawPos = | |
| 210 | + | transformPositionTo2D(tile->getPosition(), cameraPosition); | |
| 211 | + | ||
| 212 | + | if (m_lightView) { | |
| 213 | + | ItemPtr ground = tile->getGround(); | |
| 214 | + | // FIX: Only block light on the camera's own floor, not on lower | |
| 215 | + | // floors (like floor 8 seen through floor 7 holes). | |
| 216 | + | if (ground && ground->isGround() && !ground->isTranslucent() && | |
| 217 | + | tile->getPosition().z == cameraPosition.z) { | |
| 218 | + | m_lightView->setFieldBrightness(tileDrawPos, lightFloorStart, 0); | |
| 219 | + | } | |
| 220 | + | } | |
| 221 | + | ||
| 222 | + | tile->drawGround(tileDrawPos, m_lightView.get()); | |
| 223 | + | ||
| 224 | + | tile->drawBottom(tileDrawPos, m_lightView.get()); | |
| 225 | + | ||
| 226 | + | if (m_crosshair && tile == crosshairTile) { | |
| 227 | + | g_drawQueue->addTexturedRect( | |
| 228 | + | Rect(tileDrawPos, tileDrawPos + g_sprites.spriteSize() - 1), | |
| 229 | + | m_crosshair, Rect(0, 0, m_crosshair->getSize())); | |
| 230 | + | } | |
| 231 | + | ||
| 232 | + | tile->drawCreatures(tileDrawPos, m_lightView.get()); | |
| 233 | + | tile->drawTop(tileDrawPos, m_lightView.get()); | |
| 234 | + | } | |
| 235 | + | } | |
| 236 | + | ||
| 237 | + | for (const MissilePtr &missile : g_map.getFloorMissiles(floor)) { | |
| 238 | + | missile->draw(transformPositionTo2D(missile->getPosition(), cameraPosition), | |
| 239 | + | true, m_lightView.get()); | |
| 240 | + | } | |
| 241 | + | } | |
| 242 | + | ||
| 243 | + | void MapView::drawMapForeground(const Rect &rect) { | |
| 244 | + | // this could happen if the player position is not known yet | |
| 245 | + | Position cameraPosition = getCameraPosition(); | |
| 246 | + | if (!cameraPosition.isValid()) | |
| 247 | + | return; | |
| 248 | + | ||
| 249 | + | Rect srcRect = calcFramebufferSource(rect.size()); | |
| 250 | + | Point drawOffset = srcRect.topLeft(); | |
| 251 | + | float horizontalStretchFactor = rect.width() / (float)srcRect.width(); | |
| 252 | + | float verticalStretchFactor = rect.height() / (float)srcRect.height(); | |
| 253 | + | ||
| 254 | + | // creatures | |
| 255 | + | std::vector<std::pair<CreaturePtr, Point>> creatures; | |
| 256 | + | for (const CreaturePtr &creature : g_map.getSpectatorsInRangeEx( | |
| 257 | + | cameraPosition, false, m_visibleDimension.width() / 2, | |
| 258 | + | m_visibleDimension.width() / 2 + 1, m_visibleDimension.height() / 2, | |
| 259 | + | m_visibleDimension.height() / 2 + 1)) { | |
| 260 | + | if (!creature->canBeSeen()) | |
| 261 | + | continue; | |
| 262 | + | ||
| 263 | + | PointF jumpOffset = creature->getJumpOffset(); | |
| 264 | + | Point creatureOffset = | |
| 265 | + | Point(16 * g_sprites.getOffsetFactor() - creature->getDisplacementX(), | |
| 266 | + | -creature->getDisplacementY() - 2 * g_sprites.getOffsetFactor()); | |
| 267 | + | Position pos = creature->getPrewalkingPosition(); | |
| 268 | + | Point p = transformPositionTo2D(pos, cameraPosition) - drawOffset; | |
| 269 | + | p += (creature->getDrawOffset() + creatureOffset) - | |
| 270 | + | Point(jumpOffset.x, jumpOffset.y); | |
| 271 | + | p.x = p.x * horizontalStretchFactor; | |
| 272 | + | p.y = p.y * verticalStretchFactor; | |
| 273 | + | p += rect.topLeft(); | |
| 274 | + | creatures.push_back(std::make_pair(creature, p)); | |
| 275 | + | } | |
| 276 | + | ||
| 277 | + | for (auto &c : creatures) { | |
| 278 | + | int flags = Otc::DrawIcons; | |
| 279 | + | if (m_drawNames) { | |
| 280 | + | flags |= Otc::DrawNames; | |
| 281 | + | } | |
| 282 | + | if ((!c.first->isLocalPlayer() || m_drawPlayerBars) && | |
| 283 | + | !m_drawHealthBarsOnTop) { | |
| 284 | + | if (m_drawHealthBars) { | |
| 285 | + | flags |= Otc::DrawBars; | |
| 286 | + | } | |
| 287 | + | if (m_drawManaBar) { | |
| 288 | + | flags |= Otc::DrawManaBar; | |
| 289 | + | } | |
| 290 | + | } | |
| 291 | + | c.first->drawInformation(c.second, | |
| 292 | + | g_map.isCovered(c.first->getPrewalkingPosition(), | |
| 293 | + | m_cachedFirstVisibleFloor), | |
| 294 | + | rect, flags); | |
| 295 | + | } | |
| 296 | + | ||
| 297 | + | if (m_lightView) { | |
| 298 | + | g_drawQueue->add(m_lightView.release()); | |
| 299 | + | } | |
| 300 | + | ||
| 301 | + | // texts | |
| 302 | + | int limit = g_adaptiveRenderer.textsLimit(); | |
| 303 | + | for (int i = 0; i < 2; ++i) { | |
| 304 | + | for (const StaticTextPtr &staticText : g_map.getStaticTexts()) { | |
| 305 | + | Position pos = staticText->getPosition(); | |
| 306 | + | ||
| 307 | + | if (pos.z != cameraPosition.z && | |
| 308 | + | staticText->getMessageMode() == Otc::MessageNone) | |
| 309 | + | continue; | |
| 310 | + | if ((staticText->getMessageMode() != Otc::MessageSay && | |
| 311 | + | staticText->getMessageMode() != Otc::MessageYell)) { | |
| 312 | + | if (i == 0) | |
| 313 | + | continue; | |
| 314 | + | } else if (i == 1) | |
| 315 | + | continue; | |
| 316 | + | ||
| 317 | + | Point p = transformPositionTo2D(pos, cameraPosition) - drawOffset + | |
| 318 | + | Point(8, 0) * g_sprites.getOffsetFactor(); | |
| 319 | + | p.x *= horizontalStretchFactor; | |
| 320 | + | p.y *= verticalStretchFactor; | |
| 321 | + | p += rect.topLeft(); | |
| 322 | + | staticText->drawText(p, rect); | |
| 323 | + | if (--limit == 0) | |
| 324 | + | break; | |
| 325 | + | } | |
| 326 | + | } | |
| 327 | + | ||
| 328 | + | limit = g_adaptiveRenderer.textsLimit(); | |
| 329 | + | for (const AnimatedTextPtr &animatedText : g_map.getAnimatedTexts()) { | |
| 330 | + | Position pos = animatedText->getPosition(); | |
| 331 | + | ||
| 332 | + | if (pos.z != cameraPosition.z) | |
| 333 | + | continue; | |
| 334 | + | ||
| 335 | + | Point p = transformPositionTo2D(pos, cameraPosition) - drawOffset + | |
| 336 | + | Point(16, 8) * g_sprites.getOffsetFactor(); | |
| 337 | + | p.x *= horizontalStretchFactor; | |
| 338 | + | p.y *= verticalStretchFactor; | |
| 339 | + | p += rect.topLeft(); | |
| 340 | + | animatedText->drawText(p, rect); | |
| 341 | + | if (--limit == 0) | |
| 342 | + | break; | |
| 343 | + | } | |
| 344 | + | ||
| 345 | + | // tile texts | |
| 346 | + | drawTileTexts(rect, srcRect); | |
| 347 | + | ||
| 348 | + | // bars on top | |
| 349 | + | if (m_drawHealthBarsOnTop) { | |
| 350 | + | for (auto &c : creatures) { | |
| 351 | + | int flags = 0; | |
| 352 | + | if ((!c.first->isLocalPlayer() || m_drawPlayerBars)) { | |
| 353 | + | if (m_drawHealthBars) { | |
| 354 | + | flags |= Otc::DrawBars; | |
| 355 | + | } | |
| 356 | + | if (m_drawManaBar) { | |
| 357 | + | flags |= Otc::DrawManaBar; | |
| 358 | + | } | |
| 359 | + | } | |
| 360 | + | c.first->drawInformation(c.second, | |
| 361 | + | g_map.isCovered(c.first->getPrewalkingPosition(), | |
| 362 | + | m_cachedFirstVisibleFloor), | |
| 363 | + | rect, flags); | |
| 364 | + | } | |
| 365 | + | } | |
| 366 | + | ||
| 367 | + | drawTileWidget(rect, srcRect); | |
| 368 | + | } | |
| 369 | + | ||
| 370 | + | void MapView::updateVisibleTilesCache() { | |
| 371 | + | int prevFirstVisibleFloor = m_cachedFirstVisibleFloor; | |
| 372 | + | m_cachedFirstVisibleFloor = calcFirstVisibleFloor(false); | |
| 373 | + | m_cachedFirstFadingFloor = calcFirstVisibleFloor(true); | |
| 374 | + | m_cachedLastVisibleFloor = calcLastVisibleFloor(); | |
| 375 | + | ||
| 376 | + | VALIDATE(m_cachedFirstVisibleFloor >= 0 && m_cachedLastVisibleFloor >= 0 && | |
| 377 | + | m_cachedFirstVisibleFloor <= Otc::MAX_Z && | |
| 378 | + | m_cachedLastVisibleFloor <= Otc::MAX_Z); | |
| 379 | + | ||
| 380 | + | if (m_cachedLastVisibleFloor < m_cachedFirstVisibleFloor) | |
| 381 | + | m_cachedLastVisibleFloor = m_cachedFirstVisibleFloor; | |
| 382 | + | ||
| 383 | + | m_mustUpdateVisibleTilesCache = false; | |
| 384 | + | ||
| 385 | + | // there is no tile to render on invalid positions | |
| 386 | + | Position cameraPosition = getCameraPosition(); | |
| 387 | + | if (!cameraPosition.isValid()) { | |
| 388 | + | return; | |
| 389 | + | } | |
| 390 | + | ||
| 391 | + | // fading | |
| 392 | + | if (!m_lastCameraPosition.isValid() || | |
| 393 | + | m_lastCameraPosition.z != cameraPosition.z || | |
| 394 | + | m_lastCameraPosition.distance(cameraPosition) >= 3) { | |
| 395 | + | for (int iz = m_cachedLastVisibleFloor; iz >= m_cachedFirstFadingFloor; | |
| 396 | + | --iz) { | |
| 397 | + | m_fadingFloorTimers[iz].restart(m_floorFading * 1000); | |
| 398 | + | } | |
| 399 | + | } else if (prevFirstVisibleFloor < | |
| 400 | + | m_cachedFirstVisibleFloor) { // showing new floor | |
| 401 | + | for (int iz = prevFirstVisibleFloor; iz < m_cachedFirstVisibleFloor; ++iz) { | |
| 402 | + | int shift = std::max<int>( | |
| 403 | + | 0, m_floorFading - m_fadingFloorTimers[iz].elapsed_millis()); | |
| 404 | + | m_fadingFloorTimers[iz].restart(shift * 1000); | |
| 405 | + | } | |
| 406 | + | } else if (prevFirstVisibleFloor > | |
| 407 | + | m_cachedFirstVisibleFloor) { // hiding floor | |
| 408 | + | for (int iz = m_cachedFirstVisibleFloor; iz < prevFirstVisibleFloor; ++iz) { | |
| 409 | + | int shift = std::max<int>( | |
| 410 | + | 0, m_floorFading - m_fadingFloorTimers[iz].elapsed_millis()); | |
| 411 | + | m_fadingFloorTimers[iz].restart(shift * 1000); | |
| 412 | + | } | |
| 413 | + | } | |
| 414 | + | ||
| 415 | + | m_lastCameraPosition = cameraPosition; | |
| 416 | + | ||
| 417 | + | const int numDiagonals = | |
| 418 | + | m_drawDimension.width() + m_drawDimension.height() - 1; | |
| 419 | + | for (auto &cachedVisibleTiles : m_cachedVisibleTiles) { | |
| 420 | + | cachedVisibleTiles.clear(); | |
| 421 | + | } | |
| 422 | + | ||
| 423 | + | // draw from last floor (the lower) to first floor (the higher) | |
| 424 | + | for (int iz = m_cachedLastVisibleFloor; | |
| 425 | + | iz >= | |
| 426 | + | (m_floorFading ? m_cachedFirstFadingFloor : m_cachedFirstVisibleFloor); | |
| 427 | + | --iz) { | |
| 428 | + | for (int diagonal = 0; diagonal < numDiagonals; ++diagonal) { | |
| 429 | + | // loop current diagonal tiles | |
| 430 | + | int advance = std::max<int>(diagonal - m_drawDimension.height(), 0); | |
| 431 | + | for (int iy = diagonal - advance, ix = advance; | |
| 432 | + | iy >= 0 && ix < m_drawDimension.width(); --iy, ++ix) { | |
| 433 | + | // position on current floor | |
| 434 | + | // TODO: check position limits | |
| 435 | + | Position tilePos = cameraPosition.translated( | |
| 436 | + | ix - m_virtualCenterOffset.x, iy - m_virtualCenterOffset.y); | |
| 437 | + | // adjust tilePos to the wanted floor | |
| 438 | + | tilePos.coveredUp(cameraPosition.z - iz); | |
| 439 | + | if (const TilePtr &tile = g_map.getTile(tilePos)) { | |
| 440 | + | if (!tile->isDrawable()) | |
| 441 | + | continue; | |
| 442 | + | m_cachedVisibleTiles[tilePos.z].push_back(tile); | |
| 443 | + | tile->calculateCorpseCorrection(); | |
| 444 | + | } | |
| 445 | + | } | |
| 446 | + | } | |
| 447 | + | } | |
| 448 | + | } | |
| 449 | + | ||
| 450 | + | void MapView::updateGeometry(const Size &visibleDimension, | |
| 451 | + | const Size &optimizedSize) { | |
| 452 | + | m_multifloor = true; | |
| 453 | + | m_visibleDimension = visibleDimension; | |
| 454 | + | m_drawDimension = visibleDimension + Size(3, 3); | |
| 455 | + | m_virtualCenterOffset = (m_drawDimension / 2 - Size(1, 1)).toPoint(); | |
| 456 | + | m_visibleCenterOffset = m_virtualCenterOffset; | |
| 457 | + | m_optimizedSize = m_drawDimension * g_sprites.spriteSize(); | |
| 458 | + | requestVisibleTilesCacheUpdate(); | |
| 459 | + | } | |
| 460 | + | ||
| 461 | + | void MapView::onTileUpdate(const Position &pos) { | |
| 462 | + | requestVisibleTilesCacheUpdate(); | |
| 463 | + | } | |
| 464 | + | ||
| 465 | + | void MapView::onMapCenterChange(const Position &pos) { | |
| 466 | + | requestVisibleTilesCacheUpdate(); | |
| 467 | + | } | |
| 468 | + | ||
| 469 | + | void MapView::lockFirstVisibleFloor(int firstVisibleFloor) { | |
| 470 | + | m_lockedFirstVisibleFloor = firstVisibleFloor; | |
| 471 | + | requestVisibleTilesCacheUpdate(); | |
| 472 | + | } | |
| 473 | + | ||
| 474 | + | void MapView::unlockFirstVisibleFloor() { | |
| 475 | + | m_lockedFirstVisibleFloor = -1; | |
| 476 | + | requestVisibleTilesCacheUpdate(); | |
| 477 | + | } | |
| 478 | + | ||
| 479 | + | void MapView::setVisibleDimension(const Size &visibleDimension) { | |
| 480 | + | // if(visibleDimension == m_visibleDimension) | |
| 481 | + | // return; | |
| 482 | + | ||
| 483 | + | if (visibleDimension.width() % 2 != 1 || visibleDimension.height() % 2 != 1) { | |
| 484 | + | g_logger.traceError("visible dimension must be odd"); | |
| 485 | + | return; | |
| 486 | + | } | |
| 487 | + | ||
| 488 | + | if (visibleDimension < Size(3, 3)) { | |
| 489 | + | g_logger.traceError("reach max zoom in"); | |
| 490 | + | return; | |
| 491 | + | } | |
| 492 | + | ||
| 493 | + | updateGeometry(visibleDimension, m_optimizedSize); | |
| 494 | + | } | |
| 495 | + | ||
| 496 | + | void MapView::optimizeForSize(const Size &visibleSize) { | |
| 497 | + | updateGeometry(m_visibleDimension, visibleSize); | |
| 498 | + | } | |
| 499 | + | ||
| 500 | + | void MapView::followCreature(const CreaturePtr &creature) { | |
| 501 | + | m_follow = true; | |
| 502 | + | m_followingCreature = creature; | |
| 503 | + | requestVisibleTilesCacheUpdate(); | |
| 504 | + | } | |
| 505 | + | ||
| 506 | + | void MapView::setCameraPosition(const Position &pos) { | |
| 507 | + | m_follow = false; | |
| 508 | + | m_customCameraPosition = pos; | |
| 509 | + | requestVisibleTilesCacheUpdate(); | |
| 510 | + | } | |
| 511 | + | ||
| 512 | + | Position MapView::getPosition(const Point &point, const Size &mapSize) { | |
| 513 | + | Position cameraPosition = getCameraPosition(); | |
| 514 | + | ||
| 515 | + | // if we have no camera, its impossible to get the tile | |
| 516 | + | if (!cameraPosition.isValid()) | |
| 517 | + | return Position(); | |
| 518 | + | ||
| 519 | + | Rect srcRect = calcFramebufferSource(mapSize); | |
| 520 | + | float sh = srcRect.width() / (float)mapSize.width(); | |
| 521 | + | float sv = srcRect.height() / (float)mapSize.height(); | |
| 522 | + | ||
| 523 | + | Point framebufferPos = Point(point.x * sh, point.y * sv); | |
| 524 | + | Point realPos = (framebufferPos + srcRect.topLeft()); | |
| 525 | + | Point centerOffset = realPos / g_sprites.spriteSize(); | |
| 526 | + | ||
| 527 | + | Point tilePos2D = getVisibleCenterOffset() - m_drawDimension.toPoint() + | |
| 528 | + | centerOffset + Point(2, 2); | |
| 529 | + | if (tilePos2D.x + cameraPosition.x < 0 && tilePos2D.y + cameraPosition.y < 0) | |
| 530 | + | return Position(); | |
| 531 | + | ||
| 532 | + | Position position = Position(tilePos2D.x, tilePos2D.y, 0) + cameraPosition; | |
| 533 | + | ||
| 534 | + | if (!position.isValid()) | |
| 535 | + | return Position(); | |
| 536 | + | ||
| 537 | + | return position; | |
| 538 | + | } | |
| 539 | + | ||
| 540 | + | Point MapView::getPositionOffset(const Point &point, const Size &mapSize) { | |
| 541 | + | Position cameraPosition = getCameraPosition(); | |
| 542 | + | ||
| 543 | + | // if we have no camera, its impossible to get the tile | |
| 544 | + | if (!cameraPosition.isValid()) | |
| 545 | + | return Point(0, 0); | |
| 546 | + | ||
| 547 | + | Rect srcRect = calcFramebufferSource(mapSize); | |
| 548 | + | float sh = srcRect.width() / (float)mapSize.width(); | |
| 549 | + | float sv = srcRect.height() / (float)mapSize.height(); | |
| 550 | + | ||
| 551 | + | Point framebufferPos = Point(point.x * sh, point.y * sv); | |
| 552 | + | Point realPos = (framebufferPos + srcRect.topLeft()); | |
| 553 | + | return Point(realPos.x % g_sprites.spriteSize(), | |
| 554 | + | realPos.y % g_sprites.spriteSize()); | |
| 555 | + | } | |
| 556 | + | ||
| 557 | + | void MapView::move(int x, int y) { | |
| 558 | + | m_moveOffset.x += x; | |
| 559 | + | m_moveOffset.y += y; | |
| 560 | + | ||
| 561 | + | int32_t tmp = m_moveOffset.x / g_sprites.spriteSize(); | |
| 562 | + | bool requestTilesUpdate = false; | |
| 563 | + | if (tmp != 0) { | |
| 564 | + | m_customCameraPosition.x += tmp; | |
| 565 | + | m_moveOffset.x %= g_sprites.spriteSize(); | |
| 566 | + | requestTilesUpdate = true; | |
| 567 | + | } | |
| 568 | + | ||
| 569 | + | tmp = m_moveOffset.y / g_sprites.spriteSize(); | |
| 570 | + | if (tmp != 0) { | |
| 571 | + | m_customCameraPosition.y += tmp; | |
| 572 | + | m_moveOffset.y %= g_sprites.spriteSize(); | |
| 573 | + | requestTilesUpdate = true; | |
| 574 | + | } | |
| 575 | + | ||
| 576 | + | if (requestTilesUpdate) | |
| 577 | + | requestVisibleTilesCacheUpdate(); | |
| 578 | + | } | |
| 579 | + | ||
| 580 | + | Rect MapView::calcFramebufferSource(const Size &destSize, bool inNextFrame) { | |
| 581 | + | float scaleFactor = g_sprites.spriteSize() / (float)g_sprites.spriteSize(); | |
| 582 | + | Point drawOffset = | |
| 583 | + | ((m_drawDimension - m_visibleDimension - Size(1, 1)).toPoint() / 2) * | |
| 584 | + | g_sprites.spriteSize(); | |
| 585 | + | if (isFollowingCreature()) | |
| 586 | + | drawOffset += m_followingCreature->getWalkOffset(inNextFrame) * scaleFactor; | |
| 587 | + | ||
| 588 | + | Size srcSize = destSize; | |
| 589 | + | Size srcVisible = m_visibleDimension * g_sprites.spriteSize(); | |
| 590 | + | srcSize.scale(srcVisible, Fw::KeepAspectRatio); | |
| 591 | + | drawOffset.x += (srcVisible.width() - srcSize.width()) / 2; | |
| 592 | + | drawOffset.y += (srcVisible.height() - srcSize.height()) / 2; | |
| 593 | + | ||
| 594 | + | return Rect(drawOffset, srcSize); | |
| 595 | + | } | |
| 596 | + | ||
| 597 | + | int MapView::calcFirstVisibleFloor(bool forFading) { | |
| 598 | + | int z = 7; | |
| 599 | + | // return forced first visible floor | |
| 600 | + | if (m_lockedFirstVisibleFloor != -1) { | |
| 601 | + | z = m_lockedFirstVisibleFloor; | |
| 602 | + | } else { | |
| 603 | + | Position cameraPosition = getCameraPosition(); | |
| 604 | + | ||
| 605 | + | // this could happens if the player is not known yet | |
| 606 | + | if (cameraPosition.isValid()) { | |
| 607 | + | // avoid rendering multifloors in far views | |
| 608 | + | if (!m_multifloor) { | |
| 609 | + | z = cameraPosition.z; | |
| 610 | + | } else { | |
| 611 | + | // if nothing is limiting the view, the first visible floor is 0 | |
| 612 | + | int firstFloor = 0; | |
| 613 | + | ||
| 614 | + | // limits to underground floors while under sea level | |
| 615 | + | if (cameraPosition.z > Otc::SEA_FLOOR) | |
| 616 | + | firstFloor = std::max<int>(cameraPosition.z - | |
| 617 | + | Otc::AWARE_UNDEGROUND_FLOOR_RANGE, | |
| 618 | + | (int)Otc::UNDERGROUND_FLOOR); | |
| 619 | + | ||
| 620 | + | // loop in 3x3 tiles around the camera | |
| 621 | + | for (int ix = -1; | |
| 622 | + | ix <= 1 && firstFloor < cameraPosition.z && !forFading; ++ix) { | |
| 623 | + | for (int iy = -1; iy <= 1 && firstFloor < cameraPosition.z; ++iy) { | |
| 624 | + | Position pos = cameraPosition.translated(ix, iy); | |
| 625 | + | ||
| 626 | + | // process tiles that we can look through, e.g. windows, doors | |
| 627 | + | if ((ix == 0 && iy == 0) || | |
| 628 | + | ((std::abs(ix) != std::abs(iy)) && g_map.isLookPossible(pos))) { | |
| 629 | + | Position upperPos = pos; | |
| 630 | + | Position coveredPos = pos; | |
| 631 | + | ||
| 632 | + | while (coveredPos.coveredUp() && upperPos.up() && | |
| 633 | + | upperPos.z >= firstFloor) { | |
| 634 | + | // check tiles physically above | |
| 635 | + | TilePtr tile = g_map.getTile(upperPos); | |
| 636 | + | if (tile && | |
| 637 | + | tile->limitsFloorsView(!g_map.isLookPossible(pos))) { | |
| 638 | + | firstFloor = upperPos.z + 1; | |
| 639 | + | break; | |
| 640 | + | } | |
| 641 | + | ||
| 642 | + | // check tiles geometrically above | |
| 643 | + | tile = g_map.getTile(coveredPos); | |
| 644 | + | if (tile && tile->limitsFloorsView(g_map.isLookPossible(pos))) { | |
| 645 | + | firstFloor = coveredPos.z + 1; | |
| 646 | + | break; | |
| 647 | + | } | |
| 648 | + | } | |
| 649 | + | } | |
| 650 | + | } | |
| 651 | + | } | |
| 652 | + | z = firstFloor; | |
| 653 | + | } | |
| 654 | + | } | |
| 655 | + | } | |
| 656 | + | ||
| 657 | + | // just ensure the that the floor is in the valid range | |
| 658 | + | z = stdext::clamp<int>(z, 0, (int)Otc::MAX_Z); | |
| 659 | + | return z; | |
| 660 | + | } | |
| 661 | + | ||
| 662 | + | int MapView::calcLastVisibleFloor() { | |
| 663 | + | if (!m_multifloor) | |
| 664 | + | return calcFirstVisibleFloor(); | |
| 665 | + | ||
| 666 | + | int z = 7; | |
| 667 | + | ||
| 668 | + | Position cameraPosition = getCameraPosition(); | |
| 669 | + | // this could happens if the player is not known yet | |
| 670 | + | if (cameraPosition.isValid()) { | |
| 671 | + | // view only underground floors when below sea level | |
| 672 | + | if (cameraPosition.z > Otc::SEA_FLOOR) | |
| 673 | + | z = cameraPosition.z + Otc::AWARE_UNDEGROUND_FLOOR_RANGE; | |
| 674 | + | else | |
| 675 | + | // OPTIMIZACIÓN: En superficie solo renderizamos hasta 2 pisos abajo | |
| 676 | + | // (entradas de cueva visibles). Antes renderizaba TODOS los 16 pisos | |
| 677 | + | // lo cual generaba draw calls innecesarios y bajaba los FPS. | |
| 678 | + | z = std::min((int)Otc::SEA_FLOOR + 2, (int)Otc::MAX_Z); | |
| 679 | + | } | |
| 680 | + | ||
| 681 | + | if (m_lockedFirstVisibleFloor != -1) | |
| 682 | + | z = std::max<int>(m_lockedFirstVisibleFloor, z); | |
| 683 | + | ||
| 684 | + | // just ensure the that the floor is in the valid range | |
| 685 | + | z = stdext::clamp<int>(z, 0, (int)Otc::MAX_Z); | |
| 686 | + | return z; | |
| 687 | + | } | |
| 688 | + | ||
| 689 | + | Point MapView::transformPositionTo2D(const Position &position, | |
| 690 | + | const Position &relativePosition) { | |
| 691 | + | return Point((m_virtualCenterOffset.x + (position.x - relativePosition.x) - | |
| 692 | + | (relativePosition.z - position.z)) * | |
| 693 | + | g_sprites.spriteSize(), | |
| 694 | + | (m_virtualCenterOffset.y + (position.y - relativePosition.y) - | |
| 695 | + | (relativePosition.z - position.z)) * | |
| 696 | + | g_sprites.spriteSize()); | |
| 697 | + | } | |
| 698 | + | ||
| 699 | + | Position MapView::getCameraPosition() { | |
| 700 | + | if (isFollowingCreature()) { | |
| 701 | + | return m_followingCreature->getPrewalkingPosition(); | |
| 702 | + | } | |
| 703 | + | ||
| 704 | + | return m_customCameraPosition; | |
| 705 | + | } | |
| 706 | + | ||
| 707 | + | void MapView::setDrawLights(bool enable) { m_drawLight = enable; } | |
| 708 | + | ||
| 709 | + | void MapView::setCrosshair(const std::string &file) { | |
| 710 | + | if (file == "") | |
| 711 | + | m_crosshair = nullptr; | |
| 712 | + | else | |
| 713 | + | m_crosshair = g_textures.getTexture(file); | |
| 714 | + | } | |
| 715 | + | ||
| 716 | + | /* vim: set ts=4 sw=4 et: */ |