halo.cpp

Go to the documentation of this file.
00001 /* $Id: halo.cpp 25096 2008-03-25 04:56:30Z alink $ */
00002 /*
00003    Copyright (C) 2003 - 2008 by David White <dave@whitevine.net>
00004    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License version 2
00008    or at your option any later version.
00009    This program is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY.
00011 
00012    See the COPYING file for more details.
00013 */
00014 
00015 //! @file halo.cpp 
00016 //! Maintain halo-effects for units and items.
00017 //! Examples: white mage, lighthouse.
00018 
00019 #include "global.hpp"
00020 
00021 #include "display.hpp"
00022 #include "game_preferences.hpp"
00023 #include "halo.hpp"
00024 #include "image.hpp"
00025 #include "sdl_utils.hpp"
00026 #include "util.hpp"
00027 #include "video.hpp"
00028 #include "serialization/string_utils.hpp"
00029 
00030 #include <algorithm>
00031 #include <cassert>
00032 #include <map>
00033 
00034 namespace halo
00035 {
00036 
00037 namespace {
00038 display* disp = NULL;
00039 
00040 class effect
00041 {
00042 public:
00043     effect(int xpos, int ypos, const animated<std::string>::anim_description& img,
00044             const gamemap::location& loc, ORIENTATION, bool infinite);
00045 
00046     void set_location(int x, int y);
00047 
00048     bool render();
00049     void unrender();
00050 
00051     bool expired()     const { return !images_.cycles() && images_.animation_finished(); }
00052     bool need_update() const { return images_.need_update(); }
00053     bool does_change() const { return !images_.does_not_change(); }
00054     bool on_location(const std::set<gamemap::location>& locations) const;
00055 
00056     void add_overlay_location(std::set<gamemap::location>& locations);
00057 private:
00058 
00059     const std::string& current_image() { return images_.get_current_frame(); }
00060 
00061     animated<std::string> images_;
00062 
00063     ORIENTATION orientation_;
00064 
00065     int x_, y_;
00066     surface surf_, buffer_;
00067     SDL_Rect rect_;
00068 
00069     //! The location of the center of the halo
00070     gamemap::location loc_;
00071 
00072     //! All locations over which the halo lies
00073     std::vector<gamemap::location> overlayed_hexes_;
00074 };
00075 
00076 std::map<int, effect> haloes;
00077 int halo_id = 1;
00078 
00079 //! Upon unrendering, an invalidation list is send. All haloes in 
00080 //! that area and the other invalidated haloes are stored in this set. 
00081 //! Then there'll be tested which haloes overlap 
00082 //! and they're also stored in this set.
00083 std::set<int> invalidated_haloes;
00084 
00085 //! A newly added halo will be added to this list, these haloes don't need to be 
00086 //! unrendered but do not to be rendered regardless which tiles are invalidated.
00087 //! These haloes will stay in this set until there're really rendered
00088 //! (rendering won't happen if for example the halo is offscreen).
00089 std::set<int> new_haloes;
00090 
00091 //! Upon deleting, a halo isn't deleted but added to this set, 
00092 //! upon unrendering the image is unrendered and deleted.
00093 std::set<int> deleted_haloes;
00094 
00095 //! Haloes that have an animation or expiration time 
00096 //! need to be checked every frame and are stored in this set.
00097 std::set<int> changing_haloes;
00098 
00099 effect::effect(int xpos, int ypos, const animated<std::string>::anim_description& img,
00100     const gamemap::location& loc, ORIENTATION orientation, bool infinite) :
00101         images_(img), orientation_(orientation), x_(xpos), y_(ypos),
00102         surf_(NULL), buffer_(NULL), rect_(empty_rect), loc_(loc)
00103 {
00104     assert(disp != NULL);
00105 
00106     set_location(xpos,ypos);
00107 
00108     images_.start_animation(0,infinite);
00109 
00110 }
00111 
00112 void effect::set_location(int x, int y)
00113 {
00114     const gamemap::location zero_loc(0,0);
00115     int new_x = x - disp->get_location_x(zero_loc);
00116     int new_y = y - disp->get_location_y(zero_loc);
00117     if (new_x != x_ || new_y != y_) {
00118         x_ = new_x;
00119         y_ = new_y;
00120         buffer_.assign(NULL);
00121         overlayed_hexes_.clear();
00122     }
00123 }
00124 
00125 bool effect::render()
00126 {
00127     if(disp == NULL) {
00128         return false;
00129     }
00130 
00131     if(loc_.x != -1 && loc_.y != -1) {
00132         if(disp->shrouded(loc_)) {
00133             return false;
00134         } else {
00135             // The location of a halo is an x,y value and not a map location. 
00136             // This means when a map is zoomed, the halo's won't move, 
00137             // This glitch is most visible on [item] haloes. 
00138             // This workaround always recalculates the location of the halo 
00139             // (item haloes have a location parameter to hide them under the shroud) 
00140             // and reapplies that location.
00141             // It might be optimized by storing and comparing the zoom value.
00142             set_location(
00143                 disp->get_location_x(loc_) + disp->hex_size() / 2, 
00144                 disp->get_location_y(loc_) + disp->hex_size() / 2);         
00145         }
00146     }
00147 
00148     images_.update_last_draw_time();
00149     surf_.assign(image::get_image(current_image(),image::SCALED_TO_ZOOM));
00150     if(surf_ == NULL) {
00151         return false;
00152     }
00153     if(orientation_ == HREVERSE || orientation_ == HVREVERSE) {
00154         surf_.assign(image::reverse_image(surf_));
00155     }
00156     if(orientation_ == VREVERSE || orientation_ == HVREVERSE) {
00157         surf_.assign(flop_surface(surf_));
00158     }
00159 
00160     const gamemap::location zero_loc(0,0);
00161     const int screenx = disp->get_location_x(zero_loc);
00162     const int screeny = disp->get_location_y(zero_loc);
00163 
00164     const int xpos = x_ + screenx - surf_->w/2;
00165     const int ypos = y_ + screeny - surf_->h/2;
00166 
00167     SDL_Rect rect = {xpos,ypos,surf_->w,surf_->h};
00168     rect_ = rect;
00169     SDL_Rect clip_rect = disp->map_outside_area();
00170 
00171     // If rendered the first time, need to determine the area affected. 
00172     // If a halo changes size, it is not updated.
00173     if(overlayed_hexes_.empty()) {
00174         gamemap::location topleft, bottomright;
00175         disp->get_rect_hex_bounds(rect, topleft, bottomright);
00176         for (int x = topleft.x; x <= bottomright.x; ++x) {
00177             for (int y = topleft.y; y <= bottomright.y; ++y) {
00178                 overlayed_hexes_.push_back(gamemap::location(x, y));
00179             }
00180         }
00181     }
00182 
00183     if(rects_overlap(rect,clip_rect) == false) {
00184         buffer_.assign(NULL);
00185         return false;
00186     }
00187 
00188     surface const screen = disp->get_screen_surface();
00189 
00190     const clip_rect_setter clip_setter(screen,clip_rect);
00191     if(buffer_ == NULL || buffer_->w != rect.w || buffer_->h != rect.h) {
00192         SDL_Rect rect = rect_;
00193         buffer_.assign(get_surface_portion(screen,rect));
00194     } else {
00195         SDL_Rect rect = rect_;
00196         SDL_BlitSurface(screen,&rect,buffer_,NULL);
00197     }
00198 
00199     SDL_BlitSurface(surf_,NULL,screen,&rect);
00200 
00201     update_rect(rect_);
00202 
00203     return true;
00204 }
00205 
00206 void effect::unrender()
00207 {
00208     if(buffer_ == NULL) {
00209         return;
00210     }
00211 
00212     surface const screen = disp->get_screen_surface();
00213 
00214     SDL_Rect clip_rect = disp->map_outside_area();
00215     const clip_rect_setter clip_setter(screen,clip_rect);
00216 
00217     // Due to scrolling, the location of the rendered halo 
00218     // might have changed; recalculate
00219     const gamemap::location zero_loc(0,0);
00220     const int screenx = disp->get_location_x(zero_loc);
00221     const int screeny = disp->get_location_y(zero_loc);
00222 
00223     const int xpos = x_ + screenx - surf_->w/2;
00224     const int ypos = y_ + screeny - surf_->h/2;
00225 
00226     SDL_Rect rect = {xpos,ypos,surf_->w,surf_->h};
00227     SDL_BlitSurface(buffer_,NULL,screen,&rect);
00228     update_rect(rect_);
00229 }
00230 
00231 bool effect::on_location(const std::set<gamemap::location>& locations) const
00232 {
00233     for(std::vector<gamemap::location>::const_iterator itor = overlayed_hexes_.begin();
00234             itor != overlayed_hexes_.end(); ++itor) {
00235         if(locations.find(*itor) != locations.end()) {
00236             return true;
00237         }
00238     }
00239     return false;
00240 }
00241 
00242 void effect::add_overlay_location(std::set<gamemap::location>& locations)
00243 {
00244     for(std::vector<gamemap::location>::const_iterator itor = overlayed_hexes_.begin();
00245             itor != overlayed_hexes_.end(); ++itor) {
00246 
00247         locations.insert(*itor);
00248     }
00249 }
00250 
00251 } // end anon namespace
00252 
00253 manager::manager(display& screen) : old(disp)
00254 {
00255     disp = &screen;
00256 }
00257 
00258 manager::~manager()
00259 {
00260     haloes.clear();
00261     invalidated_haloes.clear();
00262     new_haloes.clear();
00263     deleted_haloes.clear();
00264     changing_haloes.clear();
00265 
00266     disp = old;
00267 }
00268 
00269 int add(int x, int y, const std::string& image, const gamemap::location& loc,
00270         ORIENTATION orientation, bool infinite)
00271 {
00272     const int id = halo_id++;
00273     animated<std::string>::anim_description image_vector;
00274     std::vector<std::string> items = utils::split(image);
00275     std::vector<std::string>::const_iterator itor = items.begin();
00276     for(; itor != items.end(); ++itor) {
00277         const std::vector<std::string>& items = utils::split(*itor, ':');
00278         std::string str;
00279         int time;
00280 
00281         if(items.size() > 1) {
00282             str = items.front();
00283             time = atoi(items.back().c_str());
00284         } else {
00285             str = *itor;
00286             time = 100;
00287         }
00288         image_vector.push_back(animated<std::string>::frame_description(time,std::string(str)));
00289 
00290     }
00291     haloes.insert(std::pair<int,effect>(id,effect(x,y,image_vector,loc,orientation,infinite)));
00292     new_haloes.insert(id);
00293     if(haloes.find(id)->second.does_change() || !infinite) {
00294         changing_haloes.insert(id);
00295     }
00296     return id;
00297 }
00298 
00299 void set_location(int handle, int x, int y)
00300 {
00301     const std::map<int,effect>::iterator itor = haloes.find(handle);
00302     if(itor != haloes.end()) {
00303         itor->second.set_location(x,y);
00304     }
00305 }
00306 
00307 void remove(int handle)
00308 {
00309     // Silently ignore invalid haloes. 
00310     // This happens when Wesnoth is being terminated as well.
00311     if(handle == NO_HALO || haloes.find(handle) == haloes.end())  {
00312         return;
00313     }
00314 
00315     deleted_haloes.insert(handle);
00316 }
00317 
00318 void unrender(std::set<gamemap::location> invalidated_locations)
00319 {
00320     assert(invalidated_haloes.size() == 0);
00321     if(preferences::show_haloes() == false || haloes.size() == 0) {
00322         return;
00323     }
00324 
00325     // Remove expired haloes
00326     std::map<int, effect>::iterator itor = haloes.begin();
00327     for(; itor != haloes.end(); ++itor ) {
00328         if(itor->second.expired()) {
00329             deleted_haloes.insert(itor->first);
00330         }
00331     }
00332 
00333     // Add the haloes marked for deletion to the invalidation set
00334     std::set<int>::const_iterator set_itor = deleted_haloes.begin();
00335     for(;set_itor != deleted_haloes.end(); ++set_itor) {
00336         invalidated_haloes.insert(*set_itor);
00337         haloes.find(*set_itor)->second.add_overlay_location(invalidated_locations);
00338     }
00339 
00340     // Test the multi-frame haloes whether they need an update
00341     for(set_itor = changing_haloes.begin();
00342             set_itor != changing_haloes.end(); ++set_itor) {
00343         if(haloes.find(*set_itor)->second.need_update()) {
00344             invalidated_haloes.insert(*set_itor);
00345             haloes.find(*set_itor)->second.add_overlay_location(invalidated_locations);
00346         }
00347     }
00348 
00349     // Find all halo's in a the invalidated area
00350     size_t halo_count;
00351 
00352     // Repeat until set of haloes in the invalidated area didn't change 
00353     // (including none found) or all exisiting haloes are found.
00354     do {
00355         halo_count = invalidated_haloes.size();
00356         for(itor = haloes.begin(); itor != haloes.end(); ++itor) {
00357             // Test all haloes not yet in the set 
00358             // which match one of the locations
00359             if(invalidated_haloes.find(itor->first) == invalidated_haloes.end() &&
00360                     itor->second.on_location(invalidated_locations)) {
00361 
00362                 // If found, add all locations which the halo invalidates,
00363                 // and add it to the set
00364                 itor->second.add_overlay_location(invalidated_locations);
00365                 invalidated_haloes.insert(itor->first);
00366             }
00367         }
00368     } while (halo_count != invalidated_haloes.size() && halo_count != haloes.size());
00369 
00370     if(halo_count == 0) {
00371         return;
00372     }
00373 
00374     // Render the haloes:
00375     // iterate through all the haloes and invalidate if in set
00376     for(std::map<int, effect>::reverse_iterator ritor = haloes.rbegin(); ritor != haloes.rend(); ++ritor) {
00377         if(invalidated_haloes.find(ritor->first) != invalidated_haloes.end()) {
00378             ritor->second.unrender();
00379         }
00380     }
00381 
00382     // Really delete the haloes marked for deletion
00383     for(set_itor = deleted_haloes.begin(); set_itor != deleted_haloes.end(); ++set_itor) {
00384         // It can happen a deleted halo hasn't been rendered yet, invalidate them as well
00385         new_haloes.erase(*set_itor);
00386 
00387         changing_haloes.erase(*set_itor);
00388         invalidated_haloes.erase(*set_itor);
00389         haloes.erase(*set_itor);
00390     }
00391 
00392     deleted_haloes.clear();
00393 }
00394 
00395 void render()
00396 {
00397     if(preferences::show_haloes() == false || haloes.size() == 0 ||
00398             (new_haloes.size() == 0 && invalidated_haloes.size() == 0)) {
00399         return;
00400     }
00401 
00402     // Keep track of not rendered new images they have to be kept scheduled 
00403     // for rendering otherwise the invalidation area is never properly set
00404     std::set<int> unrendered_new_haloes;
00405 
00406     // Render the haloes: 
00407     // iterate through all the haloes and draw if in either set
00408     for(std::map<int, effect>::iterator itor = haloes.begin();
00409             itor != haloes.end(); ++itor) {
00410 
00411         if(new_haloes.find(itor->first) != new_haloes.end() &&
00412                 ! itor->second.render()) {
00413 
00414             unrendered_new_haloes.insert(itor->first);
00415         } else if(invalidated_haloes.find(itor->first) != invalidated_haloes.end()) {
00416             itor->second.render();
00417         }
00418     }
00419 
00420     invalidated_haloes.clear();
00421     new_haloes = unrendered_new_haloes;
00422 }
00423 
00424 } // end namespace halo
00425 

Generated by doxygen 1.5.5 on 23 May 2008 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs