initial commit, lordsawar source, slightly modified
[lordsawar] / src / gui / fight-window.cpp
1 //  Copyright (C) 2007, 2008 Ole Laursen
2 //  Copyright (C) 2007, 2008, 2009 Ben Asselstine
3 //
4 //  This program is free software; you can redistribute it and/or modify
5 //  it under the terms of the GNU General Public License as published by
6 //  the Free Software Foundation; either version 3 of the License, or
7 //  (at your option) any later version.
8 //
9 //  This program is distributed in the hope that it will be useful,
10 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //  GNU Library General Public License for more details.
13 //
14 //  You should have received a copy of the GNU General Public License
15 //  along with this program; if not, write to the Free Software
16 //  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 
17 //  02110-1301, USA.
18
19 #include <config.h>
20
21 #include <assert.h>
22 #include <math.h>
23 #include <algorithm>
24 #include <numeric>
25 #include <vector>
26 #include <gtkmm.h>
27
28 #include "fight-window.h"
29
30 #include "glade-helpers.h"
31 #include "image-helpers.h"
32 #include "ucompose.hpp"
33 #include "timing.h"
34 #include "File.h"
35 #include "defs.h"
36 #include "player.h"
37 #include "playerlist.h"
38 #include "stack.h"
39 #include "army.h"
40 #include "GraphicsCache.h"
41 #include "Configuration.h"
42 #include "sound.h"
43
44 FightWindow::FightWindow(Fight &fight)
45 {
46     Glib::RefPtr<Gtk::Builder> xml
47         = Gtk::Builder::create_from_file(get_glade_path() + "/fight-window.ui");
48
49     xml->get_widget("window", window);
50     
51     window->signal_key_release_event().connect_notify(sigc::mem_fun(*this, &FightWindow::on_key_release_event));
52
53     Gtk::VBox *attacker_close_vbox;
54     Gtk::VBox *defender_close_vbox;
55     xml->get_widget("attacker_close_vbox", attacker_close_vbox);
56     xml->get_widget("defender_close_vbox", defender_close_vbox);
57
58     // extract attackers and defenders
59     armies_type attackers, defenders;
60
61     Fight::orderArmies (fight.getAttackers(), attackers);
62     Fight::orderArmies (fight.getDefenders(), defenders);
63
64     int rows = compute_max_rows(attackers, defenders);
65     
66     // add the armies
67     std::vector<Gtk::HBox *> close_hboxes;
68     int close;
69     std::map<guint32, guint32> initial_hps = fight.getInitialHPs();
70
71     // ... attackers
72     close = 0;
73     for (armies_type::iterator i = attackers.begin(); i != attackers.end(); ++i)
74     {
75       add_army(*i, initial_hps[(*i)->getId()],
76                close_hboxes, attacker_close_vbox, close++, rows,
77                Gtk::ALIGN_LEFT);
78     }
79
80     close_hboxes.clear();
81     
82     // ... defenders
83     close = 0;
84     for (armies_type::iterator i = defenders.begin(); i != defenders.end(); ++i)
85     {
86         add_army(*i, initial_hps[(*i)->getId()],
87                  close_hboxes, defender_close_vbox, close++, rows,
88                  Gtk::ALIGN_LEFT);
89     }
90     
91     // fill in shield pictures
92     GraphicsCache *gc = GraphicsCache::getInstance();
93     Player* p;
94
95     Gtk::Image *defender_shield_image;
96     p = defenders.front()->getOwner();
97     xml->get_widget("defender_shield_image", defender_shield_image);
98     defender_shield_image->property_pixbuf()=gc->getShieldPic(2, p)->to_pixbuf();
99
100     Gtk::Image *attacker_shield_image;
101     p = attackers.front()->getOwner();
102     xml->get_widget("attacker_shield_image", attacker_shield_image);
103     attacker_shield_image->property_pixbuf()=gc->getShieldPic(2, p)->to_pixbuf();
104   
105     actions = fight.getCourseOfEvents();
106     d_quick = false;
107
108     fast_round_speed = 
109       Configuration::s_displayFightRoundDelayFast; //milliseconds
110     normal_round_speed = 
111       Configuration::s_displayFightRoundDelaySlow; //milliseconds
112     Sound::getInstance()->disableBackground(true);
113     Sound::getInstance()->playMusic("battle", -1, true);
114 }
115
116 FightWindow::~FightWindow()
117 {
118   Sound::getInstance()->haltMusic(true);
119   Sound::getInstance()->enableBackground();
120   delete window;
121 }
122
123 void FightWindow::set_parent_window(Gtk::Window &parent)
124 {
125     window->set_transient_for(parent);
126     window->set_position(Gtk::WIN_POS_CENTER_ON_PARENT);
127 }
128
129 void FightWindow::hide()
130 {
131   window->hide();
132 }
133
134 void FightWindow::run(bool *quick)
135 {
136
137     round = 0;
138     action_iterator = actions.begin();
139     
140     Timing::instance().register_timer(
141         sigc::mem_fun(this, &FightWindow::do_round), 
142         *quick == true ? fast_round_speed : normal_round_speed);
143     
144     window->show_all();
145     main_loop = Glib::MainLoop::create();
146     main_loop->run();
147     if (quick && *quick == false)
148       *quick = d_quick;
149 }
150
151 int FightWindow::compute_max_rows(const armies_type &attackers,
152                                   const armies_type &defenders)
153 {
154     assert(!attackers.empty() || !defenders.empty());
155     
156     if (attackers.size() > defenders.size())
157       return (attackers.size() / max_cols) + 
158               (attackers.size() % max_cols == 0 ? 0 : 1);
159     else
160       return (defenders.size() / max_cols) + 
161               (defenders.size() % max_cols == 0 ? 0 : 1);
162     // Find out how to distribute the close range and long range attackers and
163     // defenders, assuming that close range units are put in front.
164
165     std::vector<int> counts(2, 0);
166
167     // count the number of melee/ranged units
168     for (armies_type::const_iterator i = attackers.begin(), end = attackers.end();
169          i != end; ++i)
170     {
171         ++counts[0];
172     }
173
174     for (armies_type::const_iterator i = defenders.begin(), end = defenders.end();
175          i != end; ++i)
176     {
177         ++counts[1];
178     }
179
180     // now find the max number of rows
181     std::vector<int> heights(counts.begin(), counts.end());
182     std::vector<int> widths(2, 0);
183     for (int i = 0; i < 2; ++i)
184         if (counts[i] > 0)
185             widths[i] = 1;
186
187     double const wanted_ratio = 4.0 / 3;
188     double old_dist = 10000;
189     int old_height = *std::max_element(heights.begin(), heights.end());
190     while (true) {
191         int width = std::accumulate(widths.begin(), widths.end(), 0);
192         int height = *std::max_element(heights.begin(), heights.end());
193
194         double ratio = double(width) / height;
195 #if 0
196         std::cerr << "WIDTH " << width
197                   << " HEIGHT " << height
198                   << " RATIO " << ratio << std::endl;
199 #endif
200
201         double dist = std::abs(wanted_ratio - ratio);
202         if (dist >= old_dist)
203             break;              // we passed the optimal point
204         else
205         {
206             old_dist = dist;
207             old_height = height;
208             
209             // compute new heights and widths
210             int max_height = height - 1;
211             if (max_height < 1)
212                 break;
213             
214             for (int i = 0; i < 2; ++i)
215                 if (heights[i] > max_height)
216                 {
217                     heights[i] = max_height;
218                     // round the division up
219                     widths[i] = (counts[i] + max_height - 1) / max_height;
220                 }
221         }
222     }
223
224 #if 0
225     std::cerr << "used height " << old_height << std::endl;
226 #endif
227     // use old because the algorithm goes one step too far before it stops
228     return old_height;
229 }
230
231 void FightWindow::add_army(Army *army, int initial_hp,
232                            std::vector<Gtk::HBox *> &hboxes,
233                            Gtk::VBox *vbox,
234                            int current_no, int max_rows,
235                            Gtk::AlignmentEnum alignment)
236 {
237     GraphicsCache *gc = GraphicsCache::getInstance();
238     // construct the army box
239     Gtk::VBox *army_box = manage(new Gtk::VBox);
240         
241     // image
242     Glib::RefPtr<Gdk::Pixbuf> pic = gc->getArmyPic(army)->to_pixbuf();
243     Gtk::Image *image = new Gtk::Image();
244     image->property_pixbuf() = pic;
245     army_box->add(*manage(image));
246     
247     // hit points graph
248     Gtk::ProgressBar *progress = manage(new Gtk::ProgressBar);
249     progress->set_fraction(double(initial_hp) / army->getStat(Army::HP));
250     progress->property_width_request() = pic->get_width();
251     progress->property_height_request() = 12;
252     army_box->pack_start(*progress, Gtk::PACK_SHRINK, 4);
253
254     // then add it to the right hbox
255     int current_row = (current_no / max_cols);
256     if (current_row >= int(hboxes.size()))
257     {
258         // add an hbox
259         Gtk::HBox *hbox = manage(new Gtk::HBox);
260         hbox->set_spacing(6);
261         hboxes.push_back(hbox);
262
263         Gtk::Alignment *a = manage(new Gtk::Alignment(alignment));
264         a->property_xscale() = 0;
265         a->add(*hbox);
266         vbox->pack_start(*a, Gtk::PACK_SHRINK);
267     }
268
269     Gtk::HBox *hbox = hboxes[current_row];
270     hbox->pack_start(*army_box, Gtk::PACK_SHRINK);
271
272     // finally add an entry for later use
273     ArmyItem item;
274     item.army = army;
275     item.hp = initial_hp;
276     item.bar = progress;
277     item.image = image;
278     item.exploding = false;
279     army_items.push_back(item);
280 }
281
282 bool FightWindow::do_round()
283 {
284   GraphicsCache *gc = GraphicsCache::getInstance();
285   Glib::RefPtr<Gdk::Pixbuf> expl = gc->getExplosionPic()->to_pixbuf();
286
287   // first we clear out any explosions
288   for (army_items_type::iterator i = army_items.begin(),
289          end = army_items.end(); i != end; ++i)
290   {
291     if (!i->exploding)
292       continue;
293     
294     Glib::RefPtr<Gdk::Pixbuf> empty_pic
295       = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, expl->get_width(), expl->get_height());
296     empty_pic->fill(0x00000000);
297     i->image->property_pixbuf() = empty_pic;
298     i->exploding = false;
299     return Timing::CONTINUE;
300   }
301
302   while (action_iterator != actions.end())
303   {
304     FightItem &f = *action_iterator;
305         
306     ++action_iterator;
307
308     for (army_items_type::iterator i = army_items.begin(),
309            end = army_items.end(); i != end; ++i)
310       if (i->army->getId() == f.id)
311       {
312         i->hp -= f.damage;
313         if (i->hp < 0)
314           i->hp = 0;
315         double fraction = double(i->hp) / i->army->getStat(Army::HP);
316         i->bar->set_fraction(fraction);
317         if (fraction == 0.0)
318         {
319           i->bar->hide();
320           i->image->property_pixbuf() = expl;
321           i->exploding = true;
322         }
323                 
324         break;
325       }
326
327     if (f.turn > round)
328     {
329       ++round;
330
331       return Timing::CONTINUE;
332     }
333   }
334
335   window->hide();
336   main_loop->quit();
337     
338   return Timing::STOP;
339 }
340 void FightWindow::on_key_release_event(GdkEventKey* event)
341 {
342     Timing::instance().register_timer(
343         sigc::mem_fun(this, &FightWindow::do_round), fast_round_speed);
344     d_quick = true;
345 }