1mapview.cpp

main 715 lines · 23.9 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 }
132
133 for (int z = m_cachedLastVisibleFloor; z >= m_cachedFirstFadingFloor; --z) {
134 float fading = 1.0;
135 if (m_floorFading > 0) {
136 fading = 0.;
137 if (m_floorFading > 0) {
138 fading = stdext::clamp<float>(
139 (float)m_fadingFloorTimers[z].elapsed_millis() /
140 (float)m_floorFading,
141 0.f, 1.f);
142 if (z < m_cachedFirstVisibleFloor)
143 fading = 1.0 - fading;
144 }
145 if (fading == 0)
146 break;
147 }
148
149 size_t floorStart = g_drawQueue->size();
150 drawFloor(z, cameraPosition, crosshairTile);
151
152 if (fading < 0.99)
153 g_drawQueue->setOpacity(floorStart, fading);
154 }
155}
156
157void MapView::drawFloor(short floor, const Position &cameraPosition,
158 const TilePtr &crosshairTile) {
159 if (floor < 0 || floor > Otc::MAX_Z)
160 return;
161
162 auto &tiles = m_cachedVisibleTiles[floor];
163 size_t lightFloorStart = m_lightView ? m_lightView->size() : 0;
164
165 // light
166 if (m_lightView) {
167 for (auto &tile : tiles) {
168 Point tileDrawPos =
169 transformPositionTo2D(tile->getPosition(), cameraPosition);
170 ItemPtr ground = tile->getGround();
171 // FIX: Only block light for ground tiles ON the camera floor (z=7).
172 // For floor 8 tiles rendered under floor 7 (through holes), do NOT
173 // block their light, otherwise they render completely black.
174 if (ground && ground->isGround() && !ground->isTranslucent() &&
175 tile->getPosition().z == cameraPosition.z) {
176 m_lightView->setFieldBrightness(tileDrawPos, lightFloorStart, 0);
177 }
178 }
179 }
180
181 if (g_game.getFeature(Otc::GameMapDrawGroundFirst)) {
182 // ground
183 for (auto &tile : tiles) {
184 Point tileDrawPos =
185 transformPositionTo2D(tile->getPosition(), cameraPosition);
186 tile->drawGround(tileDrawPos, m_lightView.get());
187 }
188 // bottom, creatures, top
189 for (auto &tile : tiles) {
190 Point tileDrawPos =
191 transformPositionTo2D(tile->getPosition(), cameraPosition);
192
193 tile->drawBottom(tileDrawPos, m_lightView.get());
194
195 if (m_crosshair && tile == crosshairTile) {
196 g_drawQueue->addTexturedRect(
197 Rect(tileDrawPos, tileDrawPos + g_sprites.spriteSize() - 1),
198 m_crosshair, Rect(0, 0, m_crosshair->getSize()));
199 }
200
201 tile->drawCreatures(tileDrawPos, m_lightView.get());
202 tile->drawTop(tileDrawPos, m_lightView.get());
203 }
204 } else {
205 // ground, bottom, creatures, top
206 for (auto &tile : tiles) {
207 Point tileDrawPos =
208 transformPositionTo2D(tile->getPosition(), cameraPosition);
209
210 if (m_lightView) {
211 ItemPtr ground = tile->getGround();
212 // FIX: Only block light on the camera's own floor, not on lower
213 // floors (like floor 8 seen through floor 7 holes).
214 if (ground && ground->isGround() && !ground->isTranslucent() &&
215 tile->getPosition().z == cameraPosition.z) {
216 m_lightView->setFieldBrightness(tileDrawPos, lightFloorStart, 0);
217 }
218 }
219
220 tile->drawGround(tileDrawPos, m_lightView.get());
221
222 tile->drawBottom(tileDrawPos, m_lightView.get());
223
224 if (m_crosshair && tile == crosshairTile) {
225 g_drawQueue->addTexturedRect(
226 Rect(tileDrawPos, tileDrawPos + g_sprites.spriteSize() - 1),
227 m_crosshair, Rect(0, 0, m_crosshair->getSize()));
228 }
229
230 tile->drawCreatures(tileDrawPos, m_lightView.get());
231 tile->drawTop(tileDrawPos, m_lightView.get());
232 }
233 }
234
235 for (const MissilePtr &missile : g_map.getFloorMissiles(floor)) {
236 missile->draw(transformPositionTo2D(missile->getPosition(), cameraPosition),
237 true, m_lightView.get());
238 }
239}
240
241void MapView::drawMapForeground(const Rect &rect) {
242 // this could happen if the player position is not known yet
243 Position cameraPosition = getCameraPosition();
244 if (!cameraPosition.isValid())
245 return;
246
247 Rect srcRect = calcFramebufferSource(rect.size());
248 Point drawOffset = srcRect.topLeft();
249 float horizontalStretchFactor = rect.width() / (float)srcRect.width();
250 float verticalStretchFactor = rect.height() / (float)srcRect.height();
251
252 // creatures
253 std::vector<std::pair<CreaturePtr, Point>> creatures;
254 for (const CreaturePtr &creature : g_map.getSpectatorsInRangeEx(
255 cameraPosition, false, m_visibleDimension.width() / 2,
256 m_visibleDimension.width() / 2 + 1, m_visibleDimension.height() / 2,
257 m_visibleDimension.height() / 2 + 1)) {
258 if (!creature->canBeSeen())
259 continue;
260
261 PointF jumpOffset = creature->getJumpOffset();
262 Point creatureOffset =
263 Point(16 * g_sprites.getOffsetFactor() - creature->getDisplacementX(),
264 -creature->getDisplacementY() - 2 * g_sprites.getOffsetFactor());
265 Position pos = creature->getPrewalkingPosition();
266 Point p = transformPositionTo2D(pos, cameraPosition) - drawOffset;
267 p += (creature->getDrawOffset() + creatureOffset) -
268 Point(jumpOffset.x, jumpOffset.y);
269 p.x = p.x * horizontalStretchFactor;
270 p.y = p.y * verticalStretchFactor;
271 p += rect.topLeft();
272 creatures.push_back(std::make_pair(creature, p));
273 }
274
275 for (auto &c : creatures) {
276 int flags = Otc::DrawIcons;
277 if (m_drawNames) {
278 flags |= Otc::DrawNames;
279 }
280 if ((!c.first->isLocalPlayer() || m_drawPlayerBars) &&
281 !m_drawHealthBarsOnTop) {
282 if (m_drawHealthBars) {
283 flags |= Otc::DrawBars;
284 }
285 if (m_drawManaBar) {
286 flags |= Otc::DrawManaBar;
287 }
288 }
289 c.first->drawInformation(c.second,
290 g_map.isCovered(c.first->getPrewalkingPosition(),
291 m_cachedFirstVisibleFloor),
292 rect, flags);
293 }
294
295 if (m_lightView) {
296 g_drawQueue->add(m_lightView.release());
297 }
298
299 // texts
300 int limit = g_adaptiveRenderer.textsLimit();
301 for (int i = 0; i < 2; ++i) {
302 for (const StaticTextPtr &staticText : g_map.getStaticTexts()) {
303 Position pos = staticText->getPosition();
304
305 if (pos.z != cameraPosition.z &&
306 staticText->getMessageMode() == Otc::MessageNone)
307 continue;
308 if ((staticText->getMessageMode() != Otc::MessageSay &&
309 staticText->getMessageMode() != Otc::MessageYell)) {
310 if (i == 0)
311 continue;
312 } else if (i == 1)
313 continue;
314
315 Point p = transformPositionTo2D(pos, cameraPosition) - drawOffset +
316 Point(8, 0) * g_sprites.getOffsetFactor();
317 p.x *= horizontalStretchFactor;
318 p.y *= verticalStretchFactor;
319 p += rect.topLeft();
320 staticText->drawText(p, rect);
321 if (--limit == 0)
322 break;
323 }
324 }
325
326 limit = g_adaptiveRenderer.textsLimit();
327 for (const AnimatedTextPtr &animatedText : g_map.getAnimatedTexts()) {
328 Position pos = animatedText->getPosition();
329
330 if (pos.z != cameraPosition.z)
331 continue;
332
333 Point p = transformPositionTo2D(pos, cameraPosition) - drawOffset +
334 Point(16, 8) * g_sprites.getOffsetFactor();
335 p.x *= horizontalStretchFactor;
336 p.y *= verticalStretchFactor;
337 p += rect.topLeft();
338 animatedText->drawText(p, rect);
339 if (--limit == 0)
340 break;
341 }
342
343 // tile texts
344 drawTileTexts(rect, srcRect);
345
346 // bars on top
347 if (m_drawHealthBarsOnTop) {
348 for (auto &c : creatures) {
349 int flags = 0;
350 if ((!c.first->isLocalPlayer() || m_drawPlayerBars)) {
351 if (m_drawHealthBars) {
352 flags |= Otc::DrawBars;
353 }
354 if (m_drawManaBar) {
355 flags |= Otc::DrawManaBar;
356 }
357 }
358 c.first->drawInformation(c.second,
359 g_map.isCovered(c.first->getPrewalkingPosition(),
360 m_cachedFirstVisibleFloor),
361 rect, flags);
362 }
363 }
364
365 drawTileWidget(rect, srcRect);
366}
367
368void MapView::updateVisibleTilesCache() {
369 int prevFirstVisibleFloor = m_cachedFirstVisibleFloor;
370 m_cachedFirstVisibleFloor = calcFirstVisibleFloor(false);
371 m_cachedFirstFadingFloor = calcFirstVisibleFloor(true);
372 m_cachedLastVisibleFloor = calcLastVisibleFloor();
373
374 VALIDATE(m_cachedFirstVisibleFloor >= 0 && m_cachedLastVisibleFloor >= 0 &&
375 m_cachedFirstVisibleFloor <= Otc::MAX_Z &&
376 m_cachedLastVisibleFloor <= Otc::MAX_Z);
377
378 if (m_cachedLastVisibleFloor < m_cachedFirstVisibleFloor)
379 m_cachedLastVisibleFloor = m_cachedFirstVisibleFloor;
380
381 m_mustUpdateVisibleTilesCache = false;
382
383 // there is no tile to render on invalid positions
384 Position cameraPosition = getCameraPosition();
385 if (!cameraPosition.isValid()) {
386 return;
387 }
388
389 // fading
390 if (!m_lastCameraPosition.isValid() ||
391 m_lastCameraPosition.z != cameraPosition.z ||
392 m_lastCameraPosition.distance(cameraPosition) >= 3) {
393 for (int iz = m_cachedLastVisibleFloor; iz >= m_cachedFirstFadingFloor;
394 --iz) {
395 m_fadingFloorTimers[iz].restart(m_floorFading * 1000);
396 }
397 } else if (prevFirstVisibleFloor <
398 m_cachedFirstVisibleFloor) { // showing new floor
399 for (int iz = prevFirstVisibleFloor; iz < m_cachedFirstVisibleFloor; ++iz) {
400 int shift = std::max<int>(
401 0, m_floorFading - m_fadingFloorTimers[iz].elapsed_millis());
402 m_fadingFloorTimers[iz].restart(shift * 1000);
403 }
404 } else if (prevFirstVisibleFloor >
405 m_cachedFirstVisibleFloor) { // hiding floor
406 for (int iz = m_cachedFirstVisibleFloor; iz < prevFirstVisibleFloor; ++iz) {
407 int shift = std::max<int>(
408 0, m_floorFading - m_fadingFloorTimers[iz].elapsed_millis());
409 m_fadingFloorTimers[iz].restart(shift * 1000);
410 }
411 }
412
413 m_lastCameraPosition = cameraPosition;
414
415 const int numDiagonals =
416 m_drawDimension.width() + m_drawDimension.height() - 1;
417 for (auto &cachedVisibleTiles : m_cachedVisibleTiles) {
418 cachedVisibleTiles.clear();
419 }
420
421 // draw from last floor (the lower) to first floor (the higher)
422 for (int iz = m_cachedLastVisibleFloor;
423 iz >=
424 (m_floorFading ? m_cachedFirstFadingFloor : m_cachedFirstVisibleFloor);
425 --iz) {
426 for (int diagonal = 0; diagonal < numDiagonals; ++diagonal) {
427 // loop current diagonal tiles
428 int advance = std::max<int>(diagonal - m_drawDimension.height(), 0);
429 for (int iy = diagonal - advance, ix = advance;
430 iy >= 0 && ix < m_drawDimension.width(); --iy, ++ix) {
431 // position on current floor
432 // TODO: check position limits
433 Position tilePos = cameraPosition.translated(
434 ix - m_virtualCenterOffset.x, iy - m_virtualCenterOffset.y);
435 // adjust tilePos to the wanted floor
436 tilePos.coveredUp(cameraPosition.z - iz);
437 if (const TilePtr &tile = g_map.getTile(tilePos)) {
438 if (!tile->isDrawable())
439 continue;
440 m_cachedVisibleTiles[tilePos.z].push_back(tile);
441 tile->calculateCorpseCorrection();
442 }
443 }
444 }
445 }
446}
447
448void MapView::updateGeometry(const Size &visibleDimension,
449 const Size &optimizedSize) {
450 m_multifloor = true;
451 m_visibleDimension = visibleDimension;
452 m_drawDimension = visibleDimension + Size(3, 3);
453 m_virtualCenterOffset = (m_drawDimension / 2 - Size(1, 1)).toPoint();
454 m_visibleCenterOffset = m_virtualCenterOffset;
455 m_optimizedSize = m_drawDimension * g_sprites.spriteSize();
456 requestVisibleTilesCacheUpdate();
457}
458
459void MapView::onTileUpdate(const Position &pos) {
460 requestVisibleTilesCacheUpdate();
461}
462
463void MapView::onMapCenterChange(const Position &pos) {
464 requestVisibleTilesCacheUpdate();
465}
466
467void MapView::lockFirstVisibleFloor(int firstVisibleFloor) {
468 m_lockedFirstVisibleFloor = firstVisibleFloor;
469 requestVisibleTilesCacheUpdate();
470}
471
472void MapView::unlockFirstVisibleFloor() {
473 m_lockedFirstVisibleFloor = -1;
474 requestVisibleTilesCacheUpdate();
475}
476
477void MapView::setVisibleDimension(const Size &visibleDimension) {
478 // if(visibleDimension == m_visibleDimension)
479 // return;
480
481 if (visibleDimension.width() % 2 != 1 || visibleDimension.height() % 2 != 1) {
482 g_logger.traceError("visible dimension must be odd");
483 return;
484 }
485
486 if (visibleDimension < Size(3, 3)) {
487 g_logger.traceError("reach max zoom in");
488 return;
489 }
490
491 updateGeometry(visibleDimension, m_optimizedSize);
492}
493
494void MapView::optimizeForSize(const Size &visibleSize) {
495 updateGeometry(m_visibleDimension, visibleSize);
496}
497
498void MapView::followCreature(const CreaturePtr &creature) {
499 m_follow = true;
500 m_followingCreature = creature;
501 requestVisibleTilesCacheUpdate();
502}
503
504void MapView::setCameraPosition(const Position &pos) {
505 m_follow = false;
506 m_customCameraPosition = pos;
507 requestVisibleTilesCacheUpdate();
508}
509
510Position MapView::getPosition(const Point &point, const Size &mapSize) {
511 Position cameraPosition = getCameraPosition();
512
513 // if we have no camera, its impossible to get the tile
514 if (!cameraPosition.isValid())
515 return Position();
516
517 Rect srcRect = calcFramebufferSource(mapSize);
518 float sh = srcRect.width() / (float)mapSize.width();
519 float sv = srcRect.height() / (float)mapSize.height();
520
521 Point framebufferPos = Point(point.x * sh, point.y * sv);
522 Point realPos = (framebufferPos + srcRect.topLeft());
523 Point centerOffset = realPos / g_sprites.spriteSize();
524
525 Point tilePos2D = getVisibleCenterOffset() - m_drawDimension.toPoint() +
526 centerOffset + Point(2, 2);
527 if (tilePos2D.x + cameraPosition.x < 0 && tilePos2D.y + cameraPosition.y < 0)
528 return Position();
529
530 Position position = Position(tilePos2D.x, tilePos2D.y, 0) + cameraPosition;
531
532 if (!position.isValid())
533 return Position();
534
535 return position;
536}
537
538Point MapView::getPositionOffset(const Point &point, const Size &mapSize) {
539 Position cameraPosition = getCameraPosition();
540
541 // if we have no camera, its impossible to get the tile
542 if (!cameraPosition.isValid())
543 return Point(0, 0);
544
545 Rect srcRect = calcFramebufferSource(mapSize);
546 float sh = srcRect.width() / (float)mapSize.width();
547 float sv = srcRect.height() / (float)mapSize.height();
548
549 Point framebufferPos = Point(point.x * sh, point.y * sv);
550 Point realPos = (framebufferPos + srcRect.topLeft());
551 return Point(realPos.x % g_sprites.spriteSize(),
552 realPos.y % g_sprites.spriteSize());
553}
554
555void MapView::move(int x, int y) {
556 m_moveOffset.x += x;
557 m_moveOffset.y += y;
558
559 int32_t tmp = m_moveOffset.x / g_sprites.spriteSize();
560 bool requestTilesUpdate = false;
561 if (tmp != 0) {
562 m_customCameraPosition.x += tmp;
563 m_moveOffset.x %= g_sprites.spriteSize();
564 requestTilesUpdate = true;
565 }
566
567 tmp = m_moveOffset.y / g_sprites.spriteSize();
568 if (tmp != 0) {
569 m_customCameraPosition.y += tmp;
570 m_moveOffset.y %= g_sprites.spriteSize();
571 requestTilesUpdate = true;
572 }
573
574 if (requestTilesUpdate)
575 requestVisibleTilesCacheUpdate();
576}
577
578Rect MapView::calcFramebufferSource(const Size &destSize, bool inNextFrame) {
579 float scaleFactor = g_sprites.spriteSize() / (float)g_sprites.spriteSize();
580 Point drawOffset =
581 ((m_drawDimension - m_visibleDimension - Size(1, 1)).toPoint() / 2) *
582 g_sprites.spriteSize();
583 if (isFollowingCreature())
584 drawOffset += m_followingCreature->getWalkOffset(inNextFrame) * scaleFactor;
585
586 Size srcSize = destSize;
587 Size srcVisible = m_visibleDimension * g_sprites.spriteSize();
588 srcSize.scale(srcVisible, Fw::KeepAspectRatio);
589 drawOffset.x += (srcVisible.width() - srcSize.width()) / 2;
590 drawOffset.y += (srcVisible.height() - srcSize.height()) / 2;
591
592 return Rect(drawOffset, srcSize);
593}
594
595int MapView::calcFirstVisibleFloor(bool forFading) {
596 int z = 7;
597 // return forced first visible floor
598 if (m_lockedFirstVisibleFloor != -1) {
599 z = m_lockedFirstVisibleFloor;
600 } else {
601 Position cameraPosition = getCameraPosition();
602
603 // this could happens if the player is not known yet
604 if (cameraPosition.isValid()) {
605 // avoid rendering multifloors in far views
606 if (!m_multifloor) {
607 z = cameraPosition.z;
608 } else {
609 // if nothing is limiting the view, the first visible floor is 0
610 int firstFloor = 0;
611
612 // limits to underground floors while under sea level
613 if (cameraPosition.z > Otc::SEA_FLOOR)
614 firstFloor = std::max<int>(cameraPosition.z -
615 Otc::AWARE_UNDEGROUND_FLOOR_RANGE,
616 (int)Otc::UNDERGROUND_FLOOR);
617
618 // loop in 3x3 tiles around the camera
619 for (int ix = -1;
620 ix <= 1 && firstFloor < cameraPosition.z && !forFading; ++ix) {
621 for (int iy = -1; iy <= 1 && firstFloor < cameraPosition.z; ++iy) {
622 Position pos = cameraPosition.translated(ix, iy);
623
624 // process tiles that we can look through, e.g. windows, doors
625 if ((ix == 0 && iy == 0) ||
626 ((std::abs(ix) != std::abs(iy)) && g_map.isLookPossible(pos))) {
627 Position upperPos = pos;
628 Position coveredPos = pos;
629
630 while (coveredPos.coveredUp() && upperPos.up() &&
631 upperPos.z >= firstFloor) {
632 // check tiles physically above
633 TilePtr tile = g_map.getTile(upperPos);
634 if (tile &&
635 tile->limitsFloorsView(!g_map.isLookPossible(pos))) {
636 firstFloor = upperPos.z + 1;
637 break;
638 }
639
640 // check tiles geometrically above
641 tile = g_map.getTile(coveredPos);
642 if (tile && tile->limitsFloorsView(g_map.isLookPossible(pos))) {
643 firstFloor = coveredPos.z + 1;
644 break;
645 }
646 }
647 }
648 }
649 }
650 z = firstFloor;
651 }
652 }
653 }
654
655 // just ensure the that the floor is in the valid range
656 z = stdext::clamp<int>(z, 0, (int)Otc::MAX_Z);
657 return z;
658}
659
660int MapView::calcLastVisibleFloor() {
661 if (!m_multifloor)
662 return calcFirstVisibleFloor();
663
664 int z = 7;
665
666 Position cameraPosition = getCameraPosition();
667 // this could happens if the player is not known yet
668 if (cameraPosition.isValid()) {
669 // view only underground floors when below sea level
670 if (cameraPosition.z > Otc::SEA_FLOOR)
671 z = cameraPosition.z + Otc::AWARE_UNDEGROUND_FLOOR_RANGE;
672 else
673 // FIX (OTLand/OTCv8): Server sends floors 15->0 for ALL surface players.
674 // Render down to floor 15 so underground tiles are visible through
675 // empty/transparent holes in floor 7 (cave entrances, water, etc.)
676 z = (int)Otc::MAX_Z; // Render all floors when at surface
677 }
678
679 if (m_lockedFirstVisibleFloor != -1)
680 z = std::max<int>(m_lockedFirstVisibleFloor, z);
681
682 // just ensure the that the floor is in the valid range
683 z = stdext::clamp<int>(z, 0, (int)Otc::MAX_Z);
684 return z;
685}
686
687Point MapView::transformPositionTo2D(const Position &position,
688 const Position &relativePosition) {
689 return Point((m_virtualCenterOffset.x + (position.x - relativePosition.x) -
690 (relativePosition.z - position.z)) *
691 g_sprites.spriteSize(),
692 (m_virtualCenterOffset.y + (position.y - relativePosition.y) -
693 (relativePosition.z - position.z)) *
694 g_sprites.spriteSize());
695}
696
697Position MapView::getCameraPosition() {
698 if (isFollowingCreature()) {
699 return m_followingCreature->getPrewalkingPosition();
700 }
701
702 return m_customCameraPosition;
703}
704
705void MapView::setDrawLights(bool enable) { m_drawLight = enable; }
706
707void MapView::setCrosshair(const std::string &file) {
708 if (file == "")
709 m_crosshair = nullptr;
710 else
711 m_crosshair = g_textures.getTexture(file);
712}
713
714/* vim: set ts=4 sw=4 et: */
715
Top