mapview.cpp

main 717 lines · 24 KB Raw
Alex Alex Commit Client Upload Part 2 19/10/2026 00:06
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
49MapView::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
61MapView::~MapView() { VALIDATE(!g_app.isTerminated()); }
62
63void 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
83void 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
107void 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
159void 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
243void 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
370void 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
450void 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
461void MapView::onTileUpdate(const Position &pos) {
462 requestVisibleTilesCacheUpdate();
463}
464
465void MapView::onMapCenterChange(const Position &pos) {
466 requestVisibleTilesCacheUpdate();
467}
468
469void MapView::lockFirstVisibleFloor(int firstVisibleFloor) {
470 m_lockedFirstVisibleFloor = firstVisibleFloor;
471 requestVisibleTilesCacheUpdate();
472}
473
474void MapView::unlockFirstVisibleFloor() {
475 m_lockedFirstVisibleFloor = -1;
476 requestVisibleTilesCacheUpdate();
477}
478
479void 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
496void MapView::optimizeForSize(const Size &visibleSize) {
497 updateGeometry(m_visibleDimension, visibleSize);
498}
499
500void MapView::followCreature(const CreaturePtr &creature) {
501 m_follow = true;
502 m_followingCreature = creature;
503 requestVisibleTilesCacheUpdate();
504}
505
506void MapView::setCameraPosition(const Position &pos) {
507 m_follow = false;
508 m_customCameraPosition = pos;
509 requestVisibleTilesCacheUpdate();
510}
511
512Position 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
540Point 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
557void 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
580Rect 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
597int 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
662int 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
689Point 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
699Position MapView::getCameraPosition() {
700 if (isFollowingCreature()) {
701 return m_followingCreature->getPrewalkingPosition();
702 }
703
704 return m_customCameraPosition;
705}
706
707void MapView::setDrawLights(bool enable) { m_drawLight = enable; }
708
709void 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: */
717
Top