1 /**
2 * @file
3 * @brief Campaign XVI code
4 */
5
6 /*
7 Copyright (C) 2002-2013 UFO: Alien Invasion.
8
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
18 See the GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24 */
25
26 #include "../../cl_shared.h"
27 #include "cp_campaign.h"
28 #include "cp_overlay.h"
29 #include "cp_geoscape.h"
30 #include "cp_xvi.h"
31 #include "save/save_xvi.h"
32
33 /** @brief technology for XVI event */
34 static technology_t* rsAlienXVI;
35
36 /** @brief boolean used to know if each nation XVI level should be updated this day. */
37 static bool xviNationInfectionNeedsUpdate = false;
38
39 /**
40 * @brief The factor that is used to determine XVI radius spreading.
41 * The higher this factor, the faster XVI will spread.
42 */
43 static const float XVI_FACTOR = 15.0f;
44
45 /** @brief Number of days between 2 XVI decrease (everywhere around the world). */
46 static const int XVI_DECREASE_DAYS = 7;
47
48 /** @brief Name of the technology for the XVI event */
49 #define XVI_EVENT_NAME "rs_alien_xvi_event"
50
51 /**
52 * @brief Spread XVI at a given position.
53 * @param[in] pos Position where XVI should be spread.
54 */
CP_SpreadXVIAtPos(const vec2_t pos)55 void CP_SpreadXVIAtPos (const vec2_t pos)
56 {
57 if (!CP_IsXVIStarted())
58 return;
59
60 CP_ChangeXVILevel(pos, XVI_FACTOR);
61
62 xviNationInfectionNeedsUpdate = true;
63 }
64
65 /**
66 * @brief Reduce XVI everywhere.
67 * @note This is called daily.
68 */
CP_ReduceXVIEverywhere(void)69 void CP_ReduceXVIEverywhere (void)
70 {
71 if (!CP_IsXVIStarted())
72 return;
73
74 /* Only decrease XVI if given days has passed */
75 if (ccs.date.day % XVI_DECREASE_DAYS)
76 return;
77
78 CP_DecreaseXVILevelEverywhere();
79
80 xviNationInfectionNeedsUpdate = true;
81 }
82
83 /**
84 * @brief Update xviInfection value for each nation, using the XVI overlay.
85 * @note should be executed after all daily event that could change XVI overlay
86 */
CP_UpdateNationXVIInfection(void)87 void CP_UpdateNationXVIInfection (void)
88 {
89 /* No need to update XVI levels if the overlay didn't change */
90 if (!xviNationInfectionNeedsUpdate)
91 return;
92
93 /* width in pixel of the XVI overlay */
94 int width;
95 /* height in pixel of the XVI overlay */
96 int height;
97 CP_GetXVIMapDimensions(&width, &height);
98
99 const float heightPerDegree = height / 180.0f;
100 const float widthPerDegree = width / 360.0f;
101 /* parameter used to normalize nation XVI level.
102 * decrease this factor to increase XVI level per nation */
103 const float AREA_FACTOR = 650.0f;
104 /* area used to normalized XVI infection level for each nation.
105 * depend on overlay size so that if we change resolution of
106 * overlay it doesn't impact nation XIInfection */
107 const float normalizingArea = width * height / AREA_FACTOR;
108
109 /* temporary array to store the XVI levels */
110 float xviInfection[MAX_NATIONS];
111 /* Initialize array */
112 OBJZERO(xviInfection);
113
114 for (int y = 0; y < height; y++) {
115 int sum[MAX_NATIONS];
116 const byte* previousNationColor;
117 const nation_t* nation;
118 /* current position (in latitude / longitude) */
119 vec2_t currentPos;
120
121 OBJZERO(sum);
122
123 Vector2Set(currentPos, 180.0f, 90.0f - y / heightPerDegree);
124 previousNationColor = GEO_GetColor(currentPos, MAPTYPE_NATIONS, nullptr);
125 nation = GEO_GetNation(currentPos);
126
127 for (int x = 0; x < width; x++) {
128 const byte* nationColor;
129 currentPos[0] = 180.0f - x / widthPerDegree;
130 nationColor = GEO_GetColor(currentPos, MAPTYPE_NATIONS, nullptr);
131 if (!VectorCompare(nationColor, previousNationColor)) {
132 previousNationColor = nationColor;
133 nation = GEO_GetNation(currentPos);
134 }
135 if (nation) {
136 const int xviLevel = CP_GetXVILevel(x, y);
137 if (xviLevel > 0)
138 sum[nation->idx] += xviLevel;
139 }
140 }
141 /* divide the total XVI infection by the area of a pixel
142 * because pixel are smaller as you go closer from the pole */
143 for (int nationIdx = 0; nationIdx < ccs.numNations; nationIdx++)
144 xviInfection[nationIdx] += ((float) sum[nationIdx]) / (cos(torad * currentPos[1]) * normalizingArea);
145 }
146
147 /* copy the new values of XVI infection level into nation array */
148 for (int nationIdx = 0; nationIdx < ccs.numNations; nationIdx++) {
149 nation_t* nation = NAT_GetNationByIDX(nationIdx);
150 nation->stats[0].xviInfection = ceil(xviInfection[nation->idx]);
151 }
152
153 xviNationInfectionNeedsUpdate = false;
154 }
155
156 /**
157 * @brief Return the average XVI rate
158 * @note XVI = eXtraterrestial Viral Infection
159 */
CP_GetAverageXVIRate(void)160 int CP_GetAverageXVIRate (void)
161 {
162 int xviRate = 0;
163 int i;
164
165 assert(ccs.numNations);
166
167 /* check for XVI infection rate */
168 for (i = 0; i < ccs.numNations; i++) {
169 const nation_t* nation = NAT_GetNationByIDX(i);
170 const nationInfo_t* stats = NAT_GetCurrentMonthInfo(nation);
171 xviRate += stats->xviInfection;
172 }
173 xviRate /= ccs.numNations;
174 return xviRate;
175 }
176
177 /**
178 * @brief Spread XVI for each mission that needs to be daily spread.
179 * @note Daily called
180 */
CP_SpreadXVI(void)181 void CP_SpreadXVI (void)
182 {
183 /* don't check if XVI spreading didn't start yet */
184 if (!CP_IsXVIStarted())
185 return;
186
187 MIS_Foreach(mission) {
188 if (mission->stage == STAGE_SPREAD_XVI)
189 CP_SpreadXVIAtPos(mission->pos);
190 }
191 }
192
193 /**
194 * @brief Returns @c true if the XVI effect should be visible to the player
195 */
CP_IsXVIVisible(void)196 bool CP_IsXVIVisible (void)
197 {
198 return RS_IsResearched_ptr(rsAlienXVI);
199 }
200
CP_XVIInit(void)201 void CP_XVIInit (void)
202 {
203 rsAlienXVI = RS_GetTechByID(XVI_EVENT_NAME);
204 if (!rsAlienXVI)
205 cgi->Com_Error(ERR_DROP, "CP_XVIInit: Could not find tech definition for " XVI_EVENT_NAME);
206 }
207
208 /**
209 * @brief XVI map saving callback
210 * @note Only save transparency
211 * @sa Savegame callback
212 * @sa SAV_InitXML
213 * @sa XVI_LoadXML
214 */
XVI_SaveXML(xmlNode_t * p)215 bool XVI_SaveXML (xmlNode_t* p)
216 {
217 int y;
218 int width;
219 int height;
220 xmlNode_t* n;
221
222 CP_GetXVIMapDimensions(&width, &height);
223
224 n = cgi->XML_AddNode(p, SAVE_XVI_XVI);
225 cgi->XML_AddInt(n, SAVE_XVI_WIDTH, width);
226 cgi->XML_AddInt(n, SAVE_XVI_HEIGHT, height);
227
228 for (y = 0; y < height; y++) {
229 int x;
230 for (x = 0; x < width; x++) {
231 const int xviLevel = CP_GetXVILevel(x, y);
232 /* That saves many bytes in the savegame */
233 if (xviLevel > 0) {
234 xmlNode_t* s = cgi->XML_AddNode(n, SAVE_XVI_ENTRY);
235 cgi->XML_AddInt(s, SAVE_XVI_X, x);
236 cgi->XML_AddInt(s, SAVE_XVI_Y, y);
237 cgi->XML_AddInt(s, SAVE_XVI_LEVEL, xviLevel);
238 }
239 }
240 }
241 return true;
242 }
243
244 /**
245 * @brief Load the XVI map from the savegame.
246 * @sa Savegame callback
247 * @sa SAV_InitXML
248 * @sa XVI_SaveXML
249 */
XVI_LoadXML(xmlNode_t * p)250 bool XVI_LoadXML (xmlNode_t* p)
251 {
252 int width, height;
253 xmlNode_t* s;
254 xmlNode_t* n = cgi->XML_GetNode(p, SAVE_XVI_XVI);
255 /* If there is no XVI, it will not be loaded */
256 if (!n) {
257 CP_InitializeXVIOverlay();
258 return true;
259 }
260
261 width = cgi->XML_GetInt(n, SAVE_XVI_WIDTH, 0);
262 height = cgi->XML_GetInt(n, SAVE_XVI_HEIGHT, 0);
263
264 for (s = cgi->XML_GetNode(n, SAVE_XVI_ENTRY); s; s = cgi->XML_GetNextNode(s, n, SAVE_XVI_ENTRY)) {
265 const int x = cgi->XML_GetInt(s, SAVE_XVI_X, 0);
266 const int y = cgi->XML_GetInt(s, SAVE_XVI_Y, 0);
267 const int level = cgi->XML_GetInt(s, SAVE_XVI_LEVEL, 0);
268
269 if (x >= 0 && x < width && y >= 0 && y <= height)
270 CP_SetXVILevel(x, y, level);
271 }
272 return true;
273 }
274
275 /**
276 * @brief Start XVI spreading in campaign.
277 * @note This is called when 'a new twist' technology is discovered.
278 */
CP_StartXVISpreading_f(void)279 void CP_StartXVISpreading_f (void)
280 {
281 int i, numAlienBases;
282 const campaign_t* campaign = ccs.curCampaign;
283
284 ccs.startXVI = true;
285
286 /* Spawn a few alien bases depending on difficulty level */
287 if (campaign->difficulty > 0)
288 numAlienBases = 3;
289 else if (campaign->difficulty < 0)
290 numAlienBases = 1;
291 else
292 numAlienBases = 2;
293
294 for (i = 0; i < numAlienBases; i++)
295 CP_CreateNewMission(INTERESTCATEGORY_BUILDING, false);
296 }
297
298 /**
299 * @brief This will hide or show the geoscape button for handling the xvi overlay map
300 */
CP_UpdateXVIMapButton(void)301 void CP_UpdateXVIMapButton (void)
302 {
303 cgi->Cvar_SetValue("mn_xvimap", CP_IsXVIVisible());
304 }
305