This patch was send to the mailing list by Ryan (aka sik0fewl).
[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 <iostream>
21 #include <vector>
22 #include <assert.h>
23 #include "globals.h"
24 #include "texture.h"
25 #include "screen.h"
26 #include "lispreader.h"
27 #include "gameloop.h"
28 #include "setup.h"
29 #include "worldmap.h"
30
31 namespace WorldMapNS {
32
33 TileManager* TileManager::instance_  = 0;
34
35 TileManager::TileManager()
36 {
37   std::string stwt_filename = datadir +  "images/worldmap/antarctica.stwt";
38   lisp_object_t* root_obj = lisp_read_from_file(stwt_filename);
39  
40   if (!root_obj)
41     st_abort("Couldn't load file", stwt_filename);
42
43   if (strcmp(lisp_symbol(lisp_car(root_obj)), "supertux-worldmap-tiles") == 0)
44     {
45       lisp_object_t* cur = lisp_cdr(root_obj);
46
47       while(!lisp_nil_p(cur))
48         {
49           lisp_object_t* element = lisp_car(cur);
50
51           if (strcmp(lisp_symbol(lisp_car(element)), "tile") == 0)
52             {
53               int id = 0;
54               std::string filename = "<invalid>";
55
56               Tile* tile = new Tile;             
57               tile->north = true;
58               tile->east  = true;
59               tile->south = true;
60               tile->west  = true;
61               tile->stop  = true;
62   
63               LispReader reader(lisp_cdr(element));
64               reader.read_int("id",  &id);
65               reader.read_bool("north", &tile->north);
66               reader.read_bool("south", &tile->south);
67               reader.read_bool("west",  &tile->west);
68               reader.read_bool("east",  &tile->east);
69               reader.read_bool("stop",  &tile->stop);
70               reader.read_string("image",  &filename);
71
72               tile->sprite = new Surface(
73                            datadir +  "/images/worldmap/" + filename, 
74                            USE_ALPHA);
75
76               if (id >= int(tiles.size()))
77                 tiles.resize(id+1);
78
79               tiles[id] = tile;
80             }
81           else
82             {
83               puts("Unhandled symbol");
84             }
85
86           cur = lisp_cdr(cur);
87         }
88     }
89   else
90     {
91       assert(0);
92     }
93 }
94
95 Tile*
96 TileManager::get(int i)
97 {
98   assert(i >=0 && i < int(tiles.size()));
99   return tiles[i];
100 }
101
102 Tux::Tux(WorldMap* worldmap_)
103   : worldmap(worldmap_)
104 {
105   sprite = new Surface(datadir +  "/images/worldmap/tux.png", USE_ALPHA);
106   offset = 0;
107   moving = false;
108   tile_pos.x = 5;
109   tile_pos.y = 5;
110   direction = NONE;
111   input_direction = NONE;
112 }
113
114 void
115 Tux::draw(const Point& offset)
116 {
117   Point pos = get_pos();
118   sprite->draw(pos.x + offset.x, 
119                pos.y + offset.y);
120 }
121
122
123 Point
124 Tux::get_pos()
125 {
126   float x = tile_pos.x * 32;
127   float y = tile_pos.y * 32;
128
129   switch(direction)
130     {
131     case WEST:
132       x -= offset - 32;
133       break;
134     case EAST:
135       x += offset - 32;
136       break;
137     case NORTH:
138       y -= offset - 32;
139       break;
140     case SOUTH:
141       y += offset - 32;
142       break;
143     case NONE:
144       break;
145     }
146   
147   return Point((int)x, (int)y); 
148 }
149
150 void
151 Tux::stop()
152 {
153   offset = 0;
154   direction = NONE;
155   moving = false;
156 }
157
158 void
159 Tux::update(float delta)
160 {
161   if (!moving)
162     {
163       if (input_direction != NONE)
164         { // We got a new direction, so lets start walking when possible
165           Point next_tile;
166           if (worldmap->path_ok(input_direction, tile_pos, &next_tile))
167             {
168               tile_pos = next_tile;
169               moving = true;
170               direction = input_direction;
171             }
172         }
173     }
174   else
175     {
176       // Let tux walk a few pixels (20 pixel/sec)
177       offset += 20.0f * delta;
178
179       if (offset > 32)
180         { // We reached the next tile, so we check what to do now
181           offset -= 32;
182
183           if (worldmap->at(tile_pos)->stop)
184             {
185               stop();
186             }
187           else
188             {
189               Point next_tile;
190               if (worldmap->path_ok(direction, tile_pos, &next_tile))
191                 {
192                   tile_pos = next_tile;
193                 }
194               else
195                 {
196                   puts("Tilemap data is buggy");
197                   stop();
198                 }
199             }
200         }
201     }
202 }
203
204 WorldMap::WorldMap()
205 {
206   tux = new Tux(this);
207
208   quit = false;
209   width  = 20;
210   height = 15;
211
212   level_sprite = new Surface(datadir +  "/images/worldmap/levelmarker.png", USE_ALPHA);
213   leveldot_green = new Surface(datadir +  "/images/worldmap/leveldot_green.png", USE_ALPHA);
214   leveldot_red = new Surface(datadir +  "/images/worldmap/leveldot_red.png", USE_ALPHA);
215
216   input_direction = NONE;
217   enter_level = false;
218
219   name = "<no name>";
220   music = "SALCON.MOD";
221   song = 0;
222
223   load_map();
224 }
225
226 WorldMap::~WorldMap()
227 {
228   delete tux;
229 }
230
231 void
232 WorldMap::load_map()
233 {
234   std::string filename = datadir +  "levels/default/worldmap.stwm";
235   
236   lisp_object_t* root_obj = lisp_read_from_file(filename);
237   if (!root_obj)
238     st_abort("Couldn't load file", filename);
239   
240   if (strcmp(lisp_symbol(lisp_car(root_obj)), "supertux-worldmap") == 0)
241     {
242       lisp_object_t* cur = lisp_cdr(root_obj);
243
244       while(!lisp_nil_p(cur))
245         {
246           lisp_object_t* element = lisp_car(cur);
247
248           if (strcmp(lisp_symbol(lisp_car(element)), "tilemap") == 0)
249             {
250               LispReader reader(lisp_cdr(element));
251               reader.read_int("width",  &width);
252               reader.read_int("height", &height);
253               reader.read_int_vector("data", &tilemap);
254             }
255           else if (strcmp(lisp_symbol(lisp_car(element)), "properties") == 0)
256             {
257               LispReader reader(lisp_cdr(element));
258               reader.read_string("name",  &name);
259               reader.read_string("music", &music);
260             }
261           else if (strcmp(lisp_symbol(lisp_car(element)), "levels") == 0)
262             {
263               lisp_object_t* cur = lisp_cdr(element);
264               
265               while(!lisp_nil_p(cur))
266                 {
267                   lisp_object_t* element = lisp_car(cur);
268                   
269                   if (strcmp(lisp_symbol(lisp_car(element)), "level") == 0)
270                     {
271                       Level level;
272                       LispReader reader(lisp_cdr(element));
273                       reader.read_string("name",  &level.name);
274                       reader.read_int("x", &level.x);
275                       reader.read_int("y", &level.y);
276                       levels.push_back(level);
277                     }
278                   
279                   cur = lisp_cdr(cur);      
280                 }
281             }
282           else
283             {
284               
285             }
286           
287           cur = lisp_cdr(cur);
288         }
289     }
290 }
291
292 void
293 WorldMap::get_input()
294 {
295   enter_level = false;
296   input_direction = NONE;
297    
298   SDL_Event event;
299   while (SDL_PollEvent(&event))
300     {
301       if(show_menu)
302         {
303           current_menu->event(event);
304         }
305       else
306         {
307           switch(event.type)
308             {
309             case SDL_QUIT:
310               quit = true;
311               break;
312           
313             case SDL_KEYDOWN:
314               switch(event.key.keysym.sym)
315                 {
316                 case SDLK_ESCAPE:
317                   Menu::set_current(worldmap_menu);
318                   show_menu = !show_menu;
319                   break;
320                 case SDLK_LCTRL:
321                 case SDLK_RETURN:
322                   enter_level = true;
323                   break;
324                 default:
325                   break;
326                 }
327               break;
328           
329             case SDL_JOYAXISMOTION:
330               switch(event.jaxis.axis)
331                 {
332                 case JOY_X:
333                   if (event.jaxis.value < -JOYSTICK_DEAD_ZONE)
334                     input_direction = WEST;
335                   else if (event.jaxis.value > JOYSTICK_DEAD_ZONE)
336                     input_direction = EAST;
337                   break;
338                 case JOY_Y:
339                   if (event.jaxis.value > JOYSTICK_DEAD_ZONE)
340                     input_direction = SOUTH;
341                   else if (event.jaxis.value < -JOYSTICK_DEAD_ZONE)
342                     input_direction = NORTH;
343                   break;
344                 }
345               break;
346
347             case SDL_JOYBUTTONDOWN:
348               if (event.jbutton.button == JOY_B)
349                 enter_level = true;
350               break;
351
352             default:
353               break;
354             }
355         }
356     }
357
358   if (!show_menu)
359     {
360       Uint8 *keystate = SDL_GetKeyState(NULL);
361   
362       if (keystate[SDLK_LEFT])
363         input_direction = WEST;
364       else if (keystate[SDLK_RIGHT])
365         input_direction = EAST;
366       else if (keystate[SDLK_UP])
367         input_direction = NORTH;
368       else if (keystate[SDLK_DOWN])
369         input_direction = SOUTH;
370     }
371 }
372
373 Point
374 WorldMap::get_next_tile(Point pos, Direction direction)
375 {
376   switch(direction)
377     {
378     case WEST:
379       pos.x -= 1;
380       break;
381     case EAST:
382       pos.x += 1;
383       break;
384     case NORTH:
385       pos.y -= 1;
386       break;
387     case SOUTH:
388       pos.y += 1;
389       break;
390     case NONE:
391       break;
392     }
393   return pos;
394 }
395
396 bool
397 WorldMap::path_ok(Direction direction, Point old_pos, Point* new_pos)
398 {
399   *new_pos = get_next_tile(old_pos, direction);
400
401   if (!(new_pos->x >= 0 && new_pos->x < width
402         && new_pos->y >= 0 && new_pos->y < height))
403     { // New position is outsite the tilemap
404       return false;
405     }
406   else
407     { // Check if we the tile allows us to go to new_pos
408       switch(direction)
409         {
410         case WEST:
411           return (at(old_pos)->west && at(*new_pos)->east);
412
413         case EAST:
414           return (at(old_pos)->east && at(*new_pos)->west);
415
416         case NORTH:
417           return (at(old_pos)->north && at(*new_pos)->south);
418
419         case SOUTH:
420           return (at(old_pos)->south && at(*new_pos)->north);
421
422         case NONE:
423           assert(!"path_ok() can't work if direction is NONE");
424         }
425       return false;
426     }
427 }
428
429 void
430 WorldMap::update()
431 {
432   if (enter_level && !tux->is_moving())
433     {
434       for(Levels::iterator i = levels.begin(); i != levels.end(); ++i)
435         {
436           if (i->x == tux->get_tile_pos().x && 
437               i->y == tux->get_tile_pos().y)
438             {
439               std::cout << "Enter the current level: " << i->name << std::endl;;
440               halt_music();
441               GameSession session(datadir +  "levels/default/" + i->name,
442                                   1, ST_GL_LOAD_LEVEL_FILE);
443               session.run();
444               play_music(song, 1);
445               return;
446             }
447         }
448       std::cout << "Nothing to enter at: "
449                 << tux->get_tile_pos().x << ", " << tux->get_tile_pos().y << std::endl;
450     }
451   else
452     {
453       tux->set_direction(input_direction);
454       tux->update(0.33f);
455     }
456   
457   if(show_menu)
458     {
459       if(current_menu == worldmap_menu)
460         {
461           switch (worldmap_menu->check())
462             {
463             case 2: // Return to game
464               menu_reset();
465               break;
466             case 5: // Quit Worldmap
467               quit = true;
468               break;
469             }
470         }
471     }
472 }
473
474 Tile*
475 WorldMap::at(Point p)
476 {
477   assert(p.x >= 0 
478          && p.x < width
479          && p.y >= 0
480          && p.y < height);
481   return TileManager::instance()->get(tilemap[width * p.y + p.x]);
482 }
483
484 void
485 WorldMap::draw(const Point& offset)
486 {
487   for(int y = 0; y < height; ++y)
488     for(int x = 0; x < width; ++x)
489       {
490         Tile* tile = at(Point(x, y));
491         tile->sprite->draw(x*32 + offset.x,
492                            y*32 + offset.y);
493       }
494   
495   for(Levels::iterator i = levels.begin(); i != levels.end(); ++i)
496     {
497       leveldot_green->draw(i->x*32 + offset.x, 
498                            i->y*32 + offset.y);
499     }
500
501   tux->draw(offset);
502 }
503
504 void
505 WorldMap::display()
506 {
507   quit = false;
508
509   song = load_song(datadir +  "/music/" + music);
510   play_music(song, 1);
511
512   while(!quit) {
513     Point tux_pos = tux->get_pos();
514     if (1)
515       {
516         offset.x = -tux_pos.x + screen->w/2;
517         offset.y = -tux_pos.y + screen->h/2;
518
519         if (offset.x > 0) offset.x = 0;
520         if (offset.y > 0) offset.y = 0;
521
522         if (offset.x < screen->w - width*32) offset.x = screen->w - width*32;
523         if (offset.y < screen->h - height*32) offset.y = screen->h - height*32;
524       } 
525
526     draw(offset);
527     get_input();
528     update();
529
530     menu_process_current();
531     flipscreen();
532
533     SDL_Delay(20);
534   }
535
536   free_music(song);
537 }
538
539 } // namespace WorldMapNS
540
541 void worldmap_run()
542 {
543   WorldMapNS::WorldMap worldmap;
544   
545   worldmap.display();
546 }
547
548 /* EOF */