tron@2186: /* $Id$ */ tron@2186: truelight@0: #include "stdafx.h" Darkvater@1891: #include "openttd.h" tron@2163: #include "functions.h" Darkvater@4957: #include "macros.h" tron@1309: #include "strings.h" truelight@0: #include "gfx.h" truelight@0: #include "viewport.h" truelight@0: #include "saveload.h" truelight@543: #include "hal.h" truelight@543: #include "console.h" truelight@1595: #include "string.h" tron@2153: #include "variables.h" celestar@2218: #include "table/sprites.h" truelight@543: #include /* va_list */ rubidium@4261: #include "date.h" truelight@0: truelight@0: typedef struct TextEffect { truelight@0: StringID string_id; tron@849: int32 x; tron@849: int32 y; tron@849: int32 right; tron@849: int32 bottom; truelight@0: uint16 duration; truelight@0: uint32 params_1; truelight@0: uint32 params_2; truelight@0: } TextEffect; truelight@0: Darkvater@4957: #define MAX_TEXTMESSAGE_LENGTH 150 truelight@543: truelight@543: typedef struct TextMessage { truelight@543: char message[MAX_TEXTMESSAGE_LENGTH]; truelight@543: uint16 color; truelight@4363: Date end_date; truelight@543: } TextMessage; truelight@543: truelight@543: #define MAX_CHAT_MESSAGES 10 truelight@0: static TextEffect _text_effect_list[30]; Darkvater@4956: static TextMessage _textmsg_list[MAX_CHAT_MESSAGES]; truelight@0: TileIndex _animated_tile_list[256]; truelight@0: Darkvater@4956: static bool _textmessage_dirty = false; tron@2548: static bool _textmessage_visible = false; truelight@543: Darkvater@4956: /* The chatbox grows from the bottom so the coordinates are pixels from Darkvater@4956: * the left and pixels from the bottom. The height is the maximum height */ Darkvater@4957: static const Oblong _textmsg_box = {10, 30, 500, 150}; Darkvater@4957: static Pixel _textmessage_backup[150 * 500]; // (height * width) truelight@543: Darkvater@4958: extern void memcpy_pitch(void *dst, void *src, int w, int h, int srcpitch, int dstpitch); truelight@543: Darkvater@4957: static inline uint GetTextMessageCount(void) Darkvater@4957: { Darkvater@4959: uint i; Darkvater@4957: Darkvater@4959: for (i = 0; i < MAX_CHAT_MESSAGES; i++) { Darkvater@4957: if (_textmsg_list[i].message[0] == '\0') break; Darkvater@4957: } Darkvater@4957: Darkvater@4957: return i; Darkvater@4957: } Darkvater@4957: Darkvater@4957: /* Add a text message to the 'chat window' to be shown Darkvater@4957: * @param color The colour this message is to be shown in Darkvater@4957: * @param duration The duration of the chat message in game-days Darkvater@4957: * @param message message itself in printf() style */ darkvater@1022: void CDECL AddTextMessage(uint16 color, uint8 duration, const char *message, ...) truelight@543: { tron@2455: char buf[MAX_TEXTMESSAGE_LENGTH]; Darkvater@4957: const char *bufp; truelight@543: va_list va; Darkvater@4957: uint msg_count; Darkvater@4957: uint16 lines; truelight@543: truelight@543: va_start(va, message); tron@2373: vsnprintf(buf, lengthof(buf), message, va); truelight@543: va_end(va); truelight@543: Darkvater@4957: /* Force linebreaks for strings that are too long */ Darkvater@4957: lines = GB(FormatStringLinebreaks(buf, _textmsg_box.width - 8), 0, 16) + 1; Darkvater@4957: if (lines >= MAX_CHAT_MESSAGES) return; truelight@543: Darkvater@4957: msg_count = GetTextMessageCount(); Darkvater@4957: /* We want to add more chat messages than there is free space for, remove 'old' */ Darkvater@4957: if (lines > MAX_CHAT_MESSAGES - msg_count) { Darkvater@4957: int i = lines - (MAX_CHAT_MESSAGES - msg_count); Darkvater@4957: memmove(&_textmsg_list[0], &_textmsg_list[i], sizeof(_textmsg_list[0]) * (msg_count - i)); Darkvater@4957: msg_count = MAX_CHAT_MESSAGES - lines; truelight@543: } truelight@543: Darkvater@4957: for (bufp = buf; lines != 0; lines--) { Darkvater@4957: TextMessage *tmsg = &_textmsg_list[msg_count++]; Darkvater@4957: ttd_strlcpy(tmsg->message, bufp, sizeof(tmsg->message)); Darkvater@4957: Darkvater@4957: /* The default colour for a message is player colour. Replace this with Darkvater@4957: * white for any additional lines */ Darkvater@4957: tmsg->color = (bufp == buf && color & IS_PALETTE_COLOR) ? color : (0x1D - 15) | IS_PALETTE_COLOR; Darkvater@4957: tmsg->end_date = _date + duration; Darkvater@4957: Darkvater@4957: bufp += strlen(bufp) + 1; // jump to 'next line' in the formatted string Darkvater@4957: } truelight@543: truelight@543: _textmessage_dirty = true; truelight@543: } truelight@543: tron@1093: void InitTextMessage(void) truelight@543: { tron@2639: uint i; tron@2639: tron@2639: for (i = 0; i < MAX_CHAT_MESSAGES; i++) { Darkvater@4956: _textmsg_list[i].message[0] = '\0'; tron@2639: } truelight@543: } truelight@543: truelight@543: // Hide the textbox tron@1093: void UndrawTextMessage(void) truelight@543: { truelight@543: if (_textmessage_visible) { truelight@543: // Sometimes we also need to hide the cursor truelight@543: // This is because both textmessage and the cursor take a shot of the truelight@543: // screen before drawing. truelight@543: // Now the textmessage takes his shot and paints his data before the cursor truelight@543: // does, so in the shot of the cursor is the screen-data of the textmessage truelight@543: // included when the cursor hangs somewhere over the textmessage. To truelight@543: // avoid wrong repaints, we undraw the cursor in that case, and everything truelight@543: // looks nicely ;) truelight@543: // (and now hope this story above makes sense to you ;)) truelight@543: truelight@543: if (_cursor.visible) { Darkvater@4956: if (_cursor.draw_pos.x + _cursor.draw_size.x >= _textmsg_box.x && Darkvater@4956: _cursor.draw_pos.x <= _textmsg_box.x + _textmsg_box.width && Darkvater@4956: _cursor.draw_pos.y + _cursor.draw_size.y >= _screen.height - _textmsg_box.y - _textmsg_box.height && Darkvater@4956: _cursor.draw_pos.y <= _screen.height - _textmsg_box.y) { truelight@543: UndrawMouseCursor(); truelight@543: } truelight@543: } truelight@543: truelight@543: _textmessage_visible = false; truelight@543: // Put our 'shot' back to the screen truelight@543: memcpy_pitch( Darkvater@4956: _screen.dst_ptr + _textmsg_box.x + (_screen.height - _textmsg_box.y - _textmsg_box.height) * _screen.pitch, truelight@543: _textmessage_backup, Darkvater@4956: _textmsg_box.width, _textmsg_box.height, _textmsg_box.width, _screen.pitch); truelight@543: truelight@543: // And make sure it is updated next time Darkvater@4956: _video_driver->make_dirty(_textmsg_box.x, _screen.height - _textmsg_box.y - _textmsg_box.height, _textmsg_box.width, _textmsg_box.height); truelight@543: truelight@543: _textmessage_dirty = true; truelight@543: } truelight@543: } truelight@543: truelight@543: // Check if a message is expired every day tron@1093: void TextMessageDailyLoop(void) truelight@543: { tron@2639: uint i; tron@2639: truelight@1595: for (i = 0; i < MAX_CHAT_MESSAGES; i++) { Darkvater@4957: TextMessage *tmsg = &_textmsg_list[i]; Darkvater@4957: if (tmsg->message[0] == '\0') continue; truelight@1595: Darkvater@4957: /* Message has expired, remove from the list */ Darkvater@4957: if (tmsg->end_date < _date) { truelight@1595: /* Move the remaining messages over the current message */ Darkvater@4957: if (i != MAX_CHAT_MESSAGES - 1) memmove(tmsg, tmsg + 1, sizeof(*tmsg) * (MAX_CHAT_MESSAGES - i - 1)); truelight@1595: truelight@1595: /* Mark the last item as empty */ Darkvater@4956: _textmsg_list[MAX_CHAT_MESSAGES - 1].message[0] = '\0'; truelight@1595: _textmessage_dirty = true; truelight@1595: truelight@1595: /* Go one item back, because we moved the array 1 to the left */ truelight@543: i--; truelight@543: } truelight@543: } truelight@543: } truelight@543: truelight@543: // Draw the textmessage-box tron@1093: void DrawTextMessage(void) truelight@543: { Darkvater@4960: uint y, count; truelight@543: tron@2639: if (!_textmessage_dirty) return; truelight@543: truelight@543: // First undraw if needed truelight@543: UndrawTextMessage(); truelight@543: Darkvater@4959: if (_iconsole_mode == ICONSOLE_FULL) return; truelight@543: truelight@1595: /* Check if we have anything to draw at all */ Darkvater@4960: count = GetTextMessageCount(); Darkvater@4960: if (count == 0) return; truelight@543: truelight@543: // Make a copy of the screen as it is before painting (for undraw) truelight@543: memcpy_pitch( truelight@543: _textmessage_backup, Darkvater@4956: _screen.dst_ptr + _textmsg_box.x + (_screen.height - _textmsg_box.y - _textmsg_box.height) * _screen.pitch, Darkvater@4956: _textmsg_box.width, _textmsg_box.height, _screen.pitch, _textmsg_box.width); truelight@543: Darkvater@4960: _cur_dpi = &_screen; // switch to _screen painting truelight@543: Darkvater@4960: /* Paint a half-transparent box behind the text messages */ Darkvater@4960: GfxFillRect( Darkvater@4960: _textmsg_box.x, Darkvater@4960: _screen.height - _textmsg_box.y - count * 13 - 2, Darkvater@4960: _textmsg_box.x + _textmsg_box.width - 1, Darkvater@4960: _screen.height - _textmsg_box.y - 2, Darkvater@4960: 0x322 | USE_COLORTABLE // black, but with some alpha for background Darkvater@4960: ); truelight@1595: Darkvater@4960: /* Paint the messages starting with the lowest at the bottom */ Darkvater@4960: for (y = 13; count-- != 0; y += 13) { Darkvater@4960: DoDrawString(_textmsg_list[count].message, _textmsg_box.x + 3, _screen.height - _textmsg_box.y - y + 1, _textmsg_list[count].color); Darkvater@4960: } truelight@543: truelight@543: // Make sure the data is updated next flush Darkvater@4956: _video_driver->make_dirty(_textmsg_box.x, _screen.height - _textmsg_box.y - _textmsg_box.height, _textmsg_box.width, _textmsg_box.height); truelight@543: truelight@543: _textmessage_visible = true; truelight@543: _textmessage_dirty = false; truelight@543: } truelight@543: truelight@0: static void MarkTextEffectAreaDirty(TextEffect *te) truelight@0: { truelight@0: MarkAllViewportsDirty( truelight@0: te->x, truelight@0: te->y - 1, truelight@0: (te->right - te->x)*2 + te->x + 1, truelight@0: (te->bottom - (te->y - 1)) * 2 + (te->y - 1) + 1 truelight@0: ); truelight@0: } truelight@0: truelight@0: void AddTextEffect(StringID msg, int x, int y, uint16 duration) truelight@0: { truelight@0: TextEffect *te; truelight@0: int w; truelight@0: char buffer[100]; truelight@0: Darkvater@4958: if (_game_mode == GM_MENU) return; truelight@193: tron@2470: for (te = _text_effect_list; te->string_id != INVALID_STRING_ID; ) { tron@2639: if (++te == endof(_text_effect_list)) return; truelight@0: } truelight@0: truelight@0: te->string_id = msg; truelight@0: te->duration = duration; truelight@0: te->y = y - 5; truelight@0: te->bottom = y + 5; tron@534: te->params_1 = GetDParam(0); tron@534: te->params_2 = GetDParam(4); truelight@0: Darkvater@4912: GetString(buffer, msg, lastof(buffer)); Darkvater@4609: w = GetStringBoundingBox(buffer).width; truelight@0: truelight@0: te->x = x - (w >> 1); truelight@0: te->right = x + (w >> 1) - 1; truelight@0: MarkTextEffectAreaDirty(te); truelight@0: } truelight@0: truelight@0: static void MoveTextEffect(TextEffect *te) truelight@0: { truelight@0: if (te->duration < 8) { tron@2470: te->string_id = INVALID_STRING_ID; truelight@0: } else { tron@2549: te->duration -= 8; truelight@0: te->y--; truelight@0: te->bottom--; truelight@0: } truelight@0: MarkTextEffectAreaDirty(te); truelight@0: } truelight@0: tron@1093: void MoveAllTextEffects(void) truelight@0: { truelight@0: TextEffect *te; truelight@0: tron@2549: for (te = _text_effect_list; te != endof(_text_effect_list); te++) { tron@2549: if (te->string_id != INVALID_STRING_ID) MoveTextEffect(te); truelight@0: } truelight@0: } truelight@0: tron@1093: void InitTextEffects(void) truelight@0: { truelight@0: TextEffect *te; truelight@0: tron@2549: for (te = _text_effect_list; te != endof(_text_effect_list); te++) { tron@2470: te->string_id = INVALID_STRING_ID; truelight@0: } truelight@0: } truelight@0: truelight@0: void DrawTextEffects(DrawPixelInfo *dpi) truelight@0: { tron@4469: const TextEffect* te; truelight@0: tron@4469: switch (dpi->zoom) { tron@4469: case 0: tron@4469: for (te = _text_effect_list; te != endof(_text_effect_list); te++) { tron@4469: if (te->string_id != INVALID_STRING_ID && tron@4469: dpi->left <= te->right && tron@4469: dpi->top <= te->bottom && tron@4469: dpi->left + dpi->width > te->x && tron@4469: dpi->top + dpi->height > te->y) { tron@5026: AddStringToDraw(te->x, te->y, te->string_id, te->params_1, te->params_2); tron@4469: } tron@4469: } tron@4469: break; truelight@0: tron@4469: case 1: tron@4469: for (te = _text_effect_list; te != endof(_text_effect_list); te++) { tron@4469: if (te->string_id != INVALID_STRING_ID && tron@4469: dpi->left <= te->right * 2 - te->x && tron@4469: dpi->top <= te->bottom * 2 - te->y && tron@4469: dpi->left + dpi->width > te->x && tron@4469: dpi->top + dpi->height > te->y) { tron@5026: AddStringToDraw(te->x, te->y, (StringID)(te->string_id-1), te->params_1, te->params_2); tron@4469: } tron@4469: } tron@4469: break; truelight@0: } truelight@0: } truelight@0: tron@1977: void DeleteAnimatedTile(TileIndex tile) truelight@0: { tron@2549: TileIndex* ti; truelight@0: tron@2549: for (ti = _animated_tile_list; ti != endof(_animated_tile_list); ti++) { tron@1977: if (tile == *ti) { truelight@0: /* remove the hole */ tron@2549: memmove(ti, ti + 1, endof(_animated_tile_list) - 1 - ti); truelight@0: /* and clear last item */ truelight@0: endof(_animated_tile_list)[-1] = 0; truelight@0: MarkTileDirtyByTile(tile); truelight@0: return; truelight@193: } truelight@0: } truelight@0: } truelight@0: tron@1977: bool AddAnimatedTile(TileIndex tile) truelight@0: { tron@2549: TileIndex* ti; truelight@0: tron@2549: for (ti = _animated_tile_list; ti != endof(_animated_tile_list); ti++) { tron@1977: if (tile == *ti || *ti == 0) { truelight@0: *ti = tile; truelight@0: MarkTileDirtyByTile(tile); truelight@0: return true; truelight@0: } truelight@193: } truelight@0: truelight@0: return false; truelight@0: } truelight@0: tron@1093: void AnimateAnimatedTiles(void) truelight@0: { tron@2549: const TileIndex* ti; truelight@0: tron@2549: for (ti = _animated_tile_list; ti != endof(_animated_tile_list) && *ti != 0; ti++) { tron@2549: AnimateTile(*ti); truelight@0: } truelight@0: } truelight@0: tron@1093: void InitializeAnimatedTiles(void) truelight@0: { truelight@0: memset(_animated_tile_list, 0, sizeof(_animated_tile_list)); truelight@0: } truelight@0: tron@1093: static void SaveLoad_ANIT(void) truelight@0: { truelight@2685: // In pre version 6, we has 16bit per tile, now we have 32bit per tile, convert it ;) truelight@2685: if (CheckSavegameVersion(6)) { Darkvater@1881: SlArray(_animated_tile_list, lengthof(_animated_tile_list), SLE_FILE_U16 | SLE_VAR_U32); tron@2549: } else { tron@1174: SlArray(_animated_tile_list, lengthof(_animated_tile_list), SLE_UINT32); tron@2549: } truelight@0: } truelight@0: truelight@0: truelight@0: const ChunkHandler _animated_tile_chunk_handlers[] = { truelight@0: { 'ANIT', SaveLoad_ANIT, SaveLoad_ANIT, CH_RIFF | CH_LAST}, truelight@0: };