1 /*
2  * Copyright 2011-2013 Arx Libertatis Team (see the AUTHORS file)
3  *
4  * This file is part of Arx Libertatis.
5  *
6  * Arx Libertatis is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Arx Libertatis is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Arx Libertatis.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 /* Based on:
20 ===========================================================================
21 ARX FATALIS GPL Source Code
22 Copyright (C) 1999-2010 Arkane Studios SA, a ZeniMax Media company.
23 
24 This file is part of the Arx Fatalis GPL Source Code ('Arx Fatalis Source Code').
25 
26 Arx Fatalis Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
27 License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
28 
29 Arx Fatalis Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
30 warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
31 
32 You should have received a copy of the GNU General Public License along with Arx Fatalis Source Code.  If not, see
33 <http://www.gnu.org/licenses/>.
34 
35 In addition, the Arx Fatalis Source Code is also subject to certain additional terms. You should have received a copy of these
36 additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Arx
37 Fatalis Source Code. If not, please request a copy in writing from Arkane Studios at the address below.
38 
39 If you have questions concerning this license or the applicable additional terms, you may contact in writing Arkane Studios, c/o
40 ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
41 ===========================================================================
42 */
43 // Code: Cyril Meynier
44 //
45 // Copyright (c) 1999-2001 ARKANE Studios SA. All rights reserved
46 
47 #include "gui/MiniMap.h"
48 
49 #include <cstdio>
50 
51 #include "core/Core.h"
52 #include "core/Localisation.h"
53 
54 #include "game/EntityManager.h"
55 #include "game/Levels.h"
56 #include "game/NPC.h"
57 #include "game/Player.h"
58 
59 #include "gui/Text.h"
60 #include "gui/Interface.h"
61 
62 #include "graphics/Draw.h"
63 #include "graphics/Math.h"
64 #include "graphics/data/TextureContainer.h"
65 #include "graphics/texture/TextureStage.h"
66 #include "graphics/texture/Texture.h"
67 
68 #include "io/log/Logger.h"
69 
70 #include "scene/Interactive.h"
71 #include "scene/SaveFormat.h"
72 
73 MiniMap g_miniMap; // TODO: remove this
74 
getData(int showLevel)75 void MiniMap::getData(int showLevel) {
76 
77 	if(m_levels[showLevel].m_texContainer == NULL) {
78 
79 		char name[256];
80 		char levelMap[256];
81 		GetLevelNameByNum(showLevel, name);
82 
83 		sprintf(levelMap, "graph/levels/level%s/map", name);
84 		m_levels[showLevel].m_texContainer = TextureContainer::Load(levelMap);
85 
86 		if(m_levels[showLevel].m_texContainer) { // 4 pix/meter
87 
88 			m_levels[showLevel].m_height = static_cast<float>(m_levels[showLevel].m_texContainer->m_dwHeight);
89 			m_levels[showLevel].m_width = static_cast<float>(m_levels[showLevel].m_texContainer->m_dwWidth);
90 
91 			float minX = std::numeric_limits<float>::max();
92 			float maxX = std::numeric_limits<float>::min();
93 			float minY = std::numeric_limits<float>::max();
94 			float maxY = std::numeric_limits<float>::min();
95 
96 			EERIEPOLY *ep = NULL;
97 			EERIE_BKG_INFO *eg = NULL;
98 
99 			for(int j = 0; j < m_activeBkg->Zsize; j++) {
100 
101 				for(int i = 0; i < m_activeBkg->Xsize; i++) {
102 					eg = &m_activeBkg->Backg[i+j*m_activeBkg->Xsize];
103 					for(int k = 0; k < eg->nbpoly; k++) {
104 						ep = &eg->polydata[k];
105 						if(ep) {
106 							minX = min(minX, ep->min.x);
107 							maxX = max(maxX, ep->max.x);
108 							minY = min(minY, ep->min.z);
109 							maxY = max(maxY, ep->max.z);
110 						}
111 					}
112 				}
113 
114 				m_mapMaxY[showLevel] = maxY;
115 				m_levels[showLevel].m_ratioX = minX;
116 				m_levels[showLevel].m_ratioY = minY;
117 
118 				for(int l = 0; l < MAX_MINIMAP_LEVELS; l++) {
119 					m_levels[l].m_offsetX = 0;
120 					m_levels[l].m_offsetY = 0;
121 				}
122 			}
123 		}
124 	}
125 }
126 
validatePos()127 void MiniMap::validatePos() {
128 
129 	int showLevel = ARX_LEVELS_GetRealNum(m_currentLevel);
130 
131 	if((showLevel >= 0) && (showLevel < MAX_MINIMAP_LEVELS)) {
132 
133 		if(m_levels[showLevel].m_texContainer == NULL) {
134 			getData(showLevel);
135 		}
136 
137 		if(m_levels[m_currentLevel].m_texContainer == NULL) {
138 			getData(m_currentLevel);
139 		}
140 
141 		if(m_levels[showLevel].m_texContainer) {
142 			revealPlayerPos(ARX_LEVELS_GetRealNum(m_currentLevel));
143 		}
144 	}
145 }
146 
validatePlayerPos(int currentLevel,long blockPlayerControls,ARX_INTERFACE_BOOK_MODE bookMode)147 void MiniMap::validatePlayerPos(int currentLevel, long blockPlayerControls, ARX_INTERFACE_BOOK_MODE bookMode) {
148 
149 	m_currentLevel = currentLevel;
150 
151 	if(!blockPlayerControls) {
152 
153 		float req;
154 
155 		if((m_player->Interface & INTER_MAP) && (!(m_player->Interface & INTER_COMBATMODE)) && (bookMode == BOOKMODE_MINIMAP)) {
156 			req = 20.f;
157 		} else {
158 			req = 80.f;
159 		}
160 
161 		if(fartherThan(Vec2f(m_playerLastPosX, m_playerLastPosZ), Vec2f(m_player->pos.x, m_player->pos.z), req)) {
162 			m_playerLastPosX = m_player->pos.x;
163 			m_playerLastPosZ = m_player->pos.z;
164 			validatePos();
165 		}
166 	}
167 }
168 
loadOffsets(PakReader * pakRes)169 void MiniMap::loadOffsets(PakReader *pakRes) {
170 
171 	std::string iniMiniOffsets = "graph/levels/mini_offsets.ini";
172 
173 	PakFile *file = pakRes->getFile(iniMiniOffsets.c_str());
174 
175 	if(!file) {
176 		LogError << "Missing " << iniMiniOffsets;
177 		return;
178 	}
179 
180 	size_t fileSize = file->size();
181 	char *dat = new char[fileSize + 2];
182 	dat[fileSize + 1] = '\0';
183 
184 	file->read(dat);
185 
186 	if(dat) {
187 
188 		size_t pos = 0;
189 
190 		for(int i = 0; i < 29; i++) { // Why 29?
191 
192 			char t[512];
193 			int nRead = sscanf(dat + pos, "%s %f %f", t, &m_miniOffsetX[i], &m_miniOffsetY[i]);
194 
195 			if(nRead != 3) {
196 				LogError << "Error parsing line " << i << " of mini_offsets.ini: read " << nRead;
197 			}
198 
199 			while((pos < fileSize) && (dat[pos] != '\n')) {
200 				pos++;
201 			}
202 
203 			pos++;
204 
205 			if(pos >= fileSize) {
206 				break;
207 			}
208 		}
209 
210 		delete[] dat;
211 	}
212 
213 	m_miniOffsetX[0] = 0;
214 	m_miniOffsetY[0] = -0.5;
215 	m_miniOffsetX[1] = 0;
216 	m_miniOffsetY[1] = 0;
217 	m_miniOffsetX[14] = 130;
218 	m_miniOffsetY[14] = 0;
219 	m_miniOffsetX[15] = 31;
220 	m_miniOffsetY[15] = -3.5;
221 }
222 
reveal()223 void MiniMap::reveal() {
224 
225 	for(int d = 0; d < MAX_MINIMAP_LEVELS; d++) {
226 		for(int j = 0; j < MINIMAP_MAX_Z; j++) {
227 			for(int i = 0; i < MINIMAP_MAX_X; i++) {
228 				m_levels[d].m_revealed[i][j] = 255;
229 			}
230 		}
231 	}
232 }
233 
firstInit(ARXCHARACTER * pl,PakReader * pakRes,EntityManager * entityMng)234 void MiniMap::firstInit(ARXCHARACTER *pl, PakReader *pakRes, EntityManager *entityMng) {
235 
236 	m_pTexDetect = NULL;
237 	m_mapMarkerTexCont = NULL;
238 
239 	m_player = pl;
240 	m_playerLastPosX = -999999.f;
241 	m_playerLastPosZ = -999999.f;
242 
243 	m_modX = (float)MAX_BKGX / (float)MINIMAP_MAX_X;
244 	m_modZ = (float)MAX_BKGZ / (float)MINIMAP_MAX_Z;
245 
246 	m_currentLevel = 0;
247 	m_entities = entityMng;
248 	m_activeBkg = NULL;
249 
250 	resetLevels();
251 
252 	for(int i = 0; i < MAX_MINIMAP_LEVELS; i++) {
253 		m_miniOffsetX[i] = 0;
254 		m_miniOffsetY[i] = 0;
255 	}
256 
257 	loadOffsets(pakRes);
258 }
259 
resetLevels()260 void MiniMap::resetLevels() {
261 
262 	for(int i = 0; i < MAX_MINIMAP_LEVELS; i++) {
263 		m_levels[i].m_texContainer = NULL;
264 		m_levels[i].m_offsetX = 0.f;
265 		m_levels[i].m_offsetY = 0.f;
266 		m_levels[i].m_ratioX = 0.f;
267 		m_levels[i].m_ratioY = 0.f;
268 		m_levels[i].m_width = 0.f;
269 		m_levels[i].m_height = 0.f;
270 		memset(m_levels[i].m_revealed, 0, sizeof(m_levels[i].m_revealed[0][0] * MINIMAP_MAX_X * MINIMAP_MAX_Z)); // Sets the whole array to 0
271 	}
272 }
273 
reset()274 void MiniMap::reset() {
275 
276 	purgeTexContainer();
277 	resetLevels();
278 }
279 
purgeTexContainer()280 void MiniMap::purgeTexContainer() {
281 
282 	for(int i = 0; i < MAX_MINIMAP_LEVELS; i++) {
283 		delete m_levels[i].m_texContainer;
284 		m_levels[i].m_texContainer = NULL;
285 	}
286 }
287 
showPlayerMiniMap(int showLevel)288 void MiniMap::showPlayerMiniMap(int showLevel) {
289 
290 	const float miniMapZoom = 300.f; // zoom of the minimap
291 	const Rect miniMapRect(390, 135, 590, 295); // minimap rect on a 640*480 screen
292 	const float playerSize = 4.f; // red arrow size
293 
294 	const float decalY = -150;
295 	const float decalX = +40;
296 
297 	// First Load Minimap TC & DATA if needed
298 	if(m_levels[showLevel].m_texContainer == NULL) {
299 		getData(showLevel);
300 	}
301 
302 	if(m_levels[showLevel].m_texContainer) {
303 
304 		GRenderer->SetRenderState(Renderer::DepthTest, false);
305 
306 		float startX = 0.f;
307 		float startY = 0.f;
308 
309 		Vec2f playerPos(0.f, 0.f);
310 
311 		if(showLevel == ARX_LEVELS_GetRealNum(m_currentLevel)) {
312 			playerPos = computePlayerPos(miniMapZoom, showLevel);
313 			startX = 490.f - playerPos.x;
314 			startY = 220.f - playerPos.y;
315 			playerPos.x += startX;
316 			playerPos.y += startY;
317 		}
318 
319 		// Draw the background
320 		drawBackground(showLevel, Rect(390, 135, 590, 295), startX, startY, miniMapZoom, 20.f, decalX, decalY, true, 0.5f);
321 
322 		GRenderer->GetTextureStage(0)->SetWrapMode(TextureStage::WrapRepeat);
323 
324 		// Draw the player (red arrow)
325 		if(showLevel == ARX_LEVELS_GetRealNum(m_currentLevel)) {
326 			drawPlayer(playerSize, playerPos.x + decalX, playerPos.y + decalY, true);
327 			drawDetectedEntities(showLevel, startX + decalX, startY + decalY, miniMapZoom);
328 		}
329 
330 	}
331 }
332 
showBookMiniMap(int showLevel)333 void MiniMap::showBookMiniMap(int showLevel) {
334 
335 	// First Load Minimap TC & DATA if needed
336 	if(m_levels[showLevel].m_texContainer == NULL) {
337 		getData(showLevel);
338 	}
339 
340 	if(m_levels[showLevel].m_texContainer) {
341 
342 		GRenderer->SetRenderState(Renderer::DepthTest, false);
343 
344 		float zoom = 900.f;
345 		float startX = 0.f;
346 		float startY = 0.f;
347 
348 		Vec2f playerPos(0.f, 0.f);
349 
350 		if(showLevel == ARX_LEVELS_GetRealNum(m_currentLevel)) {
351 			playerPos = computePlayerPos(zoom, showLevel);
352 			startX = 490.f - playerPos.x;
353 			startY = 220.f - playerPos.y;
354 			playerPos.x += startX;
355 			playerPos.y += startY;
356 		}
357 
358 		drawBackground(showLevel, Rect(360, 85, 555, 355), startX, startY, zoom, 20.f);
359 
360 		GRenderer->GetTextureStage(0)->SetWrapMode(TextureStage::WrapRepeat);
361 
362 		if(showLevel == ARX_LEVELS_GetRealNum(m_currentLevel)) {
363 			drawPlayer(6.f, playerPos.x, playerPos.y);
364 			drawDetectedEntities(showLevel, startX, startY, zoom);
365 		}
366 
367 	}
368 }
369 
showBookEntireMap(int showLevel)370 void MiniMap::showBookEntireMap(int showLevel) {
371 
372 	// First Load Minimap TC & DATA if needed
373 	if(m_levels[showLevel].m_texContainer == NULL) {
374 		getData(showLevel);
375 	}
376 
377 	if(!m_levels[showLevel].m_texContainer) {
378 		return;
379 	}
380 
381 	GRenderer->SetRenderState(Renderer::DepthTest, false);
382 
383 	float zoom = 250.f;
384 	float startX = 140.f;
385 	float startY = 120.f;
386 
387 	Vec2f playerPos(0.f, 0.f);
388 
389 	if(showLevel == ARX_LEVELS_GetRealNum(m_currentLevel)) {
390 		playerPos = computePlayerPos(zoom, showLevel);
391 		playerPos.x += startX;
392 		playerPos.y += startY;
393 	}
394 
395 	drawBackground(showLevel, Rect(0, 0, 345, 290), startX, startY, zoom);
396 
397 	GRenderer->GetTextureStage(0)->SetWrapMode(TextureStage::WrapRepeat);
398 
399 	if(showLevel == ARX_LEVELS_GetRealNum(m_currentLevel)) {
400 		drawPlayer(3.f, playerPos.x, playerPos.y);
401 		drawDetectedEntities(showLevel, startX, startY, zoom);
402 	}
403 
404 	TexturedVertex verts[4];
405 	for(int k = 0; k < 4; k++) {
406 		verts[k].color = 0xFFFFFFFF;
407 		verts[k].rhw = 1;
408 		verts[k].p.z = 0.00001f;
409 	}
410 
411 	float caseX = zoom / ((float)MINIMAP_MAX_X);
412 	float caseY = zoom / ((float)MINIMAP_MAX_Z);
413 	float ratio = 1.f;
414 
415 	for(size_t i = 0; i < m_mapMarkers.size(); i++) {
416 
417 		if(m_mapMarkers[i].m_lvl != showLevel + 1) {
418 			continue;
419 		}
420 
421 		float pos_x = m_mapMarkers[i].m_x * 8 * ratio * m_activeBkg->Xmul * caseX + startX;
422 		float pos_y = m_mapMarkers[i].m_y * 8 * ratio * m_activeBkg->Zmul * caseY + startY;
423 		float size = 5.f * ratio;
424 		verts[0].color = 0xFFFF0000;
425 		verts[1].color = 0xFFFF0000;
426 		verts[2].color = 0xFFFF0000;
427 		verts[3].color = 0xFFFF0000;
428 		verts[0].p.x = (pos_x - size) * Xratio;
429 		verts[0].p.y = (pos_y - size) * Yratio;
430 		verts[1].p.x = (pos_x + size) * Xratio;
431 		verts[1].p.y = (pos_y - size) * Yratio;
432 		verts[2].p.x = (pos_x + size) * Xratio;
433 		verts[2].p.y = (pos_y + size) * Yratio;
434 		verts[3].p.x = (pos_x - size) * Xratio;
435 		verts[3].p.y = (pos_y + size) * Yratio;
436 		verts[0].uv = Vec2f::ZERO;
437 		verts[1].uv = Vec2f::X_AXIS;
438 		verts[2].uv = Vec2f::ONE;
439 		verts[3].uv = Vec2f::Y_AXIS;
440 
441 		if(MouseInRect(verts[0].p.x, verts[0].p.y, verts[2].p.x, verts[2].p.y)) {
442 			if(!m_mapMarkers[i].m_text.empty()) {
443 
444 				Rect bRect(140, 290, 140 + 205, 358);
445 
446 				Rect::Num left = checked_range_cast<Rect::Num>((bRect.left) * Xratio);
447 				Rect::Num right = checked_range_cast<Rect::Num>((bRect.right) * Xratio);
448 				Rect::Num top = checked_range_cast<Rect::Num>((bRect.top) * Yratio);
449 				Rect::Num bottom = checked_range_cast<Rect::Num>((bRect.bottom) * Yratio);
450 				Rect rRect = Rect(left, top, right, bottom);
451 
452 				long lLengthDraw = ARX_UNICODE_ForceFormattingInRect(hFontInGameNote, m_mapMarkers[i].m_text, rRect);
453 
454 				DrawBookTextInRect(hFontInGameNote, float(bRect.left), float(bRect.top), float(bRect.right), m_mapMarkers[i].m_text.substr(0, lLengthDraw), Color::none);
455 			}
456 		}
457 
458 		if(m_mapMarkerTexCont == NULL) {
459 			m_mapMarkerTexCont = TextureContainer::Load("graph/interface/icons/mapmarker");
460 		}
461 
462 		GRenderer->SetTexture(0, m_mapMarkerTexCont);
463 
464 		EERIEDRAWPRIM(Renderer::TriangleFan, verts, 4);
465 	}
466 }
467 
revealPlayerPos(int showLevel)468 void MiniMap::revealPlayerPos(int showLevel) {
469 
470 	float zoom = 250.f;
471 	float startX = 140.f;
472 	float startY = 120.f;
473 	float caseX = zoom / ((float)MINIMAP_MAX_X);
474 	float caseY = zoom / ((float)MINIMAP_MAX_Z);
475 
476 	Vec2f playerPos = computePlayerPos(zoom, showLevel);
477 	playerPos.x += startX;
478 	playerPos.y += startY;
479 
480 	// TODO this is inefficient - we don't really need to iterate over the whole minimap!
481 	// only the area around the player will be modified
482 	for(int j = 0; j < MINIMAP_MAX_Z; j++) {
483 		for(int i = 0; i < MINIMAP_MAX_X; i++) {
484 
485 			float posx = startX + i * caseX;
486 			float posy = startY + j * caseY;
487 
488 			float d = fdist(Vec2f(posx + caseX * 0.5f, posy), playerPos);
489 			if(d > 6.f) {
490 				continue;
491 			}
492 
493 			float vv = (6 - d) * (1.f / 6);
494 
495 			if(vv >= 0.5f) {
496 				vv = 1.f;
497 			} else if(vv > 0.f) {
498 				vv = vv * 2.f;
499 			} else {
500 				vv = 0.f;
501 			}
502 
503 			int r = vv * 255.f;
504 
505 			int ucLevel =  max(r, (int)m_levels[showLevel].m_revealed[i][j]);
506 			m_levels[showLevel].m_revealed[i][j] = checked_range_cast<unsigned char>(ucLevel);
507 		}
508 	}
509 }
510 
computePlayerPos(float zoom,int showLevel)511 Vec2f MiniMap::computePlayerPos(float zoom, int showLevel) {
512 
513 	float caseX = zoom / ((float)MINIMAP_MAX_X);
514 	float caseY = zoom / ((float)MINIMAP_MAX_Z);
515 	float ratio = zoom / 250.f;
516 
517 	Vec2f pos(0.f, 0.f);
518 
519 	float ofx = m_miniOffsetX[m_currentLevel];
520 	float ofx2 = m_levels[showLevel].m_ratioX;
521 	float ofy = m_miniOffsetY[m_currentLevel];
522 	float ofy2 = m_levels[showLevel].m_ratioY;
523 
524 	pos.x = ((m_player->pos.x + ofx - ofx2) * ( 1.0f / 100 ) * caseX
525 	+ m_miniOffsetX[m_currentLevel] * ratio * m_modX) / m_modX;
526 	pos.y = ((m_mapMaxY[showLevel] - ofy - ofy2) * ( 1.0f / 100 ) * caseY
527 	- (m_player->pos.z + ofy - ofy2) * ( 1.0f / 100 ) * caseY + m_miniOffsetY[m_currentLevel] * ratio * m_modZ) / m_modZ;
528 
529 	return pos;
530 }
531 
drawBackground(int showLevel,Rect boundaries,float startX,float startY,float zoom,float fadeBorder,float decalX,float decalY,bool invColor,float alpha)532 void MiniMap::drawBackground(int showLevel, Rect boundaries, float startX, float startY, float zoom, float fadeBorder, float decalX, float decalY, bool invColor, float alpha) {
533 
534 	float caseX = zoom / ((float)MINIMAP_MAX_X);
535 	float caseY = zoom / ((float)MINIMAP_MAX_Z);
536 
537 	TexturedVertex verts[4];
538 	GRenderer->SetTexture(0, m_levels[showLevel].m_texContainer);
539 
540 	for(int k = 0; k < 4; k++) {
541 		verts[k].color = 0xFFFFFFFF;
542 		verts[k].rhw = 1;
543 		verts[k].p.z = 0.00001f;
544 	}
545 
546 	float div = (1.0f / 25);
547 	TextureContainer * tc = m_levels[showLevel].m_texContainer;
548 	float dw = 1.f / tc->m_pTexture->getStoredSize().x;
549 	float dh = 1.f / tc->m_pTexture->getStoredSize().y;
550 
551 	float vx2 = 4.f * dw * m_modX;
552 	float vy2 = 4.f * dh * m_modZ;
553 
554 	float fadeDiv = 0.f;
555 	Rect fadeBounds(0, 0, 0, 0);
556 
557 	if(fadeBorder > 0.f) {
558 		fadeDiv = 1.f/fadeBorder;
559 		fadeBounds.left = checked_range_cast<Rect::Num>((boundaries.left + fadeBorder) * Xratio);
560 		fadeBounds.right = checked_range_cast<Rect::Num>((boundaries.right - fadeBorder) * Xratio);
561 		fadeBounds.top = checked_range_cast<Rect::Num>((boundaries.top + fadeBorder) * Yratio);
562 		fadeBounds.bottom = checked_range_cast<Rect::Num>((boundaries.bottom - fadeBorder) * Yratio);
563 	}
564 
565 	GRenderer->SetRenderState(Renderer::AlphaBlending, true);
566 	if(invColor) {
567 		GRenderer->SetBlendFunc(Renderer::BlendOne, Renderer::BlendInvSrcColor);
568 	} else {
569 		GRenderer->SetBlendFunc(Renderer::BlendZero, Renderer::BlendInvSrcColor);
570 	}
571 	GRenderer->GetTextureStage(0)->SetWrapMode(TextureStage::WrapClamp);
572 
573 	for(int j = -2; j < MINIMAP_MAX_Z + 2; j++) {
574 		for(int i = -2; i < MINIMAP_MAX_X + 2; i++) {
575 
576 			float vx, vy, vxx, vyy;
577 			vxx = ((float)i * (float)m_activeBkg->Xdiv * m_modX);
578 			vyy = ((float)j * (float)m_activeBkg->Zdiv * m_modZ);
579 			vx = (vxx * div) * dw;
580 			vy = (vyy * div) * dh;
581 
582 			float posx = (startX + i * caseX) * Xratio;
583 			float posy = (startY + j * caseY) * Yratio;
584 
585 			if((posx < boundaries.left * Xratio)
586 			   || (posx > boundaries.right * Xratio)
587 			   || (posy < boundaries.top * Yratio)
588 			   || (posy > boundaries.bottom * Yratio)) {
589 				continue; // out of bounds
590 			}
591 
592 			verts[3].p.x = verts[0].p.x = (posx);
593 			verts[1].p.y = verts[0].p.y = (posy);
594 			verts[2].p.x = verts[1].p.x = posx + (caseX * Xratio);
595 			verts[3].p.y = verts[2].p.y = posy + (caseY * Yratio);
596 
597 			verts[3].uv.x = verts[0].uv.x = vx;
598 			verts[1].uv.y = verts[0].uv.y = vy;
599 			verts[2].uv.x = verts[1].uv.x = vx + vx2;
600 			verts[3].uv.y = verts[2].uv.y = vy + vy2;
601 
602 			float v;
603 			float oo = 0.f;
604 
605 			for(int vert = 0; vert < 4; vert++) {
606 
607 				// Array offset according to "vert"
608 				int iOffset = 0;
609 				int jOffset = 0;
610 
611 				if(vert == 1 || vert == 2)
612 					iOffset = 1;
613 				if(vert == 2 || vert == 3)
614 					jOffset = 1;
615 
616 				if((i + iOffset < 0) || (i + iOffset >= MINIMAP_MAX_X) || (j + jOffset < 0) || (j + jOffset >= MINIMAP_MAX_Z)) {
617 					v = 0;
618 				} else {
619 					v = ((float)m_levels[showLevel].m_revealed[min(i+iOffset, MINIMAP_MAX_X-iOffset)][min(j+jOffset, MINIMAP_MAX_Z-jOffset)]) * (1.0f / 255);
620 				}
621 
622 				if(fadeBorder > 0.f) {
623 
624 					float _px = verts[vert].p.x - fadeBounds.left;
625 
626 					if(_px < 0.f) {
627 						v = 0.f;
628 					} else if(_px < fadeBorder) {
629 						v *= _px * fadeDiv;
630 					}
631 
632 					_px = fadeBounds.right - verts[vert].p.x;
633 
634 					if(_px < 0.f) {
635 						v = 0.f;
636 					} else if(_px < fadeBorder) {
637 						v *= _px * fadeDiv;
638 					}
639 
640 					_px = verts[vert].p.y - fadeBounds.top;
641 
642 					if(_px < 0.f) {
643 						v = 0.f;
644 					} else if(_px < fadeBorder) {
645 						v *= _px * fadeDiv;
646 					}
647 
648 					_px = fadeBounds.bottom - verts[vert].p.y;
649 
650 					if(_px < 0.f) {
651 						v = 0.f;
652 					} else if(_px < fadeBorder) {
653 						v *= _px * fadeDiv;
654 					}
655 				}
656 
657 				verts[vert].color = Color::gray(v * alpha).toBGR();
658 
659 				oo += v;
660 			}
661 
662 			if(oo > 0.f) {
663 
664 				verts[0].p.x += decalX * Xratio;
665 				verts[0].p.y += decalY * Yratio;
666 				verts[1].p.x += decalX * Xratio;
667 				verts[1].p.y += decalY * Yratio;
668 				verts[2].p.x += decalX * Xratio;
669 				verts[2].p.y += decalY * Yratio;
670 				verts[3].p.x += decalX * Xratio;
671 				verts[3].p.y += decalY * Yratio;
672 
673 				EERIEDRAWPRIM(Renderer::TriangleFan, verts, 4);
674 			}
675 		}
676 	}
677 
678 	GRenderer->SetRenderState(Renderer::AlphaBlending, false);
679 }
680 
drawPlayer(float playerSize,float playerX,float playerY,bool alphaBlending)681 void MiniMap::drawPlayer(float playerSize, float playerX, float playerY, bool alphaBlending) {
682 
683 	TexturedVertex verts[4];
684 
685 	for(int k = 0; k < 4; k++) {
686 		verts[k].color = 0xFFFF0000; // red
687 		verts[k].rhw = 1;
688 		verts[k].p.z = 0.00001f;
689 	}
690 
691 	float rx = 0.f;
692 	float ry = -playerSize * 1.8f;
693 	float rx2 = -playerSize * (1.0f / 2);
694 	float ry2 = playerSize;
695 	float rx3 = playerSize * (1.0f / 2);
696 	float ry3 = playerSize;
697 
698 	float angle = radians(m_player->angle.b);
699 	float ca = EEcos(angle);
700 	float sa = EEsin(angle);
701 
702 	verts[0].p.x = (playerX + rx2 * ca + ry2 * sa) * Xratio;
703 	verts[0].p.y = (playerY + ry2 * ca - rx2 * sa) * Yratio;
704 	verts[1].p.x = (playerX + rx * ca + ry * sa) * Xratio;
705 	verts[1].p.y = (playerY + ry * ca - rx * sa) * Yratio;
706 	verts[2].p.x = (playerX + rx3 * ca + ry3 * sa) * Xratio;
707 	verts[2].p.y = (playerY + ry3 * ca - rx3 * sa) * Yratio;
708 
709 	GRenderer->ResetTexture(0);
710 	GRenderer->SetRenderState(Renderer::AlphaBlending, alphaBlending);
711 	if(alphaBlending) {
712 		GRenderer->SetBlendFunc(Renderer::BlendOne, Renderer::BlendInvSrcColor);
713 	}
714 
715 	EERIEDRAWPRIM(Renderer::TriangleFan, verts);
716 
717 	GRenderer->SetRenderState(Renderer::AlphaBlending, false);
718 }
719 
drawDetectedEntities(int showLevel,float startX,float startY,float zoom)720 void MiniMap::drawDetectedEntities(int showLevel, float startX, float startY, float zoom) {
721 
722 	float caseX = zoom / ((float)MINIMAP_MAX_X);
723 	float caseY = zoom / ((float)MINIMAP_MAX_Z);
724 	float ratio = zoom / 250.f;
725 
726 	if(!m_pTexDetect) {
727 		m_pTexDetect = TextureContainer::Load("graph/particles/flare");
728 	}
729 
730 	// Computes playerpos
731 	float ofx = m_miniOffsetX[m_currentLevel];
732 	float ofx2 = m_levels[showLevel].m_ratioX;
733 	float ofy = m_miniOffsetY[m_currentLevel];
734 	float ofy2 = m_levels[showLevel].m_ratioY;
735 
736 	GRenderer->SetRenderState(Renderer::AlphaBlending, true);
737 	GRenderer->SetBlendFunc(Renderer::BlendOne, Renderer::BlendOne);
738 
739 	const EntityManager &ents = *m_entities; // for convenience
740 	for(size_t lnpc = 1; lnpc < ents.size(); lnpc++) {
741 		Entity * npc = ents[lnpc];
742 
743 		if(!npc || !(npc->ioflags & IO_NPC)) {
744 			continue; // only NPCs can be detected
745 		}
746 
747 		if(npc->_npcdata->life < 0.f) {
748 			continue; // don't show dead NPCs
749 		}
750 
751 		if((npc->gameFlags & GFLAG_MEGAHIDE) || npc->show != SHOW_FLAG_IN_SCENE) {
752 			continue; // don't show hidden NPCs
753 		}
754 
755 		if(npc->_npcdata->fDetect < 0) {
756 			continue; // don't show undetectable NPCs
757 		}
758 
759 		if(player.Full_Skill_Etheral_Link < npc->_npcdata->fDetect) {
760 			continue; // the player doesn't have enough skill to detect this NPC
761 		}
762 
763 		float fpx = startX + ((npc->pos.x - 100 + ofx - ofx2) * ( 1.0f / 100 ) * caseX
764 		+ m_miniOffsetX[m_currentLevel] * ratio * m_modX) / m_modX;
765 		float fpy = startY + ((m_mapMaxY[showLevel] - ofy - ofy2) * ( 1.0f / 100 ) * caseY
766 		- (npc->pos.z + 200 + ofy - ofy2) * ( 1.0f / 100 ) * caseY + m_miniOffsetY[m_currentLevel] * ratio * m_modZ) / m_modZ;
767 
768 		float d = fdist(Vec2f(m_player->pos.x, m_player->pos.z), Vec2f(npc->pos.x, npc->pos.z));
769 		if(d > 800 || fabs(ents.player()->pos.y - npc->pos.y) > 250.f) {
770 			continue; // the NPC is too far away to be detected
771 		}
772 
773 		float col = 1.f;
774 
775 		if(d > 600.f) {
776 			col = 1.f - (d - 600.f) * ( 1.0f / 200 );
777 		}
778 
779 		fpx *= Xratio;
780 		fpy *= Yratio;
781 		EERIEDrawBitmap(fpx, fpy, 5.f * ratio, 5.f * ratio, 0, m_pTexDetect,
782 		Color3f(col, 0, 0).to<u8>());
783 	}
784 
785 	GRenderer->SetRenderState(Renderer::AlphaBlending, false);
786 }
787 
clearMarkerTexCont()788 void MiniMap::clearMarkerTexCont() {
789 	m_mapMarkerTexCont = NULL;
790 }
791 
load(const SavedMiniMap * saved,size_t size)792 void MiniMap::load(const SavedMiniMap *saved, size_t size) {
793 	std::copy(saved, saved + size, m_levels);
794 }
795 
save(SavedMiniMap * toSave,size_t size)796 void MiniMap::save(SavedMiniMap *toSave, size_t size) {
797 	std::copy(m_levels, m_levels + size, toSave);
798 }
799 
mapMarkerInit(size_t reserveSize)800 void MiniMap::mapMarkerInit(size_t reserveSize) {
801 	m_mapMarkers.clear();
802 
803 	if(reserveSize > 0)
804 		m_mapMarkers.reserve(reserveSize);
805 }
806 
mapMarkerGetID(const std::string & name)807 int MiniMap::mapMarkerGetID(const std::string &name) {
808 
809 	for(size_t i = 0; i < m_mapMarkers.size(); i++) {
810 		if(m_mapMarkers[i].m_name == name) {
811 			return i;
812 		}
813 	}
814 
815 	return -1;
816 }
817 
mapMarkerAdd(float x,float y,int lvl,const std::string & name)818 void MiniMap::mapMarkerAdd(float x, float y, int lvl, const std::string &name) {
819 
820 	int num = mapMarkerGetID(name);
821 
822 	if(num >= 0) {
823 		// Already exists, update it
824 		m_mapMarkers[num].m_lvl = lvl;
825 		m_mapMarkers[num].m_x = x;
826 		m_mapMarkers[num].m_y = y;
827 		return;
828 	}
829 
830 	// Else, create one
831 	MapMarkerData newMMD;
832 	newMMD.m_lvl = lvl;
833 	newMMD.m_x = x;
834 	newMMD.m_y = y;
835 	newMMD.m_name = name;
836 	newMMD.m_text = getLocalised(name);
837 	m_mapMarkers.push_back(newMMD);
838 }
839 
mapMarkerRemove(const std::string & name)840 void MiniMap::mapMarkerRemove(const std::string &name) {
841 
842 	int num = mapMarkerGetID(name);
843 
844 	if(num < 0) {
845 		return; // Doesn't exist
846 	}
847 
848 	m_mapMarkers.erase(m_mapMarkers.begin() + num);
849 }
850 
mapMarkerCount()851 size_t MiniMap::mapMarkerCount() {
852 	return m_mapMarkers.size();
853 }
854 
mapMarkerGet(size_t id)855 MiniMap::MapMarkerData MiniMap::mapMarkerGet(size_t id) {
856 
857 	assert(id < m_mapMarkers.size());
858 	return m_mapMarkers[id];
859 }
860 
setActiveBackground(EERIE_BACKGROUND * activeBkg)861 void MiniMap::setActiveBackground(EERIE_BACKGROUND *activeBkg) {
862 	m_activeBkg = activeBkg;
863 }
864