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