1 /**
2  * @file
3  * @brief Campaign geoscape time 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 #include "../../cl_shared.h"
26 #include "cp_campaign.h"
27 #include "cp_time.h"
28 
29 static const int monthLength[MONTHS_PER_YEAR] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
30 
31 typedef struct gameLapse_s {
32 	const char* name;
33 	int scale;
34 } gameLapse_t;
35 
36 #define NUM_TIMELAPSE 8
37 
38 /** @brief The possible geoscape time intervalls */
39 static const gameLapse_t lapse[NUM_TIMELAPSE] = {
40 	{N_("stopped"), 0},
41 	{N_("5 sec"), 5},
42 	{N_("5 mins"), 5 * 60},
43 	{N_("20 mins"), SECONDS_PER_HOUR / 3},
44 	{N_("1 hour"), SECONDS_PER_HOUR},
45 	{N_("12 hours"), 12 * SECONDS_PER_HOUR},
46 	{N_("1 day"), 24 * SECONDS_PER_HOUR},
47 	{N_("5 days"), 5 * SECONDS_PER_DAY}
48 };
49 CASSERT(lengthof(lapse) == NUM_TIMELAPSE);
50 
51 /**
52  * @brief Converts a number of second into a char to display.
53  * @param[in] second Number of second.
54  * @todo Abstract the code into an extra function (DateConvertSeconds?) see also CP_DateConvertLong
55  */
CP_SecondConvert(int second)56 const char* CP_SecondConvert (int second)
57 {
58 	static char buffer[6];
59 	const int hour = second / SECONDS_PER_HOUR;
60 	const int min = (second - hour * SECONDS_PER_HOUR) / 60;
61 	Com_sprintf(buffer, sizeof(buffer), "%2i:%02i", hour, min);
62 	return buffer;
63 }
64 
65 /**
66  * @brief Converts a date from the engine in a (longer) human-readable format.
67  * @note The seconds from "date" are ignored here.
68  * @note The function always starts calculation from Jan. and also catches new years.
69  * @param[in] date Contains the date to be converted.
70  * @param[out] dateLong The converted date.
71   */
CP_DateConvertLong(const date_t * date,dateLong_t * dateLong)72 void CP_DateConvertLong (const date_t* date, dateLong_t* dateLong)
73 {
74 	byte i;
75 	int d;
76 	size_t length = lengthof(monthLength);
77 
78 	/* Get the year */
79 	dateLong->year = date->day / DAYS_PER_YEAR;
80 
81 	/* Get the days in the year. */
82 	d = date->day % DAYS_PER_YEAR;
83 
84 	/* Subtract days until no full month is left. */
85 	for (i = 0; i < length; i++) {
86 		if (d < monthLength[i])
87 			break;
88 		d -= monthLength[i];
89 	}
90 
91 	dateLong->day = d + 1;
92 	dateLong->month = i + 1;
93 	dateLong->hour = date->sec / SECONDS_PER_HOUR;
94 	dateLong->min = (date->sec - dateLong->hour * SECONDS_PER_HOUR) / 60;
95 	dateLong->sec = date->sec - dateLong->hour * SECONDS_PER_HOUR - dateLong->min * 60;
96 
97 	assert(dateLong->month >= 1 && dateLong->month <= MONTHS_PER_YEAR);
98 	assert(dateLong->day >= 1 && dateLong->day <= monthLength[i]);
99 }
100 
101 /**
102  * @brief Updates date/time and timescale (=timelapse) on the geoscape menu
103  * @sa SAV_GameLoad
104  * @sa CP_CampaignRun
105  */
CP_UpdateTime(void)106 void CP_UpdateTime (void)
107 {
108 	dateLong_t date;
109 	CP_DateConvertLong(&ccs.date, &date);
110 
111 	/* Update the timelapse text */
112 	if (ccs.gameLapse >= 0 && ccs.gameLapse < NUM_TIMELAPSE) {
113 		cgi->Cvar_Set("mn_timelapse", "%s", _(lapse[ccs.gameLapse].name));
114 		ccs.gameTimeScale = lapse[ccs.gameLapse].scale;
115 		cgi->Cvar_SetValue("mn_timelapse_id", ccs.gameLapse);
116 	}
117 
118 	/* Update the date */
119 	cgi->Cvar_Set("mn_mapdate", _("%i %s %02i"), date.year, Date_GetMonthName(date.month - 1), date.day);
120 
121 	/* Update the time. */
122 	cgi->Cvar_Set("mn_maptime", _("%02i:%02i"), date.hour, date.min);
123 }
124 
125 /**
126  * @brief Stop game time speed
127  */
CP_GameTimeStop(void)128 void CP_GameTimeStop (void)
129 {
130 	/* don't allow time scale in tactical mode - only on the geoscape */
131 	if (!cp_missiontest->integer && CP_OnGeoscape())
132 		ccs.gameLapse = 0;
133 
134 	/* Make sure the new lapse state is updated and it (and the time) is show in the menu. */
135 	CP_UpdateTime();
136 }
137 
138 /**
139  * @brief Check if time is stopped
140  */
CP_IsTimeStopped(void)141 bool CP_IsTimeStopped (void)
142 {
143 	return !ccs.gameLapse;
144 }
145 
146 /**
147  * Time scaling is only allowed when you are on the geoscape and when you had at least one base built.
148  */
CP_AllowTimeScale(void)149 static bool CP_AllowTimeScale (void)
150 {
151 	/* check the stats value - already build bases might have been destroyed
152 	 * so the B_GetCount() values is pointless here */
153 	if (!ccs.campaignStats.basesBuilt)
154 		return false;
155 
156 	return CP_OnGeoscape();
157 }
158 
159 /**
160  * @brief Decrease game time speed
161  */
CP_GameTimeSlow(void)162 void CP_GameTimeSlow (void)
163 {
164 	/* don't allow time scale in tactical mode - only on the geoscape */
165 	if (CP_AllowTimeScale()) {
166 		if (ccs.gameLapse > 0)
167 			ccs.gameLapse--;
168 		/* Make sure the new lapse state is updated and it (and the time) is show in the menu. */
169 		CP_UpdateTime();
170 	}
171 }
172 
173 /**
174  * @brief Increase game time speed
175  */
CP_GameTimeFast(void)176 void CP_GameTimeFast (void)
177 {
178 	/* don't allow time scale in tactical mode - only on the geoscape */
179 	if (CP_AllowTimeScale()) {
180 		if (ccs.gameLapse < NUM_TIMELAPSE)
181 			ccs.gameLapse++;
182 		/* Make sure the new lapse state is updated and it (and the time) is show in the menu. */
183 		CP_UpdateTime();
184 	}
185 }
186 
187 /**
188  * @brief Set game time speed
189  * @param[in] gameLapseValue The value to set the game time to.
190  */
CP_SetGameTime(int gameLapseValue)191 static void CP_SetGameTime (int gameLapseValue)
192 {
193 	if (gameLapseValue == ccs.gameLapse)
194 		return;
195 
196 	/* check the stats value - already build bases might have been destroyed
197 	 * so the B_GetCount() values is pointless here */
198 	if (!ccs.campaignStats.basesBuilt)
199 		return;
200 
201 	if (gameLapseValue < 0 || gameLapseValue >= NUM_TIMELAPSE)
202 		return;
203 
204 	ccs.gameLapse = gameLapseValue;
205 
206 	/* Make sure the new lapse state is updated and it (and the time) is show in the menu. */
207 	CP_UpdateTime();
208 }
209 
210 
211 /**
212  * @brief Set a new time game from id
213  * @sa CL_SetGameTime
214  * @sa lapse
215  */
CP_SetGameTime_f(void)216 void CP_SetGameTime_f (void)
217 {
218 	if (cgi->Cmd_Argc() < 2) {
219 		Com_Printf("Usage: %s <timeid>\n", cgi->Cmd_Argv(0));
220 		return;
221 	}
222 	CP_SetGameTime(atoi(cgi->Cmd_Argv(1)));
223 }
224 
225 /**
226  * @brief Convert a date_t date to seconds
227  * @param[in] date The date in date_t format
228  * @return the date in seconds
229  */
Date_DateToSeconds(const date_t * date)230 int Date_DateToSeconds (const date_t* date)
231 {
232 	return date->day * 86400 + date->sec;
233 }
234 
235 /**
236  * @brief Check whether the given date and time is later than current date.
237  * @param[in] now Current date.
238  * @param[in] compare Date to compare.
239  * @return True if current date is later than given one.
240  */
Date_LaterThan(const date_t * now,const date_t * compare)241 bool Date_LaterThan (const date_t* now, const date_t* compare)
242 {
243 	if (now->day > compare->day)
244 		return true;
245 	if (now->day < compare->day)
246 		return false;
247 	if (now->sec > compare->sec)
248 		return true;
249 	return false;
250 }
251 
252 /**
253  * @brief Checks whether a given date is equal or earlier than the current campaign date
254  * @param date The date to check
255  * @return @c true if the given date is equal or earlier than the current campaign date, @c false otherwise
256  */
Date_IsDue(const date_t * date)257 bool Date_IsDue (const date_t* date)
258 {
259 	if (date->day < ccs.date.day)
260 		return true;
261 
262 	else if (date->day == ccs.date.day && date->sec <= ccs.date.sec)
263 		return true;
264 
265 	return false;
266 }
267 
268 /**
269  * @brief Add two dates and return the result.
270  * @param[in] a First date.
271  * @param[in] b Second date.
272  * @return The result of adding date_ b to date_t a.
273  */
Date_Add(date_t a,const date_t & b)274 date_t Date_Add (date_t a, const date_t &b)
275 {
276 	a.sec += b.sec;
277 	a.day += (a.sec / SECONDS_PER_DAY) + b.day;
278 	a.sec %= SECONDS_PER_DAY;
279 	return a;
280 }
281 
282 /**
283  * @brief Substract the second date from the first and return the result.
284  * @param[in] a First date.
285  * @param[in] b Second date.
286  */
Date_Substract(date_t a,const date_t & b)287 date_t Date_Substract (date_t a, const date_t &b)
288 {
289 	a.day -= b.day;
290 	a.sec -= b.sec;
291 	if (a.sec < 0) {
292 		a.day--;
293 		a.sec += SECONDS_PER_DAY;
294 	}
295 	return a;
296 }
297 
298 /**
299  * @brief Return a random relative date which lies between a lower and upper limit.
300  * @param[in] minFrame Minimal date.
301  * @param[in] maxFrame Maximal date.
302  * @return A date value between minFrame and maxFrame.
303  */
Date_Random(date_t minFrame,date_t maxFrame)304 date_t Date_Random (date_t minFrame, date_t maxFrame)
305 {
306 	const int random = (maxFrame.day * SECONDS_PER_DAY + maxFrame.sec) * frand();
307 	maxFrame.sec = std::max(minFrame.day * SECONDS_PER_DAY + minFrame.sec, random);
308 
309 	maxFrame.day = maxFrame.sec / SECONDS_PER_DAY;
310 	maxFrame.sec = maxFrame.sec % SECONDS_PER_DAY;
311 	return maxFrame;
312 }
313 
314 /**
315  * @brief Returns the monatname to the given month index
316  * @param[in] month The month index - [0-11]
317  * @return month name as char*
318  */
Date_GetMonthName(int month)319 const char* Date_GetMonthName (int month)
320 {
321 	switch (month) {
322 	case 0:
323 		return _("Jan");
324 	case 1:
325 		return _("Feb");
326 	case 2:
327 		return _("Mar");
328 	case 3:
329 		return _("Apr");
330 	case 4:
331 		return _("May");
332 	case 5:
333 		return _("Jun");
334 	case 6:
335 		return _("Jul");
336 	case 7:
337 		return _("Aug");
338 	case 8:
339 		return _("Sep");
340 	case 9:
341 		return _("Oct");
342 	case 10:
343 		return _("Nov");
344 	case 11:
345 		return _("Dec");
346 	default:
347 		return "Error";
348 	}
349 }
350