00001
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026 #include "../client.h"
00027 #include "../renderer/r_image.h"
00028 #include "../renderer/r_framebuffer.h"
00029 #include "../renderer/r_draw.h"
00030 #include "../renderer/r_geoscape.h"
00031 #include "../ui/ui_main.h"
00032 #include "../ui/ui_font.h"
00033 #include "../ui/ui_render.h"
00034 #include "../ui/node/ui_node_abstractnode.h"
00035 #include "../ui/node/ui_node_map.h"
00036 #include "cp_overlay.h"
00037 #include "cp_campaign.h"
00038 #include "cp_popup.h"
00039 #include "cp_mapfightequip.h"
00040 #include "cp_map.h"
00041 #include "cp_missions.h"
00042 #include "cp_ufo.h"
00043 #include "cp_time.h"
00044 #include "cp_xvi.h"
00045
00046 cvar_t *cl_3dmap;
00047 static cvar_t *cl_3dmapAmbient;
00048 cvar_t *cl_mapzoommax;
00049 cvar_t *cl_mapzoommin;
00050 cvar_t *cl_geoscape_overlay;
00051
00052 extern image_t *r_dayandnightTexture;
00053 extern image_t *r_radarTexture;
00054 extern image_t *r_xviTexture;
00055
00056 #ifdef DEBUG
00057 static cvar_t *debug_showInterest;
00058 #endif
00059
00060 #define ZOOM_LIMIT 2.5f
00061
00062 enum {
00063 GEOSCAPE_IMAGE_MISSION,
00064 GEOSCAPE_IMAGE_MISSION_SELECTED,
00065 GEOSCAPE_IMAGE_MISSION_ACTIVE,
00066 GEOSCAPE_IMAGE_BASE,
00067 GEOSCAPE_IMAGE_BASE_ATTACK,
00068
00069 GEOSCAPE_IMAGE_MAX
00070 };
00071
00072 static const char *geoscapeImageNames[] = {
00073 "pics/geoscape/mission",
00074 "pics/geoscape/circle",
00075 "pics/geoscape/circleactive",
00076 "pics/geoscape/base",
00077 "pics/geoscape/baseattack"
00078 };
00079 CASSERT(lengthof(geoscapeImageNames) == GEOSCAPE_IMAGE_MAX);
00080
00081 static image_t *geoscapeImages[GEOSCAPE_IMAGE_MAX];
00082
00083
00084
00085
00086
00087
00088
00089 #define MULTISELECT_MAXSELECT 6
00094 typedef enum {
00095 MULTISELECT_TYPE_BASE,
00096 MULTISELECT_TYPE_INSTALLATION,
00097 MULTISELECT_TYPE_MISSION,
00098 MULTISELECT_TYPE_AIRCRAFT,
00099 MULTISELECT_TYPE_UFO,
00100 MULTISELECT_TYPE_NONE
00101 } multiSelectType_t;
00102
00103
00107 typedef struct multiSelect_s {
00108 int nbSelect;
00109 multiSelectType_t selectType[MULTISELECT_MAXSELECT];
00110 int selectId[MULTISELECT_MAXSELECT];
00111 char popupText[2048];
00112 } multiSelect_t;
00113
00114 static multiSelect_t multiSelect;
00117
00118
00119
00120
00121
00122
00123
00124 static qboolean MAP_IsMapPositionSelected(const uiNode_t* node, const vec2_t pos, int x, int y);
00125 static void MAP3D_ScreenToMap(const uiNode_t* node, int x, int y, vec2_t pos);
00126 static void MAP_ScreenToMap(const uiNode_t* node, int x, int y, vec2_t pos);
00127
00128
00129 static char textStandard[2048];
00130 static int centerOnEventIdx;
00132
00133 static const vec4_t green = {0.0f, 1.0f, 0.0f, 0.8f};
00134 static const vec4_t yellow = {1.0f, 0.874f, 0.294f, 1.0f};
00135 static const vec4_t red = {1.0f, 0.0f, 0.0f, 0.8f};
00136
00137
00138 static qboolean smoothRotation = qfalse;
00139 static vec3_t smoothFinalGlobeAngle = {0, GLOBE_ROTATE, 0};
00140 static vec2_t smoothFinal2DGeoscapeCenter = {0.5, 0.5};
00141 static float smoothDeltaLength = 0.0f;
00142 static float smoothFinalZoom = 0.0f;
00143 static float smoothDeltaZoom = 0.0f;
00144 static const float smoothAcceleration = 0.06f;
00145 static float curZoomSpeed = 0.0f;
00146 static float curRotationSpeed = 0.0f;
00149 static const float defaultBaseAngle = 90.0f;
00151 static byte *terrainPic;
00153 static int terrainWidth, terrainHeight;
00155 static byte *culturePic;
00157 static int cultureWidth, cultureHeight;
00159 static byte *populationPic;
00161 static int populationWidth, populationHeight;
00163 static byte *nationsPic;
00165 static int nationsWidth, nationsHeight;
00168
00169
00170
00171
00172
00173
00177 static void MAP_MultiSelectListAddItem (multiSelectType_t itemType, int itemID,
00178 const char* itemDescription, const char* itemName)
00179 {
00180 Q_strcat(multiSelect.popupText, va("%s\t%s\n", itemDescription, itemName), sizeof(multiSelect.popupText));
00181 multiSelect.selectType[multiSelect.nbSelect] = itemType;
00182 multiSelect.selectId[multiSelect.nbSelect] = itemID;
00183 multiSelect.nbSelect++;
00184 }
00185
00190 static void MAP_MultiSelectExecuteAction_f (void)
00191 {
00192 int selected, id;
00193 aircraft_t* aircraft;
00194 qboolean multiSelection = qfalse;
00195
00196 if (Cmd_Argc() < 2) {
00197
00198 selected = 0;
00199 } else {
00200
00201 UI_PopWindow(qfalse);
00202 selected = atoi(Cmd_Argv(1));
00203 multiSelection = qtrue;
00204 }
00205
00206 if (selected < 0 || selected >= multiSelect.nbSelect)
00207 return;
00208 id = multiSelect.selectId[selected];
00209
00210
00211 switch (multiSelect.selectType[selected]) {
00212 case MULTISELECT_TYPE_BASE:
00213 if (id >= ccs.numBases)
00214 break;
00215 MAP_ResetAction();
00216 B_SelectBase(B_GetFoundedBaseByIDX(id));
00217 break;
00218 case MULTISELECT_TYPE_INSTALLATION:
00219 if (id >= ccs.numInstallations)
00220 break;
00221 MAP_ResetAction();
00222 INS_SelectInstallation(INS_GetFoundedInstallationByIDX(id));
00223 break;
00224 case MULTISELECT_TYPE_MISSION:
00225 if (ccs.mapAction == MA_INTERCEPT && ccs.selectedMission && ccs.selectedMission == MAP_GetMissionByIDX(id)) {
00226 CL_DisplayPopupInterceptMission(ccs.selectedMission);
00227 return;
00228 }
00229 MAP_SelectMission(MAP_GetMissionByIDX(id));
00230 if (multiSelection) {
00231
00232 CL_DisplayPopupInterceptMission(ccs.selectedMission);
00233 return;
00234 }
00235 break;
00236 case MULTISELECT_TYPE_AIRCRAFT:
00237 aircraft = AIR_AircraftGetFromIDX(id);
00238 if (aircraft == NULL) {
00239 Com_DPrintf(DEBUG_CLIENT, "MAP_MultiSelectExecuteAction: selection of an unknown aircraft idx %i\n", id);
00240 return;
00241 }
00242
00243 if (aircraft == ccs.selectedAircraft) {
00244
00245 CL_DisplayPopupAircraft(aircraft);
00246 } else {
00247
00248 MAP_SelectAircraft(aircraft);
00249 if (multiSelection)
00250
00251 CL_DisplayPopupAircraft(aircraft);
00252 }
00253 break;
00254 case MULTISELECT_TYPE_UFO :
00255
00256 if (id < 0 || id >= ccs.numUFOs)
00257 return;
00258 aircraft = ccs.ufos + id;
00259
00260 if (aircraft == ccs.selectedUFO) {
00261
00262 CL_DisplayPopupInterceptUFO(ccs.selectedUFO);
00263 } else {
00264
00265 MAP_SelectUFO(aircraft);
00266 if (multiSelection)
00267
00268 CL_DisplayPopupInterceptUFO(ccs.selectedUFO);
00269 }
00270 break;
00271 case MULTISELECT_TYPE_NONE :
00272 break;
00273 default:
00274 Com_DPrintf(DEBUG_CLIENT, "MAP_MultiSelectExecuteAction: selection of an unknown element type %i\n",
00275 multiSelect.selectType[selected]);
00276 }
00277 }
00278
00282 void MAP_MapClick (uiNode_t* node, int x, int y)
00283 {
00284 aircraft_t *aircraft;
00285 int i;
00286 vec2_t pos;
00287 const linkedList_t *list;
00288
00289
00290 if (cl_3dmap->integer)
00291 MAP3D_ScreenToMap(node, x, y, pos);
00292 else
00293 MAP_ScreenToMap(node, x, y, pos);
00294
00295
00296 switch (ccs.mapAction) {
00297 case MA_NEWBASE:
00299 if (!MapIsWater(MAP_GetColor(pos, MAPTYPE_TERRAIN))) {
00300 Vector2Copy(pos, newBasePos);
00301
00302 CL_GameTimeStop();
00303
00304 if (ccs.numBases < MAX_BASES) {
00305 Cmd_ExecuteString("mn_set_base_title");
00306 UI_PushWindow("popup_newbase", NULL);
00307 }
00308 return;
00309 }
00310 break;
00311 case MA_NEWINSTALLATION:
00312 if (!MapIsWater(MAP_GetColor(pos, MAPTYPE_TERRAIN))) {
00313 Vector2Copy(pos, newBasePos);
00314
00315 if (ccs.numInstallations < MAX_INSTALLATIONS) {
00316 CL_GameTimeStop();
00317 UI_PushWindow("popup_newinstallation", NULL);
00318 }
00319 return;
00320 }
00321 break;
00322 case MA_UFORADAR:
00323 UI_PushWindow("popup_intercept_ufo", NULL);
00324 break;
00325 default:
00326 break;
00327 }
00328
00329
00330 multiSelect.nbSelect = 0;
00331 memset(multiSelect.popupText, 0, sizeof(multiSelect.popupText));
00332
00333
00334 for (list = ccs.missions; list; list = list->next) {
00335 const mission_t *tempMission = (mission_t *)list->data;
00336 if (multiSelect.nbSelect >= MULTISELECT_MAXSELECT)
00337 break;
00338 if (tempMission->stage == STAGE_NOT_ACTIVE || !tempMission->onGeoscape)
00339 continue;
00340 if (tempMission->pos && MAP_IsMapPositionSelected(node, tempMission->pos, x, y))
00341 MAP_MultiSelectListAddItem(MULTISELECT_TYPE_MISSION, MAP_GetIDXByMission(tempMission),
00342 CP_MissionToTypeString(tempMission), _(tempMission->location));
00343 }
00344
00345
00346 for (i = 0; i < MAX_BASES && multiSelect.nbSelect < MULTISELECT_MAXSELECT; i++) {
00347 const base_t *base = B_GetFoundedBaseByIDX(i);
00348 aircraft_t *aircraft;
00349
00350 if (!base)
00351 continue;
00352
00353 if (MAP_IsMapPositionSelected(node, base->pos, x, y))
00354 MAP_MultiSelectListAddItem(MULTISELECT_TYPE_BASE, i, _("Base"), base->name);
00355
00356
00357 aircraft = NULL;
00358 while ((aircraft = AIR_GetNextFromBase(base, aircraft)) != NULL)
00359 if (AIR_IsAircraftOnGeoscape(aircraft) && aircraft->fuel > 0 && MAP_IsMapPositionSelected(node, aircraft->pos, x, y))
00360 MAP_MultiSelectListAddItem(MULTISELECT_TYPE_AIRCRAFT, aircraft->idx, _("Aircraft"), aircraft->name);
00361 }
00362
00363
00364 for (i = 0; i < MAX_INSTALLATIONS && multiSelect.nbSelect < MULTISELECT_MAXSELECT; i++) {
00365 const installation_t *installation = INS_GetFoundedInstallationByIDX(i);
00366 if (!installation)
00367 continue;
00368 if (MAP_IsMapPositionSelected(node, installation->pos, x, y))
00369 MAP_MultiSelectListAddItem(MULTISELECT_TYPE_INSTALLATION, i, _("Installation"), installation->name);
00370 }
00371
00372
00373 for (aircraft = ccs.ufos + ccs.numUFOs - 1; aircraft >= ccs.ufos; aircraft--)
00374 if (UFO_IsUFOSeenOnGeoscape(aircraft)
00375 #ifdef DEBUG
00376 || Cvar_GetInteger("debug_showufos")
00377 #endif
00378 )
00379 if (AIR_IsAircraftOnGeoscape(aircraft) && MAP_IsMapPositionSelected(node, aircraft->pos, x, y))
00380 MAP_MultiSelectListAddItem(MULTISELECT_TYPE_UFO, aircraft - ccs.ufos, _("UFO Sighting"), UFO_AircraftToIDOnGeoscape(aircraft));
00381
00382 if (multiSelect.nbSelect == 1) {
00383
00384 Cmd_ExecuteString("multi_select_click");
00385 } else if (multiSelect.nbSelect > 1) {
00386
00387 UI_RegisterText(TEXT_MULTISELECTION, multiSelect.popupText);
00388 CL_GameTimeStop();
00389 UI_PushWindow("popup_multi_selection", NULL);
00390 } else {
00391
00392 if (!ccs.selectedAircraft)
00393 MAP_ResetAction();
00394 else if (AIR_IsAircraftOnGeoscape(ccs.selectedAircraft) && AIR_AircraftHasEnoughFuel(ccs.selectedAircraft, pos)) {
00395
00396 MAP_MapCalcLine(ccs.selectedAircraft->pos, pos, &ccs.selectedAircraft->route);
00397 ccs.selectedAircraft->status = AIR_TRANSIT;
00398 ccs.selectedAircraft->time = aircraft->point = 0;
00399 }
00400 }
00401 }
00402
00403
00404
00405
00406
00407
00408
00413 #define UI_MAP_DIST_SELECTION 15
00414
00417 static qboolean MAP_IsMapPositionSelected (const uiNode_t* node, const vec2_t pos, int x, int y)
00418 {
00419 int msx, msy;
00420
00421 if (MAP_AllMapToScreen(node, pos, &msx, &msy, NULL))
00422 if (x >= msx - UI_MAP_DIST_SELECTION && x <= msx + UI_MAP_DIST_SELECTION
00423 && y >= msy - UI_MAP_DIST_SELECTION && y <= msy + UI_MAP_DIST_SELECTION)
00424 return qtrue;
00425
00426 return qfalse;
00427 }
00428
00433 const float STANDARD_3D_ZOOM = 40.0f;
00434
00436 #define GLOBE_RADIUS EARTH_RADIUS * (ccs.zoom / STANDARD_3D_ZOOM)
00437
00450 static qboolean MAP_3DMapToScreen (const uiNode_t* node, const vec2_t pos, int *x, int *y, int *z)
00451 {
00452 vec2_t mid;
00453 vec3_t v, v1, rotationAxis;
00454 const float radius = GLOBE_RADIUS;
00455
00456 PolarToVec(pos, v);
00457
00458
00459
00460 VectorSet(rotationAxis, 0, 0, 1);
00461 RotatePointAroundVector(v1, rotationAxis, v, - ccs.angles[PITCH]);
00462
00463 VectorSet(rotationAxis, 0, 1, 0);
00464 RotatePointAroundVector(v, rotationAxis, v1, - ccs.angles[YAW]);
00465
00466
00467 Vector2Set(mid, ccs.mapPos[0] + ccs.mapSize[0] / 2.0f, ccs.mapPos[1] + ccs.mapSize[1] / 2.0f);
00468
00469
00470
00471 *x = (int) (mid[0] - radius * v[1]);
00472 *y = (int) (mid[1] - radius * v[0]);
00473
00474 if (z)
00475 *z = (int) (radius * v[2]);
00476
00477
00478 if (v[2] > 0)
00479 return qfalse;
00480
00481
00482 if (*x < ccs.mapPos[0] && *y < ccs.mapPos[1] && *x > ccs.mapPos[0] + ccs.mapSize[0] && *y > ccs.mapPos[1] + ccs.mapSize[1])
00483 return qfalse;
00484
00485 return qtrue;
00486 }
00487
00498 qboolean MAP_MapToScreen (const uiNode_t* node, const vec2_t pos, int *x, int *y)
00499 {
00500 float sx;
00501
00502
00503 sx = pos[0] / 360 + ccs.center[0] - 0.5;
00504
00505
00506 if (sx < -0.5)
00507 sx += 1.0;
00508 else if (sx > +0.5)
00509 sx -= 1.0;
00510
00511 *x = ccs.mapPos[0] + 0.5 * ccs.mapSize[0] - sx * ccs.mapSize[0] * ccs.zoom;
00512 *y = ccs.mapPos[1] + 0.5 * ccs.mapSize[1] -
00513 (pos[1] / 180 + ccs.center[1] - 0.5) * ccs.mapSize[1] * ccs.zoom;
00514
00515 if (*x < ccs.mapPos[0] && *y < ccs.mapPos[1] &&
00516 *x > ccs.mapPos[0] + ccs.mapSize[0] && *y > ccs.mapPos[1] + ccs.mapSize[1])
00517 return qfalse;
00518 return qtrue;
00519 }
00520
00532 qboolean MAP_AllMapToScreen (const uiNode_t* node, const vec2_t pos, int *x, int *y, int *z)
00533 {
00534 if (cl_3dmap->integer)
00535 return MAP_3DMapToScreen(node, pos, x, y, z);
00536 else {
00537 if (z)
00538 *z = -10;
00539 return MAP_MapToScreen(node, pos, x, y);
00540 }
00541 }
00542
00551 void MAP_Draw3DMarkerIfVisible (const uiNode_t* node, const vec2_t pos, float theta, const char *model, int skin)
00552 {
00553 if (cl_3dmap->integer) {
00554 R_Draw3DMapMarkers(ccs.mapPos[0], ccs.mapPos[1], ccs.mapSize[0], ccs.mapSize[1], ccs.angles, pos, theta, GLOBE_RADIUS, model, skin);
00555 } else {
00556 int x, y;
00557 vec3_t screenPos;
00558
00559 MAP_AllMapToScreen(node, pos, &x, &y, NULL);
00560 VectorSet(screenPos, x, y, 0);
00561
00562 R_Draw2DMapMarkers(screenPos, theta, model, skin);
00563 }
00564 }
00565
00566
00574 static void MAP_ScreenToMap (const uiNode_t* node, int x, int y, vec2_t pos)
00575 {
00576 pos[0] = (((ccs.mapPos[0] - x) / ccs.mapSize[0] + 0.5) / ccs.zoom - (ccs.center[0] - 0.5)) * 360.0;
00577 pos[1] = (((ccs.mapPos[1] - y) / ccs.mapSize[1] + 0.5) / ccs.zoom - (ccs.center[1] - 0.5)) * 180.0;
00578
00579 while (pos[0] > 180.0)
00580 pos[0] -= 360.0;
00581 while (pos[0] < -180.0)
00582 pos[0] += 360.0;
00583 }
00584
00593 static void MAP3D_ScreenToMap (const uiNode_t* node, int x, int y, vec2_t pos)
00594 {
00595 vec2_t mid;
00596 vec3_t v, v1, rotationAxis;
00597 float dist;
00598 const float radius = GLOBE_RADIUS;
00599
00600
00601 Vector2Set(mid, ccs.mapPos[0] + ccs.mapSize[0] / 2.0f, ccs.mapPos[1] + ccs.mapSize[1] / 2.0f);
00602
00603
00604 dist = sqrt((x - mid[0]) * (x - mid[0]) + (y - mid[1]) * (y - mid[1]));
00605 if (dist > radius) {
00606 Vector2Set(pos, -1.0, -1.0);
00607 return;
00608 }
00609
00610
00611
00612
00613
00614
00615
00616 v[0] = - (y - mid[1]);
00617 v[1] = - (x - mid[0]);
00618 v[2] = - sqrt(radius * radius - (x - mid[0]) * (x - mid[0]) - (y - mid[1]) * (y - mid[1]));
00619 VectorNormalize(v);
00620
00621
00622
00623
00624
00625
00626 VectorSet(rotationAxis, 0, 1, 0);
00627 RotatePointAroundVector(v1, rotationAxis, v, ccs.angles[YAW]);
00628
00629
00630
00631 VectorSet(rotationAxis, 0, 0, 1);
00632 RotatePointAroundVector(v, rotationAxis, v1, ccs.angles[PITCH]);
00633
00634
00635
00636 VecToPolar(v, pos);
00637 }
00638
00646 void MAP_MapCalcLine (const vec2_t start, const vec2_t end, mapline_t* line)
00647 {
00648 vec3_t s, e, v;
00649 vec3_t normal;
00650 vec2_t trafo, sa, ea;
00651 float cosTrafo, sinTrafo;
00652 float phiStart, phiEnd, dPhi, phi;
00653 float *p, *last;
00654 int i, n;
00655
00656
00657 PolarToVec(start, s);
00658 PolarToVec(end, e);
00659
00660 if (VectorCompareEps(s, e, UFO_EPSILON)) {
00661 line->distance = 0;
00662 line->numPoints = 2;
00663 Vector2Set(line->point[0], end[0], end[1]);
00664 Vector2Set(line->point[1], end[0], end[1]);
00665 return;
00666 }
00667
00668 CrossProduct(s, e, normal);
00669 VectorNormalize(normal);
00670
00671
00672 VecToPolar(normal, trafo);
00673 cosTrafo = cos(trafo[1] * torad);
00674 sinTrafo = sin(trafo[1] * torad);
00675
00676 sa[0] = start[0] - trafo[0];
00677 sa[1] = start[1];
00678 PolarToVec(sa, s);
00679 ea[0] = end[0] - trafo[0];
00680 ea[1] = end[1];
00681 PolarToVec(ea, e);
00682
00683 phiStart = atan2(s[1], cosTrafo * s[2] - sinTrafo * s[0]);
00684 phiEnd = atan2(e[1], cosTrafo * e[2] - sinTrafo * e[0]);
00685
00686
00687 if (phiEnd < phiStart - M_PI)
00688 phiEnd += 2 * M_PI;
00689 if (phiEnd > phiStart + M_PI)
00690 phiEnd -= 2 * M_PI;
00691
00692 n = (phiEnd - phiStart) / M_PI * LINE_MAXSEG;
00693 if (n > 0)
00694 n = n + 1;
00695 else
00696 n = -n + 1;
00697
00698 line->distance = fabs(phiEnd - phiStart) / n * todeg;
00699 line->numPoints = n + 1;
00700
00701 assert(line->numPoints <= LINE_MAXPTS);
00702 dPhi = (phiEnd - phiStart) / n;
00703 p = NULL;
00704 for (phi = phiStart, i = 0; i <= n; phi += dPhi, i++) {
00705 last = p;
00706 p = line->point[i];
00707 VectorSet(v, -sinTrafo * cos(phi), sin(phi), cosTrafo * cos(phi));
00708 VecToPolar(v, p);
00709 p[0] += trafo[0];
00710
00711 if (!last) {
00712 while (p[0] < -180.0)
00713 p[0] += 360.0;
00714 while (p[0] > +180.0)
00715 p[0] -= 360.0;
00716 } else {
00717 while (p[0] - last[0] > +180.0)
00718 p[0] -= 360.0;
00719 while (p[0] - last[0] < -180.0)
00720 p[0] += 360.0;
00721 }
00722 }
00723 }
00724
00732 static void MAP_MapDrawLine (const uiNode_t* node, const mapline_t* line)
00733 {
00734 const vec4_t color = {1, 0.5, 0.5, 1};
00735 screenPoint_t pts[LINE_MAXPTS];
00736 screenPoint_t *p;
00737 int i, start, old;
00738
00739
00740 R_Color(color);
00741 start = 0;
00742 old = ccs.mapSize[0] / 2;
00743 for (i = 0, p = pts; i < line->numPoints; i++, p++) {
00744 MAP_MapToScreen(node, line->point[i], &p->x, &p->y);
00745
00746
00747 if (i > start && abs(p->x - old) > ccs.mapSize[0] / 2) {
00748
00749 int diff;
00750
00751 if (p->x - old > ccs.mapSize[0] / 2)
00752 diff = -ccs.mapSize[0] * ccs.zoom;
00753 else
00754 diff = ccs.mapSize[0] * ccs.zoom;
00755 p->x += diff;
00756
00757
00758 R_DrawLineStrip(i - start, (int*)(&pts));
00759
00760
00761
00762 start = i;
00763 pts[0].x = p[-1].x - diff;
00764 pts[0].y = p[-1].y;
00765 p = pts;
00766 }
00767 old = p->x;
00768 }
00769
00770 R_DrawLineStrip(i - start, (int*)(&pts));
00771 R_Color(NULL);
00772 }
00773
00781 static void MAP_3DMapDrawLine (const uiNode_t* node, const mapline_t* line)
00782 {
00783 const vec4_t color = {1, 0.5, 0.5, 1};
00784 screenPoint_t pts[LINE_MAXPTS];
00785 int i, numPoints, start;
00786
00787 start = 0;
00788
00789
00790 R_Color(color);
00791 for (i = 0, numPoints = 0; i < line->numPoints; i++) {
00792 if (MAP_3DMapToScreen(node, line->point[i], &pts[i].x, &pts[i].y, NULL))
00793 numPoints++;
00794 else if (!numPoints)
00795
00796 start++;
00797 }
00798
00799 R_DrawLineStrip(numPoints, (int*)(&pts[start]));
00800 R_Color(NULL);
00801 }
00802
00803 #define CIRCLE_DRAW_POINTS 60
00804
00813 void MAP_MapDrawEquidistantPoints (const uiNode_t* node, const vec2_t center, const float angle, const vec4_t color)
00814 {
00815 int i, xCircle, yCircle;
00816 screenPoint_t pts[CIRCLE_DRAW_POINTS + 1];
00817 vec2_t posCircle;
00818 qboolean oldDraw = qfalse;
00819 int numPoints = 0;
00820 vec3_t initialVector, rotationAxis, currentPoint, centerPos;
00821
00822 R_Color(color);
00823
00824
00825 PolarToVec(center, centerPos);
00826
00827
00828 PerpendicularVector(rotationAxis, centerPos);
00829 RotatePointAroundVector(initialVector, rotationAxis, centerPos, angle);
00830
00831
00832 for (i = 0; i <= CIRCLE_DRAW_POINTS; i++) {
00833 qboolean draw = qfalse;
00834 const float degrees = i * 360.0f / (float)CIRCLE_DRAW_POINTS;
00835 RotatePointAroundVector(currentPoint, centerPos, initialVector, degrees);
00836 VecToPolar(currentPoint, posCircle);
00837 if (MAP_AllMapToScreen(node, posCircle, &xCircle, &yCircle, NULL)) {
00838 draw = qtrue;
00839 if (!cl_3dmap->integer && numPoints != 0 && abs(pts[numPoints - 1].x - xCircle) > 512)
00840 oldDraw = qfalse;
00841 }
00842
00843
00844
00845 if (draw != oldDraw && i != 0) {
00846 R_DrawLineStrip(numPoints, (int*)(&pts));
00847 numPoints = 0;
00848 }
00849
00850 if (draw) {
00851 pts[numPoints].x = xCircle;
00852 pts[numPoints].y = yCircle;
00853 numPoints++;
00854 }
00855
00856 oldDraw = draw;
00857 }
00858
00859
00860 R_DrawLineStrip(numPoints, (int*)(&pts));
00861 R_Color(NULL);
00862 }
00863
00872 static float MAP_AngleOfPath3D (const vec3_t start, const vec2_t end, vec3_t direction, vec3_t ortVector)
00873 {
00874 float angle = 0.0f;
00875 vec3_t start3D, end3D, north3D, ortToDest, ortToPole, v;
00876 const vec2_t northPole = {0.0f, 90.0f};
00878 PolarToVec(start, start3D);
00879 PolarToVec(end, end3D);
00880 PolarToVec(northPole, north3D);
00881
00882
00883 CrossProduct(start3D, end3D, ortToDest);
00884 VectorNormalize(ortToDest);
00885 if (ortVector) {
00886 VectorCopy(ortToDest, ortVector);
00887 }
00888
00889
00890 CrossProduct(start3D, north3D, ortToPole);
00891 VectorNormalize(ortToPole);
00892
00897
00898 if (direction) {
00899 float dist;
00900 VectorSubtract(ortToDest, direction, v);
00901 dist = VectorLength(v);
00902 if (dist > 0.01) {
00903 vec3_t rotationAxis;
00904 CrossProduct(direction, ortToDest, rotationAxis);
00905 VectorNormalize(rotationAxis);
00906 RotatePointAroundVector(v, rotationAxis, direction, 5.0);
00907 VectorCopy(v, direction);
00908 VectorSubtract(ortToDest, direction, v);
00909 if (VectorLength(v) < dist)
00910 VectorCopy(direction, ortToDest);
00911 else
00912 VectorCopy(ortToDest, direction);
00913 }
00914 }
00915
00916
00917 angle = todeg * acos(DotProduct(ortToDest, ortToPole));
00918
00919 CrossProduct(ortToDest, ortToPole, v);
00920 if (DotProduct(start3D, v) < 0)
00921 angle = - angle;
00922
00923 return angle;
00924 }
00925
00934 static float MAP_AngleOfPath2D (const vec3_t start, const vec2_t end, vec3_t direction, vec3_t ortVector)
00935 {
00936 float angle = 0.0f;
00937 vec3_t start3D, end3D, tangentVector, v, rotationAxis;
00938
00939
00940 PolarToVec(start, start3D);
00941 PolarToVec(end, end3D);
00942 if (ortVector) {
00943 CrossProduct(start3D, end3D, ortVector);
00944 VectorNormalize(ortVector);
00945 CrossProduct(ortVector, start3D, tangentVector);
00946 } else {
00947 CrossProduct(start3D, end3D, v);
00948 CrossProduct(v, start3D, tangentVector);
00949 }
00950 VectorNormalize(tangentVector);
00951
00952
00953 if (direction) {
00954 float dist;
00955 VectorSubtract(tangentVector, direction, v);
00956 dist = VectorLength(v);
00957 if (dist > 0.01) {
00958 CrossProduct(direction, tangentVector, rotationAxis);
00959 VectorNormalize(rotationAxis);
00960 RotatePointAroundVector(v, rotationAxis, direction, 5.0);
00961 VectorSubtract(tangentVector, direction, v);
00962 if (VectorLength(v) < dist)
00963 VectorCopy(direction, tangentVector);
00964 else
00965 VectorCopy(tangentVector, direction);
00966 }
00967 }
00968
00969 VectorSet(rotationAxis, 0, 0, 1);
00970 RotatePointAroundVector(v, rotationAxis, tangentVector, - start[0]);
00971 VectorSet(rotationAxis, 0, 1, 0);
00972 RotatePointAroundVector(tangentVector, rotationAxis, v, start[1] + 90.0f);
00973
00974
00975 angle = todeg * atan(tangentVector[0] / tangentVector[1]);
00976 if (tangentVector[1] > 0)
00977 angle -= 90.0f;
00978 else
00979 angle += 90.0f;
00980
00981 return angle;
00982 }
00983
00992 float MAP_AngleOfPath (const vec3_t start, const vec2_t end, vec3_t direction, vec3_t ortVector)
00993 {
00994 float angle;
00995
00996 if (cl_3dmap->integer)
00997 angle = MAP_AngleOfPath3D(start, end, direction, ortVector);
00998 else
00999 angle = MAP_AngleOfPath2D(start, end, direction, ortVector);
01000
01001 return angle;
01002 }
01003
01009 static void MAP_ConvertObjectPositionToGeoscapePosition (float* vector, const vec2_t objectPos)
01010 {
01011 if (cl_3dmap->integer)
01012 VectorSet(vector, objectPos[0], -objectPos[1], 0);
01013 else
01014 Vector2Set(vector, objectPos[0], objectPos[1]);
01015 }
01016
01020 static void MAP_GetMissionAngle (float *vector, int id)
01021 {
01022 mission_t *mission = MAP_GetMissionByIDX(id);
01023 assert(mission);
01024 MAP_ConvertObjectPositionToGeoscapePosition(vector, mission->pos);
01025 MAP_SelectMission(mission);
01026 }
01027
01031 static void MAP_GetUFOAngle (float *vector, int idx)
01032 {
01033 aircraft_t *aircraft;
01034
01035 for (aircraft = ccs.ufos + ccs.numUFOs - 1; aircraft >= ccs.ufos; aircraft --) {
01036 if (aircraft->idx != idx)
01037 continue;
01038 if (UFO_IsUFOSeenOnGeoscape(aircraft)) {
01039 MAP_ConvertObjectPositionToGeoscapePosition(vector, aircraft->pos);
01040 MAP_SelectUFO(aircraft);
01041 return;
01042 }
01043 }
01044 }
01045
01046
01054 static void MAP_StartCenter (void)
01055 {
01056 if (cl_3dmap->integer) {
01057
01058 vec3_t diff;
01059
01060 smoothFinalGlobeAngle[1] += GLOBE_ROTATE;
01061 VectorSubtract(smoothFinalGlobeAngle, ccs.angles, diff);
01062 smoothDeltaLength = VectorLength(diff);
01063 } else {
01064
01065 vec2_t diff;
01066
01067 Vector2Set(smoothFinal2DGeoscapeCenter, 0.5f - smoothFinal2DGeoscapeCenter[0] / 360.0f, 0.5f - smoothFinal2DGeoscapeCenter[1] / 180.0f);
01068 if (smoothFinal2DGeoscapeCenter[1] < 0.5 / ZOOM_LIMIT)
01069 smoothFinal2DGeoscapeCenter[1] = 0.5 / ZOOM_LIMIT;
01070 if (smoothFinal2DGeoscapeCenter[1] > 1.0 - 0.5 / ZOOM_LIMIT)
01071 smoothFinal2DGeoscapeCenter[1] = 1.0 - 0.5 / ZOOM_LIMIT;
01072 diff[0] = smoothFinal2DGeoscapeCenter[0] - ccs.center[0];
01073 diff[1] = smoothFinal2DGeoscapeCenter[1] - ccs.center[1];
01074 smoothDeltaLength = sqrt(diff[0] * diff[0] + diff[1] * diff[1]);
01075 }
01076
01077 smoothFinalZoom = ZOOM_LIMIT;
01078 smoothDeltaZoom = fabs(smoothFinalZoom - ccs.zoom);
01079 smoothRotation = qtrue;
01080 }
01081
01085 static void MAP_SelectObject_f (void)
01086 {
01087 const char *type;
01088 int idx;
01089
01090 if (Cmd_Argc() != 3) {
01091 Com_Printf("Usage: %s <mission|ufo> <id>\n", Cmd_Argv(0));
01092 return;
01093 }
01094
01095 type = Cmd_Argv(1);
01096 idx = atoi(Cmd_Argv(2));
01097
01098 if (cl_3dmap->integer) {
01099 if (!strcmp(type, "mission"))
01100 MAP_GetMissionAngle(smoothFinalGlobeAngle, idx);
01101 else if (!strcmp(type, "ufo"))
01102 MAP_GetUFOAngle(smoothFinalGlobeAngle, idx);
01103 else {
01104 Com_Printf("MAP_SelectObject_f: type %s unsupported.", type);
01105 return;
01106 }
01107 } else {
01108 if (!strcmp(type, "mission"))
01109 MAP_GetMissionAngle(smoothFinal2DGeoscapeCenter, idx);
01110 else if (!strcmp(type, "ufo"))
01111 MAP_GetUFOAngle(smoothFinal2DGeoscapeCenter, idx);
01112 else {
01113 Com_Printf("MAP_SelectObject_f: type %s unsupported.", type);
01114 return;
01115 }
01116 }
01117 MAP_StartCenter();
01118 }
01119
01126 static void MAP_GetGeoscapeAngle (float *vector)
01127 {
01128 int baseIdx;
01129 int counter = 0;
01130 int maxEventIdx;
01131 const int numMissions = CP_CountMissionOnGeoscape();
01132 aircraft_t *aircraft;
01133
01134
01135 maxEventIdx = numMissions + ccs.numBases + ccs.numInstallations - 1;
01136 for (baseIdx = 0; baseIdx < MAX_BASES; baseIdx++) {
01137 base_t *base = B_GetFoundedBaseByIDX(baseIdx);
01138 aircraft_t *aircraft;
01139
01140 aircraft = NULL;
01141 while ((aircraft = AIR_GetNextFromBase(base, aircraft)) != NULL) {
01142 if (AIR_IsAircraftOnGeoscape(aircraft))
01143 maxEventIdx++;
01144 }
01145 }
01146 for (aircraft = ccs.ufos + ccs.numUFOs - 1; aircraft >= ccs.ufos; aircraft --) {
01147 if (UFO_IsUFOSeenOnGeoscape(aircraft))
01148 maxEventIdx++;
01149 }
01150
01151
01152 if (maxEventIdx < 0) {
01153 MAP_ConvertObjectPositionToGeoscapePosition(vector, vec2_origin);
01154 return;
01155 }
01156
01157
01158 if (centerOnEventIdx < 0)
01159 centerOnEventIdx = maxEventIdx;
01160 if (centerOnEventIdx > maxEventIdx)
01161 centerOnEventIdx = 0;
01162
01163
01164 if (centerOnEventIdx < numMissions) {
01165 const linkedList_t *list = ccs.missions;
01166 mission_t *mission = NULL;
01167 for (;list && (centerOnEventIdx != counter - 1); list = list->next) {
01168 mission = (mission_t *)list->data;
01169 if (mission->stage != STAGE_NOT_ACTIVE && mission->stage != STAGE_OVER && mission->onGeoscape)
01170 counter++;
01171 }
01172 assert(mission);
01173
01174 MAP_ConvertObjectPositionToGeoscapePosition(vector, mission->pos);
01175 MAP_SelectMission(mission);
01176 return;
01177 }
01178 counter += numMissions;
01179
01180
01181 if (centerOnEventIdx < ccs.numBases + counter) {
01182 for (baseIdx = 0; baseIdx < MAX_BASES; baseIdx++) {
01183 const base_t *base = B_GetFoundedBaseByIDX(baseIdx);
01184 if (!base)
01185 continue;
01186
01187 if (counter == centerOnEventIdx) {
01188 MAP_ConvertObjectPositionToGeoscapePosition(vector, ccs.bases[baseIdx].pos);
01189 return;
01190 }
01191 counter++;
01192 }
01193 }
01194 counter += ccs.numBases;
01195
01196
01197 if (centerOnEventIdx < ccs.numInstallations + counter) {
01198 int instIdx;
01199 for (instIdx = 0; instIdx < MAX_INSTALLATIONS; instIdx++) {
01200 const installation_t *inst = INS_GetFoundedInstallationByIDX(instIdx);
01201 if (!inst)
01202 continue;
01203
01204 if (counter == centerOnEventIdx) {
01205 MAP_ConvertObjectPositionToGeoscapePosition(vector, ccs.installations[instIdx].pos);
01206 return;
01207 }
01208 counter++;
01209 }
01210 }
01211 counter += ccs.numInstallations;
01212
01213
01214 for (baseIdx = 0; baseIdx < MAX_BASES; baseIdx++) {
01215 base_t *base = B_GetFoundedBaseByIDX(baseIdx);
01216
01217 aircraft = NULL;
01218 while ((aircraft = AIR_GetNextFromBase(base, aircraft)) != NULL) {
01219 if (AIR_IsAircraftOnGeoscape(aircraft)) {
01220 if (centerOnEventIdx == counter) {
01221 MAP_ConvertObjectPositionToGeoscapePosition(vector, aircraft->pos);
01222 MAP_SelectAircraft(aircraft);
01223 return;
01224 }
01225 counter++;
01226 }
01227 }
01228 }
01229
01230
01231 for (aircraft = ccs.ufos + ccs.numUFOs - 1; aircraft >= ccs.ufos; aircraft --) {
01232 if (UFO_IsUFOSeenOnGeoscape(aircraft)) {
01233 if (centerOnEventIdx == counter) {
01234 MAP_ConvertObjectPositionToGeoscapePosition(vector, aircraft->pos);
01235 MAP_SelectUFO(aircraft);
01236 return;
01237 }
01238 counter++;
01239 }
01240 }
01241 }
01242
01253 void MAP_CenterOnPoint_f (void)
01254 {
01255 if (strcmp(UI_GetActiveWindowName(), "geoscape"))
01256 return;
01257
01258 centerOnEventIdx++;
01259
01260 if (cl_3dmap->integer)
01261 MAP_GetGeoscapeAngle(smoothFinalGlobeAngle);
01262 else
01263 MAP_GetGeoscapeAngle(smoothFinal2DGeoscapeCenter);
01264 MAP_StartCenter();
01265 }
01266
01273 static void MAP3D_SmoothRotate (void)
01274 {
01275 vec3_t diff;
01276 const float diffZoom = smoothFinalZoom - ccs.zoom;
01277
01278 VectorSubtract(smoothFinalGlobeAngle, ccs.angles, diff);
01279
01280 if (smoothDeltaLength > smoothDeltaZoom) {
01281
01282 const float diffAngle = VectorLength(diff);
01283 const float epsilon = 0.1f;
01284 if (diffAngle > epsilon) {
01285 float rotationSpeed;
01286
01287
01288 if (diffAngle / smoothDeltaLength > 0.5)
01289 rotationSpeed = min(diffAngle, curRotationSpeed + sin(3.05f * diffAngle / smoothDeltaLength) * diffAngle * 0.5);
01290 else
01291 rotationSpeed = sin(3.05f * diffAngle / smoothDeltaLength) * diffAngle;
01292 curRotationSpeed = rotationSpeed;
01293 VectorScale(diff, smoothAcceleration / diffAngle * rotationSpeed, diff);
01294 VectorAdd(ccs.angles, diff, ccs.angles);
01295 ccs.zoom = ccs.zoom + smoothAcceleration * diffZoom / diffAngle * rotationSpeed;
01296 return;
01297 }
01298 } else {
01299 const float epsilonZoom = 0.01f;
01300
01301 if (fabs(diffZoom) > epsilonZoom) {
01302 float speed;
01303
01304
01305 if (fabs(diffZoom) / smoothDeltaZoom > 0.5)
01306 speed = min(smoothAcceleration * 2.0, curZoomSpeed + sin(3.05f * (fabs(diffZoom) / smoothDeltaZoom)) * smoothAcceleration);
01307 else {
01308 speed = sin(3.05f * (fabs(diffZoom) / smoothDeltaZoom)) * smoothAcceleration * 2.0;
01309 }
01310 curZoomSpeed = speed;
01311 ccs.zoom = ccs.zoom + diffZoom * speed;
01312 return;
01313 }
01314 }
01315
01316
01317 VectorCopy(smoothFinalGlobeAngle, ccs.angles);
01318 smoothRotation = qfalse;
01319 ccs.zoom = smoothFinalZoom;
01320 }
01321
01327 void MAP_StopSmoothMovement (void)
01328 {
01329 smoothRotation = qfalse;
01330 }
01331
01332 #define SMOOTHING_STEP_2D 0.02f
01333
01340 static void MAP_SmoothTranslate (void)
01341 {
01342 const float dist1 = smoothFinal2DGeoscapeCenter[0] - ccs.center[0];
01343 const float dist2 = smoothFinal2DGeoscapeCenter[1] - ccs.center[1];
01344 const float length = sqrt(dist1 * dist1 + dist2 * dist2);
01345
01346 if (length < SMOOTHING_STEP_2D) {
01347 ccs.center[0] = smoothFinal2DGeoscapeCenter[0];
01348 ccs.center[1] = smoothFinal2DGeoscapeCenter[1];
01349 ccs.zoom = smoothFinalZoom;
01350 smoothRotation = qfalse;
01351 } else {
01352 const float diffZoom = smoothFinalZoom - ccs.zoom;
01353 ccs.center[0] = ccs.center[0] + SMOOTHING_STEP_2D * dist1 / length;
01354 ccs.center[1] = ccs.center[1] + SMOOTHING_STEP_2D * dist2 / length;
01355 ccs.zoom = ccs.zoom + SMOOTHING_STEP_2D * diffZoom;
01356 }
01357 }
01358
01359 #define BULLET_SIZE 1
01360
01366 static void MAP_DrawBullets (const uiNode_t* node, const vec3_t pos)
01367 {
01368 int x, y;
01369
01370 if (MAP_AllMapToScreen(node, pos, &x, &y, NULL))
01371 R_DrawFill(x, y, BULLET_SIZE, BULLET_SIZE, yellow);
01372 }
01373
01382 static void MAP_DrawBeam (const uiNode_t* node, const vec3_t start, const vec3_t end, const vec4_t color)
01383 {
01384 int points[4];
01385
01386 if (!MAP_AllMapToScreen(node, start, &(points[0]), &(points[1]), NULL))
01387 return;
01388 if (!MAP_AllMapToScreen(node, end, &(points[2]), &(points[3]), NULL))
01389 return;
01390
01391 R_Color(color);
01392 R_DrawLine(points, 2.0);
01393 R_Color(NULL);
01394 }
01395
01396 #define SELECT_CIRCLE_RADIUS 1.5f + 3.0f / ccs.zoom
01397
01403 static void MAP_DrawMapOneMission (const uiNode_t* node, const mission_t *ms)
01404 {
01405 int x, y;
01406
01407 if (ms == ccs.selectedMission)
01408 Cvar_Set("mn_mapdaytime", MAP_IsNight(ms->pos) ? _("Night") : _("Day"));
01409
01410 if (!MAP_AllMapToScreen(node, ms->pos, &x, &y, NULL))
01411 return;
01412
01413 if (ms == ccs.selectedMission) {
01414
01415 if (cl_3dmap->integer) {
01416 if (!ccs.selectedMission->active)
01417 MAP_MapDrawEquidistantPoints(node, ms->pos, SELECT_CIRCLE_RADIUS, yellow);
01418 } else {
01419 const image_t *image;
01420 if (ccs.selectedMission->active) {
01421 image = geoscapeImages[GEOSCAPE_IMAGE_MISSION_ACTIVE];
01422 } else {
01423 image = geoscapeImages[GEOSCAPE_IMAGE_MISSION_SELECTED];
01424 }
01425 R_DrawImage(x - image->width / 2, y - image->height / 2, image);
01426 }
01427 }
01428
01429
01430 if (cl_3dmap->integer) {
01431 MAP_Draw3DMarkerIfVisible(node, ms->pos, defaultBaseAngle, MAP_GetMissionModel(ms), 0);
01432 } else {
01433 const image_t *image = geoscapeImages[GEOSCAPE_IMAGE_MISSION];
01434 R_DrawImage(x - image->width / 2, y - image->height / 2, image);
01435 }
01436
01437 UI_DrawString("f_verysmall", ALIGN_UL, x + 10, y, 0, 0, 0, _(ms->location), 0, 0, NULL, qfalse, 0);
01438 }
01439
01448 static void MAP_DrawMapOneInstallation (const uiNode_t* node, const installation_t *installation,
01449 qboolean oneUFOVisible, const char* font)
01450 {
01451 const installationTemplate_t *tpl = installation->installationTemplate;
01452 int x, y;
01453
01454
01455 if (oneUFOVisible && AII_InstallationCanShoot(installation)) {
01456 int i;
01457 for (i = 0; i < tpl->maxBatteries; i++) {
01458 const aircraftSlot_t const *slot = &installation->batteries[i].slot;
01459 if (slot->item && slot->ammoLeft != 0 && slot->installationTime == 0) {
01460 MAP_MapDrawEquidistantPoints(node, installation->pos,
01461 slot->ammo->craftitem.stats[AIR_STATS_WRANGE], red);
01462 }
01463 }
01464 }
01465
01466
01467 if (MAP_IsRadarOverlayActivated())
01468 RADAR_DrawInMap(node, &installation->radar, installation->pos);
01469
01470
01471 if (cl_3dmap->integer) {
01472 MAP_Draw3DMarkerIfVisible(node, installation->pos, defaultBaseAngle, tpl->model, 0);
01473 } else if (MAP_MapToScreen(node, installation->pos, &x, &y)) {
01474 const image_t *image = R_FindImage(tpl->image, it_pic);
01475 if (image)
01476 R_DrawImage(x - image->width / 2, y - image->height / 2, image);
01477 }
01478
01479
01480 if (MAP_AllMapToScreen(node, installation->pos, &x, &y, NULL))
01481 UI_DrawString(font, ALIGN_UL, x, y + 10, 0, 0, 0, installation->name, 0, 0, NULL, qfalse, 0);
01482 }
01483
01491 static void MAP_DrawMapOneBase (const uiNode_t* node, const base_t *base,
01492 qboolean oneUFOVisible, const char* font)
01493 {
01494 int x, y;
01495
01496
01497 if (oneUFOVisible && AII_BaseCanShoot(base)) {
01498 int i;
01499 for (i = 0; i < base->numBatteries; i++) {
01500 const aircraftSlot_t const *slot = &base->batteries[i].slot;
01501 if (slot->item && slot->ammoLeft != 0 && slot->installationTime == 0) {
01502 MAP_MapDrawEquidistantPoints(node, base->pos,
01503 slot->ammo->craftitem.stats[AIR_STATS_WRANGE], red);
01504 }
01505 }
01506 for (i = 0; i < base->numLasers; i++) {
01507 const aircraftSlot_t const *slot = &base->lasers[i].slot;
01508 if (slot->item && slot->ammoLeft != 0 && slot->installationTime == 0) {
01509 MAP_MapDrawEquidistantPoints(node, base->pos,
01510 slot->ammo->craftitem.stats[AIR_STATS_WRANGE], red);
01511 }
01512 }
01513 }
01514
01515
01516 if (MAP_IsRadarOverlayActivated())
01517 RADAR_DrawInMap(node, &base->radar, base->pos);
01518
01519
01520 if (cl_3dmap->integer) {
01521 if (base->baseStatus == BASE_UNDER_ATTACK)
01522
01523 MAP_Draw3DMarkerIfVisible(node, base->pos, defaultBaseAngle, "geoscape/base", 1);
01524 else
01525 MAP_Draw3DMarkerIfVisible(node, base->pos, defaultBaseAngle, "geoscape/base", 0);
01526 } else if (MAP_MapToScreen(node, base->pos, &x, &y)) {
01527 const image_t *image;
01528 if (base->baseStatus == BASE_UNDER_ATTACK)
01529 image = geoscapeImages[GEOSCAPE_IMAGE_BASE_ATTACK];
01530 else
01531 image = geoscapeImages[GEOSCAPE_IMAGE_BASE];
01532
01533 R_DrawImage(x - image->width / 2, y - image->height / 2, image);
01534 }
01535
01536
01537 if (MAP_AllMapToScreen(node, base->pos, &x, &y, NULL))
01538 UI_DrawString(font, ALIGN_UL, x, y + 10, 0, 0, 0, base->name, 0, 0, NULL, qfalse, 0);
01539 }
01540
01547 static void MAP_DrawAircraftHealthBar (const uiNode_t* node, const aircraft_t *aircraft) {
01548 const vec4_t bordercolor = {1, 1, 1, 1};
01549 const int width = 8 * ccs.zoom;
01550 const int height = 1 * ccs.zoom * 0.9;
01551 vec4_t color;
01552 int centerX;
01553 int centerY;
01554
01555 if (!aircraft)
01556 return;
01557 if (aircraft->stats[AIR_STATS_DAMAGE] <= 0)
01558 return;
01559
01560 if (((float)aircraft->damage / aircraft->stats[AIR_STATS_DAMAGE]) <= .33) {
01561 Vector4Copy(red, color);
01562 } else if (((float)aircraft->damage / aircraft->stats[AIR_STATS_DAMAGE]) <= .75) {
01563 Vector4Copy(yellow, color);
01564 } else {
01565 Vector4Copy(green, color);
01566 }
01567
01568 MAP_AllMapToScreen(node, aircraft->pos, ¢erX, ¢erY, NULL);
01569
01570 R_DrawFill(centerX - width / 2 , centerY - 5 * ccs.zoom, round(width * ((float)aircraft->damage / aircraft->stats[AIR_STATS_DAMAGE])), height, color);
01571 R_DrawRect(centerX - width / 2, centerY - 5 * ccs.zoom, width, height, bordercolor, 1.0, 1);
01572
01573 }
01574
01581 static void MAP_DrawMapOnePhalanxAircraft (const uiNode_t* node, aircraft_t *aircraft, qboolean oneUFOVisible)
01582 {
01583 float angle;
01584
01585
01586 if (MAP_IsRadarOverlayActivated())
01587 RADAR_DrawInMap(node, &aircraft->radar, aircraft->pos);
01588
01589
01590 if (oneUFOVisible)
01591 MAP_MapDrawEquidistantPoints(node, aircraft->pos, aircraft->stats[AIR_STATS_WRANGE] / 1000.0f, red);
01592
01593
01594 if (aircraft->status >= AIR_TRANSIT) {
01595
01596 mapline_t path;
01597
01598 path.numPoints = aircraft->route.numPoints - aircraft->point;
01600 if (path.numPoints > 1) {
01601 memcpy(path.point, aircraft->pos, sizeof(vec2_t));
01602 memcpy(path.point + 1, aircraft->route.point + aircraft->point + 1, (path.numPoints - 1) * sizeof(vec2_t));
01603 if (cl_3dmap->integer)
01604 MAP_3DMapDrawLine(node, &path);
01605 else
01606 MAP_MapDrawLine(node, &path);
01607 }
01608 angle = MAP_AngleOfPath(aircraft->pos, aircraft->route.point[aircraft->route.numPoints - 1], aircraft->direction, NULL);
01609 } else {
01610
01611 angle = 0.0f;
01612 }
01613
01614
01615 if (aircraft == ccs.selectedAircraft) {
01616 const image_t *image = geoscapeImages[GEOSCAPE_IMAGE_MISSION];
01617 int x;
01618 int y;
01619
01620 if (cl_3dmap->integer)
01621 MAP_MapDrawEquidistantPoints(node, aircraft->pos, SELECT_CIRCLE_RADIUS, yellow);
01622 else {
01623 MAP_AllMapToScreen(node, aircraft->pos, &x, &y, NULL);
01624 R_DrawImage(x - image->width / 2, y - image->height / 2, image);
01625 }
01626
01627
01628 if (aircraft->status == AIR_UFO && MAP_AllMapToScreen(node, aircraft->aircraftTarget->pos, &x, &y, NULL)) {
01629 if (cl_3dmap->integer)
01630 MAP_MapDrawEquidistantPoints(node, aircraft->aircraftTarget->pos, SELECT_CIRCLE_RADIUS, yellow);
01631 else
01632 R_DrawImage(x - image->width / 2, y - image->height / 2, image);
01633 }
01634 }
01635
01636
01637 MAP_Draw3DMarkerIfVisible(node, aircraft->pos, angle, aircraft->model, 0);
01638 VectorCopy(aircraft->pos, aircraft->oldDrawPos);
01639
01641 if (oneUFOVisible || Cvar_GetInteger("debug_showcrafthealth") >= 1)
01642 MAP_DrawAircraftHealthBar(node, aircraft);
01643 }
01644
01654 static const char *MAP_GetMissionText (char *buffer, size_t size, const mission_t *mission)
01655 {
01656 assert(mission);
01657 Com_sprintf(buffer, size, _("Location: %s\nType: %s\nObjective: %s"), mission->location,
01658 CP_MissionToTypeString(mission), (mission->mapDef) ? _(mission->mapDef->description) : _("Unknown"));
01659 return buffer;
01660 }
01661
01671 static const char *MAP_GetShortMissionText (char *buffer, size_t size, const mission_t *mission)
01672 {
01673 assert(mission);
01674 Com_sprintf(buffer, size, _("%s (%s)\n%s"),
01675 CP_MissionToTypeString(mission),
01676 mission->location,
01677 (mission->mapDef) ? _(mission->mapDef->description) : _("Unknown"));
01678 return buffer;
01679 }
01680
01690 static const char *MAP_GetAircraftText (char *buffer, size_t size, const aircraft_t *aircraft)
01691 {
01692 if (aircraft->status == AIR_UFO) {
01693 const float distance = GetDistanceOnGlobe(aircraft->pos, aircraft->aircraftTarget->pos);
01694 Com_sprintf(buffer, size, _("Name:\t%s (%i/%i)\n"), aircraft->name, AIR_GetTeamSize(aircraft), aircraft->maxTeamSize);
01695 Q_strcat(buffer, va(_("Status:\t%s\n"), AIR_AircraftStatusToName(aircraft)), size);
01696 Q_strcat(buffer, va(_("Distance to target:\t\t%.0f\n"), distance), size);
01697 Q_strcat(buffer, va(_("Speed:\t%i km/h\n"), CL_AircraftMenuStatsValues(aircraft->stats[AIR_STATS_SPEED], AIR_STATS_SPEED)), size);
01698 Q_strcat(buffer, va(_("Fuel:\t%i/%i\n"), CL_AircraftMenuStatsValues(aircraft->fuel, AIR_STATS_FUELSIZE),
01699 CL_AircraftMenuStatsValues(aircraft->stats[AIR_STATS_FUELSIZE], AIR_STATS_FUELSIZE)), size);
01700 Q_strcat(buffer, va(_("ETA:\t%sh\n"), CL_SecondConvert((float)SECONDS_PER_HOUR * distance / aircraft->stats[AIR_STATS_SPEED])), size);
01701 } else {
01702 Com_sprintf(buffer, size, _("Name:\t%s (%i/%i)\n"), aircraft->name, AIR_GetTeamSize(aircraft), aircraft->maxTeamSize);
01703 Q_strcat(buffer, va(_("Status:\t%s\n"), AIR_AircraftStatusToName(aircraft)), size);
01704 Q_strcat(buffer, va(_("Speed:\t%i km/h\n"), CL_AircraftMenuStatsValues(aircraft->stats[AIR_STATS_SPEED], AIR_STATS_SPEED)), size);
01705 Q_strcat(buffer, va(_("Fuel:\t%i/%i\n"), CL_AircraftMenuStatsValues(aircraft->fuel, AIR_STATS_FUELSIZE),
01706 CL_AircraftMenuStatsValues(aircraft->stats[AIR_STATS_FUELSIZE], AIR_STATS_FUELSIZE)), size);
01707 if (aircraft->status != AIR_IDLE) {
01708 const float distance = GetDistanceOnGlobe(aircraft->pos,
01709 aircraft->route.point[aircraft->route.numPoints - 1]);
01710 Q_strcat(buffer, va(_("ETA:\t%sh\n"), CL_SecondConvert((float)SECONDS_PER_HOUR * distance / aircraft->stats[AIR_STATS_SPEED])), size);
01711 }
01712 }
01713 return buffer;
01714 }
01715
01725 static const char *MAP_GetUFOText (char *buffer, size_t size, const aircraft_t* ufo)
01726 {
01727 Com_sprintf(buffer, size, "%s\n", UFO_AircraftToIDOnGeoscape(ufo));
01728 Q_strcat(buffer, va(_("Speed: %i km/h\n"), CL_AircraftMenuStatsValues(ufo->stats[AIR_STATS_SPEED], AIR_STATS_SPEED)), size);
01729 return buffer;
01730 }
01731
01735 void MAP_UpdateGeoscapeDock (void)
01736 {
01737 const linkedList_t *list;
01738 int ufoIDX;
01739 char buf[512];
01740
01741 UI_ExecuteConfunc("clean_geoscape_object");
01742
01743
01744 for (list = ccs.missions; list; list = list->next) {
01745 const mission_t *ms = (mission_t *)list->data;
01746 if (!ms->onGeoscape)
01747 continue;
01748 UI_ExecuteConfunc("add_geoscape_object mission %i \"%s\" %s \"%s\"",
01749 ms->idx, ms->location, MAP_GetMissionModel(ms), MAP_GetShortMissionText(buf, sizeof(buf), ms));
01750 }
01751
01752
01753 for (ufoIDX = 0; ufoIDX < ccs.numUFOs; ufoIDX++) {
01754 aircraft_t *ufo = &ccs.ufos[ufoIDX];
01755 if (!UFO_IsUFOSeenOnGeoscape(ufo))
01756 continue;
01757
01758 UI_ExecuteConfunc("add_geoscape_object ufo %i %i %s \"%s\"",
01759 ufoIDX, ufoIDX, ufo->model, MAP_GetUFOText(buf, sizeof(buf), ufo));
01760 }
01761 }
01762
01772 static void MAP_DrawMapMarkers (const uiNode_t* node)
01773 {
01774 const linkedList_t *list;
01775 int x, y, i, baseIdx, installationIdx, aircraftIdx, idx;
01776 const char* font;
01777
01778 const vec4_t white = {1.f, 1.f, 1.f, 0.7f};
01779 qboolean showXVI = qfalse;
01780 qboolean oneUFOVisible = qfalse;
01781 static char buffer[512] = "";
01782 int maxInterpolationPoints;
01783
01784 assert(node);
01785
01786
01787 R_Color(node->color);
01788
01789 font = UI_GetFontFromNode(node);
01790
01791
01792 for (aircraftIdx = 0; aircraftIdx < ccs.numUFOs; aircraftIdx++) {
01793 const aircraft_t const *aircraft = &ccs.ufos[aircraftIdx];
01794 if (UFO_IsUFOSeenOnGeoscape(aircraft)) {
01795 oneUFOVisible = qtrue;
01796 break;
01797 }
01798 }
01799
01800
01801 Cvar_Set("mn_mapdaytime", "");
01802 for (list = ccs.missions; list; list = list->next) {
01803 const mission_t *ms = (mission_t *)list->data;
01804 if (!ms->onGeoscape)
01805 continue;
01806 MAP_DrawMapOneMission(node, ms);
01807 }
01808
01809
01810 for (installationIdx = 0; installationIdx < MAX_INSTALLATIONS; installationIdx++) {
01811 const installation_t *installation = INS_GetFoundedInstallationByIDX(installationIdx);
01812 if (!installation)
01813 continue;
01814 MAP_DrawMapOneInstallation(node, installation, oneUFOVisible, font);
01815 }
01816
01817
01818 for (baseIdx = 0; baseIdx < MAX_BASES; baseIdx++) {
01819 base_t *base = B_GetFoundedBaseByIDX(baseIdx);
01820 aircraft_t *aircraft;
01821 if (!base)
01822 continue;
01823
01824 MAP_DrawMapOneBase(node, base, oneUFOVisible, font);
01825
01826
01827 aircraft = NULL;
01828 while ((aircraft = AIR_GetNextFromBase(base, aircraft)) != NULL) {
01829 if (AIR_IsAircraftOnGeoscape(aircraft))
01830 MAP_DrawMapOnePhalanxAircraft(node, aircraft, oneUFOVisible);
01831 }
01832 }
01833
01834
01835 for (aircraftIdx = 0; aircraftIdx < ccs.numUFOs; aircraftIdx++) {
01836 aircraft_t *aircraft = &ccs.ufos[aircraftIdx];
01837 #ifdef DEBUG
01838
01839 if (Cvar_GetInteger("debug_showufos")) {
01840
01841 if (cl_3dmap->integer)
01842 MAP_3DMapDrawLine(node, &aircraft->route);
01843 else
01844 MAP_MapDrawLine(node, &aircraft->route);
01845 } else
01846 #endif
01847 if (!oneUFOVisible || !UFO_IsUFOSeenOnGeoscape(aircraft))
01848 continue;
01849
01850 {
01851 const float angle = MAP_AngleOfPath(aircraft->pos, aircraft->route.point[aircraft->route.numPoints - 1], aircraft->direction, NULL);
01852
01853 if (cl_3dmap->integer)
01854 MAP_MapDrawEquidistantPoints(node, aircraft->pos, SELECT_CIRCLE_RADIUS, white);
01855 if (aircraft == ccs.selectedUFO) {
01856 if (cl_3dmap->integer)
01857 MAP_MapDrawEquidistantPoints(node, aircraft->pos, SELECT_CIRCLE_RADIUS, yellow);
01858 else {
01859 const image_t *image = geoscapeImages[GEOSCAPE_IMAGE_MISSION_SELECTED];
01860 MAP_AllMapToScreen(node, aircraft->pos, &x, &y, NULL);
01861 R_DrawImage(x - image->width / 2, y - image->height / 2, image);
01862 }
01863 }
01864 MAP_Draw3DMarkerIfVisible(node, aircraft->pos, angle, aircraft->model, 0);
01865 VectorCopy(aircraft->pos, aircraft->oldDrawPos);
01866
01868 if (RS_IsResearched_ptr(aircraft->tech)
01869 || Cvar_GetInteger("debug_showcrafthealth") >= 1)
01870 MAP_DrawAircraftHealthBar(node, aircraft);
01871 }
01872 }
01873
01874 if (ccs.gameTimeScale > 0)
01875 maxInterpolationPoints = floor(1.0f / (cls.frametime * (float)ccs.gameTimeScale));
01876 else
01877 maxInterpolationPoints = 0;
01878
01879
01880 for (idx = 0; idx < ccs.numProjectiles; idx++) {
01881 aircraftProjectile_t *projectile = &ccs.projectiles[idx];
01882 vec3_t drawPos = {0, 0, 0};
01883
01884 if (projectile->hasMoved) {
01885 projectile->hasMoved = qfalse;
01886 VectorCopy(projectile->pos[0], drawPos);
01887 } else {
01888 if (maxInterpolationPoints > 2 && projectile->numInterpolationPoints < maxInterpolationPoints) {
01889
01890
01891
01892
01893 const float xInterpolStep = (projectile->projectedPos[0][0] - projectile->pos[0][0]) / (float)maxInterpolationPoints;
01894 projectile->numInterpolationPoints += 1;
01895 drawPos[0] = projectile->pos[0][0] + (xInterpolStep * projectile->numInterpolationPoints);
01896 LinearInterpolation(projectile->pos[0], projectile->projectedPos[0], drawPos[0], drawPos[1]);
01897 } else {
01898 VectorCopy(projectile->pos[0], drawPos);
01899 }
01900 }
01901
01902 if (projectile->bullets) {
01903 MAP_DrawBullets(node, drawPos);
01904 } else if (projectile->beam) {
01905 vec3_t start;
01906 vec3_t end;
01907
01908 if (projectile->attackingAircraft)
01909 VectorCopy(projectile->attackingAircraft->pos, start);
01910 else
01911 VectorCopy(projectile->attackerPos, start);
01912
01913 if (projectile->aimedAircraft)
01914 VectorCopy(projectile->aimedAircraft->pos, end);
01915 else
01916 VectorCopy(projectile->idleTarget, end);
01917
01918 MAP_DrawBeam(node, start, end, projectile->aircraftItem->craftitem.beamColor);
01919 } else {
01920 MAP_Draw3DMarkerIfVisible(node, drawPos, projectile->angle, projectile->aircraftItem->model, 0);
01921 }
01922 }
01923
01924 showXVI = CP_IsXVIResearched();
01925
01926
01927 for (i = 0; i < ccs.numNations; i++) {
01928 if (MAP_AllMapToScreen(node, ccs.nations[i].pos, &x, &y, NULL))
01929 UI_DrawString("f_verysmall", ALIGN_UC, x , y, 0, 0, 0, _(ccs.nations[i].name), 0, 0, NULL, qfalse, 0);
01930 if (showXVI)
01931 Q_strcat(buffer, va(_("%s\t%i%%\n"), _(ccs.nations[i].name), ccs.nations[i].stats[0].xviInfection), sizeof(buffer));
01932 }
01933 if (showXVI)
01934 UI_RegisterText(TEXT_XVI, buffer);
01935 else
01936 UI_ResetData(TEXT_XVI);
01937
01938 R_Color(NULL);
01939 }
01940
01946 void MAP_DrawMap (const uiNode_t* node)
01947 {
01948 vec2_t pos;
01949
01950
01951 UI_GetNodeAbsPos(node, pos);
01952 Vector2Copy(pos, ccs.mapPos);
01953 Vector2Copy(node->size, ccs.mapSize);
01954 if (cl_3dmap->integer) {
01955
01956 ccs.mapSize[0] -= UI_MAPEXTRADATACONST(node).paddingRight;
01957 }
01958
01959
01960 if (cl_3dmap->integer) {
01961 qboolean disableSolarRender = qfalse;
01964 #if 0
01965 if (ccs.zoom > cl_mapzoommax->value)
01966 #else
01967 if (ccs.zoom > 3.3)
01968 #endif
01969 disableSolarRender = qtrue;
01970
01971 R_EnableRenderbuffer(qtrue);
01972
01973 if (smoothRotation)
01974 MAP3D_SmoothRotate();
01975 R_Draw3DGlobe(ccs.mapPos[0], ccs.mapPos[1], ccs.mapSize[0], ccs.mapSize[1],
01976 ccs.date.day, ccs.date.sec, ccs.angles, ccs.zoom, ccs.curCampaign->map, disableSolarRender,
01977 cl_3dmapAmbient->value, MAP_IsNationOverlayActivated(), MAP_IsXVIOverlayActivated(),
01978 MAP_IsRadarOverlayActivated(), r_xviTexture, r_radarTexture);
01979
01980 MAP_DrawMapMarkers(node);
01981
01982 R_DrawBloom();
01983 R_EnableRenderbuffer(qfalse);
01984 } else {
01985
01986 static float lastQ = 0.0f;
01987
01988
01989 const float q = (ccs.date.day % DAYS_PER_YEAR + (float)(ccs.date.sec / (SECONDS_PER_HOUR * 6)) / 4) * 2 * M_PI / DAYS_PER_YEAR - M_PI;
01990 if (smoothRotation)
01991 MAP_SmoothTranslate();
01992 if (lastQ != q) {
01993 CP_CalcAndUploadDayAndNightTexture(q);
01994 lastQ = q;
01995 }
01996 R_DrawFlatGeoscape(ccs.mapPos[0], ccs.mapPos[1], ccs.mapSize[0], ccs.mapSize[1], (float) ccs.date.sec / SECONDS_PER_DAY,
01997 ccs.center[0], ccs.center[1], 0.5 / ccs.zoom, ccs.curCampaign->map, MAP_IsNationOverlayActivated(),
01998 MAP_IsXVIOverlayActivated(), MAP_IsRadarOverlayActivated(), r_dayandnightTexture, r_xviTexture, r_radarTexture);
01999 MAP_DrawMapMarkers(node);
02000 }
02001
02002
02003 UI_ResetData(TEXT_STANDARD);
02004 switch (ccs.mapAction) {
02005 case MA_NEWBASE:
02006 UI_RegisterText(TEXT_STANDARD, _("Select the desired location of the new base on the map.\n"));
02007 return;
02008 case MA_NEWINSTALLATION:
02009 UI_RegisterText(TEXT_STANDARD, _("Select the desired location of the new installation on the map.\n"));
02010 return;
02011 case MA_BASEATTACK:
02012 if (ccs.selectedMission)
02013 break;
02014 UI_RegisterText(TEXT_STANDARD, _("Aliens are attacking our base at this very moment.\n"));
02015 return;
02016 case MA_INTERCEPT:
02017 if (ccs.selectedMission)
02018 break;
02019 UI_RegisterText(TEXT_STANDARD, _("Select ufo or mission on map\n"));
02020 return;
02021 case MA_UFORADAR:
02022 if (ccs.selectedMission)
02023 break;
02024 UI_RegisterText(TEXT_STANDARD, _("UFO in radar range\n"));
02025 return;
02026 case MA_NONE:
02027 break;
02028 }
02029
02030
02031 if (ccs.selectedMission) {
02032 UI_RegisterText(TEXT_STANDARD, MAP_GetMissionText(textStandard, sizeof(textStandard), ccs.selectedMission));
02033 } else if (ccs.selectedAircraft) {
02034 const aircraft_t *aircraft = ccs.selectedAircraft;
02035 if (AIR_IsAircraftInBase(aircraft)) {
02036 UI_RegisterText(TEXT_STANDARD, NULL);
02037 MAP_ResetAction();
02038 return;
02039 }
02040 UI_RegisterText(TEXT_STANDARD, MAP_GetAircraftText(textStandard, sizeof(textStandard), ccs.selectedAircraft));
02041 } else if (ccs.selectedUFO) {
02042 UI_RegisterText(TEXT_STANDARD, MAP_GetUFOText(textStandard, sizeof(textStandard), ccs.selectedUFO));
02043 } else {
02044 #ifdef DEBUG
02045 if (debug_showInterest->integer) {
02046 static char t[64];
02047 Com_sprintf(t, lengthof(t), "Interest level: %i\n", ccs.overallInterest);
02048 UI_RegisterText(TEXT_STANDARD, t);
02049 } else
02050 #endif
02051 UI_RegisterText(TEXT_STANDARD, "");
02052 }
02053 }
02054
02058 void MAP_ResetAction (void)
02059 {
02060
02061 if (ccs.numBases)
02062 ccs.mapAction = MA_NONE;
02063
02064 ccs.interceptAircraft = NULL;
02065 ccs.selectedMission = NULL;
02066 ccs.selectedAircraft = NULL;
02067 ccs.selectedUFO = NULL;
02068
02069 if (!radarOverlayWasSet)
02070 MAP_DeactivateOverlay("radar");
02071 }
02072
02076 void MAP_SelectUFO (aircraft_t* ufo)
02077 {
02078 MAP_ResetAction();
02079 ccs.selectedUFO = ufo;
02080 }
02081
02085 void MAP_SelectAircraft (aircraft_t* aircraft)
02086 {
02087 MAP_ResetAction();
02088 ccs.selectedAircraft = aircraft;
02089 }
02090
02094 void MAP_SelectMission (mission_t* mission)
02095 {
02096 if (!mission || mission == ccs.selectedMission)
02097 return;
02098 MAP_ResetAction();
02099 ccs.mapAction = MA_INTERCEPT;
02100 ccs.selectedMission = mission;
02101 }
02102
02106 void MAP_NotifyMissionRemoved (const mission_t* mission)
02107 {
02108
02109 if (ccs.selectedMission == mission)
02110 MAP_ResetAction();
02111
02112 MAP_UpdateGeoscapeDock();
02113 }
02114
02120 void MAP_NotifyUFORemoved (const aircraft_t* ufo, qboolean destroyed)
02121 {
02122 MAP_UpdateGeoscapeDock();
02123
02124 if (!ccs.selectedUFO)
02125 return;
02126
02127
02128 if (ccs.selectedUFO == ufo)
02129 MAP_ResetAction();
02130 else if (destroyed && ccs.selectedUFO > ufo)
02131 ccs.selectedUFO--;
02132 }
02133
02139 void MAP_NotifyAircraftRemoved (const aircraft_t* aircraft)
02140 {
02141
02142 if (ccs.selectedAircraft == aircraft || ccs.interceptAircraft == aircraft)
02143 MAP_ResetAction();
02144 }
02145
02154 nation_t* MAP_GetNation (const vec2_t pos)
02155 {
02156 int i;
02157 const byte* color = MAP_GetColor(pos, MAPTYPE_NATIONS);
02158 #ifdef PARANOID
02159 Com_DPrintf(DEBUG_CLIENT, "MAP_GetNation: color value for %.0f:%.0f is r:%i, g:%i, b: %i\n", pos[0], pos[1], color[0], color[1], color[2]);
02160 #endif
02161 for (i = 0; i < ccs.numNations; i++) {
02162 nation_t *nation = &ccs.nations[i];
02163
02164 if (VectorCompare(nation->color, color))
02165 return nation;
02166 }
02167 Com_DPrintf(DEBUG_CLIENT, "MAP_GetNation: No nation found at %.0f:%.0f - color: %i:%i:%i\n", pos[0], pos[1], color[0], color[1], color[2]);
02168 return NULL;
02169 }
02170
02171
02180 const char* MAP_GetTerrainType (const byte* const color)
02181 {
02182 if (MapIsDesert(color))
02183 return "desert";
02184 else if (MapIsArctic(color))
02185 return "arctic";
02186 else if (MapIsWater(color))
02187 return "water";
02188 else if (MapIsMountain(color))
02189 return "mountain";
02190 else if (MapIsTropical(color))
02191 return "tropical";
02192 else if (MapIsCold(color))
02193 return "cold";
02194 else if (MapIsWasted(color))
02195 return "wasted";
02196 else
02197 return "grass";
02198 }
02199
02207 static const char* MAP_GetCultureType (const byte* color)
02208 {
02209 if (MapIsWater(color))
02210 return "water";
02211 else if (MapIsEastern(color))
02212 return "eastern";
02213 else if (MapIsWestern(color))
02214 return "western";
02215 else if (MapIsOriental(color))
02216 return "oriental";
02217 else if (MapIsAfrican(color))
02218 return "african";
02219 else
02220 return "western";
02221 }
02222
02230 static const char* MAP_GetPopulationType (const byte* color)
02231 {
02232 if (MapIsWater(color))
02233 return "water";
02234 else if (MapIsUrban(color))
02235 return "urban";
02236 else if (MapIsSuburban(color))
02237 return "suburban";
02238 else if (MapIsVillage(color))
02239 return "village";
02240 else if (MapIsRural(color))
02241 return "rural";
02242 else if (MapIsNopopulation(color))
02243 return "nopopulation";
02244 else
02245 return "nopopulation";
02246 }
02247
02254 static inline const char* MAP_GetTerrainTypeByPos (const vec2_t pos)
02255 {
02256 const byte* color = MAP_GetColor(pos, MAPTYPE_TERRAIN);
02257 return MAP_GetTerrainType(color);
02258 }
02259
02266 static inline const char* MAP_GetCultureTypeByPos (const vec2_t pos)
02267 {
02268 const byte* color = MAP_GetColor(pos, MAPTYPE_CULTURE);
02269 return MAP_GetCultureType(color);
02270 }
02271
02278 static inline const char* MAP_GetPopulationTypeByPos (const vec2_t pos)
02279 {
02280 const byte* color = MAP_GetColor(pos, MAPTYPE_POPULATION);
02281 return MAP_GetPopulationType(color);
02282 }
02283
02290 int MAP_GetCivilianNumberByPosition (const vec2_t pos)
02291 {
02292 const byte* color = MAP_GetColor(pos, MAPTYPE_POPULATION);
02293
02294 if (MapIsWater(color))
02295 Com_Error(ERR_DROP, "MAP_GetPopulationType: Trying to get number of civilian in a position on water");
02296 else if (MapIsUrban(color))
02297 return 10;
02298 else if (MapIsSuburban(color))
02299 return 8;
02300 else if (MapIsVillage(color))
02301 return 6;
02302 else if (MapIsRural(color))
02303 return 4;
02304 else if (MapIsNopopulation(color))
02305 return 2;
02306 else
02307 return 0;
02308 }
02309
02316 void MAP_PrintParameterStringByPos (const vec2_t pos)
02317 {
02318 const char *terrainType = MAP_GetTerrainTypeByPos(pos);
02319 const char *cultureType = MAP_GetCultureTypeByPos(pos);
02320 const char *populationType = MAP_GetPopulationTypeByPos(pos);
02321
02322 Com_Printf (" (Terrain: %s, Culture: %s, Population: %s)\n", terrainType, cultureType, populationType);
02323 }
02324
02329 void MAP_CheckPositionBoundaries (float *pos)
02330 {
02331 while (pos[0] > 180.0)
02332 pos[0] -= 360.0;
02333 while (pos[0] < -180.0)
02334 pos[0] += 360.0;
02335 while (pos[1] > 90.0)
02336 pos[1] -= 180.0;
02337 while (pos[1] < -90.0)
02338 pos[1] += 180.0;
02339 }
02340
02346 qboolean MAP_IsNight (const vec2_t pos)
02347 {
02348 float p, q, a, root, x;
02349
02350
02351 p = (float) ccs.date.sec / SECONDS_PER_DAY;
02352
02353 q = (ccs.date.day + p) * (2 * M_PI / DAYS_PER_YEAR_AVG) - M_PI;
02354 p = (0.5 + pos[0] / 360 - p) * (2 * M_PI) - q;
02355 a = -sin(pos[1] * torad);
02356 root = sqrt(1.0 - a * a);
02357 x = sin(p) * root * sin(q) - (a * SIN_ALPHA + cos(p) * root * COS_ALPHA) * cos(q);
02358 return (x > 0);
02359 }
02360
02374 byte *MAP_GetColor (const vec2_t pos, mapType_t type)
02375 {
02376 int x, y;
02377 int width, height;
02378 byte *mask;
02379
02380 switch (type) {
02381 case MAPTYPE_TERRAIN:
02382 mask = terrainPic;
02383 width = terrainWidth;
02384 height = terrainHeight;
02385 break;
02386 case MAPTYPE_CULTURE:
02387 mask = culturePic;
02388 width = cultureWidth;
02389 height = cultureHeight;
02390 break;
02391 case MAPTYPE_POPULATION:
02392 mask = populationPic;
02393 width = populationWidth;
02394 height = populationHeight;
02395 break;
02396 case MAPTYPE_NATIONS:
02397 mask = nationsPic;
02398 width = nationsWidth;
02399 height = nationsHeight;
02400 break;
02401 default:
02402 Com_Error(ERR_DROP, "Unknown maptype %i\n", type);
02403 }
02404
02406 assert(pos[0] >= -180);
02407 assert(pos[0] <= 180);
02408 assert(pos[1] >= -90);
02409 assert(pos[1] <= 90);
02410
02411
02412 x = (180 - pos[0]) / 360 * width;
02413 x--;
02414 y = (90 - pos[1]) / 180 * height;
02415 y--;
02416 if (x < 0)
02417 x = 0;
02418 if (y < 0)
02419 y = 0;
02420
02421
02422
02423
02424 assert(4 * (x + y * width) < width * height * 4);
02425 return mask + 4 * (x + y * width);
02426 }
02427
02431 static const float MIN_DIST_BASE = 4.0f;
02432
02437 base_t* MAP_PositionCloseToBase (const vec2_t pos)
02438 {
02439 int baseIdx;
02440
02441 for (baseIdx = 0; baseIdx < MAX_BASES; baseIdx++) {
02442 base_t *base = B_GetFoundedBaseByIDX(baseIdx);
02443 if (!base)
02444 continue;
02445
02446 if (GetDistanceOnGlobe(pos, base->pos) < MIN_DIST_BASE) {
02447 return base;
02448 }
02449 }
02450
02451 return NULL;
02452 }
02453
02464 qboolean MAP_PositionFitsTCPNTypes (const vec2_t pos, const linkedList_t* terrainTypes, const linkedList_t* cultureTypes, const linkedList_t* populationTypes, const linkedList_t* nations)
02465 {
02466 const char *terrainType = MAP_GetTerrainTypeByPos(pos);
02467 const char *cultureType = MAP_GetCultureTypeByPos(pos);
02468 const char *populationType = MAP_GetPopulationTypeByPos(pos);
02469
02470 if (MapIsWater(MAP_GetColor(pos, MAPTYPE_TERRAIN)))
02471 return qfalse;
02472
02473 if (!terrainTypes || LIST_ContainsString(terrainTypes, terrainType)) {
02474 if (!cultureTypes || LIST_ContainsString(cultureTypes, cultureType)) {
02475 if (!populationTypes || LIST_ContainsString(populationTypes, populationType)) {
02476 const nation_t *nationAtPos = MAP_GetNation(pos);
02477 if (!nations)
02478 return qtrue;
02479 if (nationAtPos && (!nations || LIST_ContainsString(nations, nationAtPos->id))) {
02480 return qtrue;
02481 }
02482 }
02483 }
02484 }
02485
02486 return qfalse;
02487 }
02488
02489 void MAP_Init (void)
02490 {
02491
02492 if (terrainPic) {
02493 Mem_Free(terrainPic);
02494 terrainPic = NULL;
02495 }
02496 R_LoadImage(va("pics/geoscape/%s_terrain", ccs.curCampaign->map), &terrainPic, &terrainWidth, &terrainHeight);
02497 if (!terrainPic || !terrainWidth || !terrainHeight)
02498 Com_Error(ERR_DROP, "Couldn't load map mask %s_terrain in pics/geoscape", ccs.curCampaign->map);
02499
02500
02501 if (culturePic) {
02502 Mem_Free(culturePic);
02503 culturePic = NULL;
02504 }
02505 R_LoadImage(va("pics/geoscape/%s_culture", ccs.curCampaign->map), &culturePic, &cultureWidth, &cultureHeight);
02506 if (!culturePic || !cultureWidth || !cultureHeight)
02507 Com_Error(ERR_DROP, "Couldn't load map mask %s_culture in pics/geoscape", ccs.curCampaign->map);
02508
02509
02510 if (populationPic) {
02511 Mem_Free(populationPic);
02512 populationPic = NULL;
02513 }
02514 R_LoadImage(va("pics/geoscape/%s_population", ccs.curCampaign->map), &populationPic, &populationWidth, &populationHeight);
02515 if (!populationPic || !populationWidth || !populationHeight)
02516 Com_Error(ERR_DROP, "Couldn't load map mask %s_population in pics/geoscape", ccs.curCampaign->map);
02517
02518
02519 if (nationsPic) {
02520 Mem_Free(nationsPic);
02521 nationsPic = NULL;
02522 }
02523 R_LoadImage(va("pics/geoscape/%s_nations", ccs.curCampaign->map), &nationsPic, &nationsWidth, &nationsHeight);
02524 if (!nationsPic || !nationsWidth || !nationsHeight)
02525 Com_Error(ERR_DROP, "Couldn't load map mask %s_nations in pics/geoscape", ccs.curCampaign->map);
02526
02527 MAP_ResetAction();
02528 MAP_UpdateGeoscapeDock();
02529 }
02530
02534 void MAP_NotifyUFODisappear (const aircraft_t* ufo)
02535 {
02536
02537 if (ccs.selectedUFO == ufo)
02538 MAP_ResetAction();
02539
02540 MAP_UpdateGeoscapeDock();
02541 }
02542
02546 void MAP_Zoom_f (void)
02547 {
02548 const char *cmd;
02549 const float zoomAmount = 50.0f;
02550
02551 if (Cmd_Argc() != 2) {
02552 Com_Printf("Usage: %s <in|out>\n", Cmd_Argv(0));
02553 return;
02554 }
02555
02556 cmd = Cmd_Argv(1);
02557 switch (cmd[0]) {
02558 case 'i':
02559 smoothFinalZoom = ccs.zoom * pow(0.995, -zoomAmount);
02560 break;
02561 case 'o':
02562 smoothFinalZoom = ccs.zoom * pow(0.995, zoomAmount);
02563 break;
02564 default:
02565 Com_Printf("MAP_Zoom_f: Invalid parameter: %s\n", cmd);
02566 return;
02567 }
02568
02569 if (smoothFinalZoom < cl_mapzoommin->value)
02570 smoothFinalZoom = cl_mapzoommin->value;
02571 else if (smoothFinalZoom > cl_mapzoommax->value)
02572 smoothFinalZoom = cl_mapzoommax->value;
02573
02574 if (!cl_3dmap->integer) {
02575 ccs.zoom = smoothFinalZoom;
02576 if (ccs.center[1] < 0.5 / ccs.zoom)
02577 ccs.center[1] = 0.5 / ccs.zoom;
02578 if (ccs.center[1] > 1.0 - 0.5 / ccs.zoom)
02579 ccs.center[1] = 1.0 - 0.5 / ccs.zoom;
02580 } else {
02581 VectorCopy(ccs.angles, smoothFinalGlobeAngle);
02582 smoothDeltaLength = 0;
02583 smoothRotation = qtrue;
02584 smoothDeltaZoom = fabs(smoothFinalZoom - ccs.zoom);
02585 }
02586 }
02587
02591 void MAP_Scroll_f (void)
02592 {
02593 const char *cmd;
02594 float scrollX = 0.0f, scrollY = 0.0f;
02595 const float scrollAmount = 80.0f;
02596
02597 if (Cmd_Argc() != 2) {
02598 Com_Printf("Usage: %s <up|down|left|right>\n", Cmd_Argv(0));
02599 return;
02600 }
02601
02602 cmd = Cmd_Argv(1);
02603 switch (cmd[0]) {
02604 case 'l':
02605 scrollX = scrollAmount;
02606 break;
02607 case 'r':
02608 scrollX = -scrollAmount;
02609 break;
02610 case 'u':
02611 scrollY = scrollAmount;
02612 break;
02613 case 'd':
02614 scrollY = -scrollAmount;
02615 break;
02616 default:
02617 Com_Printf("MAP_Scroll_f: Invalid parameter\n");
02618 return;
02619 }
02620 if (cl_3dmap->integer) {
02621
02622 vec3_t diff;
02623
02624 VectorCopy(ccs.angles, smoothFinalGlobeAngle);
02625
02626
02627 smoothFinalGlobeAngle[PITCH] += ROTATE_SPEED * (scrollX) / ccs.zoom;
02628 smoothFinalGlobeAngle[YAW] -= ROTATE_SPEED * (scrollY) / ccs.zoom;
02629
02630 while (smoothFinalGlobeAngle[YAW] < -180.0) {
02631 smoothFinalGlobeAngle[YAW] = -180.0;
02632 }
02633 while (smoothFinalGlobeAngle[YAW] > 0.0) {
02634 smoothFinalGlobeAngle[YAW] = 0.0;
02635 }
02636
02637 while (smoothFinalGlobeAngle[PITCH] > 180.0) {
02638 smoothFinalGlobeAngle[PITCH] -= 360.0;
02639 ccs.angles[PITCH] -= 360.0;
02640 }
02641 while (smoothFinalGlobeAngle[PITCH] < -180.0) {
02642 smoothFinalGlobeAngle[PITCH] += 360.0;
02643 ccs.angles[PITCH] += 360.0;
02644 }
02645 VectorSubtract(smoothFinalGlobeAngle, ccs.angles, diff);
02646 smoothDeltaLength = VectorLength(diff);
02647
02648 smoothFinalZoom = ccs.zoom;
02649 smoothDeltaZoom = 0.0f;
02650 smoothRotation = qtrue;
02651 } else {
02652 int i;
02653
02654 ccs.center[0] -= (float) (scrollX) / (ccs.mapSize[0] * ccs.zoom);
02655 ccs.center[1] -= (float) (scrollY) / (ccs.mapSize[1] * ccs.zoom);
02656 for (i = 0; i < 2; i++) {
02657 while (ccs.center[i] < 0.0)
02658 ccs.center[i] += 1.0;
02659 while (ccs.center[i] > 1.0)
02660 ccs.center[i] -= 1.0;
02661 }
02662 if (ccs.center[1] < 0.5 / ccs.zoom)
02663 ccs.center[1] = 0.5 / ccs.zoom;
02664 if (ccs.center[1] > 1.0 - 0.5 / ccs.zoom)
02665 ccs.center[1] = 1.0 - 0.5 / ccs.zoom;
02666 }
02667 }
02668
02673 void MAP_SetOverlay (const char *overlayID)
02674 {
02675 if (!strcmp(overlayID, "nations")) {
02676 if (MAP_IsNationOverlayActivated())
02677 cl_geoscape_overlay->integer ^= OVERLAY_NATION;
02678 else
02679 cl_geoscape_overlay->integer |= OVERLAY_NATION;
02680 }
02681
02682
02683 if (ccs.numBases + ccs.numInstallations == 0)
02684 return;
02685
02686 if (!strcmp(overlayID, "xvi")) {
02687 if (cl_geoscape_overlay->integer & OVERLAY_XVI)
02688 cl_geoscape_overlay->integer ^= OVERLAY_XVI;
02689 else
02690 cl_geoscape_overlay->integer |= OVERLAY_XVI;
02691 } else if (!strcmp(overlayID, "radar")) {
02692 if (MAP_IsRadarOverlayActivated())
02693 cl_geoscape_overlay->integer ^= OVERLAY_RADAR;
02694 else {
02695 cl_geoscape_overlay->integer |= OVERLAY_RADAR;
02696 RADAR_UpdateWholeRadarOverlay();
02697 }
02698 }
02699 }
02700
02704 static void MAP_SetOverlay_f (void)
02705 {
02706 const char *arg;
02707
02708 if (Cmd_Argc() != 2) {
02709 Com_Printf("Usage: %s <nations|xvi|radar>\n", Cmd_Argv(0));
02710 return;
02711 }
02712
02713 arg = Cmd_Argv(1);
02714 MAP_SetOverlay(arg);
02715
02716
02717 if (!strcmp(arg, "radar"))
02718 radarOverlayWasSet = MAP_IsRadarOverlayActivated();
02719 }
02720
02725 void MAP_DeactivateOverlay (const char *overlayID)
02726 {
02727 if (!strcmp(overlayID, "nations")) {
02728 if (MAP_IsNationOverlayActivated())
02729 MAP_SetOverlay("nations");
02730 else
02731 return;
02732 }
02733
02734 if (!strcmp(overlayID, "xvi")) {
02735 if (MAP_IsXVIOverlayActivated())
02736 MAP_SetOverlay("xvi");
02737 else
02738 return;
02739 } else if (!strcmp(overlayID, "radar")) {
02740 if (MAP_IsRadarOverlayActivated())
02741 MAP_SetOverlay("radar");
02742 else
02743 return;
02744 }
02745 }
02746
02750 static void MAP_DeactivateOverlay_f (void)
02751 {
02752 const char *arg;
02753
02754 if (Cmd_Argc() != 2) {
02755 Com_Printf("Usage: %s <nations|xvi|radar>\n", Cmd_Argv(0));
02756 return;
02757 }
02758
02759 arg = Cmd_Argv(1);
02760 MAP_DeactivateOverlay(arg);
02761 }
02762
02763 qboolean MAP_IsRadarOverlayActivated (void)
02764 {
02765 return cl_geoscape_overlay->integer & OVERLAY_RADAR;
02766 }
02767
02768 qboolean MAP_IsNationOverlayActivated (void)
02769 {
02770 return cl_geoscape_overlay->integer & OVERLAY_NATION;
02771 }
02772
02773 qboolean MAP_IsXVIOverlayActivated (void)
02774 {
02775 return cl_geoscape_overlay->integer & OVERLAY_XVI;
02776 }
02777
02781 void MAP_InitStartup (void)
02782 {
02783 int i;
02784
02785 Cmd_AddCommand("multi_select_click", MAP_MultiSelectExecuteAction_f, NULL);
02786 Cmd_AddCommand("map_overlay", MAP_SetOverlay_f, "Set the geoscape overlay");
02787 Cmd_AddCommand("map_deactivateoverlay", MAP_DeactivateOverlay_f, "Deactivate overlay");
02788 Cmd_AddCommand("map_selectobject", MAP_SelectObject_f, "Select an object an center on it");
02789 Cmd_AddCommand("mn_mapaction_reset", MAP_ResetAction, NULL);
02790
02791 cl_geoscape_overlay = Cvar_Get("cl_geoscape_overlay", "0", 0, "Geoscape overlays - Bitmask");
02792 cl_3dmap = Cvar_Get("cl_3dmap", "1", CVAR_ARCHIVE, "3D geoscape or flat geoscape");
02793 cl_3dmapAmbient = Cvar_Get("cl_3dmapAmbient", "0", CVAR_ARCHIVE, "3D geoscape ambient lighting factor");
02794 cl_mapzoommax = Cvar_Get("cl_mapzoommax", "6.0", CVAR_ARCHIVE, "Maximum geoscape zooming value");
02795 cl_mapzoommin = Cvar_Get("cl_mapzoommin", "1.0", CVAR_ARCHIVE, "Minimum geoscape zooming value");
02796 #ifdef DEBUG
02797 debug_showInterest = Cvar_Get("debug_showinterest", "0", CVAR_DEVELOPER, "Shows the global interest value on geoscape");
02798 #endif
02799
02800 for (i = 0; i < GEOSCAPE_IMAGE_MAX; i++) {
02801 geoscapeImages[i] = R_FindImage(geoscapeImageNames[i], it_pic);
02802 if (geoscapeImages[i] == r_noTexture)
02803 Com_Error(ERR_DROP, "Could not find image: %s", geoscapeImageNames[i]);
02804 }
02805 }