1 /* Copyright (C) 2013-2014 Michal Brzozowski (rusolis@poczta.fm)
2
3 This file is part of KeeperRL.
4
5 KeeperRL is free software; you can redistribute it and/or modify it under the terms of the
6 GNU General Public License as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 KeeperRL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
10 even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along with this program.
14 If not, see http://www.gnu.org/licenses/ . */
15
16 #include "stdafx.h"
17
18 #include "map_gui.h"
19 #include "view_object.h"
20 #include "map_layout.h"
21 #include "view_index.h"
22 #include "tile.h"
23 #include "window_view.h"
24 #include "renderer.h"
25 #include "clock.h"
26 #include "view_id.h"
27 #include "level.h"
28 #include "creature_view.h"
29 #include "options.h"
30 #include "drag_and_drop.h"
31 #include "game_info.h"
32
33 using SDL::SDL_Keysym;
34 using SDL::SDL_Keycode;
35
MapGui(Callbacks call,Clock * c,Options * o,GuiFactory * f)36 MapGui::MapGui(Callbacks call, Clock* c, Options* o, GuiFactory* f) : objects(Level::getMaxBounds()), callbacks(call),
37 clock(c), options(o), fogOfWar(Level::getMaxBounds(), false), extraBorderPos(Level::getMaxBounds(), {}),
38 lastSquareUpdate(Level::getMaxBounds()), connectionMap(Level::getMaxBounds()), guiFactory(f) {
39 clearCenter();
40 }
41
42 static int fireVar = 50;
43
getFireColor()44 static Color getFireColor() {
45 return Color(200 + Random.get(-fireVar, fireVar), Random.get(fireVar), Random.get(fireVar), 150);
46 }
47
setButtonViewId(ViewId id)48 void MapGui::setButtonViewId(ViewId id) {
49 buttonViewId = id;
50 }
51
clearButtonViewId()52 void MapGui::clearButtonViewId() {
53 buttonViewId = none;
54 }
55
getLastHighlighted()56 const MapGui::HighlightedInfo& MapGui::getLastHighlighted() {
57 return lastHighlighted;
58 }
59
highlightTeam(const vector<UniqueEntity<Creature>::Id> & ids)60 void MapGui::highlightTeam(const vector<UniqueEntity<Creature>::Id>& ids) {
61 for (auto& id : ids)
62 ++teamHighlight.getOrInit(id);
63 }
64
unhighlightTeam(const vector<UniqueEntity<Creature>::Id> & ids)65 void MapGui::unhighlightTeam(const vector<UniqueEntity<Creature>::Id>& ids) {
66 for (auto& id : ids)
67 CHECK(--teamHighlight.getOrFail(id) >= 0);
68 }
69
getScreenPos() const70 Vec2 MapGui::getScreenPos() const {
71 return Vec2(
72 (int) (min<double>(levelBounds.right(), max(0.0, center.x - mouseOffset.x)) * layout->getSquareSize().x),
73 (int) (min<double>(levelBounds.bottom(), max(0.0, center.y - mouseOffset.y)) * layout->getSquareSize().y));
74 }
75
setSpriteMode(bool s)76 void MapGui::setSpriteMode(bool s) {
77 spriteMode = s;
78 }
79
addAnimation(PAnimation animation,Vec2 pos)80 void MapGui::addAnimation(PAnimation animation, Vec2 pos) {
81 animation->setBegin(clock->getRealMillis());
82 animations.push_back({std::move(animation), pos});
83 }
84
getMousePos()85 optional<Vec2> MapGui::getMousePos() {
86 if (lastMouseMove && lastMouseMove->inRectangle(getBounds()))
87 return lastMouseMove;
88 else
89 return none;
90 }
91
projectOnMap(Vec2 screenCoord)92 optional<Vec2> MapGui::projectOnMap(Vec2 screenCoord) {
93 if (screenCoord.inRectangle(getBounds()))
94 return layout->projectOnMap(getBounds(), getScreenPos(), screenCoord);
95 else
96 return none;
97 }
98
getHighlightedTile(Renderer & renderer)99 optional<Vec2> MapGui::getHighlightedTile(Renderer& renderer) {
100 if (auto pos = getMousePos())
101 return layout->projectOnMap(getBounds(), getScreenPos(), *pos);
102 else
103 return none;
104 }
105
getHighlightColor(const ViewIndex & index,HighlightType type)106 Color MapGui::getHighlightColor(const ViewIndex& index, HighlightType type) {
107 double amount = index.getHighlight(type);
108 switch (type) {
109 case HighlightType::RECT_DESELECTION: return Color::RED.transparency(90);
110 case HighlightType::DIG: return Color::YELLOW.transparency(120);
111 case HighlightType::FETCH_ITEMS: //return Color(Color::YELLOW).transparency(50);
112 case HighlightType::CUT_TREE: return Color::YELLOW.transparency(100);
113 case HighlightType::PERMANENT_FETCH_ITEMS: return Color::ORANGE.transparency(50);
114 case HighlightType::STORAGE_EQUIPMENT: return Color::BLUE.transparency(buttonViewId ? 120 : 50);
115 case HighlightType::STORAGE_RESOURCES: return Color::GREEN.transparency(buttonViewId ? 120 : 50);
116 case HighlightType::RECT_SELECTION: return Color::YELLOW.transparency(90);
117 case HighlightType::FOG: return Color::WHITE.transparency(120 * amount);
118 case HighlightType::POISON_GAS: return Color(0, min<Uint8>(255., amount * 500), 0, (Uint8)(amount * 140));
119 case HighlightType::MEMORY: return Color::BLACK.transparency(80);
120 case HighlightType::NIGHT: return Color::NIGHT_BLUE.transparency(amount * 160);
121 case HighlightType::EFFICIENCY: return Color(255, 0, 0, 120 * (1 - amount));
122 case HighlightType::PRIORITY_TASK: return Color(0, 255, 0, 120);
123 case HighlightType::CREATURE_DROP:
124 if (index.hasObject(ViewLayer::FLOOR) && getHighlightedFurniture() == index.getObject(ViewLayer::FLOOR).id())
125 return Color(0, 255, 0);
126 else
127 return Color(0, 255, 0, 120);
128 case HighlightType::CLICKABLE_FURNITURE: return Color(255, 255, 0, 120);
129 case HighlightType::CLICKED_FURNITURE: return Color(255, 255, 0);
130 case HighlightType::FORBIDDEN_ZONE: return Color(255, 0, 0, 120);
131 case HighlightType::UNAVAILABLE: return Color(0, 0, 0, 120);
132 }
133 }
134
getConnectionId(ViewId id)135 static ViewId getConnectionId(ViewId id) {
136 switch (id) {
137 case ViewId::BLACK_WALL:
138 case ViewId::WOOD_WALL:
139 case ViewId::CASTLE_WALL:
140 case ViewId::MUD_WALL:
141 case ViewId::MOUNTAIN:
142 case ViewId::DUNGEON_WALL:
143 case ViewId::GOLD_ORE:
144 case ViewId::IRON_ORE:
145 case ViewId::STONE:
146 case ViewId::WALL: return ViewId::WALL;
147 default: return id;
148 }
149 }
150
getConnectionSet(Vec2 tilePos,ViewId id)151 DirSet MapGui::getConnectionSet(Vec2 tilePos, ViewId id) {
152 DirSet ret;
153 int cnt = 0;
154 for (Vec2 dir : Vec2::directions8()) {
155 Vec2 pos = tilePos + dir;
156 if (pos.inRectangle(levelBounds) && connectionMap[pos].contains(getConnectionId(id)))
157 ret.insert((Dir) cnt);
158 ++cnt;
159 }
160 return ret;
161 }
162
setSoftCenter(Vec2 pos)163 void MapGui::setSoftCenter(Vec2 pos) {
164 setSoftCenter(pos.x, pos.y);
165 }
166
setSoftCenter(double x,double y)167 void MapGui::setSoftCenter(double x, double y) {
168 Coords coords {x, y};
169 if (softCenter != coords) {
170 softCenter = coords;
171 lastScrollUpdate = clock->getRealMillis();
172 }
173 }
174
softScroll(double x,double y)175 void MapGui::softScroll(double x, double y) {
176 if (!softCenter)
177 softCenter = center;
178 softCenter->x = max(0.0, min<double>(softCenter->x + x, levelBounds.right()));
179 softCenter->y = max(0.0, min<double>(softCenter->y + y, levelBounds.bottom()));
180 lastScrollUpdate = clock->getRealMillis();
181 }
182
onKeyPressed2(SDL_Keysym key)183 bool MapGui::onKeyPressed2(SDL_Keysym key) {
184 const double scrollDist = 9 * 32 / layout->getSquareSize().x;
185 if (!keyScrolling)
186 return false;
187 switch (key.sym) {
188 case SDL::SDLK_w:
189 if (!options->getBoolValue(OptionId::WASD_SCROLLING) || GuiFactory::isAlt(key))
190 break;
191 FALLTHROUGH;
192 case SDL::SDLK_UP:
193 case SDL::SDLK_KP_8:
194 softScroll(0, -scrollDist);
195 break;
196 case SDL::SDLK_KP_9:
197 softScroll(scrollDist, -scrollDist);
198 break;
199 case SDL::SDLK_d:
200 if (!options->getBoolValue(OptionId::WASD_SCROLLING) || GuiFactory::isAlt(key))
201 break;
202 FALLTHROUGH;
203 case SDL::SDLK_RIGHT:
204 case SDL::SDLK_KP_6:
205 softScroll(scrollDist, 0);
206 break;
207 case SDL::SDLK_KP_3:
208 softScroll(scrollDist, scrollDist);
209 break;
210 case SDL::SDLK_s:
211 if (!options->getBoolValue(OptionId::WASD_SCROLLING) || GuiFactory::isAlt(key))
212 break;
213 FALLTHROUGH;
214 case SDL::SDLK_DOWN:
215 case SDL::SDLK_KP_2:
216 softScroll(0, scrollDist);
217 break;
218 case SDL::SDLK_KP_1:
219 softScroll(-scrollDist, scrollDist);
220 break;
221 case SDL::SDLK_a:
222 if (!options->getBoolValue(OptionId::WASD_SCROLLING) || GuiFactory::isAlt(key))
223 break;
224 FALLTHROUGH;
225 case SDL::SDLK_LEFT:
226 case SDL::SDLK_KP_4:
227 softScroll(-scrollDist, 0);
228 break;
229 case SDL::SDLK_KP_7:
230 softScroll(-scrollDist, -scrollDist);
231 break;
232 default: break;
233 }
234 return false;
235 }
236
onLeftClick(Vec2 v)237 bool MapGui::onLeftClick(Vec2 v) {
238 if (v.inRectangle(getBounds())) {
239 mouseHeldPos = v;
240 mouseOffset.x = mouseOffset.y = 0;
241 if (auto c = getCreature(v))
242 draggedCandidate = c;
243 return true;
244 }
245 return false;
246 }
247
onRightClick(Vec2 pos)248 bool MapGui::onRightClick(Vec2 pos) {
249 if (lastRightClick && clock->getRealMillis() - *lastRightClick < milliseconds{200}) {
250 lockedView = true;
251 lastRightClick = none;
252 return true;
253 }
254 lastRightClick = clock->getRealMillis();
255 if (pos.inRectangle(getBounds())) {
256 lastMousePos = pos;
257 isScrollingNow = true;
258 mouseOffset.x = mouseOffset.y = 0;
259 lockedView = false;
260 return true;
261 }
262 return false;
263 }
264
onMouseGone()265 void MapGui::onMouseGone() {
266 lastMouseMove = none;
267 }
268
considerContinuousLeftClick(Vec2 mousePos)269 void MapGui::considerContinuousLeftClick(Vec2 mousePos) {
270 Vec2 pos = layout->projectOnMap(getBounds(), getScreenPos(), mousePos);
271 if (!lastMapLeftClick || lastMapLeftClick != pos) {
272 callbacks.continuousLeftClickFun(pos);
273 lastMapLeftClick = pos;
274 }
275 }
276
onMouseMove(Vec2 v)277 bool MapGui::onMouseMove(Vec2 v) {
278 lastMouseMove = v;
279 auto draggedCreature = getDraggedCreature();
280 if (v.inRectangle(getBounds()) && mouseHeldPos && !draggedCreature)
281 considerContinuousLeftClick(v);
282 if (!draggedCreature && draggedCandidate && mouseHeldPos && mouseHeldPos->distD(v) > 30) {
283 callbacks.creatureDragFun(draggedCandidate->id, draggedCandidate->viewId, v);
284 setDraggedCreature(draggedCandidate->id, draggedCandidate->viewId, v);
285 }
286 if (isScrollingNow) {
287 mouseOffset.x = double(v.x - lastMousePos.x) / layout->getSquareSize().x;
288 mouseOffset.y = double(v.y - lastMousePos.y) / layout->getSquareSize().y;
289 callbacks.refreshFun();
290 }
291 return false;
292 }
293
getCreature(Vec2 mousePos)294 optional<MapGui::CreatureInfo> MapGui::getCreature(Vec2 mousePos) {
295 auto info = getHighlightedInfo(layout->getSquareSize(), clock->getRealMillis());
296 if (info.creaturePos && info.object && info.object->getCreatureId())
297 return CreatureInfo {*info.object->getCreatureId(), info.object->id()};
298 else
299 return none;
300 }
301
onMouseRelease(Vec2 v)302 void MapGui::onMouseRelease(Vec2 v) {
303 if (isScrollingNow) {
304 if (fabs(mouseOffset.x) + fabs(mouseOffset.y) < 1)
305 callbacks.rightClickFun(layout->projectOnMap(getBounds(), getScreenPos(), lastMousePos));
306 else {
307 center.x = min<double>(levelBounds.right(), max(0.0, center.x - mouseOffset.x));
308 center.y = min<double>(levelBounds.bottom(), max(0.0, center.y - mouseOffset.y));
309 }
310 isScrollingNow = false;
311 callbacks.refreshFun();
312 mouseOffset.x = mouseOffset.y = 0;
313 }
314 auto draggedCreature = getDraggedCreature();
315 if (auto& draggedElem = guiFactory->getDragContainer().getElement())
316 if (v.inRectangle(getBounds()) && guiFactory->getDragContainer().getOrigin().distD(v) > 10) {
317 switch (draggedElem->getId()) {
318 case DragContentId::CREATURE:
319 callbacks.creatureDroppedFun(draggedElem->get<UniqueEntity<Creature>::Id>(),
320 layout->projectOnMap(getBounds(), getScreenPos(), v));
321 break;
322 case DragContentId::TEAM:
323 callbacks.teamDroppedFun(draggedElem->get<TeamId>(),
324 layout->projectOnMap(getBounds(), getScreenPos(), v));
325 break;
326 default:
327 break;
328 }
329 }
330 if (mouseHeldPos) {
331 if (mouseHeldPos->distD(v) > 10) {
332 if (!draggedCreature)
333 considerContinuousLeftClick(v);
334 } else {
335 if (auto c = getCreature(*mouseHeldPos))
336 callbacks.creatureClickFun(c->id);
337 else {
338 callbacks.leftClickFun(layout->projectOnMap(getBounds(), getScreenPos(), v));
339 considerContinuousLeftClick(v);
340 }
341 }
342 }
343 mouseHeldPos = none;
344 lastMapLeftClick = none;
345 draggedCandidate = none;
346 }
347
348 /*void MapGui::drawFloorBorders(Renderer& renderer, DirSet borders, int x, int y) {
349 for (const Dir& dir : borders) {
350 int coord;
351 switch (dir) {
352 case Dir::N: coord = 0; break;
353 case Dir::E: coord = 1; break;
354 case Dir::S: coord = 2; break;
355 case Dir::W: coord = 3; break;
356 default: continue;
357 }
358 renderer.drawTile(x, y, {Vec2(coord, 18), 1});
359 }
360 }*/
361
getMoraleColor(double morale)362 static Color getMoraleColor(double morale) {
363 if (morale < 0)
364 return Color(255, 0, 0, -morale * 150);
365 else
366 return Color(0, 255, 0, morale * 150);
367 }
368
getAttachmentOffset(Dir dir,Vec2 size)369 static Vec2 getAttachmentOffset(Dir dir, Vec2 size) {
370 switch (dir) {
371 case Dir::N: return Vec2(0, -size.y * 2 / 3);
372 case Dir::S: return Vec2(0, size.y / 4);
373 case Dir::E:
374 case Dir::W: return Vec2(dir) * size.x / 2;
375 default: FATAL << "Bad attachment dir " << int(dir);
376 }
377 return Vec2();
378 }
379
getJumpOffset(const ViewObject & object,double state)380 static double getJumpOffset(const ViewObject& object, double state) {
381 if (object.hasModifier(ViewObjectModifier::NO_UP_MOVEMENT))
382 return 0;
383 if (state > 0.5)
384 state -= 0.5;
385 state *= 2;
386 const double maxH = 0.09;
387 return maxH * (1.0 - (2.0 * state - 1) * (2.0 * state - 1));
388 }
389
getMovementOffset(const ViewObject & object,Vec2 size,double time,milliseconds curTimeReal,bool verticalMovement)390 Vec2 MapGui::getMovementOffset(const ViewObject& object, Vec2 size, double time, milliseconds curTimeReal,
391 bool verticalMovement) {
392 if (auto dir = object.getAttachmentDir())
393 return getAttachmentOffset(*dir, size);
394 if (!object.hasAnyMovementInfo())
395 return Vec2(0, 0);
396 double state;
397 Vec2 dir;
398 if (screenMovement &&
399 curTimeReal >= screenMovement->startTimeReal &&
400 curTimeReal <= screenMovement->endTimeReal) {
401 state = (double) (curTimeReal - screenMovement->startTimeReal).count() /
402 (double) (screenMovement->endTimeReal - screenMovement->startTimeReal).count();
403 dir = object.getMovementInfo(screenMovement->startTimeGame);
404 }
405 else if (!screenMovement) {
406 MovementInfo info = object.getLastMovementInfo();
407 dir = info.direction;
408 /* if (info.direction.length8() == 0 || time >= info.tEnd + 0.001 || time <= info.tBegin - 0.001)
409 return Vec2(0, 0);*/
410 state = (time - info.tBegin) / (info.tEnd - info.tBegin);
411 double minStopTime = 0.2;
412 state = min(1.0, max(0.0, (state - minStopTime) / (1.0 - 2 * minStopTime)));
413 } else
414 return Vec2(0, 0);
415 double vertical = verticalMovement ? getJumpOffset(object, state) : 0;
416 if (object.getLastMovementInfo().type == MovementInfo::ATTACK)
417 if (dir.length8() == 1) {
418 if (verticalMovement)
419 return Vec2(0.8 * (state < 0.5 ? state : 1 - state) * dir.x * size.x,
420 (0.8 * (state < 0.5 ? state : 1 - state)* dir.y - vertical) * size.y);
421 else
422 return Vec2(0, 0);
423 }
424 return Vec2((state - 1) * dir.x * size.x, ((state - 1)* dir.y - vertical) * size.y);
425 }
426
drawCreatureHighlights(Renderer & renderer,const ViewObject & object,Vec2 pos,Vec2 sz,milliseconds curTime)427 void MapGui::drawCreatureHighlights(Renderer& renderer, const ViewObject& object, Vec2 pos, Vec2 sz,
428 milliseconds curTime) {
429 auto getHighlight = [](Color id) { return Color(id).transparency(200); };
430 if (object.hasModifier(ViewObject::Modifier::HOSTILE) && highlightEnemies)
431 drawCreatureHighlight(renderer, pos, sz, getHighlight(Color::PURPLE));
432 if (object.hasModifier(ViewObject::Modifier::DRAW_MORALE) && highlightMorale)
433 if (auto morale = object.getAttribute(ViewObject::Attribute::MORALE))
434 drawCreatureHighlight(renderer, pos, sz, getMoraleColor(*morale));
435 if (object.hasModifier(ViewObject::Modifier::PLAYER)) {
436 if ((curTime.count() / 500) % 2 == 0)
437 drawCreatureHighlight(renderer, pos, sz, getHighlight(Color::YELLOW));
438 } else
439 if (object.hasModifier(ViewObject::Modifier::TEAM_HIGHLIGHT))
440 drawCreatureHighlight(renderer, pos, sz, getHighlight(Color::YELLOW));
441 if (auto id = object.getCreatureId())
442 if (isCreatureHighlighted(*id))
443 drawCreatureHighlight(renderer, pos, sz, getHighlight(Color::YELLOW));
444 }
445
isCreatureHighlighted(UniqueEntity<Creature>::Id creature)446 bool MapGui::isCreatureHighlighted(UniqueEntity<Creature>::Id creature) {
447 return teamHighlight.getMaybe(creature).value_or(0) > 0;
448 }
449
mirrorSprite(ViewId id)450 static bool mirrorSprite(ViewId id) {
451 switch (id) {
452 case ViewId::GRASS:
453 case ViewId::HILL:
454 return true;
455 default:
456 return false;
457 }
458 }
459
drawHealthBar(Renderer & renderer,Vec2 pos,Vec2 size,double health)460 void MapGui::drawHealthBar(Renderer& renderer, Vec2 pos, Vec2 size, double health) {
461 if (hideFullHealthBars && health == 1)
462 return;
463 pos.y -= size.y * 0.2;
464 double barWidth = 0.12;
465 double barLength = 0.8;
466 auto getBar = [&](double state) {
467 return Rectangle((int) (pos.x + size.x * (1 - barLength) / 2), pos.y,
468 (int) (pos.x + size.x * state * (1 + barLength) / 2), (int) (pos.y + size.y * barWidth));
469 };
470 auto color = Color::f(min(1.0, 2 - health * 2), min(1.0, 2 * health), 0);
471 auto fullRect = getBar(1);
472 renderer.drawFilledRectangle(fullRect.minusMargin(-1), Color::TRANSPARENT, Color::BLACK.transparency(100));
473 renderer.drawFilledRectangle(fullRect, color.transparency(100));
474 if (health > 0)
475 renderer.drawFilledRectangle(getBar(health), color.transparency(200));
476 Rectangle shadowRect(fullRect.bottomLeft() - Vec2(0, 1), fullRect.bottomRight());
477 renderer.drawFilledRectangle(shadowRect, Color::BLACK.transparency(100));
478 }
479
480
481
drawObjectAbs(Renderer & renderer,Vec2 pos,const ViewObject & object,Vec2 size,Vec2 movement,Vec2 tilePos,milliseconds curTimeReal)482 void MapGui::drawObjectAbs(Renderer& renderer, Vec2 pos, const ViewObject& object, Vec2 size, Vec2 movement,
483 Vec2 tilePos, milliseconds curTimeReal) {
484 auto id = object.id();
485 const Tile& tile = Tile::getTile(id, spriteMode);
486 Color color = colorWoundedRed ? Renderer::getBleedingColor(object) : Color::WHITE;
487 if (object.hasModifier(ViewObject::Modifier::INVISIBLE) || object.hasModifier(ViewObject::Modifier::HIDDEN))
488 color = color.transparency(70);
489 else
490 if (tile.translucent > 0)
491 color = color.transparency(255 * (1 - tile.translucent));
492 else if (object.hasModifier(ViewObject::Modifier::ILLUSION))
493 color = color.transparency(150);
494 if (object.hasModifier(ViewObject::Modifier::PLANNED))
495 color = color.transparency(100);
496 if (auto waterDepth = object.getAttribute(ViewObject::Attribute::WATER_DEPTH))
497 if (*waterDepth > 0) {
498 Uint8 val = max(0.0, 255.0 - min(2.0f, *waterDepth) * 60);
499 color = Color(val, val, val);
500 }
501 if (spriteMode && tile.hasSpriteCoord()) {
502 DirSet dirs;
503 if (tile.hasAnyConnections() || tile.hasAnyCorners())
504 dirs = getConnectionSet(tilePos, id);
505 Vec2 move;
506 drawCreatureHighlights(renderer, object, pos + movement, size, curTimeReal);
507 if (object.layer() == ViewLayer::CREATURE || tile.roundShadow) {
508 static auto coord = renderer.getTileCoord("round_shadow");
509 renderer.drawTile(pos + movement, coord, size, Color(255, 255, 255, 160));
510 move.y = -4* size.y / renderer.getNominalSize().y;
511 }
512 if (auto background = tile.getBackgroundCoord())
513 renderer.drawTile(pos, *background, size, color);
514 move += movement;
515 if (mirrorSprite(id))
516 renderer.drawTile(pos + move, tile.getSpriteCoord(dirs), size, color,
517 Renderer::SpriteOrientation((bool) (tilePos.getHash() % 2), (bool) (tilePos.getHash() % 4 > 1)));
518 else
519 renderer.drawTile(pos + move, tile.getSpriteCoord(dirs), size, color);
520 if (tile.hasAnyCorners()) {
521 for (auto coord : tile.getCornerCoords(dirs))
522 renderer.drawTile(pos + move, coord, size, color);
523 }
524 /* if (tile.floorBorders) {
525 drawFloorBorders(renderer, borderDirs, x, y);
526 }*/
527 static auto shortShadow = renderer.getTileCoord("short_shadow");
528 if (object.layer() == ViewLayer::FLOOR_BACKGROUND && shadowed.count(tilePos))
529 renderer.drawTile(pos, shortShadow, size, Color(255, 255, 255, 170));
530 if (auto burningVal = object.getAttribute(ViewObject::Attribute::BURNING))
531 if (*burningVal > 0) {
532 static auto fire1 = renderer.getTileCoord("fire1");
533 static auto fire2 = renderer.getTileCoord("fire2");
534 renderer.drawTile(pos, (curTimeReal.count() + pos.getHash()) % 500 < 250 ? fire1 : fire2, size);
535 }
536 if (displayAllHealthBars || lastHighlighted.creaturePos == pos + movement)
537 if (auto wounded = object.getAttribute(ViewObject::Attribute::WOUNDED))
538 drawHealthBar(renderer, pos + move, size, 1 - *wounded);
539 } else {
540 Vec2 movement = getMovementOffset(object, size, currentTimeGame, curTimeReal, true);
541 Vec2 tilePos = pos + movement + Vec2(size.x / 2, -3);
542 drawCreatureHighlights(renderer, object, pos, size, curTimeReal);
543 renderer.drawText(tile.symFont ? Renderer::SYMBOL_FONT : Renderer::TILE_FONT, size.y, Tile::getColor(object),
544 tilePos.x, tilePos.y, tile.text, Renderer::HOR);
545 if (auto burningVal = object.getAttribute(ViewObject::Attribute::BURNING))
546 if (*burningVal > 0) {
547 renderer.drawText(Renderer::SYMBOL_FONT, size.y, getFireColor(), pos.x + size.x / 2, pos.y - 3, u8"ѡ",
548 Renderer::HOR);
549 if (*burningVal > 0.5)
550 renderer.drawText(Renderer::SYMBOL_FONT, size.y, getFireColor(), pos.x + size.x / 2, pos.y - 3, u8"Ѡ",
551 Renderer::HOR);
552 }
553 }
554 }
555
resetScrolling()556 void MapGui::resetScrolling() {
557 lockedView = true;
558 }
559
clearCenter()560 void MapGui::clearCenter() {
561 center = mouseOffset = {0.0, 0.0};
562 softCenter = none;
563 screenMovement = none;
564 }
565
isCentered() const566 bool MapGui::isCentered() const {
567 return center.x != 0 || center.y != 0;
568 }
569
setCenter(double x,double y)570 void MapGui::setCenter(double x, double y) {
571 center = {x, y};
572 center.x = max(0.0, min<double>(center.x, levelBounds.right()));
573 center.y = max(0.0, min<double>(center.y, levelBounds.bottom()));
574 softCenter = none;
575 }
576
setCenter(Vec2 v)577 void MapGui::setCenter(Vec2 v) {
578 setCenter(v.x, v.y);
579 }
580
drawFoWSprite(Renderer & renderer,Vec2 pos,Vec2 size,DirSet dirs)581 void MapGui::drawFoWSprite(Renderer& renderer, Vec2 pos, Vec2 size, DirSet dirs) {
582 const Tile& tile = Tile::getTile(ViewId::FOG_OF_WAR, true);
583 const Tile& tile2 = Tile::getTile(ViewId::FOG_OF_WAR_CORNER, true);
584 static DirSet fourDirs = DirSet({Dir::N, Dir::S, Dir::E, Dir::W});
585 auto coord = tile.getSpriteCoord(dirs & fourDirs);
586 renderer.drawTile(pos, coord, size);
587 for (Dir dir : dirs.intersection(fourDirs.complement())) {
588 static DirSet ne({Dir::N, Dir::E});
589 static DirSet se({Dir::S, Dir::E});
590 static DirSet nw({Dir::N, Dir::W});
591 static DirSet sw({Dir::S, Dir::W});
592 switch (dir) {
593 case Dir::NE: if (!dirs.contains(ne)) continue;
594 FALLTHROUGH;
595 case Dir::SE: if (!dirs.contains(se)) continue;
596 FALLTHROUGH;
597 case Dir::NW: if (!dirs.contains(nw)) continue;
598 FALLTHROUGH;
599 case Dir::SW: if (!dirs.contains(sw)) continue;
600 FALLTHROUGH;
601 default: break;
602 }
603 renderer.drawTile(pos, tile2.getSpriteCoord(DirSet::oneElement(dir)), size);
604 }
605 }
606
isFoW(Vec2 pos) const607 bool MapGui::isFoW(Vec2 pos) const {
608 return !pos.inRectangle(Level::getMaxBounds()) || fogOfWar.getValue(pos);
609 }
610
renderExtraBorders(Renderer & renderer,milliseconds currentTimeReal)611 void MapGui::renderExtraBorders(Renderer& renderer, milliseconds currentTimeReal) {
612 extraBorderPos.clear();
613 for (Vec2 wpos : layout->getAllTiles(getBounds(), levelBounds, getScreenPos()))
614 if (objects[wpos] && objects[wpos]->hasObject(ViewLayer::FLOOR_BACKGROUND)) {
615 ViewId viewId = objects[wpos]->getObject(ViewLayer::FLOOR_BACKGROUND).id();
616 if (Tile::getTile(viewId, true).hasExtraBorders())
617 for (Vec2 v : wpos.neighbors4())
618 if (v.inRectangle(extraBorderPos.getBounds())) {
619 if (extraBorderPos.isDirty(v))
620 extraBorderPos.getDirtyValue(v).push_back(viewId);
621 else
622 extraBorderPos.setValue(v, {viewId});
623 }
624 }
625 for (Vec2 wpos : layout->getAllTiles(getBounds(), levelBounds, getScreenPos()))
626 for (ViewId id : extraBorderPos.getValue(wpos)) {
627 const Tile& tile = Tile::getTile(id, true);
628 for (ViewId underId : tile.getExtraBorderIds())
629 if (connectionMap[wpos].contains(underId)) {
630 DirSet dirs = 0;
631 for (Vec2 v : Vec2::directions4())
632 if ((wpos + v).inRectangle(levelBounds) && connectionMap[wpos + v].contains(id))
633 dirs.insert(v.getCardinalDir());
634 if (auto coord = tile.getExtraBorderCoord(dirs)) {
635 Vec2 pos = projectOnScreen(wpos);
636 renderer.drawTile(pos, *coord, layout->getSquareSize());
637 }
638 }
639 }
640 }
641
projectOnScreen(Vec2 wpos)642 Vec2 MapGui::projectOnScreen(Vec2 wpos) {
643 double x = wpos.x;
644 double y = wpos.y;
645 /*if (screenMovement) {
646 if (curTime >= screenMovement->startTimeReal && curTime <= screenMovement->endTimeReal) {
647 double state = (double)(curTime - screenMovement->startTimeReal).count() /
648 (double) (screenMovement->endTimeReal - screenMovement->startTimeReal).count();
649 x += (1 - state) * (screenMovement->to.x - screenMovement->from.x);
650 y += (1 - state) * (screenMovement->to.y - screenMovement->from.y);
651 }
652 }*/
653 return layout->projectOnScreen(getBounds(), getScreenPos(), x, y);
654 }
655
getHighlightedFurniture()656 optional<ViewId> MapGui::getHighlightedFurniture() {
657 if (auto mousePos = getMousePos()) {
658 Vec2 curPos = layout->projectOnMap(getBounds(), getScreenPos(), *mousePos);
659 if (curPos.inRectangle(objects.getBounds()) &&
660 objects[curPos] &&
661 objects[curPos]->hasObject(ViewLayer::FLOOR) &&
662 (objects[curPos]->getHighlight(HighlightType::CLICKABLE_FURNITURE) > 0 ||
663 (objects[curPos]->getHighlight(HighlightType::CREATURE_DROP) > 0 && !!getDraggedCreature())))
664 return objects[curPos]->getObject(ViewLayer::FLOOR).id();
665 }
666 return none;
667 }
668
isRenderedHighlight(const ViewIndex & index,HighlightType type)669 bool MapGui::isRenderedHighlight(const ViewIndex& index, HighlightType type) {
670 if (index.getHighlight(type) > 0)
671 switch (type) {
672 case HighlightType::CLICKABLE_FURNITURE:
673 return
674 index.hasObject(ViewLayer::FLOOR) &&
675 getHighlightedFurniture() == index.getObject(ViewLayer::FLOOR).id() &&
676 !getDraggedCreature() &&
677 !buttonViewId;
678 case HighlightType::CREATURE_DROP:
679 return !!getDraggedCreature();
680 default: return true;
681 }
682 else
683 return false;
684 }
685
isRenderedHighlightLow(const ViewIndex & index,HighlightType type)686 bool MapGui::isRenderedHighlightLow(const ViewIndex& index, HighlightType type) {
687 switch (type) {
688 case HighlightType::PRIORITY_TASK:
689 return index.getHighlight(HighlightType::DIG) == 0;
690 case HighlightType::CLICKABLE_FURNITURE:
691 case HighlightType::CREATURE_DROP:
692 case HighlightType::FORBIDDEN_ZONE:
693 case HighlightType::FETCH_ITEMS:
694 case HighlightType::PERMANENT_FETCH_ITEMS:
695 case HighlightType::STORAGE_EQUIPMENT:
696 case HighlightType::STORAGE_RESOURCES:
697 case HighlightType::CLICKED_FURNITURE:
698 case HighlightType::CUT_TREE:
699 return true;
700 default: return false;
701 }
702 }
703
renderTexturedHighlight(Renderer & renderer,Vec2 pos,Vec2 size,Color color)704 void MapGui::renderTexturedHighlight(Renderer& renderer, Vec2 pos, Vec2 size, Color color) {
705 if (spriteMode)
706 renderer.drawTile(pos, Tile::getTile(ViewId::DIG_MARK, true).getSpriteCoord(), size, color);
707 else
708 renderer.addQuad(Rectangle(pos, pos + size), color);
709 }
710
renderHighlight(Renderer & renderer,Vec2 pos,Vec2 size,const ViewIndex & index,HighlightType highlight)711 void MapGui::renderHighlight(Renderer& renderer, Vec2 pos, Vec2 size, const ViewIndex& index, HighlightType highlight) {
712 auto color = getHighlightColor(index, highlight);
713 switch (highlight) {
714 case HighlightType::MEMORY:
715 case HighlightType::POISON_GAS:
716 case HighlightType::NIGHT:
717 renderer.addQuad(Rectangle(pos, pos + size), color);
718 break;
719 /* case HighlightType::CUT_TREE:
720 if (spriteMode && index.hasObject(ViewLayer::FLOOR))
721 break;
722 FALLTHROUGH;*/
723 default:
724 renderTexturedHighlight(renderer, pos, size, color);
725 break;
726 }
727 }
728
renderHighlights(Renderer & renderer,Vec2 size,milliseconds currentTimeReal,bool lowHighlights)729 void MapGui::renderHighlights(Renderer& renderer, Vec2 size, milliseconds currentTimeReal, bool lowHighlights) {
730 Rectangle allTiles = layout->getAllTiles(getBounds(), levelBounds, getScreenPos());
731 Vec2 topLeftCorner = projectOnScreen(allTiles.topLeft());
732 for (Vec2 wpos : allTiles)
733 if (auto& index = objects[wpos])
734 if (index->hasAnyHighlight()) {
735 Vec2 pos = topLeftCorner + (wpos - allTiles.topLeft()).mult(size);
736 for (HighlightType highlight : ENUM_ALL(HighlightType))
737 if (isRenderedHighlight(*index, highlight) && isRenderedHighlightLow(*index, highlight) == lowHighlights)
738 renderHighlight(renderer, pos, size, *index, highlight);
739 }
740 for (Vec2 wpos : lowHighlights ? tutorialHighlightLow : tutorialHighlightHigh) {
741 Vec2 pos = topLeftCorner + (wpos - allTiles.topLeft()).mult(size);
742 if ((currentTimeReal.count() / 1000) % 2 == 0)
743 renderTexturedHighlight(renderer, pos, size, Color(255, 255, 0, lowHighlights ? 120 : 40));
744 }
745 }
746
renderAnimations(Renderer & renderer,milliseconds currentTimeReal)747 void MapGui::renderAnimations(Renderer& renderer, milliseconds currentTimeReal) {
748 animations = std::move(animations).filter([=](const AnimationInfo& elem)
749 { return !elem.animation->isDone(currentTimeReal);});
750 for (auto& elem : animations)
751 elem.animation->render(
752 renderer,
753 getBounds(),
754 projectOnScreen(elem.position),
755 currentTimeReal);
756 }
757
getHighlightedInfo(Vec2 size,milliseconds currentTimeReal)758 MapGui::HighlightedInfo MapGui::getHighlightedInfo(Vec2 size, milliseconds currentTimeReal) {
759 HighlightedInfo ret {};
760 Rectangle allTiles = layout->getAllTiles(getBounds(), levelBounds, getScreenPos());
761 Vec2 topLeftCorner = projectOnScreen(allTiles.topLeft());
762 if (auto mousePos = getMousePos())
763 if (mouseUI) {
764 ret.tilePos = layout->projectOnMap(getBounds(), getScreenPos(), *mousePos);
765 if (!buttonViewId && ret.tilePos->inRectangle(objects.getBounds()))
766 for (Vec2 wpos : Rectangle(*ret.tilePos - Vec2(2, 2), *ret.tilePos + Vec2(2, 2))
767 .intersection(objects.getBounds())) {
768 Vec2 pos = topLeftCorner + (wpos - allTiles.topLeft()).mult(size);
769 if (objects[wpos] && objects[wpos]->hasObject(ViewLayer::CREATURE)) {
770 const ViewObject& object = objects[wpos]->getObject(ViewLayer::CREATURE);
771 Vec2 movement = getMovementOffset(object, size, currentTimeGame, currentTimeReal, true);
772 if (mousePos->inRectangle(Rectangle(pos + movement, pos + movement + size))) {
773 ret.tilePos = wpos;
774 ret.object = object;
775 ret.creaturePos = pos + movement;
776 break;
777 }
778 }
779 }
780 }
781 return ret;
782 }
783
renderMapObjects(Renderer & renderer,Vec2 size,milliseconds currentTimeReal)784 void MapGui::renderMapObjects(Renderer& renderer, Vec2 size, milliseconds currentTimeReal) {
785 Rectangle allTiles = layout->getAllTiles(getBounds(), levelBounds, getScreenPos());
786 Vec2 topLeftCorner = projectOnScreen(allTiles.topLeft());
787 fogOfWar.clear();
788 for (ViewLayer layer : layout->getLayers()) {
789 for (Vec2 wpos : allTiles) {
790 Vec2 pos = topLeftCorner + (wpos - allTiles.topLeft()).mult(size);
791 if (!objects[wpos] || objects[wpos]->noObjects()) {
792 if (layer == layout->getLayers().back()) {
793 if (wpos.inRectangle(levelBounds))
794 renderer.addQuad(Rectangle(pos, pos + size), Color::BLACK);
795 }
796 fogOfWar.setValue(wpos, true);
797 continue;
798 }
799 const ViewIndex& index = *objects[wpos];
800 const ViewObject* object = nullptr;
801 if (spriteMode) {
802 if (index.hasObject(layer))
803 object = &index.getObject(layer);
804 } else
805 object = index.getTopObject(layout->getLayers());
806 if (object) {
807 Vec2 movement = getMovementOffset(*object, size, currentTimeGame, currentTimeReal, true);
808 drawObjectAbs(renderer, pos, *object, size, movement, wpos, currentTimeReal);
809 if (lastHighlighted.tilePos == wpos && !lastHighlighted.creaturePos && object->layer() != ViewLayer::CREATURE)
810 lastHighlighted.object = *object;
811 }
812 if (spriteMode && layer == layout->getLayers().back())
813 if (!isFoW(wpos))
814 drawFoWSprite(renderer, pos, size, DirSet(
815 !isFoW(wpos + Vec2(Dir::N)),
816 !isFoW(wpos + Vec2(Dir::S)),
817 !isFoW(wpos + Vec2(Dir::E)),
818 !isFoW(wpos + Vec2(Dir::W)),
819 isFoW(wpos + Vec2(Dir::NE)),
820 isFoW(wpos + Vec2(Dir::NW)),
821 isFoW(wpos + Vec2(Dir::SE)),
822 isFoW(wpos + Vec2(Dir::SW))));
823 }
824 if (layer == ViewLayer::FLOOR || !spriteMode) {
825 if (!buttonViewId && lastHighlighted.creaturePos)
826 drawCreatureHighlight(renderer, *lastHighlighted.creaturePos, size, Color::ALMOST_WHITE);
827 else if (lastHighlighted.tilePos && (!getHighlightedFurniture() || !!buttonViewId))
828 drawSquareHighlight(renderer, topLeftCorner + (*lastHighlighted.tilePos - allTiles.topLeft()).mult(size),
829 size);
830 }
831 if (layer == ViewLayer::FLOOR_BACKGROUND)
832 renderHighlights(renderer, size, currentTimeReal, true);
833 if (!spriteMode)
834 break;
835 if (layer == ViewLayer::FLOOR_BACKGROUND)
836 renderExtraBorders(renderer, currentTimeReal);
837 }
838 renderHighlights(renderer, size, currentTimeReal, false);
839 }
840
drawCreatureHighlight(Renderer & renderer,Vec2 pos,Vec2 size,Color color)841 void MapGui::drawCreatureHighlight(Renderer& renderer, Vec2 pos, Vec2 size, Color color) {
842 if (spriteMode)
843 renderer.drawViewObject(pos + Vec2(0, size.y / 5), ViewId::CREATURE_HIGHLIGHT, true, size, color);
844 else
845 renderer.drawFilledRectangle(Rectangle(pos, pos + size), Color::TRANSPARENT, color);
846 }
847
drawSquareHighlight(Renderer & renderer,Vec2 pos,Vec2 size)848 void MapGui::drawSquareHighlight(Renderer& renderer, Vec2 pos, Vec2 size) {
849 if (spriteMode)
850 renderer.drawViewObject(pos, ViewId::SQUARE_HIGHLIGHT, true, size, Color::ALMOST_WHITE);
851 else
852 renderer.drawFilledRectangle(Rectangle(pos, pos + size), Color::TRANSPARENT, Color::LIGHT_GRAY);
853 }
854
considerRedrawingSquareHighlight(Renderer & renderer,milliseconds currentTimeReal,Vec2 pos,Vec2 size)855 void MapGui::considerRedrawingSquareHighlight(Renderer& renderer, milliseconds currentTimeReal, Vec2 pos, Vec2 size) {
856 Rectangle allTiles = layout->getAllTiles(getBounds(), levelBounds, getScreenPos());
857 Vec2 topLeftCorner = projectOnScreen(allTiles.topLeft());
858 for (Vec2 v : concat({pos}, pos.neighbors8()))
859 if (v.inRectangle(objects.getBounds()) && (!objects[v] || objects[v]->noObjects())) {
860 drawSquareHighlight(renderer, topLeftCorner + (pos - allTiles.topLeft()).mult(size), size);
861 break;
862 }
863 }
864
processScrolling(milliseconds time)865 void MapGui::processScrolling(milliseconds time) {
866 if (!!softCenter && !!lastScrollUpdate) {
867 double offsetx = softCenter->x - center.x;
868 double offsety = softCenter->y - center.y;
869 double offset = sqrt(offsetx * offsetx + offsety * offsety);
870 if (offset < 0.1)
871 softCenter = none;
872 else {
873 double timeDiff = (time - *lastScrollUpdate).count();
874 double moveDist = min(offset, max(offset, 4.0) * 10 * timeDiff / 1000);
875 offsetx /= offset;
876 offsety /= offset;
877 center.x += offsetx * moveDist;
878 center.y += offsety * moveDist;
879 }
880 lastScrollUpdate = time;
881 }
882 }
883
getDraggedCreature() const884 optional<UniqueEntity<Creature>::Id> MapGui::getDraggedCreature() const {
885 if (auto draggedContent = guiFactory->getDragContainer().getElement())
886 switch (draggedContent->getId()) {
887 case DragContentId::CREATURE:
888 return draggedContent->get<UniqueEntity<Creature>::Id>();
889 default:
890 break;
891 }
892 return none;
893 }
894
setDraggedCreature(UniqueEntity<Creature>::Id id,ViewId viewId,Vec2 origin)895 void MapGui::setDraggedCreature(UniqueEntity<Creature>::Id id, ViewId viewId, Vec2 origin) {
896 guiFactory->getDragContainer().put({DragContentId::CREATURE, id}, guiFactory->viewObject(viewId), origin);
897 }
898
considerScrollingToCreature()899 void MapGui::considerScrollingToCreature() {
900 if (auto& info = centeredCreaturePosition) {
901 Vec2 size = layout->getSquareSize();
902 Vec2 offset;
903 if (auto index = objects[info->pos])
904 if (index->hasObject(ViewLayer::CREATURE))
905 offset = getMovementOffset(index->getObject(ViewLayer::CREATURE), size, 0, clock->getRealMillis(), false);
906 double targetx = info->pos.x + (double)offset.x / size.x;
907 double targety = info->pos.y + (double)offset.y / size.y;
908 if (info->softScroll)
909 setSoftCenter(targetx, targety);
910 else
911 setCenter(targetx, targety);
912 // soft scrolling is done once when the creature is first controlled, so if we are centered then turn it off
913 if (fabs(center.x - targetx) + fabs(center.y - targety) < 0.01)
914 info->softScroll = false;
915 }
916 }
917
render(Renderer & renderer)918 void MapGui::render(Renderer& renderer) {
919 considerScrollingToCreature();
920 Vec2 size = layout->getSquareSize();
921 auto currentTimeReal = clock->getRealMillis();
922 lastHighlighted = getHighlightedInfo(size, currentTimeReal);
923 renderMapObjects(renderer, size, currentTimeReal);
924 renderAnimations(renderer, currentTimeReal);
925 if (lastHighlighted.tilePos)
926 considerRedrawingSquareHighlight(renderer, currentTimeReal, *lastHighlighted.tilePos, size);
927 if (spriteMode && buttonViewId && renderer.getMousePos().inRectangle(getBounds()))
928 renderer.drawViewObject(renderer.getMousePos() + Vec2(15, 15), *buttonViewId, spriteMode, size);
929 processScrolling(currentTimeReal);
930 }
931
updateObject(Vec2 pos,CreatureView * view,milliseconds currentTime)932 void MapGui::updateObject(Vec2 pos, CreatureView* view, milliseconds currentTime) {
933 WLevel level = view->getLevel();
934 objects[pos].emplace();
935 auto& index = *objects[pos];
936 view->getViewIndex(pos, index);
937 level->setNeedsRenderUpdate(pos, false);
938 if (index.hasObject(ViewLayer::FLOOR) || index.hasObject(ViewLayer::FLOOR_BACKGROUND))
939 index.setHighlight(HighlightType::NIGHT, 1.0 - view->getLevel()->getLight(pos));
940 lastSquareUpdate[pos] = currentTime;
941 connectionMap[pos].clear();
942 shadowed.erase(pos + Vec2(0, 1));
943 if (index.hasObject(ViewLayer::FLOOR)) {
944 auto& object = index.getObject(ViewLayer::FLOOR);
945 auto& tile = Tile::getTile(object.id());
946 if (tile.wallShadow) {
947 shadowed.insert(pos + Vec2(0, 1));
948 }
949 if (tile.hasAnyConnections() || tile.hasExtraBorders() || tile.hasAnyCorners())
950 connectionMap[pos].insert(getConnectionId(object.id()));
951 }
952 if (index.hasObject(ViewLayer::FLOOR_BACKGROUND)) {
953 auto& object = index.getObject(ViewLayer::FLOOR_BACKGROUND);
954 auto& tile = Tile::getTile(object.id());
955 if (tile.hasAnyConnections() || tile.hasExtraBorders() || tile.hasAnyCorners())
956 connectionMap[pos].insert(getConnectionId(object.id()));
957 }
958 if (auto viewId = index.getHiddenId()) {
959 auto& tile = Tile::getTile(*viewId);
960 if (tile.hasAnyConnections() || tile.hasExtraBorders() || tile.hasAnyCorners())
961 connectionMap[pos].insert(getConnectionId(*viewId));
962 }
963 }
964
getDistanceToEdgeRatio(Vec2 pos)965 double MapGui::getDistanceToEdgeRatio(Vec2 pos) {
966 Vec2 v = projectOnScreen(pos);
967 double ret = 100000;
968 auto bounds = getBounds();
969 ret = min(ret, fabs((double) v.x - bounds.left()) / bounds.width());
970 ret = min(ret, fabs((double) v.x - bounds.right()) / bounds.width());
971 ret = min(ret, fabs((double) v.y - bounds.top()) / bounds.height());
972 ret = min(ret, fabs((double) v.y - bounds.bottom()) / bounds.height());
973 if (!v.inRectangle(bounds))
974 ret = -ret;
975 return ret;
976 }
977
updateObjects(CreatureView * view,MapLayout * mapLayout,bool smoothMovement,bool ui,const optional<TutorialInfo> & tutorial)978 void MapGui::updateObjects(CreatureView* view, MapLayout* mapLayout, bool smoothMovement, bool ui,
979 const optional<TutorialInfo>& tutorial) {
980 if (tutorial) {
981 tutorialHighlightLow = tutorial->highlightedSquaresLow;
982 tutorialHighlightHigh = tutorial->highlightedSquaresHigh;
983 } else {
984 tutorialHighlightLow.clear();
985 tutorialHighlightHigh.clear();
986 }
987 WLevel level = view->getLevel();
988 levelBounds = view->getLevel()->getBounds();
989 mouseUI = ui;
990 layout = mapLayout;
991 auto currentTimeReal = clock->getRealMillis();
992 if (view != previousView || level != previousLevel)
993 for (Vec2 pos : level->getBounds())
994 level->setNeedsRenderUpdate(pos, true);
995 else
996 for (Vec2 pos : mapLayout->getAllTiles(getBounds(), Level::getMaxBounds(), getScreenPos()))
997 if (level->needsRenderUpdate(pos) || lastSquareUpdate[pos] < currentTimeReal - milliseconds{1000})
998 updateObject(pos, view, currentTimeReal);
999 previousView = view;
1000 if (previousLevel != level) {
1001 screenMovement = none;
1002 clearCenter();
1003 setCenter(view->getPosition());
1004 previousLevel = level;
1005 mouseOffset = {0, 0};
1006 }
1007 keyScrolling = view->getCenterType() == CreatureView::CenterType::NONE;
1008 bool newTurn = false;
1009 {
1010 double newCurrentTimeGame = smoothMovement ? view->getLocalTime() : 1000000000;
1011 if (currentTimeGame != newCurrentTimeGame) {
1012 lastEndTimeGame = currentTimeGame;
1013 currentTimeGame = newCurrentTimeGame;
1014 newTurn = true;
1015 }
1016 }
1017 if (smoothMovement && view->getCenterType() != CreatureView::CenterType::NONE) {
1018 if (!screenMovement || newTurn) {
1019 screenMovement = ScreenMovement {
1020 clock->getRealMillis(),
1021 clock->getRealMillis() + milliseconds{100},
1022 lastEndTimeGame
1023 };
1024 }
1025 } else
1026 screenMovement = none;
1027 if (view->getCenterType() == CreatureView::CenterType::FOLLOW) {
1028 if (centeredCreaturePosition) {
1029 centeredCreaturePosition->pos = view->getPosition();
1030 if (newTurn)
1031 centeredCreaturePosition->softScroll = false;
1032 } else
1033 centeredCreaturePosition = CenteredCreatureInfo { view->getPosition(), true };
1034 } else {
1035 centeredCreaturePosition = none;
1036 if (!isCentered() ||
1037 (view->getCenterType() == CreatureView::CenterType::STAY_ON_SCREEN && getDistanceToEdgeRatio(view->getPosition()) < 0.33)) {
1038 setSoftCenter(view->getPosition());
1039 }
1040 }
1041 }
1042
1043