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