0c4b900ff43e6d03fce781f0d68c77e9e432d691
[supertux.git] / src / worldmap.cpp
1 //  $Id$
2 //
3 //  SuperTux -  A Jump'n Run
4 //  Copyright (C) 2004 Ingo Ruhnke <grumbel@gmx.de>
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19
20 #include <config.h>
21
22 #include <iostream>
23 #include <fstream>
24 #include <vector>
25 #include <cassert>
26 #include <unistd.h>
27
28 #include "app/globals.h"
29 #include "video/surface.h"
30 #include "video/screen.h"
31 #include "video/drawing_context.h"
32 #include "utils/lispreader.h"
33 #include "utils/lispwriter.h"
34 #include "special/frame_rate.h"
35 #include "gameloop.h"
36 #include "app/setup.h"
37 #include "sector.h"
38 #include "worldmap.h"
39 #include "audio/sound_manager.h"
40 #include "resources.h"
41 #include "app/gettext.h"
42 #include "misc.h"
43 #include "scene.h"
44
45 #define map_message_TIME 2.8
46
47 Menu* worldmap_menu  = 0;
48
49 namespace WorldMapNS {
50
51 Direction reverse_dir(Direction direction)
52 {
53   switch(direction)
54     {
55     case D_WEST:
56       return D_EAST;
57     case D_EAST:
58       return D_WEST;
59     case D_NORTH:
60       return D_SOUTH;
61     case D_SOUTH:
62       return D_NORTH;
63     case D_NONE:
64       return D_NONE;
65     }
66   return D_NONE;
67 }
68
69 std::string
70 direction_to_string(Direction direction)
71 {
72   switch(direction)
73     {
74     case D_WEST:
75       return "west";
76     case D_EAST:
77       return "east";
78     case D_NORTH:
79       return "north";
80     case D_SOUTH:
81       return "south";
82     default:
83       return "none";
84     }
85 }
86
87 Direction
88 string_to_direction(const std::string& directory)
89 {
90   if (directory == "west")
91     return D_WEST;
92   else if (directory == "east")
93     return D_EAST;
94   else if (directory == "north")
95     return D_NORTH;
96   else if (directory == "south")
97     return D_SOUTH;
98   else
99     return D_NONE;
100 }
101
102 TileManager::TileManager()
103 {
104   std::string stwt_filename = datadir +  "/images/worldmap/antarctica.stwt";
105   lisp_object_t* root_obj = lisp_read_from_file(stwt_filename);
106  
107   if (!root_obj)
108     Termination::abort("Couldn't load file", stwt_filename);
109
110   if (strcmp(lisp_symbol(lisp_car(root_obj)), "supertux-worldmap-tiles") == 0)
111     {
112       lisp_object_t* cur = lisp_cdr(root_obj);
113
114       while(!lisp_nil_p(cur))
115         {
116           lisp_object_t* element = lisp_car(cur);
117
118           if (strcmp(lisp_symbol(lisp_car(element)), "tile") == 0)
119             {
120               int id = 0;
121
122               Tile* tile = new Tile;             
123               tile->north = tile->east = tile->south = tile->west = true;
124               tile->stop  = true;
125               tile->auto_walk = false;
126
127               LispReader reader(lisp_cdr(element));
128               reader.read_int("id", id);
129
130               std::string temp;
131               reader.read_string("possible-directions", temp);
132               if(!temp.empty())
133                 {
134                 tile->north = tile->east = tile->south = tile->west = false;
135                 if(temp.find("north") != std::string::npos)
136                   tile->north = true;
137                 if(temp.find("south") != std::string::npos)
138                   tile->south = true;
139                 if(temp.find("east") != std::string::npos)
140                   tile->east = true;
141                 if(temp.find("west") != std::string::npos)
142                   tile->west = true;
143                 }
144
145               /* For backward compatibility */
146               reader.read_bool("north", tile->north);
147               reader.read_bool("south", tile->south);
148               reader.read_bool("west",  tile->west);
149               reader.read_bool("east",  tile->east);
150
151               reader.read_bool("stop",  tile->stop);
152               reader.read_bool("auto-walk",  tile->auto_walk);
153
154               reader.read_string("one-way", temp);
155               tile->one_way = BOTH_WAYS;
156               if(!temp.empty())
157                 {
158                 if(temp == "north-south")
159                   tile->one_way = NORTH_SOUTH_WAY;
160                 else if(temp == "south-north")
161                   tile->one_way = SOUTH_NORTH_WAY;
162                 else if(temp == "east-west")
163                   tile->one_way = EAST_WEST_WAY;
164                 else if(temp == "west-east")
165                   tile->one_way = WEST_EAST_WAY;
166                 }
167
168               std::vector<std::string> filenames;
169               reader.read_string_vector("image", filenames);
170
171               if(filenames.size() == 0)
172                 std::cerr << "Warning: no image specified for tile " << id
173                           << ".\nIgnoring...\n" << std::endl;
174
175               for(int i = 0; static_cast<unsigned int>(i) < filenames.size(); i++)
176                 {
177                 Surface* image = new Surface(
178                          datadir +  "/images/worldmap/" + filenames[i], true);
179                 tile->images.push_back(image);
180                 }
181
182               tile->anim_speed = 25;
183               reader.read_int("anim-speed", tile->anim_speed);
184
185
186               if (id >= int(tiles.size()))
187                 tiles.resize(id+1);
188
189               tiles[id] = tile;
190             }
191           else
192             {
193               puts("Unhandled symbol");
194             }
195
196           cur = lisp_cdr(cur);
197         }
198     }
199   else
200     {
201       assert(0);
202     }
203
204   lisp_free(root_obj);
205 }
206
207 TileManager::~TileManager()
208 {
209   for(std::vector<Tile*>::iterator i = tiles.begin(); i != tiles.end(); ++i)
210     delete *i;
211 }
212
213 Tile*
214 TileManager::get(int i)
215 {
216   assert(i >=0 && i < int(tiles.size()));
217   return tiles[i];
218 }
219
220 //---------------------------------------------------------------------------
221
222 Tux::Tux(WorldMap* worldmap_)
223   : worldmap(worldmap_)
224 {
225   largetux_sprite = new Surface(datadir +  "/images/worldmap/tux.png", true);
226   firetux_sprite = new Surface(datadir +  "/images/worldmap/firetux.png", true);
227   smalltux_sprite = new Surface(datadir +  "/images/worldmap/smalltux.png", true);
228
229   offset = 0;
230   moving = false;
231   tile_pos.x = worldmap->get_start_x();
232   tile_pos.y = worldmap->get_start_y();
233   direction = D_NONE;
234   input_direction = D_NONE;
235 }
236
237 Tux::~Tux()
238 {
239   delete smalltux_sprite;
240   delete firetux_sprite;
241   delete largetux_sprite;
242 }
243
244 void
245 Tux::draw(DrawingContext& context, const Vector& offset)
246 {
247   Vector pos = get_pos();
248   switch (player_status.bonus)
249     {
250     case PlayerStatus::GROWUP_BONUS:
251       context.draw_surface(largetux_sprite,
252           Vector(pos.x + offset.x, pos.y + offset.y - 10), LAYER_OBJECTS);
253       break;
254     case PlayerStatus::FLOWER_BONUS:
255       context.draw_surface(firetux_sprite,
256           Vector(pos.x + offset.x, pos.y + offset.y - 10), LAYER_OBJECTS);
257       break;
258     case PlayerStatus::NO_BONUS:
259       context.draw_surface(smalltux_sprite,
260           Vector(pos.x + offset.x, pos.y + offset.y - 10), LAYER_OBJECTS);
261       break;
262     }
263 }
264
265
266 Vector
267 Tux::get_pos()
268 {
269   float x = tile_pos.x * 32;
270   float y = tile_pos.y * 32;
271
272   switch(direction)
273     {
274     case D_WEST:
275       x -= offset - 32;
276       break;
277     case D_EAST:
278       x += offset - 32;
279       break;
280     case D_NORTH:
281       y -= offset - 32;
282       break;
283     case D_SOUTH:
284       y += offset - 32;
285       break;
286     case D_NONE:
287       break;
288     }
289   
290   return Vector((int)x, (int)y); 
291 }
292
293 void
294 Tux::stop()
295 {
296   offset = 0;
297   direction = D_NONE;
298   input_direction = D_NONE;
299   moving = false;
300 }
301
302 void
303 Tux::set_direction(Direction dir)
304 {
305 input_direction = dir;
306 }
307
308 void
309 Tux::action(float delta)
310 {
311   if (!moving)
312     {
313       if (input_direction != D_NONE)
314         { 
315           WorldMap::Level* level = worldmap->at_level();
316
317           // We got a new direction, so lets start walking when possible
318           Vector next_tile;
319           if ((!level || level->solved)
320               && worldmap->path_ok(input_direction, tile_pos, &next_tile))
321             {
322               tile_pos = next_tile;
323               moving = true;
324               direction = input_direction;
325               back_direction = reverse_dir(direction);
326             }
327           else if (input_direction == back_direction)
328             {
329               moving = true;
330               direction = input_direction;
331               tile_pos = worldmap->get_next_tile(tile_pos, direction);
332               back_direction = reverse_dir(direction);
333             }
334         }
335     }
336   else
337     {
338       // Let tux walk a few pixels (20 pixel/sec)
339       offset += 20.0f * delta;
340
341       if (offset > 32)
342         { // We reached the next tile, so we check what to do now
343           offset -= 32;
344
345           WorldMap::SpecialTile* special_tile = worldmap->at_special_tile();
346           if(special_tile && special_tile->passive_message)
347             {  // direction and the apply_action_ are opposites, since they "see"
348                // directions in a different way
349             if((direction == D_NORTH && special_tile->apply_action_south) ||
350                (direction == D_SOUTH && special_tile->apply_action_north) ||
351                (direction == D_WEST && special_tile->apply_action_east) ||
352                (direction == D_EAST && special_tile->apply_action_west))
353               {
354               worldmap->passive_message = special_tile->map_message;
355               worldmap->passive_message_timer.start(map_message_TIME);
356               }
357             }
358
359           if (worldmap->at(tile_pos)->stop ||
360              (special_tile && !special_tile->passive_message) ||
361               worldmap->at_level())
362             {
363               if(special_tile && !special_tile->map_message.empty() &&
364                 !special_tile->passive_message)
365                 worldmap->passive_message_timer.start(0);
366               stop();
367             }
368           else
369             {
370               if (worldmap->at(tile_pos)->auto_walk || direction != input_direction)
371                 { // Turn to a new direction
372                   Tile* tile = worldmap->at(tile_pos);
373
374                   if(direction != input_direction && 
375                      ((tile->north && input_direction == D_NORTH) ||
376                      (tile->south && input_direction == D_SOUTH) ||
377                      (tile->east && input_direction == D_EAST) ||
378                      (tile->west && input_direction == D_WEST)))
379                     {  // player has changed direction during auto-movement
380                     direction = input_direction;
381                     back_direction = reverse_dir(direction);
382                     }
383                   else if(direction != input_direction)
384                     {  // player has changed to impossible tile
385                       back_direction = reverse_dir(direction);
386                       stop();
387                     }
388                   else
389                     {
390                     Direction dir = D_NONE;
391                   
392                     if (tile->north && back_direction != D_NORTH)
393                       dir = D_NORTH;
394                     else if (tile->south && back_direction != D_SOUTH)
395                       dir = D_SOUTH;
396                     else if (tile->east && back_direction != D_EAST)
397                       dir = D_EAST;
398                     else if (tile->west && back_direction != D_WEST)
399                       dir = D_WEST;
400
401                     if (dir != D_NONE)
402                       {
403                       direction = dir;
404                       input_direction = direction;
405                       back_direction = reverse_dir(direction);
406                       }
407                     else
408                       {
409                       // Should never be reached if tiledata is good
410                       stop();
411                       return;
412                       }
413                     }
414                   }
415
416               // Walk automatically to the next tile
417               if(direction != D_NONE)
418                 {
419                 Vector next_tile;
420                 if (worldmap->path_ok(direction, tile_pos, &next_tile))
421                   {
422                   tile_pos = next_tile;
423                   }
424                 else
425                   {
426                   puts("Tilemap data is buggy");
427                   stop();
428                   }
429                 }
430             }
431         }
432     }
433 }
434
435 //---------------------------------------------------------------------------
436 Tile::Tile()
437 {
438 }
439
440 Tile::~Tile()
441 {
442   for(std::vector<Surface*>::iterator i = images.begin(); i != images.end(); i++)
443     delete *i;
444 }
445
446
447 void
448 Tile::draw(DrawingContext& context, Vector pos)
449 {
450   // same code as from tile_manager.cpp draw_tile()
451
452   if(!images.size())
453     return;
454
455   if(images.size() > 1)
456     {
457     size_t frame 
458       = ((global_frame_counter*25) / anim_speed) % images.size();
459
460     context.draw_surface(images[frame], pos, LAYER_TILES);
461     }
462   else if (images.size() == 1)
463     {
464     context.draw_surface(images[0], pos, LAYER_TILES);
465     }
466 }
467
468 //---------------------------------------------------------------------------
469
470 WorldMap::WorldMap()
471 {
472   tile_manager = new TileManager();
473   //tux = new Tux(this);
474   
475   width  = 20;
476   height = 15;
477   
478   start_x = 4;
479   start_y = 5;
480
481   tux = new Tux(this);
482   
483   leveldot_green = new Surface(datadir +  "/images/worldmap/leveldot_green.png", true);
484   leveldot_red = new Surface(datadir +  "/images/worldmap/leveldot_red.png", true);
485   messagedot   = new Surface(datadir +  "/images/worldmap/messagedot.png", true);
486   teleporterdot   = new Surface(datadir +  "/images/worldmap/teleporterdot.png", true);
487
488   enter_level = false;
489
490   name = "<no title>";
491   music = "SALCON.MOD";
492
493   global_frame_counter = 0;
494
495   total_stats.reset();
496 }
497
498 WorldMap::~WorldMap()
499 {
500   delete tux;
501   delete tile_manager;
502
503   delete leveldot_green;
504   delete leveldot_red;
505   delete messagedot;
506   delete teleporterdot;
507 }
508
509 // Don't forget to set map_filename before calling this
510 void
511 WorldMap::load_map()
512 {
513   lisp_object_t* root_obj = lisp_read_from_file(datadir + "/levels/worldmap/" + map_filename);
514   if (!root_obj)
515     Termination::abort("Couldn't load file", datadir + "/levels/worldmap/" + map_filename);
516
517   if (strcmp(lisp_symbol(lisp_car(root_obj)), "supertux-worldmap") == 0)
518     {
519       lisp_object_t* cur = lisp_cdr(root_obj);
520
521       while(!lisp_nil_p(cur))
522         {
523           lisp_object_t* element = lisp_car(cur);
524
525           if (strcmp(lisp_symbol(lisp_car(element)), "tilemap") == 0)
526             {
527               LispReader reader(lisp_cdr(element));
528               reader.read_int("width",  width);
529               reader.read_int("height", height);
530               reader.read_int_vector("data", tilemap);
531             }
532           else if (strcmp(lisp_symbol(lisp_car(element)), "properties") == 0)
533             {
534               LispReader reader(lisp_cdr(element));
535               reader.read_string("name", name, true);
536               reader.read_string("music", music);
537               reader.read_int("start_pos_x", start_x);
538               reader.read_int("start_pos_y", start_y);
539             }
540           else if (strcmp(lisp_symbol(lisp_car(element)), "special-tiles") == 0 ||
541                    strcmp(lisp_symbol(lisp_car(element)), "levels") == 0)
542             {
543               lisp_object_t* cur = lisp_cdr(element);
544               
545               while(!lisp_nil_p(cur))
546                 {
547                   lisp_object_t* element = lisp_car(cur);
548
549                   if (strcmp(lisp_symbol(lisp_car(element)), "special-tile") == 0)
550                     {
551                       SpecialTile special_tile;
552                       LispReader reader(lisp_cdr(element));
553                       
554                       reader.read_float("x", special_tile.pos.x);
555                       reader.read_float("y", special_tile.pos.y);
556
557                       special_tile.map_message.erase();
558                       reader.read_string("map-message", special_tile.map_message);
559                       special_tile.passive_message = false;
560                       reader.read_bool("passive-message", special_tile.passive_message);
561
562                       special_tile.teleport_dest = Vector(-1,-1);
563                       reader.read_float("teleport-to-x", special_tile.teleport_dest.x);
564                       reader.read_float("teleport-to-y", special_tile.teleport_dest.y);
565
566                       special_tile.invisible = false;
567                       reader.read_bool("invisible-tile", special_tile.invisible);
568
569                       special_tile.apply_action_north = special_tile.apply_action_south =
570                           special_tile.apply_action_east = special_tile.apply_action_west =
571                           true;
572
573                       std::string apply_direction;
574                       reader.read_string("apply-to-direction", apply_direction);
575                       if(!apply_direction.empty())
576                         {
577                         special_tile.apply_action_north = special_tile.apply_action_south =
578                             special_tile.apply_action_east = special_tile.apply_action_west =
579                             false;
580                         if(apply_direction.find("north") != std::string::npos)
581                           special_tile.apply_action_north = true;
582                         if(apply_direction.find("south") != std::string::npos)
583                           special_tile.apply_action_south = true;
584                         if(apply_direction.find("east") != std::string::npos)
585                           special_tile.apply_action_east = true;
586                         if(apply_direction.find("west") != std::string::npos)
587                           special_tile.apply_action_west = true;
588                         }
589
590                       special_tiles.push_back(special_tile);
591                     }
592
593                   else if (strcmp(lisp_symbol(lisp_car(element)), "level") == 0)
594                     {
595                       Level level;
596                       LispReader reader(lisp_cdr(element));
597                       level.solved = false;
598                       
599                       level.north = true;
600                       level.east  = true;
601                       level.south = true;
602                       level.west  = true;
603
604                       reader.read_string("extro-filename", level.extro_filename);
605                       reader.read_string("next-worldmap", level.next_worldmap);
606
607                       level.quit_worldmap = false;
608                       reader.read_bool("quit-worldmap", level.quit_worldmap);
609
610                       reader.read_string("name", level.name, true);
611                       reader.read_float("x", level.pos.x);
612                       reader.read_float("y", level.pos.y);
613
614                       level.auto_path = true;
615                       reader.read_bool("auto-path", level.auto_path);
616
617                       level.vertical_flip = false;
618                       reader.read_bool("vertical-flip", level.vertical_flip);
619
620                       levels.push_back(level);
621                     }
622                   
623                   cur = lisp_cdr(cur);      
624                 }
625             }
626           else
627             {
628               
629             }
630           
631           cur = lisp_cdr(cur);
632         }
633     }
634
635     lisp_free(root_obj);
636
637     delete tux;
638     tux = new Tux(this);
639 }
640
641 void WorldMap::get_level_title(Level& level)
642 {
643   /** get special_tile's title */
644   level.title = "<no title>";
645
646   LispReader* reader = LispReader::load(datadir + "/levels/" + level.name, "supertux-level");
647   if(!reader)
648     {
649     std::cerr << "Error: Could not open level file. Ignoring...\n";
650     return;
651     }
652
653   reader->read_string("name", level.title, true);
654   delete reader;
655 }
656
657 void WorldMap::calculate_total_stats()
658 {
659   total_stats.reset();
660   for(Levels::iterator i = levels.begin(); i != levels.end(); ++i)
661     {
662     if (i->solved)
663       {
664       total_stats += i->statistics;
665       }
666     }
667 }
668
669 void
670 WorldMap::on_escape_press()
671 {
672   // Show or hide the menu
673   if(!Menu::current())
674     {
675     Menu::set_current(worldmap_menu); 
676     tux->set_direction(D_NONE);  // stop tux movement when menu is called
677     }
678   else
679     Menu::set_current(0); 
680 }
681
682 void
683 WorldMap::get_input()
684 {
685   enter_level = false;
686   SDLKey key;
687
688   SDL_Event event;
689   while (SDL_PollEvent(&event))
690     {
691       if (Menu::current())
692         {
693           Menu::current()->event(event);
694         }
695       else
696         {
697           switch(event.type)
698             {
699             case SDL_QUIT:
700               Termination::abort("Received window close", "");
701               break;
702           
703             case SDL_KEYDOWN:
704               key = event.key.keysym.sym;
705
706               if(key == SDLK_ESCAPE)
707                 on_escape_press();
708               else if(key == SDLK_RETURN || key == keymap.power)
709                 enter_level = true;
710               else if(key == SDLK_LEFT || key == keymap.power)
711                 tux->set_direction(D_WEST);
712               else if(key == SDLK_RIGHT || key == keymap.right)
713                 tux->set_direction(D_EAST);
714               else if(key == SDLK_UP || key == keymap.up ||
715                 key == keymap.jump)
716                   // there might be ppl that use jump as up key
717                 tux->set_direction(D_NORTH);
718               else if(key == SDLK_DOWN || key == keymap.down)
719                 tux->set_direction(D_SOUTH);
720               break;
721
722             case SDL_JOYHATMOTION:
723               if(event.jhat.value & SDL_HAT_UP) {
724                 tux->set_direction(D_NORTH);
725               } else if(event.jhat.value & SDL_HAT_DOWN) {
726                 tux->set_direction(D_SOUTH);
727               } else if(event.jhat.value & SDL_HAT_LEFT) {
728                 tux->set_direction(D_WEST);
729               } else if(event.jhat.value & SDL_HAT_RIGHT) {
730                 tux->set_direction(D_EAST);
731               }
732               break;
733           
734             case SDL_JOYAXISMOTION:
735               if (event.jaxis.axis == joystick_keymap.x_axis)
736                 {
737                   if (event.jaxis.value < -joystick_keymap.dead_zone)
738                     tux->set_direction(D_WEST);
739                   else if (event.jaxis.value > joystick_keymap.dead_zone)
740                     tux->set_direction(D_EAST);
741                 }
742               else if (event.jaxis.axis == joystick_keymap.y_axis)
743                 {
744                   if (event.jaxis.value > joystick_keymap.dead_zone)
745                     tux->set_direction(D_SOUTH);
746                   else if (event.jaxis.value < -joystick_keymap.dead_zone)
747                     tux->set_direction(D_NORTH);
748                 }
749               break;
750
751             case SDL_JOYBUTTONDOWN:
752               if (event.jbutton.button == joystick_keymap.b_button)
753                 enter_level = true;
754               else if (event.jbutton.button == joystick_keymap.start_button)
755                 on_escape_press();
756               break;
757
758             default:
759               break;
760             }
761         }
762     }
763 }
764
765 Vector
766 WorldMap::get_next_tile(Vector pos, Direction direction)
767 {
768   switch(direction)
769     {
770     case D_WEST:
771       pos.x -= 1;
772       break;
773     case D_EAST:
774       pos.x += 1;
775       break;
776     case D_NORTH:
777       pos.y -= 1;
778       break;
779     case D_SOUTH:
780       pos.y += 1;
781       break;
782     case D_NONE:
783       break;
784     }
785   return pos;
786 }
787
788 bool
789 WorldMap::path_ok(Direction direction, Vector old_pos, Vector* new_pos)
790 {
791   *new_pos = get_next_tile(old_pos, direction);
792
793   if (!(new_pos->x >= 0 && new_pos->x < width
794         && new_pos->y >= 0 && new_pos->y < height))
795     { // New position is outsite the tilemap
796       return false;
797     }
798   else if(at(*new_pos)->one_way != BOTH_WAYS)
799     {
800 std::cerr << "one way only\n";
801       if((at(*new_pos)->one_way == NORTH_SOUTH_WAY && direction != D_SOUTH) ||
802          (at(*new_pos)->one_way == SOUTH_NORTH_WAY && direction != D_NORTH) ||
803          (at(*new_pos)->one_way == EAST_WEST_WAY && direction != D_WEST) ||
804          (at(*new_pos)->one_way == WEST_EAST_WAY && direction != D_EAST))
805         return false;
806       return true;
807     }
808   else
809     { // Check if we the tile allows us to go to new_pos
810       switch(direction)
811         {
812         case D_WEST:
813           return (at(old_pos)->west && at(*new_pos)->east);
814
815         case D_EAST:
816           return (at(old_pos)->east && at(*new_pos)->west);
817
818         case D_NORTH:
819           return (at(old_pos)->north && at(*new_pos)->south);
820
821         case D_SOUTH:
822           return (at(old_pos)->south && at(*new_pos)->north);
823
824         case D_NONE:
825           assert(!"path_ok() can't work if direction is NONE");
826         }
827       return false;
828     }
829 }
830
831 void
832 WorldMap::update(float delta)
833 {
834   if(!frame_timer.check()) {
835     global_frame_counter++;
836   }
837
838   if (enter_level && !tux->is_moving())
839     {
840       /* Check special tile action */
841       SpecialTile* special_tile = at_special_tile();
842       if(special_tile)
843         {
844         if (special_tile->teleport_dest != Vector(-1,-1))
845           {
846           // TODO: an animation, camera scrolling or a fading would be a nice touch
847           SoundManager::get()->play_sound(IDToSound(SND_WARP));
848           tux->back_direction = D_NONE;
849           tux->set_tile_pos(special_tile->teleport_dest);
850           SDL_Delay(1000);
851           }
852         }
853
854       /* Check level action */
855       bool level_finished = true;
856       Level* level = at_level();
857       if (!level)
858         {
859         std::cout << "No level to enter at: "
860           << tux->get_tile_pos().x << ", " << tux->get_tile_pos().y << std::endl;
861         return;
862         }
863
864
865           if (level->pos == tux->get_tile_pos())
866             {
867               PlayerStatus old_player_status = player_status;
868
869               std::cout << "Enter the current level: " << level->name << std::endl;
870               // do a shriking fade to the level
871               shrink_fade(Vector((level->pos.x*32 + 16 + offset.x),(level->pos.y*32 + 16
872                       + offset.y)), 500);
873               GameSession session(level->name,
874                                   ST_GL_LOAD_LEVEL_FILE, level->vertical_flip,
875                                   &level->statistics);
876
877               switch (session.run())
878                 {
879                 case GameSession::ES_LEVEL_FINISHED:
880                   {
881                     level_finished = true;
882                     bool old_level_state = level->solved;
883                     level->solved = true;
884
885                     // deal with statistics
886                     level->statistics.merge(global_stats);
887                     calculate_total_stats();
888
889                     if (session.get_current_sector()->player->got_power !=
890                           session.get_current_sector()->player->NONE_POWER)
891                       player_status.bonus = PlayerStatus::FLOWER_BONUS;
892                     else if (session.get_current_sector()->player->size == BIG)
893                       player_status.bonus = PlayerStatus::GROWUP_BONUS;
894                     else
895                       player_status.bonus = PlayerStatus::NO_BONUS;
896
897                     if (old_level_state != level->solved && level->auto_path)
898                       { // Try to detect the next direction to which we should walk
899                         // FIXME: Mostly a hack
900                         Direction dir = D_NONE;
901                     
902                         Tile* tile = at(tux->get_tile_pos());
903
904                         if (tile->north && tux->back_direction != D_NORTH)
905                           dir = D_NORTH;
906                         else if (tile->south && tux->back_direction != D_SOUTH)
907                           dir = D_SOUTH;
908                         else if (tile->east && tux->back_direction != D_EAST)
909                           dir = D_EAST;
910                         else if (tile->west && tux->back_direction != D_WEST)
911                           dir = D_WEST;
912
913                         if (dir != D_NONE)
914                           {
915                             tux->set_direction(dir);
916                             //tux->update(delta);
917                           }
918
919                         std::cout << "Walk to dir: " << dir << std::endl;
920                       }
921                   }
922
923                   break;
924                 case GameSession::ES_LEVEL_ABORT:
925                   level_finished = false;
926                   /* In case the player's abort the level, keep it using the old
927                       status. But the minimum lives and no bonus. */
928                   player_status.distros = old_player_status.distros;
929                   player_status.lives = std::min(old_player_status.lives, player_status.lives);
930                   player_status.bonus = player_status.NO_BONUS;
931
932                   break;
933                 case GameSession::ES_GAME_OVER:
934                   {
935                   level_finished = false;
936                   /* draw an end screen */
937                   /* TODO: in the future, this should make a dialog a la SuperMario, asking
938                   if the player wants to restart the world map with no score and from
939                   level 1 */
940                   char str[80];
941
942                   DrawingContext context;
943                   context.draw_gradient(Color (200,240,220), Color(200,200,220),
944                       LAYER_BACKGROUND0);
945
946                   context.draw_text(blue_text, _("GAMEOVER"), 
947                       Vector(screen->w/2, 200), CENTER_ALLIGN, LAYER_FOREGROUND1);
948
949                   sprintf(str, _("COINS: %d"), player_status.distros);
950                   context.draw_text(gold_text, str,
951                       Vector(screen->w/2, screen->w - 32), CENTER_ALLIGN, LAYER_FOREGROUND1);
952
953                   total_stats.draw_message_info(context, _("Total Statistics"));
954
955                   context.do_drawing();
956   
957                   SDL_Event event;
958                   wait_for_event(event,2000,6000,true);
959
960                   quit = true;
961                   player_status.reset();
962                   break;
963                   }
964                 case GameSession::ES_NONE:
965                   assert(false);
966                   // Should never be reached 
967                   break;
968                 }
969
970               SoundManager::get()->play_music(song);
971               Menu::set_current(0);
972               if (!savegame_file.empty())
973                 savegame(savegame_file);
974             }
975       /* The porpose of the next checking is that if the player lost
976          the level (in case there is one), don't show anything */
977       if(level_finished)
978         {
979         if (!level->extro_filename.empty())
980           {
981           // Display a text file
982           display_text_file(level->extro_filename, SCROLL_SPEED_MESSAGE, white_big_text , white_text, white_small_text, blue_text );
983           }
984
985         if (!level->next_worldmap.empty())
986           {
987           // Load given worldmap
988           loadmap(level->next_worldmap);
989           }
990         if (level->quit_worldmap)
991           quit = true;
992         }
993     }
994   else
995     {
996       tux->action(delta);
997 //      tux->set_direction(input_direction);
998     }
999   
1000   Menu* menu = Menu::current();
1001   if(menu)
1002     {
1003       menu->action();
1004
1005       if(menu == worldmap_menu)
1006         {
1007           switch (worldmap_menu->check())
1008             {
1009             case MNID_RETURNWORLDMAP: // Return to game
1010               break;
1011             case MNID_QUITWORLDMAP: // Quit Worldmap
1012               quit = true;
1013               break;
1014             }
1015         }
1016       else if(menu == options_menu)
1017         {
1018           process_options_menu();
1019         }
1020     }
1021 }
1022
1023 Tile*
1024 WorldMap::at(Vector p)
1025 {
1026   assert(p.x >= 0 
1027          && p.x < width
1028          && p.y >= 0
1029          && p.y < height);
1030
1031   int x = int(p.x);
1032   int y = int(p.y);
1033   return tile_manager->get(tilemap[width * y + x]);
1034 }
1035
1036 WorldMap::Level*
1037 WorldMap::at_level()
1038 {
1039   for(Levels::iterator i = levels.begin(); i != levels.end(); ++i)
1040     {
1041       if (i->pos == tux->get_tile_pos())
1042         return &*i; 
1043     }
1044
1045   return 0;
1046 }
1047
1048 WorldMap::SpecialTile*
1049 WorldMap::at_special_tile()
1050 {
1051   for(SpecialTiles::iterator i = special_tiles.begin(); i != special_tiles.end(); ++i)
1052     {
1053       if (i->pos == tux->get_tile_pos())
1054         return &*i; 
1055     }
1056
1057   return 0;
1058 }
1059
1060
1061 void
1062 WorldMap::draw(DrawingContext& context, const Vector& offset)
1063 {
1064   for(int y = 0; y < height; ++y)
1065     for(int x = 0; x < width; ++x)
1066       {
1067         Tile* tile = at(Vector(x, y));
1068         tile->draw(context, Vector(x*32 + offset.x, y*32 + offset.y));
1069       }
1070
1071   for(Levels::iterator i = levels.begin(); i != levels.end(); ++i)
1072     {
1073       if (i->solved)
1074         context.draw_surface(leveldot_green,
1075             Vector(i->pos.x*32 + offset.x, i->pos.y*32 + offset.y), LAYER_TILES+1);
1076       else
1077         context.draw_surface(leveldot_red,
1078             Vector(i->pos.x*32 + offset.x, i->pos.y*32 + offset.y), LAYER_TILES+1);
1079     }
1080
1081   for(SpecialTiles::iterator i = special_tiles.begin(); i != special_tiles.end(); ++i)
1082     {
1083       if(i->invisible)
1084         continue;
1085
1086       if (i->teleport_dest != Vector(-1, -1))
1087         context.draw_surface(teleporterdot,
1088                 Vector(i->pos.x*32 + offset.x, i->pos.y*32 + offset.y), LAYER_TILES+1);
1089
1090       else if (!i->map_message.empty() && !i->passive_message)
1091         context.draw_surface(messagedot,
1092                 Vector(i->pos.x*32 + offset.x, i->pos.y*32 + offset.y), LAYER_TILES+1);
1093     }
1094
1095   tux->draw(context, offset);
1096   draw_status(context);
1097 }
1098
1099 void
1100 WorldMap::draw_status(DrawingContext& context)
1101 {
1102   char str[80];
1103   sprintf(str, " %d", total_stats.get_points(SCORE_STAT));
1104
1105   context.draw_text(white_text, _("SCORE"), Vector(0, 0), LEFT_ALLIGN, LAYER_FOREGROUND1);
1106   context.draw_text(gold_text, str, Vector(96, 0), LEFT_ALLIGN, LAYER_FOREGROUND1);
1107
1108   sprintf(str, "%d", player_status.distros);
1109   context.draw_text(white_text, _("COINS"), Vector(screen->w/2 - 16*5, 0),
1110       LEFT_ALLIGN, LAYER_FOREGROUND1);
1111   context.draw_text(gold_text, str, Vector(screen->w/2 + (16*5)/2, 0),
1112         LEFT_ALLIGN, LAYER_FOREGROUND1);
1113
1114   if (player_status.lives >= 5)
1115     {
1116       sprintf(str, "%dx", player_status.lives);
1117       context.draw_text(gold_text, str, 
1118           Vector(screen->w - gold_text->get_text_width(str) - tux_life->w, 0),
1119           LEFT_ALLIGN, LAYER_FOREGROUND1);
1120       context.draw_surface(tux_life, Vector(screen->w -
1121             gold_text->get_text_width("9"), 0), LEFT_ALLIGN, LAYER_FOREGROUND1);
1122     }
1123   else
1124     {
1125       for(int i= 0; i < player_status.lives; ++i)
1126         context.draw_surface(tux_life,
1127             Vector(screen->w - tux_life->w*4 + (tux_life->w*i), 0),
1128             LAYER_FOREGROUND1);
1129     }
1130   context.draw_text(white_text, _("LIVES"),
1131       Vector(screen->w - white_text->get_text_width(_("LIVES")) - white_text->get_text_width("   99"), 0),
1132       LEFT_ALLIGN, LAYER_FOREGROUND1);
1133
1134   if (!tux->is_moving())
1135     {
1136       for(Levels::iterator i = levels.begin(); i != levels.end(); ++i)
1137         {
1138           if (i->pos == tux->get_tile_pos())
1139             {
1140               if(i->title == "")
1141                 get_level_title(*i);
1142
1143               context.draw_text(white_text, i->title, 
1144                   Vector(screen->w/2,
1145                          screen->h - white_text->get_height() - 30),
1146                   CENTER_ALLIGN, LAYER_FOREGROUND1);
1147
1148               i->statistics.draw_worldmap_info(context);
1149               break;
1150             }
1151         }
1152       for(SpecialTiles::iterator i = special_tiles.begin(); i != special_tiles.end(); ++i)
1153         {
1154           if (i->pos == tux->get_tile_pos())
1155             {
1156                /* Display an in-map message in the map, if any as been selected */
1157               if(!i->map_message.empty() && !i->passive_message)
1158                 context.draw_text(gold_text, i->map_message, 
1159                     Vector(screen->w/2,
1160                            screen->h - white_text->get_height() - 60),
1161                     CENTER_ALLIGN, LAYER_FOREGROUND1);
1162               break;
1163             }
1164         }
1165     }
1166   /* Display a passive message in the map, if needed */
1167   if(passive_message_timer.check())
1168     context.draw_text(gold_text, passive_message, 
1169             Vector(screen->w/2, screen->h - white_text->get_height() - 60),
1170             CENTER_ALLIGN, LAYER_FOREGROUND1);
1171 }
1172
1173 void
1174 WorldMap::display()
1175 {
1176   Menu::set_current(0);
1177
1178   quit = false;
1179
1180   song = SoundManager::get()->load_music(datadir +  "/music/" + music);
1181   SoundManager::get()->play_music(song);
1182
1183   FrameRate frame_rate(10);
1184   frame_rate.set_frame_limit(false);
1185
1186   frame_rate.start();
1187   frame_timer.start(.25, true);
1188
1189   DrawingContext context;
1190   while(!quit)
1191     {
1192       float delta = frame_rate.get();
1193
1194       delta *= 1.3f;
1195
1196       if (delta > 10.0f)
1197         delta = .3f;
1198         
1199       frame_rate.update();
1200
1201       Vector tux_pos = tux->get_pos();
1202       if (1)
1203         {
1204           offset.x = -tux_pos.x + screen->w/2;
1205           offset.y = -tux_pos.y + screen->h/2;
1206
1207           if (offset.x > 0) offset.x = 0;
1208           if (offset.y > 0) offset.y = 0;
1209
1210           if (offset.x < screen->w - width*32) offset.x = screen->w - width*32;
1211           if (offset.y < screen->h - height*32) offset.y = screen->h - height*32;
1212         } 
1213
1214       draw(context, offset);
1215       get_input();
1216       update(delta);
1217       
1218       if(Menu::current())
1219         {
1220           Menu::current()->draw(context);
1221           mouse_cursor->draw(context);
1222         }
1223
1224       context.do_drawing();
1225
1226       SDL_Delay(20);
1227     }
1228 }
1229
1230 void
1231 WorldMap::savegame(const std::string& filename)
1232 {
1233   if(filename == "")
1234     return;
1235
1236   std::cout << "savegame: " << filename << std::endl;
1237
1238    std::ofstream file(filename.c_str(), std::ios::out);
1239    LispWriter* writer = new LispWriter(file);
1240
1241   int nb_solved_levels = 0, total_levels = 0;
1242   for(Levels::iterator i = levels.begin(); i != levels.end(); ++i)
1243     {
1244       ++total_levels;
1245       if (i->solved)
1246         ++nb_solved_levels;
1247     }
1248   char nb_solved_levels_str[80], total_levels_str[80];
1249   sprintf(nb_solved_levels_str, "%d", nb_solved_levels);
1250   sprintf(total_levels_str, "%d", total_levels);
1251
1252   writer->write_comment("Worldmap save file");
1253
1254   writer->start_list("supertux-savegame");
1255
1256   writer->write_int("version", 1);
1257   writer->write_string("title", std::string(name + " - " + nb_solved_levels_str + "/" + total_levels_str));
1258   writer->write_string("map", map_filename);
1259   writer->write_int("lives", player_status.lives);
1260   writer->write_int("distros", player_status.lives);
1261   writer->write_int("max-score-multiplier", player_status.max_score_multiplier);
1262
1263   writer->start_list("tux");
1264
1265   writer->write_float("x", tux->get_tile_pos().x);
1266   writer->write_float("y", tux->get_tile_pos().y);
1267   writer->write_string("back", direction_to_string(tux->back_direction));
1268   writer->write_string("bonus", bonus_to_string(player_status.bonus));
1269
1270   writer->end_list("tux");
1271
1272   writer->start_list("levels");
1273
1274   for(Levels::iterator i = levels.begin(); i != levels.end(); ++i)
1275     {
1276       if (i->solved)
1277         {
1278         writer->start_list("level");
1279
1280         writer->write_string("name", i->name);
1281         writer->write_bool("solved", true);
1282         i->statistics.write(*writer);
1283
1284         writer->end_list("level");
1285         }
1286     }  
1287
1288   writer->end_list("levels");
1289
1290   writer->end_list("supertux-savegame");
1291 }
1292
1293 void
1294 WorldMap::loadgame(const std::string& filename)
1295 {
1296   std::cout << "loadgame: " << filename << std::endl;
1297   savegame_file = filename;
1298
1299   if (access(filename.c_str(), F_OK) != 0)
1300     {
1301     load_map();
1302
1303     player_status.reset();
1304
1305     return;
1306     }
1307   
1308   lisp_object_t* savegame = lisp_read_from_file(filename);
1309   if (!savegame)
1310     {
1311       std::cout << "WorldMap:loadgame: File not found: " << filename << std::endl;
1312       load_map();
1313       return;
1314     }
1315
1316   lisp_object_t* cur = savegame;
1317
1318   if (strcmp(lisp_symbol(lisp_car(cur)), "supertux-savegame") != 0)
1319     {
1320     load_map();
1321     return;
1322     }
1323
1324   cur = lisp_cdr(cur);
1325   LispReader reader(cur);
1326
1327   /* Get the Map filename and then load it before setting level settings */
1328   std::string cur_map_filename = map_filename;
1329   reader.read_string("map", map_filename);
1330 //  if(cur_map_filename != map_filename)
1331     load_map(); 
1332
1333   reader.read_int("lives", player_status.lives);
1334   reader.read_int("distros", player_status.distros);
1335   reader.read_int("max-score-multiplier", player_status.max_score_multiplier);
1336
1337   if (player_status.lives < 0)
1338     player_status.lives = START_LIVES;
1339
1340   lisp_object_t* tux_cur = 0;
1341   if (reader.read_lisp("tux", tux_cur))
1342     {
1343       Vector p;
1344       std::string back_str = "none";
1345       std::string bonus_str = "none";
1346
1347       LispReader tux_reader(tux_cur);
1348       tux_reader.read_float("x", p.x);
1349       tux_reader.read_float("y", p.y);
1350       tux_reader.read_string("back", back_str);
1351       tux_reader.read_string("bonus", bonus_str);
1352       
1353       player_status.bonus = string_to_bonus(bonus_str);
1354       tux->back_direction = string_to_direction(back_str);      
1355       tux->set_tile_pos(p);
1356     }
1357
1358   lisp_object_t* level_cur = 0;
1359   if (reader.read_lisp("levels", level_cur))
1360     {
1361       while(level_cur)
1362         {
1363           lisp_object_t* sym  = lisp_car(lisp_car(level_cur));
1364           lisp_object_t* data = lisp_cdr(lisp_car(level_cur));
1365
1366           if (strcmp(lisp_symbol(sym), "level") == 0)
1367             {
1368               std::string name;
1369               bool solved = false;
1370
1371               LispReader level_reader(data);
1372               level_reader.read_string("name", name);
1373               level_reader.read_bool("solved", solved);
1374
1375               for(Levels::iterator i = levels.begin(); i != levels.end(); ++i)
1376                 {
1377                   if (name == i->name)
1378                     {
1379                     i->solved = solved;
1380                     i->statistics.parse(level_reader);
1381                     break;
1382                     }
1383                 }
1384             }
1385
1386           level_cur = lisp_cdr(level_cur);
1387         }
1388     }
1389  
1390   lisp_free(savegame);
1391
1392   calculate_total_stats();
1393 }
1394
1395 void
1396 WorldMap::loadmap(const std::string& filename)
1397 {
1398   savegame_file = "";
1399   map_filename = filename;
1400   load_map();
1401 }
1402
1403 } // namespace WorldMapNS
1404
1405 /* Local Variables: */
1406 /* mode:c++ */
1407 /* End: */
1408