1 /************************************************************************
2 * *
3 * FreeSynd - a remake of the classic Bullfrog game "Syndicate". *
4 * *
5 * Copyright (C) 2005 Stuart Binge <skbinge@gmail.com> *
6 * Copyright (C) 2005 Joost Peters <joostp@users.sourceforge.net> *
7 * Copyright (C) 2006 Trent Waddington <qg@biodome.org> *
8 * Copyright (C) 2006 Tarjei Knapstad <tarjei.knapstad@gmail.com> *
9 * Copyright (C) 2010 Benoit Blancard <benblan@users.sourceforge.net>*
10 * *
11 * This program is free software; you can redistribute it and / or *
12 * modify it under the terms of the GNU General Public License as *
13 * published by the Free Software Foundation; either version 2 of the *
14 * License, or (at your option) any later version. *
15 * *
16 * This program is distributed in the hope that it will be useful, *
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
19 * General Public License for more details. *
20 * *
21 * You can view the GNU General Public License, online, at the GNU *
22 * project's web site; see <http://www.gnu.org/licenses/gpl.html>. *
23 * The full text of the license is also included in the file COPYING. *
24 * *
25 ************************************************************************/
26
27 #include <stdio.h>
28 #include <assert.h>
29
30 #include "utils/file.h"
31 #include "menus/mapmenu.h"
32 #include "menus/gamemenuid.h"
33 #include "core/gamesession.h"
34 #include "gfx/screen.h"
35 #include "system.h"
36 #include "menus/menumanager.h"
37
38 struct pos {
39 int x;
40 int y;
41 };
42
43 /*!
44 * This structure holds the position of the various
45 * blocks on the map.
46 * The order in the array is the same as the one in
47 * the block file.
48 */
49 struct BlockDisplay {
50 struct pos pos;
51 struct pos logo_pos;
52 struct pos line_start;
53 struct pos line_end;
54 } g_BlocksDisplay[50] = {
55 { {46, 18}, {14, 12}, {48, 32}, {76, 36}},
56 { {90, 16}, {14, 12}, {48, 32}, {107, 40}},
57 { {122, 6}, {226, 78}, {226, 78}, {181, 33}},
58 { {218, 16}, {226, 78}, {245, 78}, {250, 40}},
59 { {320, 22}, {289, 24}, {322, 52}, {344, 60}},
60 { {370, 20}, {353, 12}, {387, 45}, {404, 62}},
61 { {434, 10}, {353, 12}, {388, 34}, {458, 48}},
62 { {482, 30}, {585, 69}, {583, 79}, {527, 53}},
63 { {70, 36}, {13, 59}, {49, 73}, {100, 63}},
64 { {290, 70}, {243, 93}, {279, 107}, {316, 97}},
65 { {334, 54}, {289, 22}, {325, 57}, {360, 88}},
66 { {372, 58}, {353, 12}, {381, 47}, {406, 90}},
67 { {436, 74}, {353, 12}, {389, 45}, {452, 102}},
68 { {434, 62}, {585, 69}, {584, 85}, {487, 81}},
69 { {532, 76}, {585, 69}, {584, 89}, {541, 93}},
70 { {160, 52}, {225, 77}, {224, 87}, {177, 69}},
71 { {56, 76}, {13, 59}, {49, 83}, {74, 91}},
72 { {76, 76}, {13, 59}, {49, 83}, {96, 93}},
73 { {110, 76}, {149, 123}, {149, 123}, {117, 91}},
74 { {140, 76}, {149, 123}, {163, 122}, {157, 91}},
75 { {278, 124}, {243, 93}, {279, 123}, {300, 135}},
76 { {288, 124}, {243, 93}, {279, 123}, {326, 153}},
77 { {344, 116}, {421, 185}, {423, 183}, {385, 137}},
78 { {398, 106}, {421, 185}, {435, 184}, {427, 129}},
79 { {474, 98}, {565, 133}, {564, 143}, {511, 119}},
80 { {56, 98}, {13, 127}, {49, 135}, {86, 113}},
81 { {100, 106}, {149, 123}, {148, 133}, {107, 117}},
82 { {198, 130}, {207, 165}, {217, 164}, {207, 137}},
83 { {266, 140}, {207, 165}, {243, 177}, {284, 163}},
84 { {336, 156}, {243, 209}, {279, 215}, {358, 167}},
85 { {378, 146}, {421, 185}, {421, 184}, {401, 159}},
86 { {440, 118}, {421, 185}, {449, 184}, {468, 143}},
87 { {492, 136}, {565, 133}, {564, 149}, {509, 147}},
88 { {66, 122}, {13, 127}, {49, 141}, {80, 135}},
89 { {120, 172}, {61, 179}, {97, 195}, {138, 189}},
90 { {280, 170}, {243, 209}, {279, 211}, {312, 181}},
91 { {326, 166}, {285, 251}, {317, 250}, {352, 205}},
92 { {370, 172}, {421, 185}, {420, 203}, {385, 203}},
93 { {118, 206}, {89, 257}, {115, 256}, {131, 217}},
94 { {142, 184}, {207, 165}, {206, 191}, {159, 211}},
95 { {180, 196}, {243, 209}, {242, 225}, {195, 223}},
96 { {328, 226}, {285, 251}, {321, 259}, {342, 249}},
97 { {354, 212}, {412, 249}, {412, 259}, {363, 243}},
98 { {502, 250}, {459, 253}, {495, 278}, {522, 285}},
99 { {542, 246}, {597, 259}, {596, 275}, {569, 271}},
100 { {542, 282}, {597, 259}, {596, 283}, {567, 293}},
101 { {152, 230}, {215, 265}, {214, 269}, {173, 243}},
102 { {148, 242}, {215, 265}, {214, 283}, {159, 281}},
103 { {170, 252}, {215, 265}, {214, 277}, {179, 269}},
104 { {504, 160}, {471, 197}, {507, 211}, {536, 207}}
105 };
106
107
108 /*!
109 * Class constructor.
110 * \param m The menu manager.
111 */
MapMenu(MenuManager * m)112 MapMenu::MapMenu(MenuManager * m)
113 : Menu(m, fs_game_menus::kMenuIdMap, fs_game_menus::kMenuIdMain, "mmap.dat", "mmapout.dat"),
114 mapblk_data_(NULL), select_tick_count_(0) {
115 //
116 briefButId_ = addOption(17, 347, 128, 25, "#MAP_BRIEF_BUT",
117 FontManager::SIZE_2, fs_game_menus::kMenuIdBrief);
118 addOption(500, 347, 128, 25, "#MENU_MAIN_BUT",
119 FontManager::SIZE_2, fs_game_menus::kMenuIdMain);
120
121 // Country name
122 txtCountryId_ = addStatic(188, 312, 270, "", FontManager::SIZE_1,
123 true);
124 // Pop label
125 addStatic(194, 332, "#MAP_POP", FontManager::SIZE_2, true);
126 // Pop value
127 txtPopId_ = addStatic(268, 332, "", FontManager::SIZE_1, true);
128 // Tax label
129 addStatic(194, 346, "#MAP_TAX", FontManager::SIZE_2, true);
130 // Tax value
131 txtTaxValueId_ = addStatic(268, 346, "", FontManager::SIZE_1, true);
132 // Own label
133 txtOwnLblId_ = addStatic(194, 360, "#MAP_OWN", FontManager::SIZE_2, true);
134 // Own status
135 txtOwnId_ = addStatic(268, 360, "", FontManager::SIZE_1, true);
136
137 txtTimeId_ = addStatic(500, 9, "", FontManager::SIZE_2, true); // Time
138
139 // Tax cursors
140 txtTaxPctId_ = addStatic(350, 346, "@ 30%", FontManager::SIZE_1, true);
141 decrTaxButId_ = addImageOption(375, 346, Sprite::MSPR_TAX_DECR,
142 Sprite::MSPR_TAX_DECR, false);
143 registerHotKey(K_MINUS, decrTaxButId_);
144 incrTaxButId_ = addImageOption(435, 346, Sprite::MSPR_TAX_INCR,
145 Sprite::MSPR_TAX_INCR, false);
146 registerHotKey(K_PLUS, incrTaxButId_);
147
148 // 64 x 44 x 50
149 // Load map block informations
150 mapblk_data_ = File::loadOriginalFile("mmapblk.dat", mapblk_size_);
151
152 blk_tick_count_ = 0;
153 blink_status_ = true;
154 }
155
~MapMenu()156 MapMenu::~MapMenu() {
157 delete[] mapblk_data_;
158 mapblk_data_ = NULL;
159 }
160
161 /*!
162 * Update map informations depending on
163 * the currently selected mission.
164 */
handleBlockSelected()165 void MapMenu::handleBlockSelected() {
166 Block blk = g_Session.getBlock(g_Session.getSelectedBlockId());
167
168 if (blk.status == BLK_FINISHED) { // A mission is finished
169 // Brief is available only if replay mission cheat is set
170 if (g_Session.canReplayMission()) {
171 getOption(briefButId_)->setVisible(true);
172 } else {
173 getOption(briefButId_)->setVisible(false);
174 }
175 } else if (blk.status == BLK_UNAVAIL) { // A mission is unavailable
176 getOption(briefButId_)->setVisible(false);
177 } else {
178 // Brief is available because mission is either available
179 // or on rebellion
180 getOption(briefButId_)->setVisible(true);
181 }
182
183 // Update the country informations
184 getStatic(txtCountryId_)->setText(blk.name);
185
186 char tmp[100];
187
188 // Population
189 sprintf(tmp, "%i", blk.population);
190 getStatic(txtPopId_)->setText(tmp);
191
192 // Mission is finished
193 if (blk.status == BLK_FINISHED) {
194 // Status
195 getStatic(txtOwnLblId_)->setText("#MAP_STAT");
196 switch (blk.popStatus) {
197 case STAT_VERY_HAPPY:
198 getStatic(txtOwnId_)->setText("#MAP_STAT_VHAPPY");
199 break;
200 case STAT_HAPPY:
201 getStatic(txtOwnId_)->setText("#MAP_STAT_HAPPY");
202 break;
203 case STAT_CONTENT:
204 getStatic(txtOwnId_)->setText("#MAP_STAT_CTNT");
205 break;
206 case STAT_UNHAPPY:
207 getStatic(txtOwnId_)->setText("#MAP_STAT_UNHAPPY");
208 break;
209 case STAT_DISCONTENT:
210 getStatic(txtOwnId_)->setText("#MAP_STAT_DISCTNT");
211 break;
212 default:
213 // should never happend
214 getStatic(txtOwnId_)->setText("");
215 }
216
217 // Tax
218 int tax = blk.tax + blk.addToTax;
219 sprintf(tmp, "%i", g_Session.getTaxRevenue(blk.population, tax));
220 getStatic(txtTaxValueId_)->setText(tmp);
221
222 getOption(decrTaxButId_)->setVisible(true);
223 getOption(incrTaxButId_)->setVisible(true);
224 sprintf(tmp, "@ %d%%", tax);
225 getStatic(txtTaxPctId_)->setText(tmp);
226
227 } else {
228 int tax = blk.tax + blk.addToTax;
229 // Status
230 getStatic(txtOwnLblId_)->setText("#MAP_OWN");
231 getStatic(txtOwnId_)->setText("");
232 // Tax
233 if (blk.status == BLK_REBEL) {
234 getStatic(txtTaxValueId_)->setText("#MAP_STAT_REBEL");
235 sprintf(tmp, "@ %d%%", tax);
236 getStatic(txtTaxPctId_)->setText(tmp);
237 } else {
238 getStatic(txtTaxValueId_)->setText("#MAP_TAX_UNKWN");
239 getStatic(txtTaxPctId_)->setText("");
240 }
241
242 getOption(decrTaxButId_)->setVisible(false);
243 getOption(incrTaxButId_)->setVisible(false);
244 }
245
246 /*addDirtyRect(192, 310, 260, 70);*/
247 }
248
handleTick(int elapsed)249 void MapMenu::handleTick(int elapsed) {
250 // This a count to refresh the blinking line of the selector
251 select_tick_count_ += elapsed;
252 if (select_tick_count_ > 200) {
253 static int count = 0;
254 select_tick_count_ = count++;
255 needRendering();
256 }
257
258 blk_tick_count_ += elapsed;
259 if (blk_tick_count_ > 500) {
260 blk_tick_count_ = 0;
261 blink_status_ = !blink_status_;
262 needRendering();
263 }
264
265 if (g_Session.updateTime(elapsed)) {
266 handleBlockSelected();
267 updateClock();
268 }
269 }
270
271 /*!
272 * Update the game time display
273 */
updateClock()274 void MapMenu::updateClock() {
275 char tmp[100];
276 g_Session.getTimeAsStr(tmp);
277 getStatic(txtTimeId_)->setText(tmp);
278 }
279
280 /*!
281 * Utility method to draw the mission selector on the map
282 * depending on the current selection.<br/>
283 * The selector consists of the player logo inside a box and
284 * a line that links the logo to the selected region.
285 */
drawSelector()286 void MapMenu::drawSelector() {
287 uint8 selId = g_Session.getSelectedBlockId();
288 int logo_x = g_BlocksDisplay[selId].logo_pos.x;
289 int logo_y = g_BlocksDisplay[selId].logo_pos.y;
290 g_Screen.drawLogo(logo_x, logo_y, g_Session.getLogo(),
291 g_Session.getLogoColour(), true);
292
293 // Draw box enclosing logo
294 uint8 box[18 * 18];
295 memset(box, 255, 18 * 18);
296 for (int i = 0; i < 18; i++) {
297 box[i] = 252;
298 box[i + 17 * 18] = 252;
299 }
300 for (int j = 0; j < 18; j++) {
301 box[j * 18] = 252;
302 box[j * 18 + 17] = 252;
303 }
304 g_Screen.scale2x(logo_x - 2, logo_y - 2, 18, 18, box);
305
306 // Draw line between country and logobox
307 int blk_line_end_x = g_BlocksDisplay[selId].line_end.x;
308 int blk_line_end_y = g_BlocksDisplay[selId].line_end.y;
309 int blk_line_start_x = g_BlocksDisplay[selId].line_start.x;
310 int blk_line_start_y = g_BlocksDisplay[selId].line_start.y;
311 g_Screen.drawLine(blk_line_start_x, blk_line_start_y, blk_line_end_x,
312 blk_line_end_y, 252, 5, select_tick_count_ % 10);
313 g_Screen.drawLine(blk_line_start_x, blk_line_start_y - 1,
314 blk_line_end_x, blk_line_end_y - 1, 252, 5,
315 select_tick_count_ % 10);
316 g_Screen.drawLine(blk_line_start_x, blk_line_start_y, blk_line_end_x,
317 blk_line_end_y, 4, 5, select_tick_count_ % 10 + 5);
318 g_Screen.drawLine(blk_line_start_x, blk_line_start_y - 1,
319 blk_line_end_x, blk_line_end_y - 1, 4, 5,
320 select_tick_count_ % 10 + 5);
321
322 // Reset the counter
323 select_tick_count_ = 0;
324 }
325
handleShow()326 void MapMenu::handleShow() {
327 // save a background without country colour
328 menu_manager_->saveBackground();
329
330 // Show the mouse
331 g_System.showCursor();
332
333 // State of the briefing button
334 handleBlockSelected();
335
336 // Update the time
337 updateClock();
338 }
339
handleRender(DirtyList & dirtyList)340 void MapMenu::handleRender(DirtyList &dirtyList) {
341 // Draws all countries
342 for (int i = 0; i < GameSession::NB_MISSION; i++) {
343 Block blk = g_Session.getBlock(i);
344 if ((i == g_Session.getSelectedBlockId()) ||
345 (blk.status == BLK_AVAIL && blink_status_) ||
346 blk.status != BLK_AVAIL) {
347 uint8 data[64 * 44];
348 memcpy(data, mapblk_data_ + i * 64 * 44, 64 * 44);
349 for (int j = 0; j < 64 * 44; j++)
350 if (data[j] == 0)
351 data[j] = 255;
352 else
353 data[j] = g_Session.get_owner_color(blk);
354 g_Screen.scale2x(g_BlocksDisplay[i].pos.x,
355 g_BlocksDisplay[i].pos.y, 64, 44, data, 64);
356 }
357 }
358
359 // Draws the selector
360 drawSelector();
361 }
362
handleLeave()363 void MapMenu::handleLeave() {
364 g_System.hideCursor();
365 }
366
handleMouseDown(int x,int y,int button,const int modKeys)367 bool MapMenu::handleMouseDown(int x, int y, int button, const int modKeys) {
368 // Checks among the missions which one has been clicked on
369 for (int i = 0; i < 50; i++) {
370 if (x > g_BlocksDisplay[i].pos.x && x < g_BlocksDisplay[i].pos.x + 64 &&
371 y > g_BlocksDisplay[i].pos.y && y < g_BlocksDisplay[i].pos.y + 44) {
372 if (mapblk_data_
373 [i * 64 * 44 + (y - g_BlocksDisplay[i].pos.y) / 2 * 64 +
374 (x - g_BlocksDisplay[i].pos.x) / 2] != 0) {
375 // Do something only if the selected block is new
376 // ie the user did not click on the same mission
377 if (g_Session.getSelectedBlockId() != i) {
378 g_Session.setSelectedBlockId(i);
379
380 handleBlockSelected();
381 needRendering();
382 }
383 return true;
384 }
385 }
386 }
387
388 return false;
389 }
390
handleAction(const int actionId,void * ctx,const int modKeys)391 void MapMenu::handleAction(const int actionId, void *ctx, const int modKeys) {
392 bool refresh = false;
393 if ( actionId == incrTaxButId_ ) {
394 if (modKeys & KMD_CTRL) {
395 refresh = g_Session.addToTaxRate(10);
396 } else {
397 refresh = g_Session.addToTaxRate(1);
398 }
399 } else if ( actionId == decrTaxButId_ ) {
400 if (modKeys & KMD_CTRL) {
401 refresh = g_Session.addToTaxRate(-10);
402 } else {
403 refresh = g_Session.addToTaxRate(-1);
404 }
405 }
406
407 if (refresh) {
408 handleBlockSelected();
409 needRendering();
410 }
411 }
412
handleUnknownKey(Key key,const int modKeys)413 bool MapMenu::handleUnknownKey(Key key, const int modKeys) {
414 bool consumed = false;
415 if (key.keyFunc == KFC_LEFT) {
416 // navigate among available missions by decreasing index
417 int start = g_Session.getSelectedBlockId();
418 for (int i = 1; i < GameSession::NB_MISSION; i++) {
419 int index = start - i;
420 if (index < 0) {
421 index = GameSession::NB_MISSION + index;
422 }
423 Block blk = g_Session.getBlock(index);
424 if (blk.status == BLK_AVAIL) {
425 g_Session.setSelectedBlockId(index);
426 consumed = true;
427 break;
428 }
429 }
430 } else if (key.keyFunc == KFC_RIGHT) {
431 // navigate among available missions by increasing index
432 int start = g_Session.getSelectedBlockId();
433 for (int i = 1; i < GameSession::NB_MISSION; i++) {
434 int index = (start + i) % GameSession::NB_MISSION;
435 Block blk = g_Session.getBlock(index);
436 if (blk.status == BLK_AVAIL) {
437 g_Session.setSelectedBlockId(index);
438 consumed = true;
439 break;
440 }
441 }
442 } else if (key.keyFunc == KFC_PAGEUP) {
443 // Pressing PageUp increase tax of 10 percents
444 Block blk = g_Session.getBlock(g_Session.getSelectedBlockId());
445 if (blk.status == BLK_FINISHED) {
446 consumed = g_Session.addToTaxRate(10);
447 }
448 } else if ( key.keyFunc == KFC_PAGEDOWN ) {
449 // Pressing PageDown decrease tax of 10 percents
450 Block blk = g_Session.getBlock(g_Session.getSelectedBlockId());
451 if (blk.status == BLK_FINISHED) {
452 consumed = g_Session.addToTaxRate(-10);
453 }
454 }
455
456 if (consumed) {
457 needRendering();
458 }
459
460 handleBlockSelected();
461
462 return consumed;
463 }
464