diff --git a/src/industry_gui.cpp b/src/industry_gui.cpp index 12f9854576..76b9a8fea9 100644 --- a/src/industry_gui.cpp +++ b/src/industry_gui.cpp @@ -503,7 +503,10 @@ static void IndustryViewWndProc(Window *w, WindowEvent *e) StringID message = GetGRFStringID(ind->grf_prop.grffile->grfid, 0xD000 + callback_res); if (message != STR_NULL && message != STR_UNDEFINED) { y += 10; + + PrepareTextRefStackUsage(); DrawString(2, y, message, 0); + StopTextRefStackUsage(); } } } diff --git a/src/newgrf_spritegroup.cpp b/src/newgrf_spritegroup.cpp index 557c5dcfcb..9bc708cd6a 100644 --- a/src/newgrf_spritegroup.cpp +++ b/src/newgrf_spritegroup.cpp @@ -76,7 +76,7 @@ void InitializeSpriteGroupPool() _spritegroup_count = 0; } -TemporaryStorageArray _temp_store; +TemporaryStorageArray _temp_store; static inline uint32 GetVariable(const ResolverObject *object, byte variable, byte parameter, bool *available) diff --git a/src/newgrf_spritegroup.h b/src/newgrf_spritegroup.h index 3f84b9af9e..496ce5c36e 100644 --- a/src/newgrf_spritegroup.h +++ b/src/newgrf_spritegroup.h @@ -17,7 +17,7 @@ */ static inline uint32 GetRegister(uint i) { - extern TemporaryStorageArray _temp_store; + extern TemporaryStorageArray _temp_store; return _temp_store.Get(i); } diff --git a/src/newgrf_text.cpp b/src/newgrf_text.cpp index f9ef1ec5c3..067e080c50 100644 --- a/src/newgrf_text.cpp +++ b/src/newgrf_text.cpp @@ -21,6 +21,7 @@ #include "newgrf_text.h" #include "table/control_codes.h" #include "helpers.hpp" +#include "date.h" #define GRFTAB 28 #define TABSIZE 11 @@ -228,9 +229,9 @@ char *TranslateTTDPatchCodes(const char *str) case 0x7B: case 0x7C: case 0x7D: - case 0x7E: d += Utf8Encode(d, SCC_NUM); break; - case 0x7F: d += Utf8Encode(d, SCC_CURRENCY); break; - case 0x80: d += Utf8Encode(d, SCC_STRING); break; + case 0x7E: + case 0x7F: + case 0x80: d += Utf8Encode(d, SCC_NEWGRF_PRINT_DWORD + c - 0x7B); break; case 0x81: { StringID string; string = *str++; @@ -239,12 +240,12 @@ char *TranslateTTDPatchCodes(const char *str) d += Utf8Encode(d, string); break; } - case 0x82: d += Utf8Encode(d, SCC_DATE_TINY); break; - case 0x83: d += Utf8Encode(d, SCC_DATE_SHORT); break; - case 0x84: d += Utf8Encode(d, SCC_VELOCITY); break; - case 0x85: d += Utf8Encode(d, SCC_SKIP); break; - case 0x86: /* "Rotate down top 4 words on stack" */ break; - case 0x87: d += Utf8Encode(d, SCC_VOLUME); break; + case 0x82: + case 0x83: + case 0x84: d += Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_SPEED + c - 0x82); break; + case 0x85: d += Utf8Encode(d, SCC_NEWGRF_DISCARD_WORD); break; + case 0x86: d += Utf8Encode(d, SCC_NEWGRF_ROTATE_TOP_4_WORDS); break; + case 0x87: d += Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_LITRES); break; case 0x88: d += Utf8Encode(d, SCC_BLUE); break; case 0x89: d += Utf8Encode(d, SCC_SILVER); break; case 0x8A: d += Utf8Encode(d, SCC_GOLD); break; @@ -262,6 +263,20 @@ char *TranslateTTDPatchCodes(const char *str) case 0x96: d += Utf8Encode(d, SCC_GRAY); break; case 0x97: d += Utf8Encode(d, SCC_DKBLUE); break; case 0x98: d += Utf8Encode(d, SCC_BLACK); break; + case 0x9A: + switch (*str++) { + case 0: /* FALL THROUGH */ + case 1: d += Utf8Encode(d, SCC_NEWGRF_PRINT_QWORD_CURRENCY); break; + case 3: { + uint16 tmp = *str++; + tmp |= (*str++) << 8; + d += Utf8Encode(d, SCC_NEWGRF_PUSH_WORD); d += Utf8Encode(d, tmp); + } break; + case 4: d += Utf8Encode(d, SCC_NEWGRF_UNPRINT); d += Utf8Encode(d, *str++); break; + default: grfmsg(1, "missing handler for extended format code"); break; + } + break; + case 0x9E: d += Utf8Encode(d, 0x20AC); break; // Euro case 0x9F: d += Utf8Encode(d, 0x0178); break; // Y with diaeresis case 0xA0: d += Utf8Encode(d, SCC_UPARROW); break; @@ -481,3 +496,142 @@ void CleanUpStrings() _num_grf_texts = 0; } + +struct TextRefStack { + byte stack[0x30]; + byte position; + bool used; + + TextRefStack() : used(false) {} + + uint8 PopUnsignedByte() { assert(this->position < lengthof(this->stack)); return this->stack[this->position++]; } + int8 PopSignedByte() { return (int8)this->PopUnsignedByte(); } + + uint16 PopUnsignedWord() { return this->PopUnsignedByte() | (((uint16)this->PopUnsignedByte()) << 8); } + int16 PopSignedWord() { return (int32)this->PopUnsignedWord(); } + + uint32 PopUnsignedDWord() { return this->PopUnsignedWord() | (((uint32)this->PopUnsignedWord()) << 16); } + int32 PopSignedDWord() { return (int32)this->PopUnsignedDWord(); } + + uint64 PopUnsignedQWord() { return this->PopUnsignedDWord() | (((uint64)this->PopUnsignedDWord()) << 32); } + int64 PopSignedQWord() { return (int64)this->PopUnsignedQWord(); } + + /** Rotate the top four words down: W1, W2, W3, W4 -> W4, W1, W2, W3 */ + void RotateTop4Words() + { + byte tmp[2]; + for (int i = 0; i < 2; i++) tmp[i] = this->stack[this->position + i + 6]; + for (int i = 5; i >= 0; i--) this->stack[this->position + i + 2] = this->stack[this->position + i]; + for (int i = 0; i < 2; i++) this->stack[this->position + i] = tmp[i]; + } + + void PushWord(uint16 word) + { + if (this->position >= 2) { + this->position -= 2; + } else { + for (uint i = lengthof(stack) - 3; i >= this->position; i--) { + this->stack[this->position + 2] = this->stack[this->position]; + } + } + this->stack[this->position] = GB(word, 0, 8); + this->stack[this->position + 1] = GB(word, 8, 8); + } + + void ResetStack() { this->used = true; this->position = 0; } +}; + +/** The stack that is used for TTDP compatible string code parsing */ +static TextRefStack _newgrf_textrefstack; + +/** Prepare the TTDP compatible string code parsing */ +void PrepareTextRefStackUsage() +{ + extern TemporaryStorageArray _temp_store; + + _newgrf_textrefstack.ResetStack(); + + byte *p = _newgrf_textrefstack.stack; + for (uint i = 0; i < 6; i++) { + for (uint j = 0; j < 32; j += 8) { + *p = GB(_temp_store.Get(0x100 + i), 32 - j, 8); + p++; + } + } +} + +/** Stop using the TTDP compatible string code parsing */ +void StopTextRefStackUsage() { _newgrf_textrefstack.used = false; } + +/** + * FormatString for NewGRF specific "magic" string control codes + * @param scc the string control code that has been read + * @param stack the current "stack" + * @return the string control code to "execute" now + */ +uint RemapNewGRFStringControlCode(uint scc, char **buff, const char **str, int64 *argv) +{ + if (_newgrf_textrefstack.used) { + switch (scc) { + default: NOT_REACHED(); + case SCC_NEWGRF_PRINT_SIGNED_BYTE: *argv = _newgrf_textrefstack.PopSignedByte(); break; + case SCC_NEWGRF_PRINT_SIGNED_WORD: *argv = _newgrf_textrefstack.PopSignedWord(); break; + case SCC_NEWGRF_PRINT_QWORD_CURRENCY: *argv = _newgrf_textrefstack.PopUnsignedQWord(); break; + + case SCC_NEWGRF_PRINT_DWORD_CURRENCY: + case SCC_NEWGRF_PRINT_DWORD: *argv = _newgrf_textrefstack.PopSignedDWord(); break; + + case SCC_NEWGRF_PRINT_WORD_SPEED: + case SCC_NEWGRF_PRINT_WORD_LITRES: + case SCC_NEWGRF_PRINT_UNSIGNED_WORD: *argv = _newgrf_textrefstack.PopUnsignedWord(); break; + + case SCC_NEWGRF_PRINT_DATE: + case SCC_NEWGRF_PRINT_MONTH_YEAR: *argv = _newgrf_textrefstack.PopSignedWord() + DAYS_TILL_ORIGINAL_BASE_YEAR; break; + + case SCC_NEWGRF_DISCARD_WORD: _newgrf_textrefstack.PopUnsignedWord(); break; + + case SCC_NEWGRF_ROTATE_TOP_4_WORDS: _newgrf_textrefstack.RotateTop4Words(); break; + case SCC_NEWGRF_PUSH_WORD: _newgrf_textrefstack.PushWord(Utf8Consume(str)); break; + case SCC_NEWGRF_UNPRINT: *buff -= Utf8Consume(str); break; + + case SCC_NEWGRF_PRINT_STRING_ID: + *argv = _newgrf_textrefstack.PopUnsignedWord(); + if (*argv == STR_NULL) *argv = STR_EMPTY; + break; + } + } + + switch (scc) { + default: NOT_REACHED(); + case SCC_NEWGRF_PRINT_DWORD: + case SCC_NEWGRF_PRINT_SIGNED_WORD: + case SCC_NEWGRF_PRINT_SIGNED_BYTE: + case SCC_NEWGRF_PRINT_UNSIGNED_WORD: + return SCC_NUM; + + case SCC_NEWGRF_PRINT_DWORD_CURRENCY: + case SCC_NEWGRF_PRINT_QWORD_CURRENCY: + return SCC_CURRENCY; + + case SCC_NEWGRF_PRINT_STRING_ID: + return SCC_STRING; + + case SCC_NEWGRF_PRINT_DATE: + return SCC_DATE_LONG; + + case SCC_NEWGRF_PRINT_MONTH_YEAR: + return SCC_DATE_TINY; + + case SCC_NEWGRF_PRINT_WORD_SPEED: + return SCC_VELOCITY; + + case SCC_NEWGRF_PRINT_WORD_LITRES: + return SCC_VOLUME; + + case SCC_NEWGRF_DISCARD_WORD: + case SCC_NEWGRF_ROTATE_TOP_4_WORDS: + case SCC_NEWGRF_PUSH_WORD: + case SCC_NEWGRF_UNPRINT: + return 0; + } +} diff --git a/src/newgrf_text.h b/src/newgrf_text.h index ec0fcdc912..6db086d0e6 100644 --- a/src/newgrf_text.h +++ b/src/newgrf_text.h @@ -15,4 +15,8 @@ char *TranslateTTDPatchCodes(const char *str); bool CheckGrfLangID(byte lang_id, byte grf_version); +void PrepareTextRefStackUsage(); +void StopTextRefStackUsage(); +uint RemapNewGRFStringControlCode(uint scc, char **buff, const char **str, int64 *argv); + #endif /* NEWGRF_TEXT_H */ diff --git a/src/strings.cpp b/src/strings.cpp index 6916d2aaa5..a82bdd15a1 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -531,6 +531,12 @@ static char* FormatString(char* buff, const char* str, const int64* argv, uint c uint modifier = 0; while ((b = Utf8Consume(&str)) != '\0') { + if (SCC_NEWGRF_FIRST <= b && b <= SCC_NEWGRF_LAST) { + /* We need to pass some stuff as it might be modified; oh boy. */ + b = RemapNewGRFStringControlCode(b, &buff, &str, (int64*)argv); + if (b == 0) continue; + } + switch (b) { case SCC_SETX: // {SETX} if (buff + Utf8CharLen(SCC_SETX) + 1 < last) { diff --git a/src/table/control_codes.h b/src/table/control_codes.h index e853ca8981..5c1ae73e72 100644 --- a/src/table/control_codes.h +++ b/src/table/control_codes.h @@ -6,7 +6,7 @@ /* List of string control codes used for string formatting, displaying, and * by strgen to generate the language files. */ -enum { +enum StringControlCode { SCC_CONTROL_START = 0xE000, SCC_CONTROL_END = 0xE1FF, @@ -90,6 +90,28 @@ enum { SCC_BLACK, SCC_PREVIOUS_COLOUR, + /** + * The next variables are part of a NewGRF subsystem for creating text strings. + * It uses a "stack" of bytes and reads from there. + */ + SCC_NEWGRF_FIRST, + SCC_NEWGRF_PRINT_DWORD = SCC_NEWGRF_FIRST, ///< Read 4 bytes from the stack + SCC_NEWGRF_PRINT_SIGNED_WORD, ///< Read 2 bytes from the stack as signed value + SCC_NEWGRF_PRINT_SIGNED_BYTE, ///< Read 1 bytes from the stack as signed value + SCC_NEWGRF_PRINT_UNSIGNED_WORD, ///< Read 2 bytes from the stack as unsigned value + SCC_NEWGRF_PRINT_DWORD_CURRENCY, ///< Read 4 bytes from the stack as currency + SCC_NEWGRF_PRINT_STRING_ID, ///< Read 2 bytes from the stack as String ID + SCC_NEWGRF_PRINT_DATE, ///< Read 2 bytes from the stack as base 1920 date + SCC_NEWGRF_PRINT_MONTH_YEAR, ///< Read 2 bytes from the stack as base 1920 date + SCC_NEWGRF_PRINT_WORD_SPEED, ///< Read 2 bytes from the stack as signed speed + SCC_NEWGRF_PRINT_WORD_LITRES, ///< Read 2 bytes from the stack as signed litres + SCC_NEWGRF_PRINT_QWORD_CURRENCY, ///< Read 8 bytes from the stack as currency + SCC_NEWGRF_PUSH_WORD, ///< Pushes 2 bytes onto the stack + SCC_NEWGRF_UNPRINT, ///< "Unprints" the given number of bytes from the string + SCC_NEWGRF_DISCARD_WORD, ///< Discard the next two bytes + SCC_NEWGRF_ROTATE_TOP_4_WORDS, ///< Rotate the top 4 words of the stack (W4 W1 W2 W3) + SCC_NEWGRF_LAST = SCC_NEWGRF_ROTATE_TOP_4_WORDS, + /* Special printable symbols. * These are mapped to the original glyphs */ SCC_LESSTHAN = SCC_SPRITE_START + 0x3C,