Files @ r5427:5c600ac28c4a
Branch filter:

Location: cpp/openttd-patchpack/source/texteff.c - annotation

rubidium
(svn r7631) -Fix (r1): some pointer arithmetic gave the number of entries instead of the number of bytes to move when removing the animated state from a tile. This caused desyncs between Little and Big Endian machines. Thanks to pv2b for helping me in locating the bug and to Darkvater for figuring out that the arithmetic returned the number of entries instead of bytes.
r2186:5ee653b1b5e1
r2186:5ee653b1b5e1
r0:d63b455452f6
r1891:c5c5466afa35
r2163:ae001e2aa5b0
r4957:06cb8a369f9c
r1309:4100b4013e37
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r543:efdb197f91ad
r543:efdb197f91ad
r1595:1367a56badd8
r2153:b45e3461c6c4
r2218:ce91db2b836e
r543:efdb197f91ad
r4261:8c2d0c75e37a
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r849:8924bae789dc
r849:8924bae789dc
r849:8924bae789dc
r849:8924bae789dc
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r4957:06cb8a369f9c
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r4363:7ad927b3bb4d
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r0:d63b455452f6
r4956:9b1bcb55fa61
r0:d63b455452f6
r0:d63b455452f6
r4956:9b1bcb55fa61
r2548:9570bbbdacab
r543:efdb197f91ad
r4956:9b1bcb55fa61
r4956:9b1bcb55fa61
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r543:efdb197f91ad
r4958:eb3dff1fb4d9
r543:efdb197f91ad
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r4959:7b50b5964cdb
r4957:06cb8a369f9c
r4959:7b50b5964cdb
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r1022:057879ab9292
r543:efdb197f91ad
r2455:1cf91091af81
r4957:06cb8a369f9c
r543:efdb197f91ad
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r543:efdb197f91ad
r543:efdb197f91ad
r2373:a9ea9754767f
r543:efdb197f91ad
r543:efdb197f91ad
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r543:efdb197f91ad
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r543:efdb197f91ad
r543:efdb197f91ad
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r1093:18f56ef2d029
r543:efdb197f91ad
r2639:e298192a7c03
r2639:e298192a7c03
r2639:e298192a7c03
r4956:9b1bcb55fa61
r2639:e298192a7c03
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r1093:18f56ef2d029
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r4956:9b1bcb55fa61
r4956:9b1bcb55fa61
r4956:9b1bcb55fa61
r4956:9b1bcb55fa61
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r4956:9b1bcb55fa61
r543:efdb197f91ad
r4956:9b1bcb55fa61
r543:efdb197f91ad
r543:efdb197f91ad
r4956:9b1bcb55fa61
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r1093:18f56ef2d029
r543:efdb197f91ad
r2639:e298192a7c03
r2639:e298192a7c03
r1595:1367a56badd8
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r1595:1367a56badd8
r4957:06cb8a369f9c
r4957:06cb8a369f9c
r1595:1367a56badd8
r4957:06cb8a369f9c
r1595:1367a56badd8
r1595:1367a56badd8
r4956:9b1bcb55fa61
r1595:1367a56badd8
r1595:1367a56badd8
r1595:1367a56badd8
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r1093:18f56ef2d029
r543:efdb197f91ad
r4960:2c31f840f1b1
r543:efdb197f91ad
r2639:e298192a7c03
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r4959:7b50b5964cdb
r543:efdb197f91ad
r1595:1367a56badd8
r4960:2c31f840f1b1
r4960:2c31f840f1b1
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r4956:9b1bcb55fa61
r4956:9b1bcb55fa61
r543:efdb197f91ad
r4960:2c31f840f1b1
r543:efdb197f91ad
r4960:2c31f840f1b1
r4960:2c31f840f1b1
r4960:2c31f840f1b1
r4960:2c31f840f1b1
r4960:2c31f840f1b1
r4960:2c31f840f1b1
r4960:2c31f840f1b1
r4960:2c31f840f1b1
r1595:1367a56badd8
r4960:2c31f840f1b1
r4960:2c31f840f1b1
r4960:2c31f840f1b1
r4960:2c31f840f1b1
r543:efdb197f91ad
r543:efdb197f91ad
r4956:9b1bcb55fa61
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r543:efdb197f91ad
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r4958:eb3dff1fb4d9
r193:6aa65dc5a4b4
r2470:e744cbfb0a0f
r2639:e298192a7c03
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r534:46e9db8de483
r534:46e9db8de483
r0:d63b455452f6
r4912:d420151de4c6
r4609:43908f0c2b9c
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r2470:e744cbfb0a0f
r0:d63b455452f6
r2549:1549d4b2ba28
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r1093:18f56ef2d029
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r2549:1549d4b2ba28
r2549:1549d4b2ba28
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r1093:18f56ef2d029
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r2549:1549d4b2ba28
r2470:e744cbfb0a0f
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r4469:8ba8a481d745
r0:d63b455452f6
r4469:8ba8a481d745
r4469:8ba8a481d745
r4469:8ba8a481d745
r4469:8ba8a481d745
r4469:8ba8a481d745
r4469:8ba8a481d745
r4469:8ba8a481d745
r4469:8ba8a481d745
r5026:15f78890c335
r4469:8ba8a481d745
r4469:8ba8a481d745
r4469:8ba8a481d745
r0:d63b455452f6
r4469:8ba8a481d745
r4469:8ba8a481d745
r4469:8ba8a481d745
r4469:8ba8a481d745
r4469:8ba8a481d745
r4469:8ba8a481d745
r4469:8ba8a481d745
r5026:15f78890c335
r4469:8ba8a481d745
r4469:8ba8a481d745
r4469:8ba8a481d745
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r1977:1f8b99c96041
r0:d63b455452f6
r2549:1549d4b2ba28
r0:d63b455452f6
r2549:1549d4b2ba28
r1977:1f8b99c96041
r0:d63b455452f6
r5427:5c600ac28c4a
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r193:6aa65dc5a4b4
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r1977:1f8b99c96041
r0:d63b455452f6
r2549:1549d4b2ba28
r0:d63b455452f6
r2549:1549d4b2ba28
r1977:1f8b99c96041
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r193:6aa65dc5a4b4
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r1093:18f56ef2d029
r0:d63b455452f6
r2549:1549d4b2ba28
r0:d63b455452f6
r2549:1549d4b2ba28
r2549:1549d4b2ba28
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r1093:18f56ef2d029
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r1093:18f56ef2d029
r0:d63b455452f6
r2685:a6ff7cfc7f7f
r2685:a6ff7cfc7f7f
r1881:ce1fe6a2816a
r2549:1549d4b2ba28
r1174:4ffa62f5467c
r2549:1549d4b2ba28
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
r0:d63b455452f6
/* $Id$ */

#include "stdafx.h"
#include "openttd.h"
#include "functions.h"
#include "macros.h"
#include "strings.h"
#include "gfx.h"
#include "viewport.h"
#include "saveload.h"
#include "hal.h"
#include "console.h"
#include "string.h"
#include "variables.h"
#include "table/sprites.h"
#include <stdarg.h> /* va_list */
#include "date.h"

typedef struct TextEffect {
	StringID string_id;
	int32 x;
	int32 y;
	int32 right;
	int32 bottom;
	uint16 duration;
	uint32 params_1;
	uint32 params_2;
} TextEffect;

#define MAX_TEXTMESSAGE_LENGTH 150

typedef struct TextMessage {
	char message[MAX_TEXTMESSAGE_LENGTH];
	uint16 color;
	Date end_date;
} TextMessage;

#define MAX_CHAT_MESSAGES 10
static TextEffect _text_effect_list[30];
static TextMessage _textmsg_list[MAX_CHAT_MESSAGES];
TileIndex _animated_tile_list[256];

static bool _textmessage_dirty = false;
static bool _textmessage_visible = false;

/* The chatbox grows from the bottom so the coordinates are pixels from
 * the left and pixels from the bottom. The height is the maximum height */
static const Oblong _textmsg_box = {10, 30, 500, 150};
static Pixel _textmessage_backup[150 * 500]; // (height * width)

extern void memcpy_pitch(void *dst, void *src, int w, int h, int srcpitch, int dstpitch);

static inline uint GetTextMessageCount(void)
{
	uint i;

	for (i = 0; i < MAX_CHAT_MESSAGES; i++) {
		if (_textmsg_list[i].message[0] == '\0') break;
	}

	return i;
}

/* Add a text message to the 'chat window' to be shown
 * @param color The colour this message is to be shown in
 * @param duration The duration of the chat message in game-days
 * @param message message itself in printf() style */
void CDECL AddTextMessage(uint16 color, uint8 duration, const char *message, ...)
{
	char buf[MAX_TEXTMESSAGE_LENGTH];
	const char *bufp;
	va_list va;
	uint msg_count;
	uint16 lines;

	va_start(va, message);
	vsnprintf(buf, lengthof(buf), message, va);
	va_end(va);

	/* Force linebreaks for strings that are too long */
	lines = GB(FormatStringLinebreaks(buf, _textmsg_box.width - 8), 0, 16) + 1;
	if (lines >= MAX_CHAT_MESSAGES) return;

	msg_count = GetTextMessageCount();
	/* We want to add more chat messages than there is free space for, remove 'old' */
	if (lines > MAX_CHAT_MESSAGES - msg_count) {
		int i = lines - (MAX_CHAT_MESSAGES - msg_count);
		memmove(&_textmsg_list[0], &_textmsg_list[i], sizeof(_textmsg_list[0]) * (msg_count - i));
		msg_count = MAX_CHAT_MESSAGES - lines;
	}

	for (bufp = buf; lines != 0; lines--) {
		TextMessage *tmsg = &_textmsg_list[msg_count++];
		ttd_strlcpy(tmsg->message, bufp, sizeof(tmsg->message));

		/* The default colour for a message is player colour. Replace this with
		 * white for any additional lines */
		tmsg->color = (bufp == buf && color & IS_PALETTE_COLOR) ? color : (0x1D - 15) | IS_PALETTE_COLOR;
		tmsg->end_date = _date + duration;

		bufp += strlen(bufp) + 1; // jump to 'next line' in the formatted string
	}

	_textmessage_dirty = true;
}

void InitTextMessage(void)
{
	uint i;

	for (i = 0; i < MAX_CHAT_MESSAGES; i++) {
		_textmsg_list[i].message[0] = '\0';
	}
}

// Hide the textbox
void UndrawTextMessage(void)
{
	if (_textmessage_visible) {
		// Sometimes we also need to hide the cursor
		//   This is because both textmessage and the cursor take a shot of the
		//   screen before drawing.
		//   Now the textmessage takes his shot and paints his data before the cursor
		//   does, so in the shot of the cursor is the screen-data of the textmessage
		//   included when the cursor hangs somewhere over the textmessage. To
		//   avoid wrong repaints, we undraw the cursor in that case, and everything
		//   looks nicely ;)
		// (and now hope this story above makes sense to you ;))

		if (_cursor.visible) {
			if (_cursor.draw_pos.x + _cursor.draw_size.x >= _textmsg_box.x &&
				_cursor.draw_pos.x <= _textmsg_box.x + _textmsg_box.width &&
				_cursor.draw_pos.y + _cursor.draw_size.y >= _screen.height - _textmsg_box.y - _textmsg_box.height &&
				_cursor.draw_pos.y <= _screen.height - _textmsg_box.y) {
				UndrawMouseCursor();
			}
		}

		_textmessage_visible = false;
		// Put our 'shot' back to the screen
		memcpy_pitch(
			_screen.dst_ptr + _textmsg_box.x + (_screen.height - _textmsg_box.y - _textmsg_box.height) * _screen.pitch,
			_textmessage_backup,
			_textmsg_box.width, _textmsg_box.height, _textmsg_box.width, _screen.pitch);

		// And make sure it is updated next time
		_video_driver->make_dirty(_textmsg_box.x, _screen.height - _textmsg_box.y - _textmsg_box.height, _textmsg_box.width, _textmsg_box.height);

		_textmessage_dirty = true;
	}
}

// Check if a message is expired every day
void TextMessageDailyLoop(void)
{
	uint i;

	for (i = 0; i < MAX_CHAT_MESSAGES; i++) {
		TextMessage *tmsg = &_textmsg_list[i];
		if (tmsg->message[0] == '\0') continue;

		/* Message has expired, remove from the list */
		if (tmsg->end_date < _date) {
			/* Move the remaining messages over the current message */
			if (i != MAX_CHAT_MESSAGES - 1) memmove(tmsg, tmsg + 1, sizeof(*tmsg) * (MAX_CHAT_MESSAGES - i - 1));

			/* Mark the last item as empty */
			_textmsg_list[MAX_CHAT_MESSAGES - 1].message[0] = '\0';
			_textmessage_dirty = true;

			/* Go one item back, because we moved the array 1 to the left */
			i--;
		}
	}
}

// Draw the textmessage-box
void DrawTextMessage(void)
{
	uint y, count;

	if (!_textmessage_dirty) return;

	// First undraw if needed
	UndrawTextMessage();

	if (_iconsole_mode == ICONSOLE_FULL) return;

	/* Check if we have anything to draw at all */
	count = GetTextMessageCount();
	if (count == 0) return;

	// Make a copy of the screen as it is before painting (for undraw)
	memcpy_pitch(
		_textmessage_backup,
		_screen.dst_ptr + _textmsg_box.x + (_screen.height - _textmsg_box.y - _textmsg_box.height) * _screen.pitch,
		_textmsg_box.width, _textmsg_box.height, _screen.pitch, _textmsg_box.width);

	_cur_dpi = &_screen; // switch to _screen painting

	/* Paint a half-transparent box behind the text messages */
	GfxFillRect(
			_textmsg_box.x,
			_screen.height - _textmsg_box.y - count * 13 - 2,
			_textmsg_box.x + _textmsg_box.width - 1,
			_screen.height - _textmsg_box.y - 2,
			0x322 | USE_COLORTABLE // black, but with some alpha for background
		);

	/* Paint the messages starting with the lowest at the bottom */
	for (y = 13; count-- != 0; y += 13) {
		DoDrawString(_textmsg_list[count].message, _textmsg_box.x + 3, _screen.height - _textmsg_box.y - y + 1, _textmsg_list[count].color);
 	}

	// Make sure the data is updated next flush
	_video_driver->make_dirty(_textmsg_box.x, _screen.height - _textmsg_box.y - _textmsg_box.height, _textmsg_box.width, _textmsg_box.height);

	_textmessage_visible = true;
	_textmessage_dirty = false;
}

static void MarkTextEffectAreaDirty(TextEffect *te)
{
	MarkAllViewportsDirty(
		te->x,
		te->y - 1,
		(te->right - te->x)*2 + te->x + 1,
		(te->bottom - (te->y - 1)) * 2 + (te->y - 1) + 1
	);
}

void AddTextEffect(StringID msg, int x, int y, uint16 duration)
{
	TextEffect *te;
	int w;
	char buffer[100];

	if (_game_mode == GM_MENU) return;

	for (te = _text_effect_list; te->string_id != INVALID_STRING_ID; ) {
		if (++te == endof(_text_effect_list)) return;
	}

	te->string_id = msg;
	te->duration = duration;
	te->y = y - 5;
	te->bottom = y + 5;
	te->params_1 = GetDParam(0);
	te->params_2 = GetDParam(4);

	GetString(buffer, msg, lastof(buffer));
	w = GetStringBoundingBox(buffer).width;

	te->x = x - (w >> 1);
	te->right = x + (w >> 1) - 1;
	MarkTextEffectAreaDirty(te);
}

static void MoveTextEffect(TextEffect *te)
{
	if (te->duration < 8) {
		te->string_id = INVALID_STRING_ID;
	} else {
		te->duration -= 8;
		te->y--;
		te->bottom--;
	}
	MarkTextEffectAreaDirty(te);
}

void MoveAllTextEffects(void)
{
	TextEffect *te;

	for (te = _text_effect_list; te != endof(_text_effect_list); te++) {
		if (te->string_id != INVALID_STRING_ID) MoveTextEffect(te);
	}
}

void InitTextEffects(void)
{
	TextEffect *te;

	for (te = _text_effect_list; te != endof(_text_effect_list); te++) {
		te->string_id = INVALID_STRING_ID;
	}
}

void DrawTextEffects(DrawPixelInfo *dpi)
{
	const TextEffect* te;

	switch (dpi->zoom) {
		case 0:
			for (te = _text_effect_list; te != endof(_text_effect_list); te++) {
				if (te->string_id != INVALID_STRING_ID &&
						dpi->left <= te->right &&
						dpi->top  <= te->bottom &&
						dpi->left + dpi->width  > te->x &&
						dpi->top  + dpi->height > te->y) {
					AddStringToDraw(te->x, te->y, te->string_id, te->params_1, te->params_2);
				}
			}
			break;

		case 1:
			for (te = _text_effect_list; te != endof(_text_effect_list); te++) {
				if (te->string_id != INVALID_STRING_ID &&
						dpi->left <= te->right  * 2 - te->x &&
						dpi->top  <= te->bottom * 2 - te->y &&
						dpi->left + dpi->width  > te->x &&
						dpi->top  + dpi->height > te->y) {
					AddStringToDraw(te->x, te->y, (StringID)(te->string_id-1), te->params_1, te->params_2);
				}
			}
			break;
	}
}

void DeleteAnimatedTile(TileIndex tile)
{
	TileIndex* ti;

	for (ti = _animated_tile_list; ti != endof(_animated_tile_list); ti++) {
		if (tile == *ti) {
			/* remove the hole */
			memmove(ti, ti + 1, (lastof(_animated_tile_list) - ti) * sizeof(_animated_tile_list[0]));
			/* and clear last item */
			endof(_animated_tile_list)[-1] = 0;
			MarkTileDirtyByTile(tile);
			return;
		}
	}
}

bool AddAnimatedTile(TileIndex tile)
{
	TileIndex* ti;

	for (ti = _animated_tile_list; ti != endof(_animated_tile_list); ti++) {
		if (tile == *ti || *ti == 0) {
			*ti = tile;
			MarkTileDirtyByTile(tile);
			return true;
		}
	}

	return false;
}

void AnimateAnimatedTiles(void)
{
	const TileIndex* ti;

	for (ti = _animated_tile_list; ti != endof(_animated_tile_list) && *ti != 0; ti++) {
		AnimateTile(*ti);
	}
}

void InitializeAnimatedTiles(void)
{
	memset(_animated_tile_list, 0, sizeof(_animated_tile_list));
}

static void SaveLoad_ANIT(void)
{
	// In pre version 6, we has 16bit per tile, now we have 32bit per tile, convert it ;)
	if (CheckSavegameVersion(6)) {
		SlArray(_animated_tile_list, lengthof(_animated_tile_list), SLE_FILE_U16 | SLE_VAR_U32);
	} else {
		SlArray(_animated_tile_list, lengthof(_animated_tile_list), SLE_UINT32);
	}
}


const ChunkHandler _animated_tile_chunk_handlers[] = {
	{ 'ANIT', SaveLoad_ANIT, SaveLoad_ANIT, CH_RIFF | CH_LAST},
};