# 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 };