initial commit, lordsawar source, slightly modified
[lordsawar] / src / bigmap.cpp
1 // Copyright (C) 2003 Michael Bartl
2 // Copyright (C) 2003, 2004, 2005, 2006, 2007 Ulf Lorenz
3 // Copyright (C) 2004, 2005 Bryan Duff
4 // Copyright (C) 2004, 2005, 2006 Andrea Paternesi
5 // Copyright (C) 2006, 2007, 2008, 2009 Ben Asselstine
6 // Copyright (C) 2007 Ole Laursen
7 //
8 //  This program is free software; you can redistribute it and/or modify
9 //  it under the terms of the GNU General Public License as published by
10 //  the Free Software Foundation; either version 3 of the License, or
11 //  (at your option) any later version.
12 //
13 //  This program is distributed in the hope that it will be useful,
14 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 //  GNU Library General Public License for more details.
17 //
18 //  You should have received a copy of the GNU General Public License
19 //  along with this program; if not, write to the Free Software
20 //  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 
21 //  02110-1301, USA.
22
23 #include <config.h>
24
25 #include <assert.h>
26 #include <stdlib.h>
27
28 #include "bigmap.h"
29
30 #include "army.h"
31 #include "path.h"
32 #include "stacklist.h"
33 #include "stack.h"
34 #include "citylist.h"
35 #include "city.h"
36 #include "ruinlist.h"
37 #include "ruin.h"
38 #include "signpostlist.h"
39 #include "signpost.h"
40 #include "templelist.h"
41 #include "temple.h"
42 #include "armysetlist.h"
43 #include "portlist.h"
44 #include "port.h"
45 #include "bridgelist.h"
46 #include "bridge.h"
47 #include "roadlist.h"
48 #include "road.h"
49 #include "playerlist.h"
50 #include "File.h"
51 #include "GameMap.h"
52 #include "Configuration.h"
53 #include "GraphicsCache.h"
54 #include "MapRenderer.h"
55 #include "FogMap.h"
56 #include "MapBackpack.h"
57 #include "GameScenarioOptions.h"
58
59 #include <iostream>
60 using namespace std;
61 //#define debug(x) {cerr<<__FILE__<<": "<<__LINE__<<": "<<x<<endl<<flush;}
62 #define debug(x)
63
64 BigMap::BigMap()
65     : d_renderer(0), buffer(0), magnified_buffer(0), magnification_factor(1.0)
66 {
67     // note: we are not fully initialized before set_view is called
68     view.x = view.y = 0;
69     view.w = 0;
70     view.h = 0;
71
72     d_grid_toggled = false;
73
74     blank_screen = false;
75     image = Gtk::Allocation(0, 0, 320, 200);
76 }
77
78 BigMap::~BigMap()
79 {
80     if (buffer == true)
81       buffer.clear();
82
83     if (magnified_buffer == true)
84       magnified_buffer.clear();
85
86     delete d_renderer;
87 }
88
89 void BigMap::set_view(Rectangle new_view)
90 {
91     int tilesize = GameMap::getInstance()->getTileset()->getTileSize();
92     
93     int width = 0;
94     int height = 0;
95     if (outgoing == true)
96       outgoing->get_size(width, height);
97     if (view.dim == new_view.dim && buffer && image.get_width() == width && image.get_height() == height)
98     {
99         // someone wants us to move the view, not resize it, no need to
100         // construct new surfaces and all that stuff
101         //
102         // fixme: if we're moving the view, maybe there's some pixmap in common
103         // between this view and the new view.  why render?
104
105         view = new_view;
106         Vector<int> new_view_pos = get_view_pos_from_view();
107
108         if (view_pos != new_view_pos)
109         {
110             view_pos = new_view_pos;
111             draw(Playerlist::getViewingplayer());
112         }
113         
114         return;
115     }
116
117     view = new_view;
118     view_pos = get_view_pos_from_view();
119     
120     // now create a buffer surface which is two maptiles wider and
121     // higher than the screen you actually see. That is how smooth scrolling
122     // becomes comparatively easy. You just blit from the extended screen to
123     // the screen with some offset.
124     // this represents a 1 tile border around the outside of the picture.
125     // it gets rid of the black border.
126
127     if (buffer == true)
128       buffer.clear();
129     
130     buffer_view.dim = view.dim + Vector<int>(2, 2);
131
132     buffer = Gdk::Pixmap::create (Glib::RefPtr<Gdk::Drawable>(0), buffer_view.w * tilesize, buffer_view.h * tilesize, 24);
133     buffer_gc = Gdk::GC::create(buffer);
134
135     //now create the part that will go out to the gtk::image
136     if (outgoing == true)
137       outgoing.clear();
138     outgoing = Gdk::Pixmap::create(Glib::RefPtr<Gdk::Drawable>(buffer), image.get_width(), image.get_height(), 24);
139
140
141     if (d_renderer)
142         delete d_renderer;
143     // now set the MapRenderer so that it draws on the buffer
144     d_renderer = new MapRenderer(buffer);
145 }
146
147 void BigMap::clip_viewable_buffer(Glib::RefPtr<Gdk::Pixmap> pixmap, Glib::RefPtr<Gdk::GC> gc, Vector<int> pos, Glib::RefPtr<Gdk::Pixmap> out)
148 {
149     //Glib::RefPtr<Gdk::Pixmap> outgoing = Gdk::Pixmap::create(Glib::RefPtr<Gdk::Drawable>(pixmap), image.get_width(), image.get_height(), 24) ;
150     int width = 0;
151     int height = 0;
152     pixmap->get_size(width,height);
153     out->draw_drawable(gc, pixmap, pos.x, pos.y, 0, 0, image.get_width(), image.get_height());
154     return;
155 }
156
157 void BigMap::draw(Player *player, bool redraw_buffer)
158 {
159     // no size and buffer yet, return
160     if (!buffer)
161         return;
162     Playerlist::getInstance()->setViewingplayer(player);
163
164     int tilesize = GameMap::getInstance()->getTileset()->getTileSize();
165
166     // align the buffer view
167     Vector<int> new_buffer_view = clip(
168         Vector<int>(0, 0),
169         view.pos - Vector<int>(1, 1),
170         GameMap::get_dim() - buffer_view.dim + Vector<int>(1, 1));
171     buffer_view.pos = new_buffer_view;
172
173     // redraw the buffer
174     if (redraw_buffer)
175         draw_buffer();
176
177     // blit the visible part of buffer to the screen
178     Vector<int> p = view_pos - (buffer_view.pos * tilesize * magnification_factor);
179     //Glib::RefPtr<Gdk::Pixmap> outgoing;
180     if (magnification_factor != 1.0)
181       {
182         if (magnified_buffer == true)
183           magnified_buffer.clear();
184         magnified_buffer = magnify(buffer);
185         clip_viewable_buffer(magnified_buffer, buffer_gc, p, outgoing);
186       }
187     else
188       {
189         clip_viewable_buffer(buffer, buffer_gc, p, outgoing);
190       }
191
192     if (blank_screen)
193       {
194         Gdk::Color fog_color = Gdk::Color();
195         fog_color.set_rgb_p(0.0,0.0,0.0);
196         int width = 0;
197         int height = 0;
198         outgoing->get_size(width, height);
199         Glib::RefPtr<Gdk::GC> outgoing_gc = Gdk::GC::create(outgoing);
200         outgoing_gc->set_rgb_fg_color(fog_color);
201         outgoing->draw_rectangle(outgoing_gc, true, 0, 0, width, height);
202       }
203     map_changed.emit(outgoing);
204 }
205
206 void BigMap::screen_size_changed(Gtk::Allocation box)
207 {
208     int ts = GameMap::getInstance()->getTileset()->getTileSize();
209
210     Rectangle new_view = view;
211     
212     new_view.w = box.get_width() / (ts * magnification_factor) + 0;
213     new_view.h = box.get_height() / (ts * magnification_factor) + 0;
214
215     if (new_view.w <= GameMap::getWidth() && new_view.h <= GameMap::getHeight()
216         && new_view.w >= 0 && new_view.h >= 0)
217       {
218         new_view.pos = clip(Vector<int>(0,0), new_view.pos,
219                             GameMap::get_dim() - new_view.dim);
220
221         if (image.get_width() != box.get_width() ||
222             image.get_height() != box.get_height() ||
223             new_view != view)
224           {
225             image = box;
226             set_view(new_view);
227             view_changed.emit(view);
228           }
229       }
230     image = box;
231 }
232
233 Vector<int> BigMap::get_view_pos_from_view()
234 {
235     Vector<int> screen_dim(image.get_width(), image.get_height());
236     int ts = GameMap::getInstance()->getTileset()->getTileSize();
237
238     // clip to make sure we don't see a black border at the bottom and right
239     return clip(Vector<int>(0, 0), view.pos * ts * magnification_factor,
240                 GameMap::get_dim() * ts * magnification_factor - screen_dim);
241 }
242
243 Vector<int> BigMap::tile_to_buffer_pos(Vector<int> tile)
244 {
245     int ts = GameMap::getInstance()->getTileset()->getTileSize();
246     return (tile - buffer_view.pos) * ts;
247 }
248
249 Vector<int> BigMap::mouse_pos_to_tile(Vector<int> pos)
250 {
251     int ts = GameMap::getInstance()->getTileset()->getTileSize();
252
253     return (view_pos + pos) / (ts * magnification_factor);
254 }
255
256 Vector<int> BigMap::mouse_pos_to_tile_offset(Vector<int> pos)
257 {
258     int ts = GameMap::getInstance()->getTileset()->getTileSize();
259
260     return (view_pos + pos) % (int)rint(ts * magnification_factor);
261 }
262
263 MapTipPosition BigMap::map_tip_position(Vector<int> tile)
264 {
265   return map_tip_position (Rectangle(tile.x, tile.y, 1, 1)); 
266 }
267
268 MapTipPosition BigMap::map_tip_position(Rectangle tile_area)
269 {
270     // convert area to pixels on the screen
271     int tilesize = GameMap::getInstance()->getTileset()->getTileSize();
272
273     Rectangle area(tile_area.pos * tilesize * magnification_factor - view_pos,
274                    tile_area.dim * tilesize * magnification_factor);
275
276     // calculate screen edge distances
277     int left, right, top, bottom;
278     
279     left = area.x;
280     right = image.get_width() - (area.x + area.w);
281     top = area.y;
282     bottom = image.get_height() - (area.y + area.h);
283
284     int const MARGIN = 2;
285     
286     // then set the position
287     MapTipPosition m;
288     if (right >= left && right >= top && right >= bottom)
289     {
290         m.pos.x = area.x + area.w + MARGIN;
291         m.pos.y = area.y;
292         m.justification = MapTipPosition::LEFT;
293     }
294     else if (left >= top && left >= bottom)
295     {
296         m.pos.x = area.x - MARGIN;
297         m.pos.y = area.y;
298         m.justification = MapTipPosition::RIGHT;
299     }
300     else if (bottom >= top)
301     {
302         m.pos.x = area.x;
303         m.pos.y = area.y + area.h + MARGIN;
304         m.justification = MapTipPosition::TOP;
305     }
306     else
307     {
308         m.pos.x = area.x;
309         m.pos.y = area.y - MARGIN;
310         m.justification = MapTipPosition::BOTTOM;
311     }
312     
313     return m;
314 }
315
316 void BigMap::blit_object(const Location &obj, Vector<int> tile, PixMask *image, Glib::RefPtr<Gdk::Pixmap> surface, Glib::RefPtr<Gdk::GC> surface_gc)
317 {
318   Vector<int> diff = tile - obj.getPos();
319   int tilesize = GameMap::getInstance()->getTileset()->getTileSize();
320   Vector<int> p = tile_to_buffer_pos(tile);
321   image->blit(diff, tilesize, surface, p);
322 }
323
324 void BigMap::draw_stack(Stack *s, Glib::RefPtr<Gdk::Pixmap> surface, Glib::RefPtr<Gdk::GC> surface_gc)
325 {
326   GameMap *gm = GameMap::getInstance();
327   GraphicsCache *gc = GraphicsCache::getInstance();
328   Vector<int> p = s->getPos();
329   Player *player = s->getOwner();
330   int army_tilesize;
331
332   // check if the object lies in the viewed part of the map
333   // otherwise we shouldn't draw it
334   if (is_inside(buffer_view, p) && !s->getDeleting())
335     {
336       Armysetlist *al = Armysetlist::getInstance();
337       army_tilesize = al->getTileSize(player->getArmyset());
338       if (s->empty())
339         {
340           std::cerr << "WARNING: empty stack found" << std::endl;
341           return;
342         }
343
344       p = tile_to_buffer_pos(p);
345
346       // draw stack
347
348       bool show_army = true;
349       if (s->hasShip())
350         {
351           gc->getShipPic(player)->blit(surface, p);
352         }
353       else
354         {
355           if (s->getFortified() == true)
356             {
357               //We don't show the active stack here.
358               if (player->getStacklist()->getActivestack() != s &&
359                   player == Playerlist::getActiveplayer())
360                 show_army = false;
361               Maptile *tile = gm->getTile(s->getPos());
362               if (tile->getBuilding() != Maptile::CITY &&
363                   tile->getBuilding() != Maptile::RUIN &&
364                   tile->getBuilding() != Maptile::TEMPLE)
365                 gc->getTowerPic(player)->blit(surface, p);
366               else
367                 show_army = true;
368             }
369
370           if (show_army == true)
371             {
372               Army *a = *s->begin();
373               gc->getArmyPic(a)->blit(surface, p);
374             }
375         }
376
377
378       if (show_army)
379         {
380           // draw flag
381           gc->getFlagPic(s)->blit(surface, p);
382         }
383     }
384 }
385
386 void BigMap::draw_buffer()
387 {
388   draw_buffer (buffer_view, buffer, buffer_gc);
389     
390   //the idea here is that we want to show what happens when an AI-owned
391   //stack moves through our area.  in lieu of that, we just block everything
392   //if we're a computer player.
393   if (Playerlist::getViewingplayer()->getType() != Player::HUMAN &&
394       GameScenarioOptions::s_hidden_map == true)
395     {
396       int width = 0;
397       int height = 0;
398       buffer->get_size(width, height);
399       Gdk::Color fog_color = Gdk::Color();
400       fog_color.set_rgb_p(0,0,0);
401       buffer_gc->set_rgb_fg_color(fog_color);
402       buffer->draw_rectangle(buffer_gc, true, 0, 0, width, height);
403     }
404   else
405     after_draw();
406
407 }
408
409 bool BigMap::saveViewAsBitmap(std::string filename)
410 {
411   int width;
412   int height;
413   buffer->get_size(width, height);
414   remove (filename.c_str());
415   Glib::RefPtr<Gdk::Pixbuf> pixbuf = Gdk::Pixbuf::create(Glib::RefPtr<Gdk::Drawable>(buffer), 0, 0, width, height);
416   pixbuf->save (filename, "png");
417   return true;
418 }
419
420 bool BigMap::saveUnderlyingMapAsBitmap(std::string filename)
421 {
422   return d_renderer->saveAsBitmap(filename);
423 }
424
425 bool BigMap::saveAsBitmap(std::string filename)
426 {
427   int tilesize = GameMap::getInstance()->getTileset()->getTileSize();
428   int width = GameMap::getWidth() * tilesize;
429   int height = GameMap::getHeight() * tilesize;
430   Glib::RefPtr<Gdk::Pixmap> surf = Gdk::Pixmap::create(Glib::RefPtr<Gdk::Drawable>(0), width, height, 24);
431   
432   bool orig_grid = d_grid_toggled;
433   d_grid_toggled = false;
434   draw_buffer(Rectangle (0, 0, GameMap::getWidth(), GameMap::getHeight()), surf,
435               Gdk::GC::create(surf));
436   d_grid_toggled = orig_grid;
437   Glib::RefPtr<Gdk::Pixbuf> pixbuf = Gdk::Pixbuf::create(Glib::RefPtr<Gdk::Drawable>(surf), 0, 0, width, height);
438   pixbuf->save (filename, "png");
439   return true;
440 }
441
442 void BigMap::draw_buffer_tile(Vector<int> tile, Glib::RefPtr<Gdk::Pixmap> surface, Glib::RefPtr<Gdk::GC> context)
443 {
444   guint32 tilesize = GameMap::getInstance()->getTileset()->getTileSize();
445   Player *viewing = Playerlist::getViewingplayer();
446   GraphicsCache *gc = GraphicsCache::getInstance();
447   GameMap *gm = GameMap::getInstance();
448   int tile_style_id = gm->getTile(tile)->getTileStyle()->getId();
449   int fog_type_id = 0;
450   fog_type_id = viewing->getFogMap()->getShadeTile(tile);
451
452   bool has_bag = false;
453   bool has_standard = false;
454   guint32 player_standard_id = 0;
455   int stack_size = -1;
456   int stack_player_id = -1;
457   int army_type_id = -1;
458   bool has_ship = false;
459   bool has_tower = false;
460   Maptile::Building building_type = gm->getTile(tile)->getBuilding();
461   Vector<int> building_tile = Vector<int>(-1,-1);
462   int building_subtype = -1;
463   int building_player_id = -1;
464
465   if (fog_type_id == FogMap::ALL)
466     {
467       //short circuit.  the tile is completely fogged.
468       PixMask *pixmask =
469         gc->getTilePic(tile_style_id, fog_type_id, has_bag, has_standard, 
470                        player_standard_id, stack_size, stack_player_id, 
471                        army_type_id, has_tower, has_ship, building_type, 
472                        building_subtype, building_tile, building_player_id, 
473                        tilesize, d_grid_toggled);
474       pixmask->blit(surface, tile_to_buffer_pos(tile));
475       return;
476     }
477   MapBackpack *backpack = GameMap::getInstance()->getTile(tile)->getBackpack();
478   if (backpack && backpack->empty() == false)
479     {
480       bool standard_planted = false;
481       Item *flag = backpack->getFirstPlantedItem();
482       if (flag)
483         standard_planted = true;
484
485       //only show one of the bag or the flag
486       if (standard_planted && flag)
487         {
488           has_standard = true;
489           player_standard_id = flag->getPlantableOwner()->getId();
490         }
491       else
492         has_bag = true;
493     }
494
495   Stack *stack = GameMap::getStack(tile);
496   if (stack)
497     {
498       if (viewing->getFogMap()->isCompletelyObscuredFogTile(tile) == false)
499         {
500           //selected stack gets drawn in gamebigmap
501           if (Playerlist::getActiveplayer()->getActivestack() != stack)
502             {
503               stack_player_id = stack->getOwner()->getId();
504               Maptile *m = gm->getTile(tile);
505               if (stack->getFortified() == true &&
506                   m->getBuilding() != Maptile::CITY &&
507                   m->getBuilding() != Maptile::RUIN &&
508                   m->getBuilding() != Maptile::TEMPLE)
509                 has_tower = true;
510               else if (stack->hasShip() == true)
511                 {
512                   has_ship = true;
513                   stack_size = stack->size();
514                 }
515               else
516                 {
517                   army_type_id = (*stack->begin())->getTypeId();
518                   stack_size = stack->size();
519                 }
520             }
521         }
522     }
523
524   if (building_type != Maptile::NONE)
525     {
526       switch (building_type)
527         {
528         case Maptile::CITY:
529             {
530               City *city = GameMap::getCity(tile);
531               building_player_id = city->getOwner()->getId();
532               building_tile = tile - city->getPos();
533               if (city->isBurnt())
534                 building_subtype = -1;
535               else
536                 building_subtype = 0;
537             }
538           break;
539         case Maptile::RUIN:
540             {
541               Ruin *ruin = GameMap::getRuin(tile);
542               if (ruin->isHidden() == true && ruin->getOwner() == viewing)
543                 {
544                   building_tile = tile - ruin->getPos();
545                   building_subtype = ruin->getType();
546                 }
547               else if (ruin->isHidden() == false)
548                 {
549                   building_tile = tile - ruin->getPos();
550                   building_subtype = ruin->getType();
551                 }
552               else
553                 building_type = Maptile::NONE;
554             }
555           break;
556         case Maptile::TEMPLE:
557             {
558               Temple *temple = GameMap::getTemple(tile);
559               building_tile = tile - temple->getPos();
560               building_subtype = temple->getType();
561             }
562           break;
563         case Maptile::SIGNPOST:
564             {
565               Signpost *signpost = GameMap::getSignpost(tile);
566               building_tile = tile - signpost->getPos();
567             }
568           break;
569         case Maptile::ROAD:
570             {
571               Road *road = GameMap::getRoad(tile);
572               building_tile = tile - road->getPos();
573               building_subtype = road->getType();
574             }
575           break;
576         case Maptile::PORT:
577             {
578               Port *port = GameMap::getPort(tile);
579               building_tile = tile - port->getPos();
580             }
581           break;
582         case Maptile::BRIDGE:
583             {
584               Bridge *bridge = GameMap::getBridge(tile);
585               building_tile = tile - bridge->getPos();
586               building_subtype = bridge->getType();
587             }
588           break;
589         case Maptile::NONE: default:
590           break;
591         }
592     }
593   PixMask *pixmask = 
594     gc->getTilePic(tile_style_id, fog_type_id, has_bag, has_standard, 
595                    player_standard_id, stack_size, stack_player_id, 
596                    army_type_id, has_tower, has_ship, building_type, 
597                    building_subtype, building_tile, building_player_id, 
598                    tilesize, d_grid_toggled);
599   pixmask->blit(surface, tile_to_buffer_pos(tile));
600 }
601
602 void BigMap::draw_buffer_tiles(Rectangle map_view, Glib::RefPtr<Gdk::Pixmap> surface, Glib::RefPtr<Gdk::GC> context)
603 {
604   for (int i = map_view.x; i < map_view.x + map_view.w; i++)
605     for (int j = map_view.y; j < map_view.y + map_view.h; j++)
606       if (i < GameMap::getWidth() && j < GameMap::getHeight())
607         draw_buffer_tile(Vector<int>(i,j), surface, context);
608 }
609
610 void BigMap::draw_buffer(Rectangle map_view, Glib::RefPtr<Gdk::Pixmap> surface, Glib::RefPtr<Gdk::GC> context)
611 {
612   draw_buffer_tiles(map_view, surface, context);
613 }
614
615 //here we want to magnify the entire buffer, not a subset
616 Glib::RefPtr<Gdk::Pixmap> BigMap::magnify(Glib::RefPtr<Gdk::Pixmap> orig)
617 {
618   //magnify the buffer into a buffer of the correct size
619
620   int width = 0;
621   int height = 0;
622   orig->get_size(width, height);
623   if (width == 0 || height == 0)
624     return orig;
625   Glib::RefPtr<Gdk::Pixmap> result = 
626     Gdk::Pixmap::create(Glib::RefPtr<Gdk::Drawable>(orig), 
627                         width * magnification_factor,
628                         height * magnification_factor);
629   Glib::RefPtr<Gdk::Pixbuf> unzoomed_buffer;
630   unzoomed_buffer = Gdk::Pixbuf::create(Glib::RefPtr<Gdk::Drawable>(orig), 0, 0, width, height);
631
632   Glib::RefPtr<Gdk::Pixbuf> zoomed_buffer;
633   zoomed_buffer = unzoomed_buffer->scale_simple(width * magnification_factor, height * magnification_factor, Gdk::INTERP_BILINEAR);
634   Glib::RefPtr<Gdk::GC> tmpgc = Gdk::GC::create(result);
635   result->draw_pixbuf(tmpgc, zoomed_buffer, 0, 0, 0, 0, zoomed_buffer->get_width(), 
636 zoomed_buffer->get_height(), Gdk::RGB_DITHER_NONE, 0, 0);
637
638   return result;
639 }
640
641 void BigMap::toggle_grid()
642 {
643   d_grid_toggled = !d_grid_toggled;
644   draw(Playerlist::getViewingplayer(), true);
645 }
646
647 void BigMap::blank(bool on)
648 {
649   blank_screen = on;
650   draw (Playerlist::getViewingplayer());
651 }