texteff.cpp

Go to the documentation of this file.
00001 /* $Id: texteff.cpp 11834 2008-01-13 14:37:30Z rubidium $ */
00002 
00005 #include "stdafx.h"
00006 #include "openttd.h"
00007 #include "tile_cmd.h"
00008 #include "landscape.h"
00009 #include "gfx_func.h"
00010 #include "saveload.h"
00011 #include "console.h"
00012 #include "variables.h"
00013 #include "blitter/factory.hpp"
00014 #include "texteff.hpp"
00015 #include "video/video_driver.hpp"
00016 #include "transparency.h"
00017 #include "strings_func.h"
00018 #include "core/alloc_func.hpp"
00019 #include "date_func.h"
00020 #include "functions.h"
00021 #include "viewport_func.h"
00022 #include "settings_type.h"
00023 
00024 #include "table/sprites.h"
00025 
00026 #include <stdarg.h> /* va_list */
00027 
00028 enum {
00029   MAX_TEXTMESSAGE_LENGTH = 200,
00030   INIT_NUM_TEXT_MESSAGES =  20,
00031   MAX_CHAT_MESSAGES      =  10,
00032 };
00033 
00034 struct TextEffect {
00035   StringID string_id;
00036   int32 x;
00037   int32 y;
00038   int32 right;
00039   int32 bottom;
00040   uint16 duration;
00041   uint64 params_1;
00042   uint64 params_2;
00043   TextEffectMode mode;
00044 };
00045 
00046 
00047 struct ChatMessage {
00048   char message[MAX_TEXTMESSAGE_LENGTH];
00049   uint16 color;
00050   Date end_date;
00051 };
00052 
00053 /* used for text effects */
00054 static TextEffect *_text_effect_list = NULL;
00055 static uint16 _num_text_effects = INIT_NUM_TEXT_MESSAGES;
00056 
00057 /* used for chat window */
00058 static ChatMessage _chatmsg_list[MAX_CHAT_MESSAGES];
00059 static bool _chatmessage_dirty = false;
00060 static bool _chatmessage_visible = false;
00061 
00062 /* The chatbox grows from the bottom so the coordinates are pixels from
00063  * the left and pixels from the bottom. The height is the maximum height */
00064 static const PointDimension _chatmsg_box = {10, 30, 500, 150};
00065 static uint8 _chatmessage_backup[150 * 500 * 6]; // (height * width)
00066 
00067 static inline uint GetChatMessageCount()
00068 {
00069   uint i;
00070 
00071   for (i = 0; i < MAX_CHAT_MESSAGES; i++) {
00072     if (_chatmsg_list[i].message[0] == '\0') break;
00073   }
00074 
00075   return i;
00076 }
00077 
00078 /* Add a text message to the 'chat window' to be shown
00079  * @param color The colour this message is to be shown in
00080  * @param duration The duration of the chat message in game-days
00081  * @param message message itself in printf() style */
00082 void CDECL AddChatMessage(uint16 color, uint8 duration, const char *message, ...)
00083 {
00084   char buf[MAX_TEXTMESSAGE_LENGTH];
00085   const char *bufp;
00086   va_list va;
00087   uint msg_count;
00088   uint16 lines;
00089 
00090   va_start(va, message);
00091   vsnprintf(buf, lengthof(buf), message, va);
00092   va_end(va);
00093 
00094 
00095   Utf8TrimString(buf, MAX_TEXTMESSAGE_LENGTH);
00096 
00097   /* Force linebreaks for strings that are too long */
00098   lines = GB(FormatStringLinebreaks(buf, _chatmsg_box.width - 8), 0, 16) + 1;
00099   if (lines >= MAX_CHAT_MESSAGES) return;
00100 
00101   msg_count = GetChatMessageCount();
00102   /* We want to add more chat messages than there is free space for, remove 'old' */
00103   if (lines > MAX_CHAT_MESSAGES - msg_count) {
00104     int i = lines - (MAX_CHAT_MESSAGES - msg_count);
00105     memmove(&_chatmsg_list[0], &_chatmsg_list[i], sizeof(_chatmsg_list[0]) * (msg_count - i));
00106     msg_count = MAX_CHAT_MESSAGES - lines;
00107   }
00108 
00109   for (bufp = buf; lines != 0; lines--) {
00110     ChatMessage *cmsg = &_chatmsg_list[msg_count++];
00111     ttd_strlcpy(cmsg->message, bufp, sizeof(cmsg->message));
00112 
00113     /* The default colour for a message is player colour. Replace this with
00114      * white for any additional lines */
00115     cmsg->color = (bufp == buf && color & IS_PALETTE_COLOR) ? color : (0x1D - 15) | IS_PALETTE_COLOR;
00116     cmsg->end_date = _date + duration;
00117 
00118     bufp += strlen(bufp) + 1; // jump to 'next line' in the formatted string
00119   }
00120 
00121   _chatmessage_dirty = true;
00122 }
00123 
00124 void InitChatMessage()
00125 {
00126   uint i;
00127 
00128   for (i = 0; i < MAX_CHAT_MESSAGES; i++) {
00129     _chatmsg_list[i].message[0] = '\0';
00130   }
00131 }
00132 
00134 void UndrawChatMessage()
00135 {
00136   if (_chatmessage_visible) {
00137     Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
00138     /* Sometimes we also need to hide the cursor
00139      *   This is because both textmessage and the cursor take a shot of the
00140      *   screen before drawing.
00141      *   Now the textmessage takes his shot and paints his data before the cursor
00142      *   does, so in the shot of the cursor is the screen-data of the textmessage
00143      *   included when the cursor hangs somewhere over the textmessage. To
00144      *   avoid wrong repaints, we undraw the cursor in that case, and everything
00145      *   looks nicely ;)
00146      * (and now hope this story above makes sense to you ;))
00147      */
00148 
00149     if (_cursor.visible) {
00150       if (_cursor.draw_pos.x + _cursor.draw_size.x >= _chatmsg_box.x &&
00151         _cursor.draw_pos.x <= _chatmsg_box.x + _chatmsg_box.width &&
00152         _cursor.draw_pos.y + _cursor.draw_size.y >= _screen.height - _chatmsg_box.y - _chatmsg_box.height &&
00153         _cursor.draw_pos.y <= _screen.height - _chatmsg_box.y) {
00154         UndrawMouseCursor();
00155       }
00156     }
00157 
00158     int x      = _chatmsg_box.x;
00159     int y      = _screen.height - _chatmsg_box.y - _chatmsg_box.height;
00160     int width  = _chatmsg_box.width;
00161     int height = _chatmsg_box.height;
00162     if (y < 0) {
00163       height = max(height + y, min(_chatmsg_box.height, _screen.height));
00164       y = 0;
00165     }
00166     if (x + width >= _screen.width) {
00167       width = _screen.width - x;
00168     }
00169     if (width <= 0 || height <= 0) return;
00170 
00171     _chatmessage_visible = false;
00172     /* Put our 'shot' back to the screen */
00173     blitter->CopyFromBuffer(blitter->MoveTo(_screen.dst_ptr, x, y), _chatmessage_backup, width, height);
00174     /* And make sure it is updated next time */
00175     _video_driver->MakeDirty(x, y, width, height);
00176 
00177     _chatmessage_dirty = true;
00178   }
00179 }
00180 
00182 void ChatMessageDailyLoop()
00183 {
00184   uint i;
00185 
00186   for (i = 0; i < MAX_CHAT_MESSAGES; i++) {
00187     ChatMessage *cmsg = &_chatmsg_list[i];
00188     if (cmsg->message[0] == '\0') continue;
00189 
00190     /* Message has expired, remove from the list */
00191     if (cmsg->end_date < _date) {
00192       /* Move the remaining messages over the current message */
00193       if (i != MAX_CHAT_MESSAGES - 1) memmove(cmsg, cmsg + 1, sizeof(*cmsg) * (MAX_CHAT_MESSAGES - i - 1));
00194 
00195       /* Mark the last item as empty */
00196       _chatmsg_list[MAX_CHAT_MESSAGES - 1].message[0] = '\0';
00197       _chatmessage_dirty = true;
00198 
00199       /* Go one item back, because we moved the array 1 to the left */
00200       i--;
00201     }
00202   }
00203 }
00204 
00206 void DrawChatMessage()
00207 {
00208   Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
00209   if (!_chatmessage_dirty) return;
00210 
00211   /* First undraw if needed */
00212   UndrawChatMessage();
00213 
00214   if (_iconsole_mode == ICONSOLE_FULL) return;
00215 
00216   /* Check if we have anything to draw at all */
00217   uint count = GetChatMessageCount();
00218   if (count == 0) return;
00219 
00220   int x      = _chatmsg_box.x;
00221   int y      = _screen.height - _chatmsg_box.y - _chatmsg_box.height;
00222   int width  = _chatmsg_box.width;
00223   int height = _chatmsg_box.height;
00224   if (y < 0) {
00225     height = max(height + y, min(_chatmsg_box.height, _screen.height));
00226     y = 0;
00227   }
00228   if (x + width >= _screen.width) {
00229     width = _screen.width - x;
00230   }
00231   if (width <= 0 || height <= 0) return;
00232 
00233   assert(blitter->BufferSize(width, height) < (int)sizeof(_chatmessage_backup));
00234 
00235   /* Make a copy of the screen as it is before painting (for undraw) */
00236   blitter->CopyToBuffer(blitter->MoveTo(_screen.dst_ptr, x, y), _chatmessage_backup, width, height);
00237 
00238   _cur_dpi = &_screen; // switch to _screen painting
00239 
00240   /* Paint a half-transparent box behind the chat messages */
00241   GfxFillRect(
00242       _chatmsg_box.x,
00243       _screen.height - _chatmsg_box.y - count * 13 - 2,
00244       _chatmsg_box.x + _chatmsg_box.width - 1,
00245       _screen.height - _chatmsg_box.y - 2,
00246       PALETTE_TO_TRANSPARENT | (1 << USE_COLORTABLE) // black, but with some alpha for background
00247     );
00248 
00249   /* Paint the chat messages starting with the lowest at the bottom */
00250   for (uint y = 13; count-- != 0; y += 13) {
00251     DoDrawString(_chatmsg_list[count].message, _chatmsg_box.x + 3, _screen.height - _chatmsg_box.y - y + 1, _chatmsg_list[count].color);
00252   }
00253 
00254   /* Make sure the data is updated next flush */
00255   _video_driver->MakeDirty(x, y, width, height);
00256 
00257   _chatmessage_visible = true;
00258   _chatmessage_dirty = false;
00259 }
00260 
00261 /* Text Effects */
00270 static void MarkTextEffectAreaDirty(TextEffect *te)
00271 {
00272   /* Width and height of the text effect are doubled, so they are correct in both zoom out levels 1x and 2x. */
00273   MarkAllViewportsDirty(
00274     te->x,
00275     te->y - 1,
00276     (te->right - te->x)*2 + te->x + 1,
00277     (te->bottom - (te->y - 1)) * 2 + (te->y - 1) + 1
00278   );
00279 }
00280 
00281 TextEffectID AddTextEffect(StringID msg, int x, int y, uint16 duration, TextEffectMode mode)
00282 {
00283   TextEffect *te;
00284   int w;
00285   char buffer[100];
00286   TextEffectID i;
00287 
00288   if (_game_mode == GM_MENU) return INVALID_TE_ID;
00289 
00290   /* Look for a free spot in the text effect array */
00291   for (i = 0; i < _num_text_effects; i++) {
00292     if (_text_effect_list[i].string_id == INVALID_STRING_ID) break;
00293   }
00294 
00295   /* If there is none found, we grow the array */
00296   if (i == _num_text_effects) {
00297     _num_text_effects += 25;
00298     _text_effect_list = ReallocT<TextEffect>(_text_effect_list, _num_text_effects);
00299     for (; i < _num_text_effects; i++) _text_effect_list[i].string_id = INVALID_STRING_ID;
00300     i = _num_text_effects - 1;
00301   }
00302 
00303   te = &_text_effect_list[i];
00304 
00305   /* Start defining this object */
00306   te->string_id = msg;
00307   te->duration = duration;
00308   te->y = y - 5;
00309   te->bottom = y + 5;
00310   te->params_1 = GetDParam(0);
00311   te->params_2 = GetDParam(4);
00312   te->mode = mode;
00313 
00314   GetString(buffer, msg, lastof(buffer));
00315   w = GetStringBoundingBox(buffer).width;
00316 
00317   te->x = x - (w >> 1);
00318   te->right = x + (w >> 1) - 1;
00319   MarkTextEffectAreaDirty(te);
00320 
00321   return i;
00322 }
00323 
00324 void UpdateTextEffect(TextEffectID te_id, StringID msg)
00325 {
00326   assert(te_id < _num_text_effects);
00327   TextEffect *te;
00328 
00329   /* Update details */
00330   te = &_text_effect_list[te_id];
00331   te->string_id = msg;
00332   te->params_1 = GetDParam(0);
00333   te->params_2 = GetDParam(4);
00334 
00335   /* Update width of text effect */
00336   char buffer[100];
00337   GetString(buffer, msg, lastof(buffer));
00338   int w = GetStringBoundingBox(buffer).width;
00339 
00340   /* Only allow to make it broader, so it completely covers the old text. That avoids remnants of the old text. */
00341   int right_new = te->x + w;
00342   if (te->right < right_new) te->right = right_new;
00343 
00344   MarkTextEffectAreaDirty(te);
00345 }
00346 
00347 void RemoveTextEffect(TextEffectID te_id)
00348 {
00349   assert(te_id < _num_text_effects);
00350   TextEffect *te;
00351 
00352   te = &_text_effect_list[te_id];
00353   MarkTextEffectAreaDirty(te);
00354   te->string_id = INVALID_STRING_ID;
00355 }
00356 
00357 static void MoveTextEffect(TextEffect *te)
00358 {
00359   /* Never expire for duration of 0xFFFF */
00360   if (te->duration == 0xFFFF) return;
00361   if (te->duration < 8) {
00362     te->string_id = INVALID_STRING_ID;
00363   } else {
00364     te->duration -= 8;
00365     te->y--;
00366     te->bottom--;
00367   }
00368   MarkTextEffectAreaDirty(te);
00369 }
00370 
00371 void MoveAllTextEffects()
00372 {
00373   for (TextEffectID i = 0; i < _num_text_effects; i++) {
00374     TextEffect *te = &_text_effect_list[i];
00375     if (te->string_id != INVALID_STRING_ID && te->mode == TE_RISING) MoveTextEffect(te);
00376   }
00377 }
00378 
00379 void InitTextEffects()
00380 {
00381   if (_text_effect_list == NULL) _text_effect_list = MallocT<TextEffect>(_num_text_effects);
00382 
00383   for (TextEffectID i = 0; i < _num_text_effects; i++) _text_effect_list[i].string_id = INVALID_STRING_ID;
00384 }
00385 
00386 void DrawTextEffects(DrawPixelInfo *dpi)
00387 {
00388   switch (dpi->zoom) {
00389     case ZOOM_LVL_NORMAL:
00390       for (TextEffectID i = 0; i < _num_text_effects; i++) {
00391         TextEffect *te = &_text_effect_list[i];
00392         if (te->string_id != INVALID_STRING_ID &&
00393             dpi->left <= te->right &&
00394             dpi->top  <= te->bottom &&
00395             dpi->left + dpi->width  > te->x &&
00396             dpi->top  + dpi->height > te->y) {
00397           if (te->mode == TE_RISING || (_patches.loading_indicators && !IsTransparencySet(TO_LOADING))) {
00398             AddStringToDraw(te->x, te->y, te->string_id, te->params_1, te->params_2);
00399           }
00400         }
00401       }
00402       break;
00403 
00404     case ZOOM_LVL_OUT_2X:
00405       for (TextEffectID i = 0; i < _num_text_effects; i++) {
00406         TextEffect *te = &_text_effect_list[i];
00407         if (te->string_id != INVALID_STRING_ID &&
00408             dpi->left <= te->right  * 2 - te->x &&
00409             dpi->top  <= te->bottom * 2 - te->y &&
00410             dpi->left + dpi->width  > te->x &&
00411             dpi->top  + dpi->height > te->y) {
00412           if (te->mode == TE_RISING || (_patches.loading_indicators && !IsTransparencySet(TO_LOADING))) {
00413             AddStringToDraw(te->x, te->y, (StringID)(te->string_id - 1), te->params_1, te->params_2);
00414           }
00415         }
00416       }
00417       break;
00418 
00419     case ZOOM_LVL_OUT_4X:
00420     case ZOOM_LVL_OUT_8X:
00421       break;
00422 
00423     default: NOT_REACHED();
00424   }
00425 }
00426 
00428 TileIndex *_animated_tile_list = NULL;
00430 uint _animated_tile_count = 0;
00432 static uint _animated_tile_allocated = 0;
00433 
00438 void DeleteAnimatedTile(TileIndex tile)
00439 {
00440   for (TileIndex *ti = _animated_tile_list; ti < _animated_tile_list + _animated_tile_count; ti++) {
00441     if (tile == *ti) {
00442       /* Remove the hole
00443        * The order of the remaining elements must stay the same, otherwise the animation loop
00444        * may miss a tile; that's why we must use memmove instead of just moving the last element.
00445        */
00446       memmove(ti, ti + 1, (_animated_tile_list + _animated_tile_count - (ti + 1)) * sizeof(*ti));
00447       _animated_tile_count--;
00448       MarkTileDirtyByTile(tile);
00449       return;
00450     }
00451   }
00452 }
00453 
00459 void AddAnimatedTile(TileIndex tile)
00460 {
00461   MarkTileDirtyByTile(tile);
00462 
00463   for (const TileIndex *ti = _animated_tile_list; ti < _animated_tile_list + _animated_tile_count; ti++) {
00464     if (tile == *ti) return;
00465   }
00466 
00467   /* Table not large enough, so make it larger */
00468   if (_animated_tile_count == _animated_tile_allocated) {
00469     _animated_tile_allocated *= 2;
00470     _animated_tile_list = ReallocT<TileIndex>(_animated_tile_list, _animated_tile_allocated);
00471   }
00472 
00473   _animated_tile_list[_animated_tile_count] = tile;
00474   _animated_tile_count++;
00475 }
00476 
00480 void AnimateAnimatedTiles()
00481 {
00482   const TileIndex *ti = _animated_tile_list;
00483   while (ti < _animated_tile_list + _animated_tile_count) {
00484     const TileIndex curr = *ti;
00485     AnimateTile(curr);
00486     /* During the AnimateTile call, DeleteAnimatedTile could have been called,
00487      * deleting an element we've already processed and pushing the rest one
00488      * slot to the left. We can detect this by checking whether the index
00489      * in the current slot has changed - if it has, an element has been deleted,
00490      * and we should process the current slot again instead of going forward.
00491      * NOTE: this will still break if more than one animated tile is being
00492      *       deleted during the same AnimateTile call, but no code seems to
00493      *       be doing this anyway.
00494      */
00495     if (*ti == curr) ++ti;
00496   }
00497 }
00498 
00502 void InitializeAnimatedTiles()
00503 {
00504   _animated_tile_list = ReallocT<TileIndex>(_animated_tile_list, 256);
00505   _animated_tile_count = 0;
00506   _animated_tile_allocated = 256;
00507 }
00508 
00512 static void Save_ANIT()
00513 {
00514   SlSetLength(_animated_tile_count * sizeof(*_animated_tile_list));
00515   SlArray(_animated_tile_list, _animated_tile_count, SLE_UINT32);
00516 }
00517 
00521 static void Load_ANIT()
00522 {
00523   /* Before version 80 we did NOT have a variable length animated tile table */
00524   if (CheckSavegameVersion(80)) {
00525     /* In pre version 6, we has 16bit per tile, now we have 32bit per tile, convert it ;) */
00526     SlArray(_animated_tile_list, 256, CheckSavegameVersion(6) ? (SLE_FILE_U16 | SLE_VAR_U32) : SLE_UINT32);
00527 
00528     for (_animated_tile_count = 0; _animated_tile_count < 256; _animated_tile_count++) {
00529       if (_animated_tile_list[_animated_tile_count] == 0) break;
00530     }
00531     return;
00532   }
00533 
00534   _animated_tile_count = SlGetFieldLength() / sizeof(*_animated_tile_list);
00535 
00536   /* Determine a nice rounded size for the amount of allocated tiles */
00537   _animated_tile_allocated = 256;
00538   while (_animated_tile_allocated < _animated_tile_count) _animated_tile_allocated *= 2;
00539 
00540   _animated_tile_list = ReallocT<TileIndex>(_animated_tile_list, _animated_tile_allocated);
00541   SlArray(_animated_tile_list, _animated_tile_count, SLE_UINT32);
00542 }
00543 
00548 extern const ChunkHandler _animated_tile_chunk_handlers[] = {
00549   { 'ANIT', Save_ANIT, Load_ANIT, CH_RIFF | CH_LAST},
00550 };

Generated on Wed Oct 1 17:03:24 2008 for openttd by  doxygen 1.5.6