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