UFO: Alien Invasion
Loading...
Searching...
No Matches
cp_geoscape.cpp
Go to the documentation of this file.
1
5
6/*
7Copyright (C) 2002-2025 UFO: Alien Invasion.
8
9This program is free software; you can redistribute it and/or
10modify it under the terms of the GNU General Public License
11as published by the Free Software Foundation; either version 2
12of the License, or (at your option) any later version.
13
14This program is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
18See the GNU General Public License for more details.
19
20You should have received a copy of the GNU General Public License
21along with this program; if not, write to the Free Software
22Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23*/
24
25#include "../../DateTime.h"
26#include "../../cl_shared.h"
27#include "../../ui/ui_dataids.h"
29#include "cp_overlay.h"
30#include "cp_campaign.h"
31#include "cp_geoscape.h"
32#include "cp_popup.h"
33#include "cp_mapfightequip.h"
34#include "cp_missions.h"
35#include "cp_ufo.h"
36#include "cp_time.h"
37#include "cp_xvi.h"
38
40
41#ifdef DEBUG
42static cvar_t* debug_showInterest;
43#endif
44
45#define GLOBE_ROTATE -90
46#define ZOOM_LIMIT 2.5f
47
48/* Functions */
49static bool GEO_IsPositionSelected(const uiNode_t* node, const vec2_t pos, int x, int y);
50
51/* static variables */
52static char textStandard[2048];
53static int centerOnEventIdx;
54
55/* Colors */
56static const vec4_t green = {0.0f, 1.0f, 0.0f, 0.8f};
57static const vec4_t yellow = {1.0f, 0.874f, 0.294f, 1.0f};
58static const vec4_t red = {1.0f, 0.0f, 0.0f, 0.8f};
59
60static const float defaultBaseAngle = -90.0f;
61
62static byte* terrainPic;
65
66static byte* culturePic;
69
70static byte* populationPic;
73
74static byte* nationsPic;
77
78
79/*
80==============================================================
81CLICK ON MAP and MULTI SELECTION FUNCTIONS
82==============================================================
83*/
84
86{
87 return cgi->Cvar_GetInteger("geo_overlay_radar");
88}
89
90static inline bool GEO_IsNationOverlayActivated (void)
91{
92 return cgi->Cvar_GetInteger("geo_overlay_nation");
93}
94
95static inline bool GEO_IsXVIOverlayActivated (void)
96{
97 return cgi->Cvar_GetInteger("geo_overlay_xvi");
98}
99
107bool GEO_Click (const uiNode_t* node, int x, int y, const vec2_t pos)
108{
109 switch (ccs.mapAction) {
110 case MA_NEWBASE:
111 /* new base construction */
113 if (!MapIsWater(GEO_GetColor(pos, MAPTYPE_TERRAIN, nullptr))) {
114 if (B_GetCount() < MAX_BASES) {
115 Vector2Copy(pos, ccs.newBasePos);
117 cgi->Cmd_ExecuteString("mn_set_base_title");
118 cgi->UI_PushWindow("popup_newbase");
119 return true;
120 }
121 return false;
122 }
123 break;
125 if (!MapIsWater(GEO_GetColor(pos, MAPTYPE_TERRAIN, nullptr))) {
126 Vector2Copy(pos, ccs.newBasePos);
128 cgi->UI_PushWindow("popup_newinstallation");
129 return true;
130 }
131 break;
132 default:
133 break;
134 }
135
136 /* Init data for multi selection */
137 cgi->UI_ExecuteConfunc("ui_clear_geoscape_selection");
138 int selection_count = 0;
139 /* Get selected missions */
140 MIS_Foreach(tempMission) {
141 if (tempMission->stage == STAGE_NOT_ACTIVE || !tempMission->onGeoscape)
142 continue;
143 if (!tempMission->pos || !GEO_IsPositionSelected(node, tempMission->pos, x, y))
144 continue;
145 cgi->UI_ExecuteConfunc("ui_add_geoscape_selection \"%s\" \"%d\" \"%s\"",
146 "mission", MIS_GetIdx(tempMission), MIS_GetName(tempMission));
147 ++selection_count;
148 }
149
150 /* Get selected aircraft which belong */
151 AIR_Foreach(aircraft) {
152 if (AIR_IsAircraftOnGeoscape(aircraft) && aircraft->fuel > 0 && GEO_IsPositionSelected(node, aircraft->pos, x, y)) {
153 cgi->UI_ExecuteConfunc("ui_add_geoscape_selection \"%s\" \"%d\" \"%s\"",
154 "aircraft", aircraft->idx, aircraft->name);
155 ++selection_count;
156 }
157 }
158
159 /* Get selected bases */
160 base_t* base = nullptr;
161 while ((base = B_GetNext(base)) != nullptr) {
162 if (!GEO_IsPositionSelected(node, base->pos, x, y))
163 continue;
164 cgi->UI_ExecuteConfunc("ui_add_geoscape_selection \"%s\" \"%d\" \"%s\"",
165 "base", base->idx, base->name);
166 ++selection_count;
167 }
168
169 /* Get selected installations */
170 INS_Foreach(installation) {
171 if (!GEO_IsPositionSelected(node, installation->pos, x, y))
172 continue;
173 cgi->UI_ExecuteConfunc("ui_add_geoscape_selection \"%s\" \"%d\" \"%s\"",
174 "installation", installation->idx, installation->name);
175 ++selection_count;
176 }
177
178 /* Get selected ufos */
179 aircraft_t* ufo = nullptr;
180 while ((ufo = UFO_GetNextOnGeoscape(ufo)) != nullptr) {
181 if (AIR_IsAircraftOnGeoscape(ufo) && GEO_IsPositionSelected(node, ufo->pos, x, y)) {
182 cgi->UI_ExecuteConfunc("ui_add_geoscape_selection \"%s\" \"%lu\" \"%s\"",
183 "ufo", UFO_GetGeoscapeIDX(ufo), UFO_GetName(ufo));
184 ++selection_count;
185 }
186 }
187
188 if (selection_count > 0) {
190 cgi->UI_PushWindow("popup_geoscape_selection");
191 return true;
192 } else {
194 /* Nothing selected */
195 if (!aircraft) {
197 return false;
198 }
199
200 if (AIR_IsAircraftOnGeoscape(aircraft) && AIR_AircraftHasEnoughFuel(aircraft, pos)) {
201 /* Move the selected aircraft to the position clicked */
202 GEO_CalcLine(aircraft->pos, pos, &aircraft->route);
203 aircraft->status = AIR_TRANSIT;
204 aircraft->aircraftTarget = nullptr;
205 aircraft->time = 0;
206 aircraft->point = 0;
207 return true;
208 }
209 }
210 return false;
211}
212
213
214/*
215==============================================================
216GEOSCAPE DRAWING AND COORDINATES
217==============================================================
218*/
219
229static bool GEO_3DMapToScreen (const uiNode_t* node, const vec2_t pos, int* x, int* y, int* z)
230{
231 vec2_t mid;
232 vec3_t v, v1, rotationAxis;
233 const float radius = GLOBE_RADIUS;
234
235 PolarToVec(pos, v);
236
237 /* rotate the vector to switch of reference frame.
238 * We switch from the static frame of the earth to the local frame of the player */
239 VectorSet(rotationAxis, 0, 0, 1);
241 RotatePointAroundVector(v1, rotationAxis, v, - data.angles[PITCH]);
242
243 VectorSet(rotationAxis, 0, 1, 0);
244 RotatePointAroundVector(v, rotationAxis, v1, - data.angles[YAW]);
245
246 /* set mid to the coordinates of the center of the globe */
247 Vector2Set(mid, data.mapPos[0] + data.mapSize[0] / 2.0f, data.mapPos[1] + data.mapSize[1] / 2.0f);
248
249 /* We now convert those coordinates relative to the center of the globe to coordinates of the screen
250 * (which are relative to the upper left side of the screen) */
251 *x = (int) (mid[0] - radius * v[1]);
252 *y = (int) (mid[1] - radius * v[0]);
253
254 if (z)
255 *z = (int) (radius * v[2]);
256
257 /* if the point is on the wrong side of the earth, the player cannot see it */
258 if (v[2] > 0)
259 return false;
260
261 /* if the point is outside the screen, the player cannot see it */
262 if (*x < data.mapPos[0] && *y < data.mapPos[1]
263 && *x > data.mapPos[0] + data.mapSize[0]
264 && *y > data.mapPos[1] + data.mapSize[1])
265 return false;
266
267 return true;
268}
269
279static bool GEO_MapToScreen (const uiNode_t* node, const vec2_t pos, int* x, int* y)
280{
282 /* get "raw" position */
283 float sx = pos[0] / 360 + data.center[0] - 0.5;
284
285 /* shift it on screen */
286 if (sx < -0.5f)
287 sx += 1.0f;
288 else if (sx > +0.5f)
289 sx -= 1.0f;
290
291 *x = data.mapPos[0] + 0.5f * data.mapSize[0] - sx * data.mapSize[0] * data.zoom;
292 *y = data.mapPos[1] + 0.5f * data.mapSize[1] - (pos[1] / 180.0f + data.center[1] - 0.5f) * data.mapSize[1] * data.zoom;
293
294 if (*x < data.mapPos[0] && *y < data.mapPos[1]
295 && *x > data.mapPos[0] + data.mapSize[0]
296 && *y > data.mapPos[1] + data.mapSize[1])
297 return false;
298 return true;
299}
300
310static bool GEO_AllMapToScreen (const uiNode_t* node, const vec2_t pos, int* x, int* y, int* z)
311{
313 if (!data.flatgeoscape)
314 return GEO_3DMapToScreen(node, pos, x, y, z);
315
316 if (z)
317 *z = -10;
318 return GEO_MapToScreen(node, pos, x, y);
319}
320
325#define UI_MAP_DIST_SELECTION 15
329static bool GEO_IsPositionSelected (const uiNode_t* node, const vec2_t pos, int x, int y)
330{
331 int msx, msy;
332
333 if (GEO_AllMapToScreen(node, pos, &msx, &msy, nullptr))
334 if (x >= msx - UI_MAP_DIST_SELECTION && x <= msx + UI_MAP_DIST_SELECTION
335 && y >= msy - UI_MAP_DIST_SELECTION && y <= msy + UI_MAP_DIST_SELECTION)
336 return true;
337
338 return false;
339}
340
349static void GEO_Draw3DMarkerIfVisible (const uiNode_t* node, const vec2_t pos, float theta, const char* model, int skin)
350{
352 if (data.flatgeoscape) {
353 int x, y;
354 vec3_t screenPos;
355
356 GEO_AllMapToScreen(node, pos, &x, &y, nullptr);
357 VectorSet(screenPos, x, y, 0);
358 /* models are used on 2D geoscape for aircraft */
359 cgi->R_Draw2DMapMarkers(screenPos, theta, model, skin);
360 } else {
361 cgi->R_Draw3DMapMarkers(data.mapPos, data.mapSize, data.angles, pos, theta, GLOBE_RADIUS, model, skin);
362 }
363}
364
372void GEO_CalcLine (const vec2_t start, const vec2_t end, mapline_t* line)
373{
374 vec3_t s, e, v;
375 vec3_t normal;
376 vec2_t trafo, sa, ea;
377 float cosTrafo, sinTrafo;
378 float phiStart, phiEnd, dPhi, phi;
379 float* p;
380 int i, n;
381
382 /* get plane normal */
383 PolarToVec(start, s);
384 PolarToVec(end, e);
385 /* Procedure below won't work if start is the same like end */
386 if (VectorEqual(s, e)) {
387 line->distance = 0;
388 line->numPoints = 2;
389 Vector2Set(line->point[0], end[0], end[1]);
390 Vector2Set(line->point[1], end[0], end[1]);
391 return;
392 }
393
394 CrossProduct(s, e, normal);
395 VectorNormalize(normal);
396
397 /* get transformation */
398 VecToPolar(normal, trafo);
399 cosTrafo = cos(trafo[1] * torad);
400 sinTrafo = sin(trafo[1] * torad);
401
402 sa[0] = start[0] - trafo[0];
403 sa[1] = start[1];
404 PolarToVec(sa, s);
405 ea[0] = end[0] - trafo[0];
406 ea[1] = end[1];
407 PolarToVec(ea, e);
408
409 phiStart = atan2(s[1], cosTrafo * s[2] - sinTrafo * s[0]);
410 phiEnd = atan2(e[1], cosTrafo * e[2] - sinTrafo * e[0]);
411
412 /* get waypoints */
413 if (phiEnd < phiStart - M_PI)
414 phiEnd += 2 * M_PI;
415 if (phiEnd > phiStart + M_PI)
416 phiEnd -= 2 * M_PI;
417
418 n = (phiEnd - phiStart) / M_PI * LINE_MAXSEG;
419 if (n > 0)
420 n = n + 1;
421 else
422 n = -n + 1;
423
424 line->distance = fabs(phiEnd - phiStart) / n * todeg;
425 line->numPoints = n + 1;
426 /* make sure we do not exceed route array size */
427 assert(line->numPoints <= LINE_MAXPTS);
428 dPhi = (phiEnd - phiStart) / n;
429 p = nullptr;
430 for (phi = phiStart, i = 0; i <= n; phi += dPhi, i++) {
431 const float* last = p;
432 p = line->point[i];
433 VectorSet(v, -sinTrafo * cos(phi), sin(phi), cosTrafo * cos(phi));
434 VecToPolar(v, p);
435 p[0] += trafo[0];
436
437 if (!last) {
438 while (p[0] < -180.0)
439 p[0] += 360.0;
440 while (p[0] > +180.0)
441 p[0] -= 360.0;
442 } else {
443 while (p[0] - last[0] > +180.0)
444 p[0] -= 360.0;
445 while (p[0] - last[0] < -180.0)
446 p[0] += 360.0;
447 }
448 }
449}
450
458static void GEO_MapDrawLine (const uiNode_t* node, const mapline_t* line)
459{
460 const vec4_t color = {1, 0.5, 0.5, 1};
462 screenPoint_t* p;
463 int i, start, old;
464
465 /* draw */
466 cgi->R_Color(color);
467 start = 0;
469 old = data.mapSize[0] / 2;
470 for (i = 0, p = pts; i < line->numPoints; i++, p++) {
471 GEO_MapToScreen(node, line->point[i], &p->x, &p->y);
472
473 /* If we cross longitude 180 degree (right/left edge of the screen), draw the first part of the path */
474 if (i > start && abs(p->x - old) > data.mapSize[0] / 2) {
475 /* shift last point */
476 int diff;
477
478 if (p->x - old > data.mapSize[0] / 2)
479 diff = -data.mapSize[0] * data.zoom;
480 else
481 diff = data.mapSize[0] * data.zoom;
482 p->x += diff;
483
484 /* wrap around screen border */
485 cgi->R_DrawLineStrip(i - start, (int*)(&pts));
486
487 /* first path of the path is drawn, now we begin the second part of the path */
488 /* shift first point, continue drawing */
489 start = i;
490 pts[0].x = p[-1].x - diff;
491 pts[0].y = p[-1].y;
492 p = pts;
493 }
494 old = p->x;
495 }
496
497 cgi->R_DrawLineStrip(i - start, (int*)(&pts));
498 cgi->R_Color(nullptr);
499}
500
508static void GEO_3DMapDrawLine (const uiNode_t* node, const mapline_t* line)
509{
510 const vec4_t color = {1, 0.5, 0.5, 1};
512 int numPoints, start;
513
514 start = 0;
515 numPoints = 0;
516
517 /* draw only when the point of the path is visible */
518 cgi->R_Color(color);
519 for (int i = 0; i < line->numPoints; i++) {
520 if (GEO_3DMapToScreen(node, line->point[i], &pts[i].x, &pts[i].y, nullptr))
521 numPoints++;
522 else if (!numPoints)
523 /* the point which is not drawn is at the beginning of the path */
524 start++;
525 }
526
527 cgi->R_DrawLineStrip(numPoints, (int*)(&pts[start]));
528 cgi->R_Color(nullptr);
529}
530
531#define CIRCLE_DRAW_POINTS 60
541static void GEO_MapDrawEquidistantPoints (const uiNode_t* node, const vec2_t center, const float angle, const vec4_t color)
542{
544 int numPoints = 0;
545 vec3_t initialVector, rotationAxis, currentPoint, centerPos;
546
547 cgi->R_Color(color);
548
549 /* Set centerPos corresponding to cartesian coordinates of the center point */
550 PolarToVec(center, centerPos);
551
552 /* Find a perpendicular vector to centerPos, and rotate centerPos around it to obtain one point distant of angle from centerPos */
553 PerpendicularVector(rotationAxis, centerPos);
554 RotatePointAroundVector(initialVector, rotationAxis, centerPos, angle);
555
556 bool draw = false;
557 bool oldDraw = false;
559 /* Now, each equidistant point is given by a rotation around centerPos */
560 for (int i = 0; i <= CIRCLE_DRAW_POINTS; i++) {
561 int xCircle, yCircle;
562 vec2_t posCircle;
563 const float degrees = i * 360.0f / (float)CIRCLE_DRAW_POINTS;
564 RotatePointAroundVector(currentPoint, centerPos, initialVector, degrees);
565 VecToPolar(currentPoint, posCircle);
566 if (GEO_AllMapToScreen(node, posCircle, &xCircle, &yCircle, nullptr)) {
567 draw = true;
568 if (data.flatgeoscape && numPoints != 0 && abs(pts[numPoints - 1].x - xCircle) > 512)
569 oldDraw = false;
570 }
571
572 /* if moving from a point of the screen to a distant one, draw the path we already calculated, and begin a new path
573 * (to avoid unwanted lines) */
574 if (draw != oldDraw && i != 0) {
575 cgi->R_DrawLineStrip(numPoints, (int*)(&pts));
576 numPoints = 0;
577 }
578 /* if the current point is to be drawn, add it to the path */
579 if (draw) {
580 pts[numPoints].x = xCircle;
581 pts[numPoints].y = yCircle;
582 numPoints++;
583 }
584 /* update value of oldDraw */
585 oldDraw = draw;
586 }
587
588 /* Draw the last path */
589 cgi->R_DrawLineStrip(numPoints, (int*)(&pts));
590 cgi->R_Color(nullptr);
591}
592
601static float GEO_AngleOfPath3D (const vec2_t start, const vec2_t end, vec3_t direction, vec3_t ortVector)
602{
603 vec3_t start3D, end3D, north3D, ortToDest, ortToPole, v;
604 const vec2_t northPole = {0.0f, 90.0f};
605
606 PolarToVec(start, start3D);
607 PolarToVec(end, end3D);
608 PolarToVec(northPole, north3D);
609
610 /* calculate the vector othogonal to movement */
611 CrossProduct(start3D, end3D, ortToDest);
612 VectorNormalize(ortToDest);
613 if (ortVector) {
614 VectorCopy(ortToDest, ortVector);
615 }
616
617 /* calculate the vector othogonal to north pole (from model location) */
618 CrossProduct(start3D, north3D, ortToPole);
619 VectorNormalize(ortToPole);
620
625 /* smooth change of direction if the model is not idle */
626 if (direction) {
627 VectorSubtract(ortToDest, direction, v);
628 const float dist = VectorLength(v);
629 if (dist > 0.01) {
630 vec3_t rotationAxis;
631 CrossProduct(direction, ortToDest, rotationAxis);
632 VectorNormalize(rotationAxis);
633 RotatePointAroundVector(v, rotationAxis, direction, 5.0);
634 VectorCopy(v, direction);
635 VectorSubtract(ortToDest, direction, v);
636 if (VectorLength(v) < dist)
637 VectorCopy(direction, ortToDest);
638 else
639 VectorCopy(ortToDest, direction);
640 }
641 }
642
643 /* calculate the angle the model is making at earth surface with north pole direction */
644 float angle = todeg * acos(DotProduct(ortToDest, ortToPole));
645 /* with arcos, you only get the absolute value of the angle: get the sign */
646 CrossProduct(ortToDest, ortToPole, v);
647 if (DotProduct(start3D, v) < 0)
648 angle = - angle;
649
650 return angle;
651}
652
661static float GEO_AngleOfPath2D (const vec2_t start, const vec2_t end, vec3_t direction, vec3_t ortVector)
662{
663 vec3_t start3D, end3D, tangentVector, v, rotationAxis;
664
665 /* calculate the vector tangent to movement */
666 PolarToVec(start, start3D);
667 PolarToVec(end, end3D);
668 if (ortVector) {
669 CrossProduct(start3D, end3D, ortVector);
670 VectorNormalize(ortVector);
671 CrossProduct(ortVector, start3D, tangentVector);
672 } else {
673 CrossProduct(start3D, end3D, v);
674 CrossProduct(v, start3D, tangentVector);
675 }
676 VectorNormalize(tangentVector);
677
678 /* smooth change of direction if the model is not idle */
679 if (direction) {
680 VectorSubtract(tangentVector, direction, v);
681 const float dist = VectorLength(v);
682 if (dist > 0.01) {
683 CrossProduct(direction, tangentVector, rotationAxis);
684 VectorNormalize(rotationAxis);
685 RotatePointAroundVector(v, rotationAxis, direction, 5.0);
686 VectorSubtract(tangentVector, direction, v);
687 if (VectorLength(v) < dist)
688 VectorCopy(direction, tangentVector);
689 else
690 VectorCopy(tangentVector, direction);
691 }
692 }
693
694 VectorSet(rotationAxis, 0, 0, 1);
695 RotatePointAroundVector(v, rotationAxis, tangentVector, - start[0]);
696 VectorSet(rotationAxis, 0, 1, 0);
697 RotatePointAroundVector(tangentVector, rotationAxis, v, start[1] + 90.0f);
698
699 /* calculate the orientation angle of the model around axis perpendicular to the screen */
700 float angle = todeg * atan(tangentVector[0] / tangentVector[1]);
701 if (tangentVector[1] > 0)
702 angle -= 90.0f;
703 else
704 angle += 90.0f;
705
706 return angle;
707}
708
717float GEO_AngleOfPath (const vec2_t start, const vec2_t end, vec3_t direction, vec3_t ortVector)
718{
719 uiNode_t* node = geoscapeNode;
720 if (!node)
721 return 0.0f;
722
723 const mapExtraData_t& data = UI_MAPEXTRADATA(node);
724 if (!data.flatgeoscape)
725 return GEO_AngleOfPath3D(start, end, direction, ortVector);
726 return GEO_AngleOfPath2D(start, end, direction, ortVector);
727}
728
735static void GEO_ConvertObjectPositionToGeoscapePosition (bool flatgeoscape, float* vector, const vec2_t objectPos)
736{
737 if (flatgeoscape)
738 Vector2Set(vector, objectPos[0], objectPos[1]);
739 else
740 VectorSet(vector, objectPos[0], -objectPos[1], 0);
741}
742
746static void GEO_GetMissionAngle (bool flatgeoscape, float* vector, int id)
747{
748 mission_t* mission = MIS_GetByIdx(id);
749 if (mission == nullptr)
750 return;
751 GEO_ConvertObjectPositionToGeoscapePosition(flatgeoscape, vector, mission->pos);
752 GEO_SelectMission(mission);
753}
754
758static void GEO_GetUFOAngle (bool flatgeoscape, float* vector, int idx)
759{
760 aircraft_t* ufo;
761
762 /* Cycle through UFOs (only those visible on geoscape) */
763 ufo = nullptr;
764 while ((ufo = UFO_GetNextOnGeoscape(ufo)) != nullptr) {
765 if (ufo->idx != idx)
766 continue;
767 GEO_ConvertObjectPositionToGeoscapePosition(flatgeoscape, vector, ufo->pos);
768 GEO_SelectUFO(ufo);
769 return;
770 }
771}
772
773
777static void GEO_StartCenter (uiNode_t* node)
778{
780 if (data.flatgeoscape) {
781 /* case 2D geoscape */
782 vec2_t diff;
783
784 Vector2Set(data.smoothFinal2DGeoscapeCenter, 0.5f - data.smoothFinal2DGeoscapeCenter[0] / 360.0f,
785 0.5f - data.smoothFinal2DGeoscapeCenter[1] / 180.0f);
786 if (data.smoothFinal2DGeoscapeCenter[1] < 0.5 / ZOOM_LIMIT)
787 data.smoothFinal2DGeoscapeCenter[1] = 0.5 / ZOOM_LIMIT;
788 if (data.smoothFinal2DGeoscapeCenter[1] > 1.0 - 0.5 / ZOOM_LIMIT)
789 data.smoothFinal2DGeoscapeCenter[1] = 1.0 - 0.5 / ZOOM_LIMIT;
790 diff[0] = data.smoothFinal2DGeoscapeCenter[0] - data.center[0];
791 diff[1] = data.smoothFinal2DGeoscapeCenter[1] - data.center[1];
792 data.smoothDeltaLength = sqrt(diff[0] * diff[0] + diff[1] * diff[1]);
793 } else {
794 /* case 3D geoscape */
795 vec3_t diff;
796
797 data.smoothFinalGlobeAngle[1] += GLOBE_ROTATE;
798 VectorSubtract(data.smoothFinalGlobeAngle, data.angles, diff);
799 data.smoothDeltaLength = VectorLength(diff);
800 }
801
802 data.smoothFinalZoom = ZOOM_LIMIT;
803 data.smoothDeltaZoom = fabs(data.smoothFinalZoom - data.zoom);
804 data.smoothRotation = true;
805}
806
812{
813 uiNode_t* node = geoscapeNode;
814 if (!node)
815 return;
817 const bool flatgeoscape = data.flatgeoscape;
818 float* vector;
819 if (flatgeoscape)
820 vector = data.smoothFinal2DGeoscapeCenter;
821 else
822 vector = data.smoothFinalGlobeAngle;
823
824 GEO_ConvertObjectPositionToGeoscapePosition(flatgeoscape, vector, pos);
825 GEO_StartCenter(node);
826}
827
831static void GEO_SelectObject_f (void)
832{
833 uiNode_t* node = geoscapeNode;
834 if (!node)
835 return;
836
837 if (cgi->Cmd_Argc() != 3) {
838 cgi->Com_Printf("Usage: %s <mission|ufo> <id>\n", cgi->Cmd_Argv(0));
839 return;
840 }
841
842 const char* type = cgi->Cmd_Argv(1);
843 const int idx = atoi(cgi->Cmd_Argv(2));
845 const bool flatgeoscape = data.flatgeoscape;
846
847 float* vector;
848 if (flatgeoscape)
849 vector = data.smoothFinal2DGeoscapeCenter;
850 else
851 vector = data.smoothFinalGlobeAngle;
852
853 if (Q_streq(type, "mission"))
854 GEO_GetMissionAngle(flatgeoscape, vector, idx);
855 else if (Q_streq(type, "ufo"))
856 GEO_GetUFOAngle(flatgeoscape, vector, idx);
857 else {
858 cgi->Com_Printf("GEO_SelectObject_f: type %s unsupported.", type);
859 return;
860 }
861 GEO_StartCenter(node);
862}
863
869{
870 int counter = 0;
871 int maxEventIdx;
872 const int numMissions = CP_CountMissionOnGeoscape();
873 aircraft_t* ufo;
874 base_t* base;
875 int numBases = B_GetCount();
876
877 /* If the value of maxEventIdx is too big or to low, restart from beginning */
878 maxEventIdx = numMissions + numBases + INS_GetCount() - 1;
879 base = nullptr;
880 while ((base = B_GetNext(base)) != nullptr) {
881 AIR_ForeachFromBase(aircraft, base) {
882 if (AIR_IsAircraftOnGeoscape(aircraft))
883 maxEventIdx++;
884 }
885 }
886 ufo = nullptr;
887 while ((ufo = UFO_GetNextOnGeoscape(ufo)) != nullptr)
888 maxEventIdx++;
889
890 /* if there's nothing to center the view on, just go to 0,0 pos */
891 if (maxEventIdx < 0) {
893 return;
894 }
895
896 /* check centerOnEventIdx is within the bounds */
897 if (centerOnEventIdx < 0)
898 centerOnEventIdx = maxEventIdx;
899 if (centerOnEventIdx > maxEventIdx)
901
902 /* Cycle through missions */
903 if (centerOnEventIdx < numMissions) {
904 MIS_Foreach(mission) {
905 if (!mission->onGeoscape)
906 continue;
907 if (counter == centerOnEventIdx) {
908 Vector2Copy(mission->pos, pos);
909 GEO_SelectMission(mission);
910 return;
911 }
912 counter++;
913 }
914 }
915 counter = numMissions;
916
917 /* Cycle through bases */
918 if (centerOnEventIdx < numBases + counter) {
919 base = nullptr;
920 while ((base = B_GetNext(base)) != nullptr) {
921 if (counter == centerOnEventIdx) {
922 Vector2Copy(base->pos, pos);
923 return;
924 }
925 counter++;
926 }
927 }
928 counter += numBases;
929
930 /* Cycle through installations */
931 if (centerOnEventIdx < INS_GetCount() + counter) {
932 INS_Foreach(inst) {
933 if (counter == centerOnEventIdx) {
934 Vector2Copy(inst->pos, pos);
935 return;
936 }
937 counter++;
938 }
939 }
940 counter += INS_GetCount();
941
942 /* Cycle through aircraft (only those present on geoscape) */
943 AIR_Foreach(aircraft) {
944 if (AIR_IsAircraftOnGeoscape(aircraft)) {
945 if (centerOnEventIdx == counter) {
946 Vector2Copy(aircraft->pos, pos);
947 GEO_SelectAircraft(aircraft);
948 return;
949 }
950 counter++;
951 }
952 }
953
954 /* Cycle through UFOs (only those visible on geoscape) */
955 ufo = nullptr;
956 while ((ufo = UFO_GetNextOnGeoscape(ufo)) != nullptr) {
957 if (centerOnEventIdx == counter) {
958 Vector2Copy(ufo->pos, pos);
959 GEO_SelectUFO(ufo);
960 return;
961 }
962 counter++;
963 }
964}
965
973{
974 if (!Q_streq(cgi->UI_GetActiveWindowName(), "geoscape"))
975 return;
976
978
979 uiNode_t* node = geoscapeNode;
980 if (!node)
981 return;
982
983 vec2_t pos;
986}
987
988#define BULLET_SIZE 1
995static void GEO_DrawBullets (const uiNode_t* node, const vec3_t pos)
996{
997 int x, y;
998
999 if (GEO_AllMapToScreen(node, pos, &x, &y, nullptr))
1000 cgi->R_DrawFill(x, y, BULLET_SIZE, BULLET_SIZE, yellow);
1001}
1002
1011static void GEO_DrawBeam (const uiNode_t* node, const vec3_t start, const vec3_t end, const vec4_t color)
1012{
1013 int points[4];
1014
1015 if (!GEO_AllMapToScreen(node, start, &(points[0]), &(points[1]), nullptr))
1016 return;
1017 if (!GEO_AllMapToScreen(node, end, &(points[2]), &(points[3]), nullptr))
1018 return;
1019
1020 cgi->R_Color(color);
1021 cgi->R_DrawLine(points, 2.0);
1022 cgi->R_Color(nullptr);
1023}
1024
1025static inline void GEO_RenderImage (int x, int y, const char* image)
1026{
1027 cgi->R_DrawImageCentered(x, y, image);
1028}
1029
1030#define SELECT_CIRCLE_RADIUS 1.5f + 3.0f / UI_MAPEXTRADATACONST(node).zoom
1031
1037static void GEO_DrawMapOneMission (const uiNode_t* node, const mission_t* mission)
1038{
1039 int x, y;
1040 const bool isCurrentSelectedMission = GEO_IsMissionSelected(mission);
1041
1042 if (isCurrentSelectedMission)
1043 cgi->Cvar_Set("mn_mapdaytime", GEO_IsNight(mission->pos) ? _("Night") : _("Day"));
1044
1045 if (!GEO_AllMapToScreen(node, mission->pos, &x, &y, nullptr))
1046 return;
1047
1049 if (isCurrentSelectedMission) {
1050 /* Draw circle around the mission */
1051 if (data.flatgeoscape) {
1052 if (mission->active) {
1053 GEO_RenderImage(x, y, "pics/geoscape/circleactive");
1054 } else {
1055 GEO_RenderImage(x, y, "pics/geoscape/circle");
1056 }
1057 } else {
1058 if (!mission->active)
1060 }
1061 }
1062
1063 /* Draw mission model (this must be called after drawing the selection circle so that the model is rendered on top of it)*/
1064 if (data.flatgeoscape) {
1065 GEO_RenderImage(x, y, "pics/geoscape/mission");
1066 } else {
1067 GEO_Draw3DMarkerIfVisible(node, mission->pos, defaultBaseAngle, MIS_GetModel(mission), 0);
1068 }
1069
1070 cgi->UI_DrawString("f_verysmall", ALIGN_UL, x + 10, y, MIS_GetName(mission));
1071}
1072
1079static void GEO_DrawRadarLineCoverage (const uiNode_t* node, const radar_t* radar, const vec2_t pos)
1080{
1081 const vec4_t color = {1., 1., 1., .4};
1082 GEO_MapDrawEquidistantPoints(node, pos, radar->range, color);
1083 GEO_MapDrawEquidistantPoints(node, pos, radar->trackingRange, color);
1084}
1085
1092static void GEO_DrawRadarInMap (const uiNode_t* node, const radar_t* radar, const vec2_t pos)
1093{
1094 /* Show radar range zones */
1095 GEO_DrawRadarLineCoverage(node, radar, pos);
1096
1097 /* everything below is drawn only if there is at least one detected UFO */
1098 if (!radar->numUFOs)
1099 return;
1100
1101 /* Draw lines from radar to ufos sensored */
1102 int x, y;
1103 const bool display = GEO_AllMapToScreen(node, pos, &x, &y, nullptr);
1104 if (!display)
1105 return;
1106
1107 screenPoint_t pts[2];
1108 pts[0].x = x;
1109 pts[0].y = y;
1110
1111 /* Set color */
1112 const vec4_t color = {1., 1., 1., .3};
1113 cgi->R_Color(color);
1114 for (int i = 0; i < radar->numUFOs; i++) {
1115 const aircraft_t* ufo = radar->ufos[i];
1116 if (UFO_IsUFOSeenOnGeoscape(ufo) && GEO_AllMapToScreen(node, ufo->pos, &x, &y, nullptr)) {
1117 pts[1].x = x;
1118 pts[1].y = y;
1119 cgi->R_DrawLineStrip(2, (int*)pts);
1120 }
1121 }
1122 cgi->R_Color(nullptr);
1123}
1124
1133static void GEO_DrawMapOneInstallation (const uiNode_t* node, const installation_t* installation,
1134 bool oneUFOVisible, const char* font)
1135{
1136 const installationTemplate_t* tpl = installation->installationTemplate;
1137
1138 /* Draw weapon range if at least one UFO is visible */
1139 if (oneUFOVisible && AII_InstallationCanShoot(installation)) {
1140 for (int i = 0; i < tpl->maxBatteries; i++) {
1141 const aircraftSlot_t* slot = &installation->batteries[i].slot;
1142 if (slot->item && slot->ammoLeft != 0 && slot->installationTime == 0) {
1143 GEO_MapDrawEquidistantPoints(node, installation->pos,
1145 }
1146 }
1147 }
1148
1149 /* Draw installation radar (only the "wire" style part) */
1151 GEO_DrawRadarInMap(node, &installation->radar, installation->pos);
1152
1153 int x, y;
1154 /* Draw installation */
1155 if (!UI_MAPEXTRADATACONST(node).flatgeoscape) {
1156 GEO_Draw3DMarkerIfVisible(node, installation->pos, defaultBaseAngle, tpl->model, 0);
1157 } else if (GEO_MapToScreen(node, installation->pos, &x, &y)) {
1158 GEO_RenderImage(x, y, tpl->image);
1159 }
1160
1161 /* Draw installation names */
1162 if (GEO_AllMapToScreen(node, installation->pos, &x, &y, nullptr))
1163 cgi->UI_DrawString(font, ALIGN_UL, x, y + 10, installation->name);
1164}
1165
1173static void GEO_DrawMapOneBase (const uiNode_t* node, const base_t* base,
1174 bool oneUFOVisible, const char* font)
1175{
1176 /* Draw weapon range if at least one UFO is visible */
1177 if (oneUFOVisible && AII_BaseCanShoot(base)) {
1178 int i;
1179 for (i = 0; i < base->numBatteries; i++) {
1180 const aircraftSlot_t* slot = &base->batteries[i].slot;
1181 if (slot->item && slot->ammoLeft != 0 && slot->installationTime == 0) {
1184 }
1185 }
1186 for (i = 0; i < base->numLasers; i++) {
1187 const aircraftSlot_t* slot = &base->lasers[i].slot;
1188 if (slot->item && slot->ammoLeft != 0 && slot->installationTime == 0) {
1191 }
1192 }
1193 }
1194
1195 /* Draw base radar (only the "wire" style part) */
1197 GEO_DrawRadarInMap(node, &base->radar, base->pos);
1198
1199 int x, y;
1200 /* Draw base */
1201 if (!UI_MAPEXTRADATACONST(node).flatgeoscape) {
1202 if (B_IsUnderAttack(base))
1203 /* two skins - second skin is for baseattack */
1204 GEO_Draw3DMarkerIfVisible(node, base->pos, defaultBaseAngle, "geoscape/base", 1);
1205 else
1206 GEO_Draw3DMarkerIfVisible(node, base->pos, defaultBaseAngle, "geoscape/base", 0);
1207 } else if (GEO_MapToScreen(node, base->pos, &x, &y)) {
1208 if (B_IsUnderAttack(base))
1209 GEO_RenderImage(x, y, "pics/geoscape/baseattack");
1210 else
1211 GEO_RenderImage(x, y, "pics/geoscape/base");
1212 }
1213
1214 /* Draw base names */
1215 if (GEO_AllMapToScreen(node, base->pos, &x, &y, nullptr))
1216 cgi->UI_DrawString(font, ALIGN_UL, x, y + 10, base->name);
1217}
1218
1225static void GEO_DrawAircraftHealthBar (const uiNode_t* node, const aircraft_t* aircraft)
1226{
1228 const int width = 8 * data.zoom;
1229 const int height = 1 * data.zoom * 0.9f;
1230 vec4_t color;
1231 int centerX;
1232 int centerY;
1233 bool visible;
1234
1235 if (!aircraft)
1236 return;
1237 if (aircraft->stats[AIR_STATS_DAMAGE] <= 0)
1238 return;
1239
1240 if (((float)aircraft->damage / aircraft->stats[AIR_STATS_DAMAGE]) <= .33f) {
1241 Vector4Copy(red, color);
1242 } else if (((float)aircraft->damage / aircraft->stats[AIR_STATS_DAMAGE]) <= .75f) {
1243 Vector4Copy(yellow, color);
1244 } else {
1245 Vector4Copy(green, color);
1246 }
1247
1248 if (!data.flatgeoscape)
1249 visible = GEO_3DMapToScreen(node, aircraft->pos, &centerX, &centerY, nullptr);
1250 else
1251 visible = GEO_AllMapToScreen(node, aircraft->pos, &centerX, &centerY, nullptr);
1252
1253 if (visible) {
1254 const vec4_t bordercolor = {1, 1, 1, 1};
1255 cgi->R_DrawFill(centerX - width / 2 , centerY - 5 * data.zoom, round(width * ((float)aircraft->damage / aircraft->stats[AIR_STATS_DAMAGE])), height, color);
1256 cgi->R_DrawRect(centerX - width / 2, centerY - 5 * data.zoom, width, height, bordercolor, 1.0, 1);
1257 }
1258}
1259
1266static void GEO_DrawMapOnePhalanxAircraft (const uiNode_t* node, aircraft_t* aircraft, bool oneUFOVisible)
1267{
1268 float angle;
1269
1270 /* Draw aircraft radar (only the "wire" style part) */
1272 GEO_DrawRadarInMap(node, &aircraft->radar, aircraft->pos);
1273
1274 /* Draw only the bigger weapon range on geoscape: more detail will be given on airfight map */
1275 if (oneUFOVisible)
1276 GEO_MapDrawEquidistantPoints(node, aircraft->pos, aircraft->stats[AIR_STATS_WRANGE] / 1000.0f, red);
1277
1279 /* Draw aircraft route */
1280 if (aircraft->status >= AIR_TRANSIT) {
1281 /* aircraft is moving */
1282 mapline_t path;
1283
1284 path.numPoints = aircraft->route.numPoints - aircraft->point;
1286 if (path.numPoints > 1) {
1287 memcpy(path.point, aircraft->pos, sizeof(vec2_t));
1288 memcpy(path.point + 1, aircraft->route.point + aircraft->point + 1, (path.numPoints - 1) * sizeof(vec2_t));
1289 if (!data.flatgeoscape)
1290 GEO_3DMapDrawLine(node, &path);
1291 else
1292 GEO_MapDrawLine(node, &path);
1293 }
1294 angle = GEO_AngleOfPath(aircraft->pos, aircraft->route.point[aircraft->route.numPoints - 1], aircraft->direction, nullptr);
1295 } else {
1296 /* aircraft is idle */
1297 angle = 0.0f;
1298 }
1299
1300 /* Draw a circle around selected aircraft */
1301 if (GEO_IsAircraftSelected(aircraft)) {
1302 int x;
1303 int y;
1304
1305 if (!data.flatgeoscape)
1307 else {
1308 GEO_AllMapToScreen(node, aircraft->pos, &x, &y, nullptr);
1309 GEO_RenderImage(x, y, "pics/geoscape/circleactive");
1310 }
1311
1312 /* Draw a circle around the ufo pursued by selected aircraft */
1313 if (aircraft->status == AIR_UFO && GEO_AllMapToScreen(node, aircraft->aircraftTarget->pos, &x, &y, nullptr)) {
1314 if (!data.flatgeoscape)
1316 else
1317 GEO_RenderImage(x, y, "pics/geoscape/circleactive");
1318 }
1319 }
1320
1321 /* Draw aircraft (this must be called after drawing the selection circle so that the aircraft is drawn on top of it)*/
1322 GEO_Draw3DMarkerIfVisible(node, aircraft->pos, angle, aircraft->model, 0);
1323
1325 if (oneUFOVisible || cgi->Cvar_GetInteger("debug_showcrafthealth") >= 1)
1326 GEO_DrawAircraftHealthBar(node, aircraft);
1327}
1328
1336static const char* GEO_GetMissionText (char* buffer, size_t size, const mission_t* mission)
1337{
1338 assert(mission);
1339 Com_sprintf(buffer, size, _("Name: %s\nObjective: %s"),
1340 MIS_GetName(mission), (mission->mapDef) ? _(mission->mapDef->description) : _("Unknown"));
1341 return buffer;
1342}
1343
1351static const char* GEO_GetAircraftText (char* buffer, size_t size, const aircraft_t* aircraft)
1352{
1353 if (aircraft->status == AIR_UFO) {
1354 const float distance = GetDistanceOnGlobe(aircraft->pos, aircraft->aircraftTarget->pos);
1355 Com_sprintf(buffer, size, _("Name:\t%s (%i/%i)\n"), aircraft->name, AIR_GetTeamSize(aircraft), aircraft->maxTeamSize);
1356 Q_strcat(buffer, size, _("Status:\t%s\n"), AIR_AircraftStatusToName(aircraft));
1357 if (aircraft->stats[AIR_STATS_DAMAGE] > 0)
1358 Q_strcat(buffer, size, _("Health:\t%3.0f%%\n"), (double)aircraft->damage * 100 / aircraft->stats[AIR_STATS_DAMAGE]);
1359 Q_strcat(buffer, size, _("Distance to target:\t\t%.0f\n"), distance);
1360 Q_strcat(buffer, size, _("Speed:\t%i km/h\n"), AIR_AircraftMenuStatsValues(aircraft->stats[AIR_STATS_SPEED], AIR_STATS_SPEED));
1361 Q_strcat(buffer, size, _("Fuel:\t%i/%i\n"), AIR_AircraftMenuStatsValues(aircraft->fuel, AIR_STATS_FUELSIZE),
1363 Q_strcat(buffer, size, _("ETA:\t%sh\n"), CP_SecondConvert((float)DateTime::SECONDS_PER_HOUR * distance / aircraft->stats[AIR_STATS_SPEED]));
1364 } else {
1365 Com_sprintf(buffer, size, _("Name:\t%s (%i/%i)\n"), aircraft->name, AIR_GetTeamSize(aircraft), aircraft->maxTeamSize);
1366 Q_strcat(buffer, size, _("Status:\t%s\n"), AIR_AircraftStatusToName(aircraft));
1367 if (aircraft->stats[AIR_STATS_DAMAGE] > 0)
1368 Q_strcat(buffer, size, _("Health:\t%3.0f%%\n"), (double)aircraft->damage * 100 / aircraft->stats[AIR_STATS_DAMAGE]);
1369 Q_strcat(buffer, size, _("Speed:\t%i km/h\n"), AIR_AircraftMenuStatsValues(aircraft->stats[AIR_STATS_SPEED], AIR_STATS_SPEED));
1370 Q_strcat(buffer, size, _("Fuel:\t%i/%i\n"), AIR_AircraftMenuStatsValues(aircraft->fuel, AIR_STATS_FUELSIZE),
1372 if (aircraft->status != AIR_IDLE) {
1373 const float distance = GetDistanceOnGlobe(aircraft->pos,
1374 aircraft->route.point[aircraft->route.numPoints - 1]);
1375 Q_strcat(buffer, size, _("ETA:\t%sh\n"), CP_SecondConvert((float)DateTime::SECONDS_PER_HOUR * distance / aircraft->stats[AIR_STATS_SPEED]));
1376 }
1377 }
1378 return buffer;
1379}
1380
1388static const char* GEO_GetUFOText (char* buffer, size_t size, const aircraft_t* ufo)
1389{
1390 Com_sprintf(buffer, size, "%s\n", UFO_GetName(ufo));
1391 Q_strcat(buffer, size, _("Speed: %i km/h\n"), AIR_AircraftMenuStatsValues(ufo->stats[AIR_STATS_SPEED], AIR_STATS_SPEED));
1392 return buffer;
1393}
1394
1399{
1400 char buf[512];
1401 aircraft_t* ufo;
1402
1403 cgi->UI_ExecuteConfunc("clean_geoscape_object");
1404
1405 /* draw mission pics */
1406 MIS_Foreach(mission) {
1407 if (!mission->onGeoscape)
1408 continue;
1409 cgi->UI_ExecuteConfunc("add_geoscape_object mission %i \"%s\" \"%s\n%s\"",
1410 mission->idx, MIS_GetModel(mission), MIS_GetName(mission),
1411 (mission->mapDef) ? _(mission->mapDef->description) : "");
1412 }
1413
1414 /* draws ufos */
1415 ufo = nullptr;
1416 while ((ufo = UFO_GetNextOnGeoscape(ufo)) != nullptr) {
1417 const unsigned int ufoIDX = UFO_GetGeoscapeIDX(ufo);
1418 cgi->UI_ExecuteConfunc("add_geoscape_object ufo %i %s \"%s\"",
1419 ufoIDX, ufo->model, GEO_GetUFOText(buf, sizeof(buf), ufo));
1420 }
1421}
1422
1432void GEO_DrawMarkers (const uiNode_t* node)
1433{
1434 const char* font;
1435 aircraft_t* ufo;
1436 base_t* base;
1437
1438 const vec4_t white = {1.f, 1.f, 1.f, 0.7f};
1439 int maxInterpolationPoints;
1440
1441 assert(node);
1442
1443 /* font color on geoscape */
1444 cgi->R_Color(node->color);
1445 /* default font */
1446 font = cgi->UI_GetFontFromNode(node);
1447
1448 /* check if at least 1 UFO is visible */
1449 const bool oneUFOVisible = UFO_GetNextOnGeoscape(nullptr) != nullptr;
1450
1451 /* draw mission pics */
1452 MIS_Foreach(mission) {
1453 if (!mission->onGeoscape)
1454 continue;
1455 GEO_DrawMapOneMission(node, mission);
1456 }
1457
1458 /* draw installations */
1459 INS_Foreach(installation) {
1460 GEO_DrawMapOneInstallation(node, installation, oneUFOVisible, font);
1461 }
1462
1463 /* draw bases */
1464 base = nullptr;
1465 while ((base = B_GetNext(base)) != nullptr)
1466 GEO_DrawMapOneBase(node, base, oneUFOVisible, font);
1467
1468 /* draw all aircraft */
1469 AIR_Foreach(aircraft) {
1470 if (AIR_IsAircraftOnGeoscape(aircraft))
1471 GEO_DrawMapOnePhalanxAircraft(node, aircraft, oneUFOVisible);
1472 }
1473
1474 /* draws ufos */
1475 ufo = nullptr;
1476 while ((ufo = UFO_GetNextOnGeoscape(ufo)) != nullptr) {
1477#ifdef DEBUG
1478 /* in debug mode you execute set showufos 1 to see the ufos on geoscape */
1479 if (cgi->Cvar_GetInteger("debug_showufos")) {
1480 /* Draw ufo route */
1481 if (!UI_MAPEXTRADATACONST(node).flatgeoscape)
1482 GEO_3DMapDrawLine(node, &ufo->route);
1483 else
1484 GEO_MapDrawLine(node, &ufo->route);
1485 } else
1486#endif
1487 {
1488 const float angle = GEO_AngleOfPath(ufo->pos, ufo->route.point[ufo->route.numPoints - 1], ufo->direction, nullptr);
1490
1491 if (!data.flatgeoscape)
1493
1494 if (GEO_IsUFOSelected(ufo)) {
1495 if (!data.flatgeoscape) {
1497 } else {
1498 int x, y;
1499 GEO_AllMapToScreen(node, ufo->pos, &x, &y, nullptr);
1500 GEO_RenderImage(x, y, "pics/geoscape/circleactive");
1501 }
1502 }
1503 GEO_Draw3DMarkerIfVisible(node, ufo->pos, angle, ufo->model, 0);
1504
1506 if (RS_IsResearched_ptr(ufo->tech)
1507 || cgi->Cvar_GetInteger("debug_showcrafthealth") >= 1)
1508 GEO_DrawAircraftHealthBar(node, ufo);
1509 }
1510 }
1511
1512 if (ccs.gameTimeScale > 0)
1513 maxInterpolationPoints = floor(1.0f / (ccs.frametime * (float)ccs.gameTimeScale));
1514 else
1515 maxInterpolationPoints = 0;
1516
1517 /* draws projectiles */
1518 for (int i = 0; i < ccs.numProjectiles; i++) {
1519 aircraftProjectile_t* projectile = &ccs.projectiles[i];
1520 vec3_t drawPos = {0, 0, 0};
1521
1522 if (projectile->hasMoved) {
1523 projectile->hasMoved = false;
1524 VectorCopy(projectile->pos[0], drawPos);
1525 } else {
1526 if (maxInterpolationPoints > 2 && projectile->numInterpolationPoints < maxInterpolationPoints) {
1527 /* If a new point hasn't been given and there is at least 3 points need to be filled in then
1528 * use linear interpolation to draw the points until a new projectile point is provided.
1529 * The reason you need at least 3 points is that acceptable results can be achieved with 2 or less
1530 * gaps in points so don't add the overhead of interpolation. */
1531 const float xInterpolStep = (projectile->projectedPos[0][0] - projectile->pos[0][0]) / (float)maxInterpolationPoints;
1532 projectile->numInterpolationPoints += 1;
1533 drawPos[0] = projectile->pos[0][0] + (xInterpolStep * projectile->numInterpolationPoints);
1534 LinearInterpolation(projectile->pos[0], projectile->projectedPos[0], drawPos[0], drawPos[1]);
1535 } else {
1536 VectorCopy(projectile->pos[0], drawPos);
1537 }
1538 }
1539
1540 if (projectile->bullets) {
1541 GEO_DrawBullets(node, drawPos);
1542 } else if (projectile->beam) {
1543 vec3_t start;
1544 vec3_t end;
1545
1546 if (projectile->attackingAircraft)
1547 VectorCopy(projectile->attackingAircraft->pos, start);
1548 else
1549 VectorCopy(projectile->attackerPos, start);
1550
1551 if (projectile->aimedAircraft)
1552 VectorCopy(projectile->aimedAircraft->pos, end);
1553 else
1554 VectorCopy(projectile->idleTarget, end);
1555
1556 GEO_DrawBeam(node, start, end, projectile->aircraftItem->craftitem.beamColor);
1557 } else {
1558 GEO_Draw3DMarkerIfVisible(node, drawPos, projectile->angle, projectile->aircraftItem->model, 0);
1559 }
1560 }
1561
1562 const bool showXVI = CP_IsXVIVisible();
1563 static char buffer[512];
1564
1565 /* Draw nation names */
1566 buffer[0] = 0;
1567 NAT_Foreach(nation) {
1568 int x, y;
1569 if (GEO_AllMapToScreen(node, nation->pos, &x, &y, nullptr))
1570 cgi->UI_DrawString("f_verysmall", ALIGN_UC, x , y, _(nation->name));
1571 if (showXVI) {
1572 const nationInfo_t* stats = NAT_GetCurrentMonthInfo(nation);
1573 Q_strcat(buffer, sizeof(buffer), _("%s\t%i%%\n"), _(nation->name), stats->xviInfection);
1574 }
1575 }
1576
1577 if (showXVI)
1578 cgi->UI_RegisterText(TEXT_XVI, buffer);
1579 else
1580 cgi->UI_ResetData(TEXT_XVI);
1581
1582 cgi->R_Color(nullptr);
1583}
1584
1590{
1591 if (!CP_IsRunning()) {
1592 data->active = false;
1593 return;
1594 }
1595
1596 data->active = true;
1597 data->map = ccs.curCampaign->map;
1598 data->nationOverlay = GEO_IsNationOverlayActivated();
1599 data->xviOverlay = GEO_IsXVIOverlayActivated();
1600 data->radarOverlay = GEO_IsRadarOverlayActivated();
1601 data->date = ccs.date;
1602
1603 geoscapeNode = static_cast<uiNode_t* >(data->geoscapeNode);
1604
1605 mission_t* mission = GEO_GetSelectedMission();
1606 /* display text */
1607 cgi->UI_ResetData(TEXT_STANDARD);
1608 switch (ccs.mapAction) {
1609 case MA_NEWBASE:
1610 cgi->UI_RegisterText(TEXT_STANDARD, _("Select the desired location of the new base on the map.\n"));
1611 return;
1612 case MA_NEWINSTALLATION:
1613 cgi->UI_RegisterText(TEXT_STANDARD, _("Select the desired location of the new installation on the map.\n"));
1614 return;
1615 case MA_NONE:
1616 break;
1617 }
1618
1619 /* Nothing is displayed yet */
1620 if (mission) {
1621 cgi->UI_RegisterText(TEXT_STANDARD, GEO_GetMissionText(textStandard, sizeof(textStandard), mission));
1622 } else if (GEO_GetSelectedAircraft() != nullptr) {
1623 const aircraft_t* aircraft = GEO_GetSelectedAircraft();
1624 if (AIR_IsAircraftInBase(aircraft)) {
1625 cgi->UI_RegisterText(TEXT_STANDARD, nullptr);
1627 return;
1628 }
1629 cgi->UI_RegisterText(TEXT_STANDARD, GEO_GetAircraftText(textStandard, sizeof(textStandard), aircraft));
1630 } else if (GEO_GetSelectedUFO() != nullptr) {
1632 } else {
1633#ifdef DEBUG
1634 if (debug_showInterest->integer) {
1635 static char t[64];
1636 Com_sprintf(t, lengthof(t), "Interest level: %i\n", ccs.overallInterest);
1637 cgi->UI_RegisterText(TEXT_STANDARD, t);
1638 } else
1639#endif
1640 cgi->UI_RegisterText(TEXT_STANDARD, "");
1641 }
1642}
1643
1648{
1649 /* don't allow a reset when no base is set up */
1650 if (B_AtLeastOneExists())
1651 ccs.mapAction = MA_NONE;
1652
1654 GEO_SetSelectedMission(nullptr);
1655 GEO_SetSelectedAircraft(nullptr);
1656 GEO_SetSelectedUFO(nullptr);
1657
1658 if (!radarOverlayWasSet)
1659 GEO_SetOverlay("radar", 0);
1660}
1661
1666{
1668 GEO_SetSelectedUFO(ufo);
1669}
1670
1675{
1677 GEO_SetSelectedAircraft(aircraft);
1678}
1679
1686{
1687 if (!mission || GEO_IsMissionSelected(mission))
1688 return GEO_GetSelectedMission();
1690 GEO_SetSelectedMission(mission);
1691 return GEO_GetSelectedMission();
1692}
1693
1698{
1699 /* Unselect the current selected mission if it's the same */
1700 if (GEO_IsMissionSelected(mission))
1702
1704}
1705
1711void GEO_NotifyUFORemoved (const aircraft_t* ufo, bool destroyed)
1712{
1714
1715 if (GEO_GetSelectedUFO() == nullptr)
1716 return;
1717
1718 /* Unselect the current selected ufo if it's the same */
1719 if (GEO_IsUFOSelected(ufo))
1721 else if (destroyed && ccs.geoscape.selectedUFO > ufo)
1723 ccs.geoscape.selectedUFO--;
1724}
1725
1731{
1732 /* Unselect the current selected ufo if its the same */
1733 if (GEO_IsAircraftSelected(aircraft) || GEO_IsInterceptorSelected(aircraft))
1735}
1736
1746{
1747 const byte* color = GEO_GetColor(pos, MAPTYPE_NATIONS, nullptr);
1748 const vec3_t fcolor = {color[0] / 255.0f, color[1] / 255.0f, color[2] / 255.0f};
1749#ifdef PARANOID
1750 cgi->Com_DPrintf(DEBUG_CLIENT, "GEO_GetNation: color value for %.0f:%.0f is r:%i, g:%i, b: %i\n", pos[0], pos[1], color[0], color[1], color[2]);
1751#endif
1752 NAT_Foreach(nation) {
1753 /* compare the first three color values with color value at pos */
1754 /* 0.02 x 255 = 5.1, which allow a variation of +-5 for each color components */
1755 if (VectorEqualEpsilon(nation->color, fcolor, 0.02))
1756 return nation;
1757 }
1758 cgi->Com_DPrintf(DEBUG_CLIENT, "GEO_GetNation: No nation found at %.0f:%.0f - color: %i:%i:%i\n", pos[0], pos[1], color[0], color[1], color[2]);
1759 return nullptr;
1760}
1761
1769static const char* GEO_GetCultureType (const byte* color)
1770{
1771 if (MapIsWater(color))
1772 return "water";
1773 else if (MapIsEastern(color))
1774 return "eastern";
1775 else if (MapIsWestern(color))
1776 return "western";
1777 else if (MapIsOriental(color))
1778 return "oriental";
1779 else if (MapIsAfrican(color))
1780 return "african";
1781 return "western";
1782}
1783
1791static const char* GEO_GetPopulationType (const byte* color)
1792{
1793 if (MapIsWater(color))
1794 return "water";
1795 else if (MapIsUrban(color))
1796 return "urban";
1797 else if (MapIsSuburban(color))
1798 return "suburban";
1799 else if (MapIsVillage(color))
1800 return "village";
1801 else if (MapIsRural(color))
1802 return "rural";
1803 return "nopopulation";
1804}
1805
1813static inline const char* GEO_GetTerrainTypeByPos (const vec2_t pos, bool* coast)
1814{
1815 const byte* color = GEO_GetColor(pos, MAPTYPE_TERRAIN, coast);
1816 return cgi->csi->terrainDefs.getTerrainName(color);
1817}
1818
1825static inline const char* GEO_GetCultureTypeByPos (const vec2_t pos)
1826{
1827 const byte* color = GEO_GetColor(pos, MAPTYPE_CULTURE, nullptr);
1828 return GEO_GetCultureType(color);
1829}
1830
1837static inline const char* GEO_GetPopulationTypeByPos (const vec2_t pos)
1838{
1839 const byte* color = GEO_GetColor(pos, MAPTYPE_POPULATION, nullptr);
1840 return GEO_GetPopulationType(color);
1841}
1842
1850{
1851 const byte* color = GEO_GetColor(pos, MAPTYPE_POPULATION, nullptr);
1852
1853 if (MapIsWater(color))
1854 cgi->Com_Error(ERR_DROP, "GEO_GetPopulationType: Trying to get number of civilian in a position on water");
1855
1856 if (MapIsUrban(color))
1857 return 10;
1858 else if (MapIsSuburban(color))
1859 return 8;
1860 else if (MapIsVillage(color))
1861 return 6;
1862 else if (MapIsRural(color))
1863 return 4;
1864 else if (MapIsNopopulation(color))
1865 return 2;
1866
1867 return 0;
1868}
1869
1877{
1878 bool coast = false;
1879 const char* terrainType = GEO_GetTerrainTypeByPos(pos, &coast);
1880 const char* cultureType = GEO_GetCultureTypeByPos(pos);
1881 const char* populationType = GEO_GetPopulationTypeByPos(pos);
1882
1883 cgi->Com_Printf(" (Terrain: %s, Culture: %s, Population: %s, Coast: %s)\n",
1884 terrainType, cultureType, populationType, coast ? "true" : "false");
1885}
1886
1892{
1893 while (pos[0] > 180.0)
1894 pos[0] -= 360.0;
1895 while (pos[0] < -180.0)
1896 pos[0] += 360.0;
1897 while (pos[1] > 90.0)
1898 pos[1] -= 180.0;
1899 while (pos[1] < -90.0)
1900 pos[1] += 180.0;
1901}
1902
1908bool GEO_IsNight (const vec2_t pos)
1909{
1910 float p, q, a, root, x;
1911
1912 /* set p to hours (we don't use ccs.day here because we need a float value) */
1913 p = (float) ccs.date.getTimeAsSeconds() / DateTime::SECONDS_PER_DAY;
1914 /* convert current day to angle (-pi on 1st january, pi on 31 december) */
1915 q = (ccs.date.getDateAsDays() + p) * (2 * M_PI / DateTime::DAYS_PER_YEAR_AVG) - M_PI;
1916 p = (0.5 + pos[0] / 360 - p) * (2 * M_PI) - q;
1917 a = -sin(pos[1] * torad);
1918 root = sqrt(1.0 - a * a);
1919 x = sin(p) * root * sin(q) - (a * SIN_ALPHA + cos(p) * root * COS_ALPHA) * cos(q);
1920 return (x > 0);
1921}
1922
1935const byte* GEO_GetColor (const vec2_t pos, mapType_t type, bool* coast)
1936{
1937 int x, y;
1938 int width, height;
1939 const byte* mask;
1940 const byte* color;
1941
1942 switch (type) {
1943 case MAPTYPE_TERRAIN:
1944 mask = terrainPic;
1945 width = terrainWidth;
1946 height = terrainHeight;
1947 break;
1948 case MAPTYPE_CULTURE:
1949 mask = culturePic;
1950 width = cultureWidth;
1951 height = cultureHeight;
1952 break;
1953 case MAPTYPE_POPULATION:
1954 mask = populationPic;
1955 width = populationWidth;
1956 height = populationHeight;
1957 break;
1958 case MAPTYPE_NATIONS:
1959 mask = nationsPic;
1960 width = nationsWidth;
1961 height = nationsHeight;
1962 break;
1963 default:
1964 cgi->Com_Error(ERR_DROP, "Unknown maptype %i\n", type);
1965 }
1966
1967 assert(mask);
1968
1970 assert(pos[0] >= -180);
1971 assert(pos[0] <= 180);
1972 assert(pos[1] >= -90);
1973 assert(pos[1] <= 90);
1974
1975 /* get coordinates */
1976 x = (180 - pos[0]) / 360 * width;
1977 x--; /* we start from 0 */
1978 y = (90 - pos[1]) / 180 * height;
1979 y--; /* we start from 0 */
1980 if (x < 0)
1981 x = 0;
1982 if (y < 0)
1983 y = 0;
1984
1985 /* 4 => RGBA */
1986 /* terrainWidth is the width of the image */
1987 /* this calculation returns the pixel in col x and in row y */
1988 assert(4 * (x + y * width) < width * height * 4);
1989 color = mask + 4 * (x + y * width);
1990 if (coast != nullptr) {
1991 if (MapIsWater(color)) {
1992 *coast = false;
1993 } else {
1994 /* only check four directions */
1995 const int gap = 4;
1996 if (x > gap) {
1997 const byte* coastCheck = mask + 4 * ((x - gap) + y * width);
1998 *coast = MapIsWater(coastCheck);
1999 }
2000 if (!*coast && x < width - 1 - gap) {
2001 const byte* coastCheck = mask + 4 * ((x + gap) + y * width);
2002 *coast = MapIsWater(coastCheck);
2003 }
2004
2005 if (!*coast) {
2006 if (y > gap) {
2007 const byte* coastCheck = mask + 4 * (x + (y - gap) * width);
2008 *coast = MapIsWater(coastCheck);
2009 }
2010 if (!*coast && y < height - 1 - gap) {
2011 const byte* coastCheck = mask + 4 * (x + (y + gap) * width);
2012 *coast = MapIsWater(coastCheck);
2013 }
2014 }
2015 }
2016 }
2017
2018 return color;
2019}
2020
2024static const float MIN_DIST_BASE = 4.0f;
2025
2031{
2032 base_t* base = nullptr;
2033 while ((base = B_GetNext(base)) != nullptr)
2034 if (GetDistanceOnGlobe(pos, base->pos) < MIN_DIST_BASE)
2035 return base;
2036
2037 return nullptr;
2038}
2039
2050bool GEO_PositionFitsTCPNTypes (const vec2_t pos, const linkedList_t* terrainTypes, const linkedList_t* cultureTypes, const linkedList_t* populationTypes, const linkedList_t* nations)
2051{
2052 bool coast = false;
2053 const char* terrainType = GEO_GetTerrainTypeByPos(pos, &coast);
2054 const char* cultureType = GEO_GetCultureTypeByPos(pos);
2055 const char* populationType = GEO_GetPopulationTypeByPos(pos);
2056
2057 if (MapIsWater(GEO_GetColor(pos, MAPTYPE_TERRAIN, nullptr)))
2058 return false;
2059
2060 if (!terrainTypes || cgi->LIST_ContainsString(terrainTypes, terrainType) || (coast && cgi->LIST_ContainsString(terrainTypes, "coast"))) {
2061 if (!cultureTypes || cgi->LIST_ContainsString(cultureTypes, cultureType)) {
2062 if (!populationTypes || cgi->LIST_ContainsString(populationTypes, populationType)) {
2063 const nation_t* nationAtPos = GEO_GetNation(pos);
2064 if (!nations)
2065 return true;
2066 if (nationAtPos && (!nations || cgi->LIST_ContainsString(nations, nationAtPos->id))) {
2067 return true;
2068 }
2069 }
2070 }
2071 }
2072
2073 return false;
2074}
2075
2076
2085void CP_GetRandomPosOnGeoscape (vec2_t pos, bool noWater)
2086{
2087 do {
2088 pos[0] = (frand() - 0.5f) * 360.0f;
2089 pos[1] = asin((frand() - 0.5f) * 2.0f) * todeg;
2090 } while (noWater && MapIsWater(GEO_GetColor(pos, MAPTYPE_TERRAIN, nullptr)));
2091
2092 cgi->Com_DPrintf(DEBUG_CLIENT, "CP_GetRandomPosOnGeoscape: Get random position on geoscape %.2f:%.2f\n", pos[0], pos[1]);
2093}
2094
2109bool CP_GetRandomPosOnGeoscapeWithParameters (vec2_t pos, const linkedList_t* terrainTypes, const linkedList_t* cultureTypes, const linkedList_t* populationTypes, const linkedList_t* nations)
2110{
2111 float x, y;
2112 int num;
2113 int randomNum;
2114
2115 /* RASTER might reduce amount of tested locations to get a better performance */
2116 /* Number of points in latitude and longitude that will be tested. Therefore, the total number of position tried
2117 * will be numPoints * numPoints */
2118 const float numPoints = 360.0 / RASTER;
2119 /* RASTER is minimizing the amount of locations, so an offset is introduced to enable access to all locations, depending on a random factor */
2120 const float offsetX = frand() * RASTER;
2121 const float offsetY = -1.0 + frand() * 2.0 / numPoints;
2122 vec2_t posT;
2123 int hits = 0;
2124
2125 /* check all locations for suitability in 2 iterations */
2126 /* prepare 1st iteration */
2127
2128 /* ITERATION 1 */
2129 for (y = 0; y < numPoints; y++) {
2130 const float posY = asin(2.0 * y / numPoints + offsetY) * todeg; /* Use non-uniform distribution otherwise we favour the poles */
2131 for (x = 0; x < numPoints; x++) {
2132 const float posX = x * RASTER - 180.0 + offsetX;
2133
2134 Vector2Set(posT, posX, posY);
2135
2136 if (GEO_PositionFitsTCPNTypes(posT, terrainTypes, cultureTypes, populationTypes, nations)) {
2137 /* the location given in pos belongs to the terrain, culture, population types and nations
2138 * that are acceptable, so count it */
2140 hits++;
2141 }
2142 }
2143 }
2144
2145 /* if there have been no hits, the function failed to find a position */
2146 if (hits == 0)
2147 return false;
2148
2149 /* the 2nd iteration goes through the locations again, but does so only until a random point */
2150 /* prepare 2nd iteration */
2151 randomNum = num = rand() % hits;
2152
2153 /* ITERATION 2 */
2154 for (y = 0; y < numPoints; y++) {
2155 const float posY = asin(2.0 * y / numPoints + offsetY) * todeg;
2156 for (x = 0; x < numPoints; x++) {
2157 const float posX = x * RASTER - 180.0 + offsetX;
2158
2159 Vector2Set(posT, posX, posY);
2160
2161 if (GEO_PositionFitsTCPNTypes(posT, terrainTypes, cultureTypes, populationTypes, nations)) {
2162 num--;
2163
2164 if (num < 1) {
2165 Vector2Set(pos, posX, posY);
2166 cgi->Com_DPrintf(DEBUG_CLIENT, "CP_GetRandomPosOnGeoscapeWithParameters: New random coords for a mission are %.0f:%.0f, chosen as #%i out of %i possible locations\n",
2167 pos[0], pos[1], randomNum, hits);
2168 return true;
2169 }
2170 }
2171 }
2172 }
2173
2174 cgi->Com_DPrintf(DEBUG_CLIENT, "CP_GetRandomPosOnGeoscapeWithParameters: New random coordinates for a mission are %.0f:%.0f, chosen as #%i out of %i possible locations\n",
2175 pos[0], pos[1], num, hits);
2176
2178 /* Make sure that position is within bounds */
2179 assert(pos[0] >= -180);
2180 assert(pos[0] <= 180);
2181 assert(pos[1] >= -90);
2182 assert(pos[1] <= 90);
2183
2184 return true;
2185}
2186
2187void GEO_Shutdown (void)
2188{
2189 cgi->Free(terrainPic);
2190 terrainPic = nullptr;
2191
2192 cgi->Free(culturePic);
2193 culturePic = nullptr;
2194
2195 cgi->Free(populationPic);
2196 populationPic = nullptr;
2197
2198 cgi->Free(nationsPic);
2199 nationsPic = nullptr;
2200}
2201
2202void GEO_Init (const char* map)
2203{
2204 /* load terrain mask */
2205 cgi->R_LoadImage(va("pics/geoscape/%s_terrain", map), &terrainPic, &terrainWidth, &terrainHeight);
2207 cgi->Com_Error(ERR_DROP, "Couldn't load map mask %s_terrain in pics/geoscape", map);
2208
2209 /* load culture mask */
2210 cgi->R_LoadImage(va("pics/geoscape/%s_culture", map), &culturePic, &cultureWidth, &cultureHeight);
2212 cgi->Com_Error(ERR_DROP, "Couldn't load map mask %s_culture in pics/geoscape", map);
2213
2214 /* load population mask */
2215 cgi->R_LoadImage(va("pics/geoscape/%s_population", map), &populationPic, &populationWidth, &populationHeight);
2217 cgi->Com_Error(ERR_DROP, "Couldn't load map mask %s_population in pics/geoscape", map);
2218
2219 /* load nations mask */
2220 cgi->R_LoadImage(va("pics/geoscape/%s_nations", map), &nationsPic, &nationsWidth, &nationsHeight);
2222 cgi->Com_Error(ERR_DROP, "Couldn't load map mask %s_nations in pics/geoscape", map);
2223}
2224
2225void GEO_Reset (const char* map)
2226{
2227 GEO_Shutdown();
2228 GEO_Init(map);
2231}
2232
2237{
2238 /* Unselect the currently selected ufo if it's the same */
2239 if (GEO_IsUFOSelected(ufo))
2241
2243}
2244
2250void GEO_SetOverlay (const char* overlayID, int status)
2251{
2252 if (Q_streq(overlayID, "nation")) {
2253 cgi->Cvar_SetValue("geo_overlay_nation", status);
2254 return;
2255 }
2256
2257 /* do nothing while the first base is not build */
2258 if (!B_AtLeastOneExists())
2259 return;
2260
2261 if (Q_streq(overlayID, "xvi")) {
2262 cgi->Cvar_SetValue("geo_overlay_xvi", status);
2263 }
2264 if (Q_streq(overlayID, "radar")) {
2265 cgi->Cvar_SetValue("geo_overlay_radar", status);
2268 }
2269}
2270
2274static void GEO_SetOverlay_f (void)
2275{
2276 if (cgi->Cmd_Argc() != 3) {
2277 cgi->Com_Printf("Usage: %s <nation|xvi|radar> <1|0>\n", cgi->Cmd_Argv(0));
2278 return;
2279 }
2280
2281 const char* overlay = cgi->Cmd_Argv(1);
2282 const int status = atoi(cgi->Cmd_Argv(2));
2283 const bool setRadar = Q_streq(overlay, "radar");
2284 GEO_SetOverlay(overlay, status);
2285
2286 /* save last decision player took on radar display, in order to be able to restore it later */
2287 if (setRadar)
2289}
2290
2295{
2296 cgi->Cmd_AddCommand("geo_setoverlay", GEO_SetOverlay_f, "Set the geoscape overlay");
2297 cgi->Cmd_AddCommand("map_selectobject", GEO_SelectObject_f, "Select an object and center on it");
2298 cgi->Cmd_AddCommand("mn_mapaction_reset", GEO_ResetAction, nullptr);
2299
2300#ifdef DEBUG
2301 debug_showInterest = cgi->Cvar_Get("debug_showinterest", "0", CVAR_DEVELOPER, "Shows the global interest value on geoscape");
2302#endif
2303}
DateTime class definition.
Share stuff between the different cgame implementations.
#define _(String)
Definition cl_shared.h:44
static const int SECONDS_PER_DAY
Definition DateTime.h:43
static const short SECONDS_PER_HOUR
Definition DateTime.h:42
static constexpr float DAYS_PER_YEAR_AVG
Definition DateTime.h:38
#define ERR_DROP
Definition common.h:211
const char * AIR_AircraftStatusToName(const aircraft_t *aircraft)
Translates the aircraft status id to a translatable string.
bool AIR_AircraftHasEnoughFuel(const aircraft_t *aircraft, const vec2_t destination)
check if aircraft has enough fuel to go to destination, and then come back home
int AIR_AircraftMenuStatsValues(const int value, const int stat)
Some of the aircraft values needs special calculations when they are shown in the menus.
bool AIR_IsAircraftOnGeoscape(const aircraft_t *aircraft)
Checks whether given aircraft is on geoscape.
int AIR_GetTeamSize(const aircraft_t *aircraft)
Counts the number of soldiers in given aircraft.
bool AIR_IsAircraftInBase(const aircraft_t *aircraft)
Checks whether given aircraft is in its homebase.
#define LINE_MAXPTS
Definition cp_aircraft.h:33
#define AIR_Foreach(var)
iterates trough all aircraft
#define AIR_ForeachFromBase(var, base)
iterates trough all aircraft from a specific homebase
#define LINE_MAXSEG
Definition cp_aircraft.h:32
@ AIR_UFO
@ AIR_IDLE
@ AIR_TRANSIT
base_t * B_GetNext(base_t *lastBase)
Iterates through founded bases.
Definition cp_base.cpp:286
int B_GetCount(void)
Returns the count of founded bases.
Definition cp_base.cpp:277
#define B_AtLeastOneExists()
Definition cp_base.h:55
#define B_IsUnderAttack(base)
Definition cp_base.h:53
#define MAX_BASES
Definition cp_base.h:32
bool CP_IsRunning(void)
Checks whether a campaign mode game is running.
ccs_t ccs
Header file for single player campaign control.
const cgame_import_t * cgi
@ MA_NONE
@ MA_NEWINSTALLATION
@ MA_NEWBASE
mapType_t
Definition cp_campaign.h:92
@ MAPTYPE_POPULATION
Definition cp_campaign.h:95
@ MAPTYPE_NATIONS
Definition cp_campaign.h:96
@ MAPTYPE_CULTURE
Definition cp_campaign.h:94
@ MAPTYPE_TERRAIN
Definition cp_campaign.h:93
static const char * GEO_GetTerrainTypeByPos(const vec2_t pos, bool *coast)
Determine the terrain type under a given position.
static bool GEO_IsNationOverlayActivated(void)
mission_t * GEO_SelectMission(mission_t *mission)
Select the specified mission.
static void GEO_SelectObject_f(void)
Center the view and select an object from the geoscape.
bool GEO_IsNight(const vec2_t pos)
Check whether given position is Day or Night.
static void GEO_DrawMapOnePhalanxAircraft(const uiNode_t *node, aircraft_t *aircraft, bool oneUFOVisible)
Draws one Phalanx aircraft on the geoscape map (2D and 3D).
static void GEO_GetGeoscapeAngle(vec2_t pos)
Returns position of the model corresponding to centerOnEventIdx.
void GEO_Reset(const char *map)
bool GEO_PositionFitsTCPNTypes(const vec2_t pos, const linkedList_t *terrainTypes, const linkedList_t *cultureTypes, const linkedList_t *populationTypes, const linkedList_t *nations)
Checks for a given location, if it fulfills all criteria given via parameters (terrain,...
nation_t * GEO_GetNation(const vec2_t pos)
Translate nation map color to nation.
static void GEO_DrawMapOneBase(const uiNode_t *node, const base_t *base, bool oneUFOVisible, const char *font)
Draws one base on the geoscape map (2D and 3D).
static const char * GEO_GetPopulationTypeByPos(const vec2_t pos)
Determine the population type under a given position.
void GEO_Init(const char *map)
void GEO_DrawMarkers(const uiNode_t *node)
Draws all ufos, aircraft, bases and so on to the geoscape map (2D and 3D).
static bool GEO_3DMapToScreen(const uiNode_t *node, const vec2_t pos, int *x, int *y, int *z)
Transform a 2D position on the map to screen coordinates.
static byte * nationsPic
bool CP_GetRandomPosOnGeoscapeWithParameters(vec2_t pos, const linkedList_t *terrainTypes, const linkedList_t *cultureTypes, const linkedList_t *populationTypes, const linkedList_t *nations)
Determines a random position on geoscape that fulfills certain criteria given via parameters.
static int nationsWidth
static bool GEO_IsXVIOverlayActivated(void)
static void GEO_Draw3DMarkerIfVisible(const uiNode_t *node, const vec2_t pos, float theta, const char *model, int skin)
Draws a 3D marker on geoscape if the player can see it.
void GEO_NotifyUFORemoved(const aircraft_t *ufo, bool destroyed)
Notify that a UFO has been removed.
bool GEO_Click(const uiNode_t *node, int x, int y, const vec2_t pos)
Click on the map/geoscape.
void GEO_SetOverlay(const char *overlayID, int status)
Turn overlay on/off.
static char textStandard[2048]
static const char * GEO_GetUFOText(char *buffer, size_t size, const aircraft_t *ufo)
Assembles a string for a UFO that is on the geoscape.
static void GEO_DrawRadarLineCoverage(const uiNode_t *node, const radar_t *radar, const vec2_t pos)
Draw only the "wire" Radar coverage.
#define UI_MAP_DIST_SELECTION
maximum distance (in pixel) to get a valid mouse click
const byte * GEO_GetColor(const vec2_t pos, mapType_t type, bool *coast)
Returns the color value from geoscape of a certain mask (terrain, culture or population) at a given p...
static const char * GEO_GetPopulationType(const byte *color)
Translate color value to population type.
static int terrainWidth
void GEO_NotifyAircraftRemoved(const aircraft_t *aircraft)
Notify that an aircraft has been removed from game.
void GEO_InitStartup(void)
Initialise MAP/Geoscape.
static const char * GEO_GetCultureType(const byte *color)
Translate color value to culture type.
static float GEO_AngleOfPath3D(const vec2_t start, const vec2_t end, vec3_t direction, vec3_t ortVector)
Return the angle of a model given its position and destination, on 3D geoscape.
static uiNode_t * geoscapeNode
static int nationsHeight
static int populationHeight
static const vec4_t yellow
#define CIRCLE_DRAW_POINTS
#define GLOBE_ROTATE
static void GEO_DrawBullets(const uiNode_t *node, const vec3_t pos)
Draws a bunch of bullets on the geoscape map.
void GEO_SelectAircraft(aircraft_t *aircraft)
Select the specified aircraft on the geoscape.
static bool GEO_MapToScreen(const uiNode_t *node, const vec2_t pos, int *x, int *y)
Transform a 2D position on the map to screen coordinates.
static const vec4_t red
static byte * terrainPic
#define BULLET_SIZE
static const float defaultBaseAngle
#define ZOOM_LIMIT
bool GEO_IsRadarOverlayActivated(void)
static void GEO_RenderImage(int x, int y, const char *image)
static void GEO_DrawRadarInMap(const uiNode_t *node, const radar_t *radar, const vec2_t pos)
Draw only the "wire" part of the radar coverage in geoscape.
static byte * populationPic
void GEO_CenterPosition(const vec2_t pos)
Start to rotate or shift the globe to the given position.
void GEO_CenterOnPoint_f(void)
Switch to next model on 2D and 3D geoscape.
static void GEO_DrawAircraftHealthBar(const uiNode_t *node, const aircraft_t *aircraft)
Draws health bar for an aircraft (either phalanx or ufo).
void GEO_UpdateGeoscapeDock(void)
Will add missions and UFOs to the geoscape dock panel.
void GEO_SelectUFO(aircraft_t *ufo)
Select the specified ufo on the geoscape.
static void GEO_MapDrawEquidistantPoints(const uiNode_t *node, const vec2_t center, const float angle, const vec4_t color)
Draw equidistant points from a given point on a menu node.
static void GEO_MapDrawLine(const uiNode_t *node, const mapline_t *line)
Draw a path on a menu node (usually the 2D geoscape map).
static const char * GEO_GetMissionText(char *buffer, size_t size, const mission_t *mission)
Assembles a string for a mission that is on the geoscape.
static int terrainHeight
static void GEO_SetOverlay_f(void)
Console command to call GEO_SetOverlay.
static void GEO_GetUFOAngle(bool flatgeoscape, float *vector, int idx)
center to a ufo
void GEO_PrintParameterStringByPos(const vec2_t pos)
Prints positions parameter in console.
static int cultureWidth
void GEO_CalcLine(const vec2_t start, const vec2_t end, mapline_t *line)
Calculate the shortest way to go from start to end on a sphere.
base_t * GEO_PositionCloseToBase(const vec2_t pos)
Check if given pos is close to an existing base.
int GEO_GetCivilianNumberByPosition(const vec2_t pos)
Get number of civilian on a map at given position.
static void GEO_GetMissionAngle(bool flatgeoscape, float *vector, int id)
center to a mission
void GEO_NotifyMissionRemoved(const mission_t *mission)
Notify that a mission has been removed.
static void GEO_ConvertObjectPositionToGeoscapePosition(bool flatgeoscape, float *vector, const vec2_t objectPos)
Will set the vector for the geoscape position.
static bool GEO_AllMapToScreen(const uiNode_t *node, const vec2_t pos, int *x, int *y, int *z)
Call either GEO_MapToScreen or GEO_3DMapToScreen depending on the geoscape you're using.
#define SELECT_CIRCLE_RADIUS
static const vec4_t green
static int cultureHeight
static const char * GEO_GetCultureTypeByPos(const vec2_t pos)
Determine the culture type under a given position.
static void GEO_DrawMapOneInstallation(const uiNode_t *node, const installation_t *installation, bool oneUFOVisible, const char *font)
Draws one installation on the geoscape map (2D and 3D).
static float GEO_AngleOfPath2D(const vec2_t start, const vec2_t end, vec3_t direction, vec3_t ortVector)
Return the angle of a model given its position and destination, on 2D geoscape.
static const char * GEO_GetAircraftText(char *buffer, size_t size, const aircraft_t *aircraft)
Assembles a string for an aircraft that is on the geoscape.
static byte * culturePic
float GEO_AngleOfPath(const vec2_t start, const vec2_t end, vec3_t direction, vec3_t ortVector)
Select which function should be used for calculating the direction of model on 2D or 3D geoscape.
static void GEO_DrawBeam(const uiNode_t *node, const vec3_t start, const vec3_t end, const vec4_t color)
Draws a energy beam on the geoscape map (laser/particle).
static int populationWidth
void CP_GetRandomPosOnGeoscape(vec2_t pos, bool noWater)
Determines a random position on geoscape.
static void GEO_3DMapDrawLine(const uiNode_t *node, const mapline_t *line)
Draw a path on a menu node (usually the 3Dgeoscape map).
void GEO_Shutdown(void)
static void GEO_DrawMapOneMission(const uiNode_t *node, const mission_t *mission)
Draws one mission on the geoscape map (2D and 3D).
void GEO_NotifyUFODisappear(const aircraft_t *ufo)
Notify that a UFO disappears on radars.
void GEO_CheckPositionBoundaries(float *pos)
Check that a position (in latitude / longitude) is within boundaries.
static int centerOnEventIdx
static const float MIN_DIST_BASE
Minimum distance between a new mission and an existing base.
static bool GEO_IsPositionSelected(const uiNode_t *node, const vec2_t pos, int x, int y)
Tell if the specified position is considered clicked.
static void GEO_StartCenter(uiNode_t *node)
Start center to the selected point.
void GEO_ResetAction(void)
No more special action on the geoscape.
void GEO_Draw(geoscapeData_t *data)
Draw the geoscape.
Header for Geoscape management.
#define GEO_IsAircraftSelected(aircraft)
Definition cp_geoscape.h:51
#define MapIsRural(color)
Definition cp_geoscape.h:44
#define MapIsNopopulation(color)
Definition cp_geoscape.h:45
#define GEO_SetInterceptorAircraft(interceptor)
Definition cp_geoscape.h:63
#define GEO_SetSelectedAircraft(aircraft)
Definition cp_geoscape.h:62
#define MapIsUrban(color)
Definition cp_geoscape.h:41
#define GEO_SetSelectedUFO(ufo)
Definition cp_geoscape.h:64
#define GEO_IsUFOSelected(ufo)
Definition cp_geoscape.h:53
#define GEO_GetSelectedMission()
Definition cp_geoscape.h:59
#define MapIsSuburban(color)
Definition cp_geoscape.h:42
#define GEO_GetSelectedUFO()
Definition cp_geoscape.h:58
#define RASTER
Definition cp_geoscape.h:49
#define GEO_IsInterceptorSelected(aircraft)
Definition cp_geoscape.h:52
#define MapIsAfrican(color)
Definition cp_geoscape.h:38
#define MapIsVillage(color)
Definition cp_geoscape.h:43
#define MapIsWater(color)
Definition cp_geoscape.h:32
#define MapIsWestern(color)
Definition cp_geoscape.h:35
#define MapIsOriental(color)
Definition cp_geoscape.h:37
#define GEO_GetSelectedAircraft()
Definition cp_geoscape.h:56
#define MapIsEastern(color)
Definition cp_geoscape.h:36
#define GEO_IsMissionSelected(mission)
Definition cp_geoscape.h:54
#define GEO_SetSelectedMission(mission)
Definition cp_geoscape.h:65
int INS_GetCount(void)
Get number of installations.
#define INS_Foreach(var)
int AII_BaseCanShoot(const base_t *base)
Check if the base has weapon and ammo.
bool AII_InstallationCanShoot(const installation_t *installation)
Check if the installation has a weapon and ammo.
Header for slot management related stuff.
int MIS_GetIdx(const mission_t *mis)
Find idx corresponding to mission.
int CP_CountMissionOnGeoscape(void)
Count the number of mission active and displayed on geoscape.
const char * MIS_GetModel(const mission_t *mission)
Get mission model that should be shown on the geoscape.
const char * MIS_GetName(const mission_t *mission)
Returns a short translated name for a mission.
mission_t * MIS_GetByIdx(int id)
Find mission corresponding to idx.
Campaign missions headers.
#define MIS_Foreach(var)
iterates through missions
@ STAGE_NOT_ACTIVE
Definition cp_missions.h:35
const nationInfo_t * NAT_GetCurrentMonthInfo(const nation_t *const nation)
Get the current month nation stats.
#define NAT_Foreach(var)
iterates trough nations
Definition cp_nation.h:80
Functions to generate and render overlay for geoscape.
void RADAR_UpdateWholeRadarOverlay(void)
Update radar overlay of base, installation and aircraft range.
Definition cp_radar.cpp:89
bool radarOverlayWasSet
Definition cp_radar.cpp:36
bool RS_IsResearched_ptr(const technology_t *tech)
Checks whether an item is already researched.
const char * CP_SecondConvert(int second)
Converts a number of second into a char to display.
Definition cp_time.cpp:57
void CP_GameTimeStop(void)
Stop game time speed.
Definition cp_time.cpp:126
Campaign geoscape time header.
const char * UFO_GetName(const aircraft_t *ufocraft)
Returns name of the UFO if UFO has been researched.
Definition cp_ufo.cpp:243
bool UFO_IsUFOSeenOnGeoscape(const aircraft_t *ufo)
Check if an aircraft should be seen on geoscape.
Definition cp_ufo.cpp:989
aircraft_t * UFO_GetNextOnGeoscape(aircraft_t *lastUFO)
Definition cp_ufo.cpp:66
#define UFO_GetGeoscapeIDX(ufo)
Definition cp_ufo.h:33
bool CP_IsXVIVisible(void)
Returns true if the XVI effect should be visible to the player.
Definition cp_xvi.cpp:196
Campaign XVI header.
#define CVAR_DEVELOPER
Definition cvar.h:45
#define DEBUG_CLIENT
Definition defines.h:59
@ AIR_STATS_FUELSIZE
Definition inv_shared.h:232
@ AIR_STATS_WRANGE
Definition inv_shared.h:233
@ AIR_STATS_SPEED
Definition inv_shared.h:226
@ AIR_STATS_DAMAGE
Definition inv_shared.h:230
voidpf void uLong size
Definition ioapi.h:42
typedef int(ZCALLBACK *close_file_func) OF((voidpf opaque
voidpf void * buf
Definition ioapi.h:42
vec_t VectorNormalize(vec3_t v)
Calculate unit vector for a given vec3_t.
Definition mathlib.cpp:745
vec_t VectorLength(const vec3_t v)
Calculate the length of a vector.
Definition mathlib.cpp:434
void RotatePointAroundVector(vec3_t dst, const vec3_t dir, const vec3_t point, float degrees)
Rotate a point around a given vector.
Definition mathlib.cpp:849
const vec2_t vec2_origin
Definition mathlib.cpp:34
void PolarToVec(const vec2_t a, vec3_t v)
Converts longitude and latitude to a 3D vector in Euclidean coordinates.
Definition mathlib.cpp:910
void PerpendicularVector(vec3_t dst, const vec3_t src)
Finds a vector perpendicular to the source vector.
Definition mathlib.cpp:780
double GetDistanceOnGlobe(const vec2_t pos1, const vec2_t pos2)
Calculate distance on the geoscape.
Definition mathlib.cpp:171
void VecToPolar(const vec3_t v, vec2_t a)
Converts vector coordinates into polar coordinates.
Definition mathlib.cpp:922
void CrossProduct(const vec3_t v1, const vec3_t v2, vec3_t cross)
binary operation on vectors in a three-dimensional space
Definition mathlib.cpp:820
float frand(void)
Return random values between 0 and 1.
Definition mathlib.cpp:506
#define torad
Definition mathlib.h:50
#define YAW
Definition mathlib.h:55
#define PITCH
Definition mathlib.h:54
#define COS_ALPHA
Definition mathlib.h:66
#define SIN_ALPHA
Definition mathlib.h:65
#define todeg
Definition mathlib.h:51
#define M_PI
Definition mathlib.h:34
QGL_EXTERN int GLboolean GLfloat * v
Definition r_gl.h:120
QGL_EXTERN GLsizei const GLvoid * data
Definition r_gl.h:89
QGL_EXTERN GLint i
Definition r_gl.h:113
QGL_EXTERN GLint GLenum type
Definition r_gl.h:94
@ ALIGN_UL
Definition scripts.h:90
@ ALIGN_UC
Definition scripts.h:91
#define Q_streq(a, b)
Definition shared.h:136
#define lengthof(x)
Definition shared.h:105
void Q_strcat(char *dest, size_t destsize, const char *format,...)
Safely (without overflowing the destination buffer) concatenates two strings.
Definition shared.cpp:475
bool Com_sprintf(char *dest, size_t size, const char *fmt,...)
copies formatted string with buffer-size checking
Definition shared.cpp:494
const char * va(const char *format,...)
does a varargs printf into a temp buffer, so I don't need to have varargs versions of all text functi...
Definition shared.cpp:410
An aircraft with all it's data.
struct aircraft_s * aircraftTarget
aircraftStatus_t status
int stats[AIR_STATS_MAX]
mapline_t route
char name[MAX_VAR]
char * model
struct radar_s radar
struct technology_s * tech
vec3_t direction
projectile used during fight between two or more aircraft
Definition cp_airfight.h:43
const objDef_t * aircraftItem
Definition cp_airfight.h:44
aircraft_t * aimedAircraft
Definition cp_airfight.h:57
vec3_t projectedPos[MAX_MULTIPLE_PROJECTILES]
Definition cp_airfight.h:47
vec3_t pos[MAX_MULTIPLE_PROJECTILES]
Definition cp_airfight.h:46
aircraft_t * attackingAircraft
Definition cp_airfight.h:54
slot of aircraft
Definition cp_aircraft.h:78
const objDef_t * item
Definition cp_aircraft.h:85
const objDef_t * ammo
Definition cp_aircraft.h:86
A base with all it's data.
Definition cp_base.h:84
int numLasers
Definition cp_base.h:120
struct radar_s radar
Definition cp_base.h:106
baseWeapon_t batteries[MAX_BASE_SLOT]
Definition cp_base.h:116
baseWeapon_t lasers[MAX_BASE_SLOT]
Definition cp_base.h:119
int idx
Definition cp_base.h:85
char name[MAX_VAR]
Definition cp_base.h:86
vec3_t pos
Definition cp_base.h:91
int numBatteries
Definition cp_base.h:117
aircraftSlot_t slot
Definition cp_base.h:78
float stats[AIR_STATS_MAX]
Definition inv_shared.h:248
vec4_t beamColor
Definition inv_shared.h:255
This is a cvar definition. Cvars can be user modified and used in our menus e.g.
Definition cvar.h:71
int integer
Definition cvar.h:81
A installation with all it's data.
char name[MAX_VAR]
struct radar_s radar
const installationTemplate_t * installationTemplate
baseWeapon_t batteries[MAX_INSTALLATION_BATTERIES]
char * description
Definition q_shared.h:466
A path on the map described by 2D points.
Definition cp_aircraft.h:39
float distance
Definition cp_aircraft.h:41
vec2_t point[LINE_MAXPTS]
Definition cp_aircraft.h:43
int numPoints
Definition cp_aircraft.h:40
mission definition
Definition cp_missions.h:86
mapDef_t * mapDef
Definition cp_missions.h:89
bool active
Definition cp_missions.h:90
vec2_t pos
Nation definition.
Definition cp_nation.h:46
const char * id
Definition cp_nation.h:47
Detailed information about the nation relationship (currently per month, but could be used elsewhere)...
Definition cp_nation.h:35
int xviInfection
Definition cp_nation.h:40
craftItem craftitem
Definition inv_shared.h:331
const char * model
Definition inv_shared.h:269
Atomic structure used to define most of the UI.
Definition ui_nodes.h:80
vec4_t color
Definition ui_nodes.h:127
vec_t vec3_t[3]
Definition ufotypes.h:39
vec_t vec4_t[4]
Definition ufotypes.h:40
vec_t vec2_t[2]
Definition ufotypes.h:38
@ TEXT_XVI
Definition ui_dataids.h:57
@ TEXT_STANDARD
Definition ui_dataids.h:30
#define UI_MAPEXTRADATACONST(node)
#define GLOBE_RADIUS
radius of the globe in screen coordinates
#define UI_MAPEXTRADATA(node)
static hudRadar_t radar
#define VectorEqualEpsilon(a, b, epsilon)
Definition vector.h:64
#define LinearInterpolation(a, b, x, y)
Definition vector.h:78
#define VectorEqual(a, b)
Definition vector.h:65
#define Vector4Copy(src, dest)
Definition vector.h:53
#define VectorSubtract(a, b, dest)
Definition vector.h:45
#define VectorCopy(src, dest)
Definition vector.h:51
#define Vector2Set(v, x, y)
Definition vector.h:61
#define DotProduct(x, y)
Returns the distance between two 3-dimensional vectors.
Definition vector.h:44
#define VectorSet(v, x, y, z)
Definition vector.h:59
#define Vector2Copy(src, dest)
Definition vector.h:52