1
0
Fork 0

Codechange: Preprocess text ref stack parameters. (#13642)

NewGRF text ref stack is now processed in advance, creating parameters as necessary, and then encoding this into an EncodedString.
pull/13625/head
Peter Nelson 2025-02-22 22:03:38 +00:00 committed by GitHub
parent 4ac81656ee
commit b28dca2222
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 168 additions and 354 deletions

View File

@ -804,10 +804,7 @@ static std::optional<std::string> GetNewGRFAdditionalText(EngineID engine)
return std::nullopt;
}
StartTextRefStackUsage(grffile, 6);
std::string result = GetString(GetGRFStringID(grffile->grfid, GRFSTR_MISC_GRF_TEXT + callback));
StopTextRefStackUsage();
return result;
return GetGRFStringWithTextStack(grffile, GRFSTR_MISC_GRF_TEXT + callback, 6);
}
/**

View File

@ -407,32 +407,8 @@ void CommandCost::AddCost(const CommandCost &ret)
}
}
/**
* Values to put on the #TextRefStack for the error message.
* There is only one static instance of the array, just like there is only one
* instance of normal DParams.
*/
/* static */ uint32_t CommandCost::textref_stack[16];
/* static */ EncodedString CommandCost::encoded_message;
/**
* Activate usage of the NewGRF #TextRefStack for the error message.
* @param grffile NewGRF that provides the #TextRefStack
* @param num_registers number of entries to copy from the temporary NewGRF registers
*/
void CommandCost::UseTextRefStack(const GRFFile *grffile, uint num_registers)
{
extern TemporaryStorageArray<int32_t, 0x110> _temp_store;
assert(num_registers < lengthof(textref_stack));
this->textref_stack_grffile = grffile;
this->textref_stack_size = num_registers;
for (uint i = 0; i < num_registers; i++) {
textref_stack[i] = _temp_store.GetValue(0x100 + i);
}
}
/**
* Return an error status, with string and parameter.
* @param str StringID of error.

View File

@ -27,12 +27,8 @@ class CommandCost {
ExpensesType expense_type; ///< the type of expence as shown on the finances view
bool success; ///< Whether the command went fine up to this moment
Owner owner = CompanyID::Invalid(); ///< Originator owner of error.
const GRFFile *textref_stack_grffile = nullptr; ///< NewGRF providing the #TextRefStack content.
uint textref_stack_size = 0; ///< Number of uint32_t values to put on the #TextRefStack for the error message.
StringID extra_message = INVALID_STRING_ID; ///< Additional warning message for when success is unset
static uint32_t textref_stack[16];
static EncodedString encoded_message; ///< Encoded error message, used if the error message includes parameters.
public:
@ -146,35 +142,6 @@ public:
this->extra_message = INVALID_STRING_ID;
}
void UseTextRefStack(const GRFFile *grffile, uint num_registers);
/**
* Returns the NewGRF providing the #TextRefStack of the error message.
* @return the NewGRF.
*/
const GRFFile *GetTextRefStackGRF() const
{
return this->textref_stack_grffile;
}
/**
* Returns the number of uint32_t values for the #TextRefStack of the error message.
* @return number of uint32_t values.
*/
uint GetTextRefStackSize() const
{
return this->textref_stack_size;
}
/**
* Returns a pointer to the values for the #TextRefStack of the error message.
* @return uint32_t values for the #TextRefStack
*/
const uint32_t *GetTextRefStack() const
{
return textref_stack;
}
/**
* Returns the error message of a command
* @return the error message, if succeeded #INVALID_STRING_ID

View File

@ -31,9 +31,6 @@ enum WarningLevel : uint8_t {
class ErrorMessageData {
protected:
bool is_critical; ///< Whether the error message is critical.
const GRFFile *textref_stack_grffile; ///< NewGRF that filled the #TextRefStack for the error message.
uint textref_stack_size; ///< Number of uint32_t values to put on the #TextRefStack for the error message.
uint32_t textref_stack[16]; ///< Values to put on the #TextRefStack for the error message.
EncodedString summary_msg; ///< General error message showed in first line. Must be valid.
EncodedString detailed_msg; ///< Detailed error message showed in second line. Can be #INVALID_STRING_ID.
EncodedString extra_msg; ///< Extra error message shown in third line. Can be #INVALID_STRING_ID.
@ -41,11 +38,7 @@ protected:
CompanyID company; ///< Company belonging to the face being shown. #CompanyID::Invalid() if no face present.
public:
ErrorMessageData(const ErrorMessageData &data);
ErrorMessageData(EncodedString &&summary_msg, EncodedString &&detailed_msg, bool is_critical = false, int x = 0, int y = 0, const GRFFile *textref_stack_grffile = nullptr, uint textref_stack_size = 0, const uint32_t *textref_stack = nullptr, EncodedString &&extra_msg = {}, CompanyID company = CompanyID::Invalid());
/* Remove the copy assignment, as the default implementation will not do the right thing. */
ErrorMessageData &operator=(ErrorMessageData &rhs) = delete;
ErrorMessageData(EncodedString &&summary_msg, EncodedString &&detailed_msg, bool is_critical = false, int x = 0, int y = 0, EncodedString &&extra_msg = {}, CompanyID company = CompanyID::Invalid());
/** Check whether error window shall display a company manager face */
bool HasFace() const { return company != CompanyID::Invalid(); }
@ -58,7 +51,7 @@ void ScheduleErrorMessage(ErrorList &datas);
void ScheduleErrorMessage(const ErrorMessageData &data);
void ShowErrorMessage(EncodedString &&summary_msg, int x, int y, const CommandCost &cc);
void ShowErrorMessage(EncodedString &&summary_msg, EncodedString &&detailed_msg, WarningLevel wl, int x = 0, int y = 0, const GRFFile *textref_stack_grffile = nullptr, uint textref_stack_size = 0, const uint32_t *textref_stack = nullptr, EncodedString &&extra_msg = {}, CompanyID company = CompanyID::Invalid());
void ShowErrorMessage(EncodedString &&summary_msg, EncodedString &&detailed_msg, WarningLevel wl, int x = 0, int y = 0, EncodedString &&extra_msg = {}, CompanyID company = CompanyID::Invalid());
bool HideActiveErrorMessage();
void ClearErrorMessages();

View File

@ -70,17 +70,6 @@ static WindowDesc _errmsg_face_desc(
_nested_errmsg_face_widgets
);
/**
* Copy the given data into our instance.
* @param data The data to copy.
*/
ErrorMessageData::ErrorMessageData(const ErrorMessageData &data) :
is_critical(data.is_critical), textref_stack_grffile(data.textref_stack_grffile), textref_stack_size(data.textref_stack_size),
summary_msg(data.summary_msg), detailed_msg(data.detailed_msg), extra_msg(data.extra_msg), position(data.position), company(data.company)
{
memcpy(this->textref_stack, data.textref_stack, sizeof(this->textref_stack));
}
/**
* Display an error message in a window.
* @param summary_msg General error message showed in first line. Must be valid.
@ -88,25 +77,16 @@ ErrorMessageData::ErrorMessageData(const ErrorMessageData &data) :
* @param is_critical Whether the error is critical. Critical messages never go away on their own.
* @param x World X position (TileVirtX) of the error location. Set both x and y to 0 to just center the message when there is no related error tile.
* @param y World Y position (TileVirtY) of the error location. Set both x and y to 0 to just center the message when there is no related error tile.
* @param textref_stack_grffile NewGRF that provides the #TextRefStack for the error message.
* @param textref_stack_size Number of uint32_t values to put on the #TextRefStack for the error message; 0 if the #TextRefStack shall not be used.
* @param textref_stack Values to put on the #TextRefStack.
* @param extra_msg Extra error message showed in third line. Can be empty.
*/
ErrorMessageData::ErrorMessageData(EncodedString &&summary_msg, EncodedString &&detailed_msg, bool is_critical, int x, int y, const GRFFile *textref_stack_grffile, uint textref_stack_size, const uint32_t *textref_stack, EncodedString &&extra_msg, CompanyID company) :
ErrorMessageData::ErrorMessageData(EncodedString &&summary_msg, EncodedString &&detailed_msg, bool is_critical, int x, int y, EncodedString &&extra_msg, CompanyID company) :
is_critical(is_critical),
textref_stack_grffile(textref_stack_grffile),
textref_stack_size(textref_stack_size),
summary_msg(std::move(summary_msg)),
detailed_msg(std::move(detailed_msg)),
extra_msg(std::move(extra_msg)),
position(x, y),
company(company)
{
this->position.x = x;
this->position.y = y;
if (textref_stack_size > 0) MemCpyT(this->textref_stack, textref_stack, textref_stack_size);
assert(!this->summary_msg.empty());
}
@ -143,14 +123,10 @@ public:
{
switch (widget) {
case WID_EM_MESSAGE: {
if (this->textref_stack_size > 0) StartTextRefStackUsage(this->textref_stack_grffile, this->textref_stack_size, this->textref_stack);
this->height_summary = GetStringHeight(this->summary_msg.GetDecodedString(), size.width);
this->height_detailed = (this->detailed_msg.empty()) ? 0 : GetStringHeight(this->detailed_msg.GetDecodedString(), size.width);
this->height_extra = (this->extra_msg.empty()) ? 0 : GetStringHeight(this->extra_msg.GetDecodedString(), size.width);
if (this->textref_stack_size > 0) StopTextRefStackUsage();
uint panel_height = this->height_summary;
if (!this->detailed_msg.empty()) panel_height += this->height_detailed + WidgetDimensions::scaled.vsep_wide;
if (!this->extra_msg.empty()) panel_height += this->height_extra + WidgetDimensions::scaled.vsep_wide;
@ -215,8 +191,6 @@ public:
}
case WID_EM_MESSAGE:
if (this->textref_stack_size > 0) StartTextRefStackUsage(this->textref_stack_grffile, this->textref_stack_size, this->textref_stack);
if (this->detailed_msg.empty()) {
DrawStringMultiLine(r, this->summary_msg.GetDecodedString(), TC_FROMSTRING, SA_CENTER);
} else if (this->extra_msg.empty()) {
@ -239,7 +213,6 @@ public:
DrawStringMultiLine(bottom_section, this->extra_msg.GetDecodedString(), TC_WHITE, SA_CENTER);
}
if (this->textref_stack_size > 0) StopTextRefStackUsage();
break;
default:
@ -310,7 +283,7 @@ void UnshowCriticalError()
* @param summary_msg General error message showed in first line. Must be valid.
* @param x World X position (TileVirtX) of the error location. Set both x and y to 0 to just center the message when there is no related error tile.
* @param y World Y position (TileVirtY) of the error location. Set both x and y to 0 to just center the message when there is no related error tile.
* @param cc CommandCost containing the optional detailed and extra error messages shown in the second and third lines (can be empty) and TextRefStack info.
* @param cc CommandCost containing the optional detailed and extra error messages shown in the second and third lines (can be empty).
*/
void ShowErrorMessage(EncodedString &&summary_msg, int x, int y, const CommandCost &cc)
{
@ -318,7 +291,6 @@ void ShowErrorMessage(EncodedString &&summary_msg, int x, int y, const CommandCo
if (error.empty()) error = GetEncodedStringIfValid(cc.GetErrorMessage());
ShowErrorMessage(std::move(summary_msg), std::move(error), WL_INFO, x, y,
cc.GetTextRefStackGRF(), cc.GetTextRefStackSize(), cc.GetTextRefStack(),
GetEncodedStringIfValid(cc.GetExtraErrorMessage()), cc.GetErrorOwner());
}
@ -329,20 +301,13 @@ void ShowErrorMessage(EncodedString &&summary_msg, int x, int y, const CommandCo
* @param wl Message severity.
* @param x World X position (TileVirtX) of the error location. Set both x and y to 0 to just center the message when there is no related error tile.
* @param y World Y position (TileVirtY) of the error location. Set both x and y to 0 to just center the message when there is no related error tile.
* @param textref_stack_grffile NewGRF providing the #TextRefStack for the error message.
* @param textref_stack_size Number of uint32_t values to put on the #TextRefStack for the error message; 0 if the #TextRefStack shall not be used.
* @param textref_stack Values to put on the #TextRefStack.
* @param extra_msg Extra error message shown in third line. Can be empty.
*/
void ShowErrorMessage(EncodedString &&summary_msg, EncodedString &&detailed_msg, WarningLevel wl, int x, int y, const GRFFile *textref_stack_grffile, uint textref_stack_size, const uint32_t *textref_stack, EncodedString &&extra_msg, CompanyID company)
void ShowErrorMessage(EncodedString &&summary_msg, EncodedString &&detailed_msg, WarningLevel wl, int x, int y, EncodedString &&extra_msg, CompanyID company)
{
assert(textref_stack_size == 0 || (textref_stack_grffile != nullptr && textref_stack != nullptr));
if (wl != WL_INFO) {
/* Print message to console */
if (textref_stack_size > 0) StartTextRefStackUsage(textref_stack_grffile, textref_stack_size, textref_stack);
std::string message = summary_msg.GetDecodedString();
if (!detailed_msg.empty()) {
message += " ";
@ -353,8 +318,6 @@ void ShowErrorMessage(EncodedString &&summary_msg, EncodedString &&detailed_msg,
message += extra_msg.GetDecodedString();
}
if (textref_stack_size > 0) StopTextRefStackUsage();
IConsolePrint(wl == WL_WARNING ? CC_WARNING : CC_ERROR, message);
}
@ -363,7 +326,7 @@ void ShowErrorMessage(EncodedString &&summary_msg, EncodedString &&detailed_msg,
if (_game_mode == GM_BOOTSTRAP) return;
if (_settings_client.gui.errmsg_duration == 0 && !is_critical) return;
ErrorMessageData data(std::move(summary_msg), std::move(detailed_msg), is_critical, x, y, textref_stack_grffile, textref_stack_size, textref_stack, std::move(extra_msg), company);
ErrorMessageData data(std::move(summary_msg), std::move(detailed_msg), is_critical, x, y, std::move(extra_msg), company);
ErrmsgWindow *w = dynamic_cast<ErrmsgWindow *>(FindWindowById(WC_ERRMSG, 0));
if (w != nullptr) {

View File

@ -104,9 +104,7 @@ static void GetCargoSuffix(uint cargo, CargoSuffixType cst, const Industry *ind,
if (indspec->grf_prop.grffile->grf_version < 8) {
if (GB(callback, 0, 8) == 0xFF) return;
if (callback < 0x400) {
StartTextRefStackUsage(indspec->grf_prop.grffile, 6);
suffix.text = GetString(GetGRFStringID(indspec->grf_prop.grfid, GRFSTR_MISC_GRF_TEXT + callback));
StopTextRefStackUsage();
suffix.text = GetGRFStringWithTextStack(indspec->grf_prop.grffile, GRFSTR_MISC_GRF_TEXT + callback, 6);
suffix.display = CSD_CARGO_AMOUNT_TEXT;
return;
}
@ -120,16 +118,12 @@ static void GetCargoSuffix(uint cargo, CargoSuffixType cst, const Industry *ind,
return;
}
if (callback < 0x400) {
StartTextRefStackUsage(indspec->grf_prop.grffile, 6);
suffix.text = GetString(GetGRFStringID(indspec->grf_prop.grfid, GRFSTR_MISC_GRF_TEXT + callback));
StopTextRefStackUsage();
suffix.text = GetGRFStringWithTextStack(indspec->grf_prop.grffile, GRFSTR_MISC_GRF_TEXT + callback, 6);
suffix.display = CSD_CARGO_AMOUNT_TEXT;
return;
}
if (callback >= 0x800 && callback < 0xC00) {
StartTextRefStackUsage(indspec->grf_prop.grffile, 6);
suffix.text = GetString(GetGRFStringID(indspec->grf_prop.grfid, GRFSTR_MISC_GRF_TEXT + callback - 0x800));
StopTextRefStackUsage();
suffix.text = GetGRFStringWithTextStack(indspec->grf_prop.grffile, GRFSTR_MISC_GRF_TEXT + callback - 0x800, 6);
suffix.display = CSD_CARGO_TEXT;
return;
}
@ -604,11 +598,9 @@ public:
if (callback_res > 0x400) {
ErrorUnknownCallbackResult(indsp->grf_prop.grfid, CBID_INDUSTRY_FUND_MORE_TEXT, callback_res);
} else {
StringID str = GetGRFStringID(indsp->grf_prop.grfid, GRFSTR_MISC_GRF_TEXT + callback_res); // No. here's the new string
if (str != STR_UNDEFINED) {
StartTextRefStackUsage(indsp->grf_prop.grffile, 6);
std::string str = GetGRFStringWithTextStack(indsp->grf_prop.grffile, GRFSTR_MISC_GRF_TEXT + callback_res, 6);
if (!str.empty()) {
DrawStringMultiLine(ir, str, TC_YELLOW);
StopTextRefStackUsage();
}
}
}
@ -1006,13 +998,10 @@ public:
if (callback_res > 0x400) {
ErrorUnknownCallbackResult(ind->grf_prop.grfid, CBID_INDUSTRY_WINDOW_MORE_TEXT, callback_res);
} else {
StringID message = GetGRFStringID(ind->grf_prop.grfid, GRFSTR_MISC_GRF_TEXT + callback_res);
if (message != STR_NULL && message != STR_UNDEFINED) {
std::string str = GetGRFStringWithTextStack(ind->grf_prop.grffile, GRFSTR_MISC_GRF_TEXT + callback_res, 6);
if (!str.empty()) {
ir.top += WidgetDimensions::scaled.vsep_wide;
StartTextRefStackUsage(ind->grf_prop.grffile, 6);
ir.top = DrawStringMultiLine(ir, message, TC_BLACK);
StopTextRefStackUsage();
ir.top = DrawStringMultiLine(ir, str, TC_YELLOW);
}
}
}

View File

@ -473,6 +473,13 @@ CommandCost GetErrorMessageFromLocationCallbackResult(uint16_t cb_res, const GRF
if (cb_res < 0x400) {
res = CommandCost(GetGRFStringID(grffile->grfid, GRFSTR_MISC_GRF_TEXT + cb_res));
/* If this error isn't for the local player then it won't be seen, so don't bother encoding anything. */
if (!IsLocalCompany()) return res;
StringID stringid = GetGRFStringID(grffile->grfid, GRFSTR_MISC_GRF_TEXT + cb_res);
auto params = GetGRFSringTextStackParameters(grffile, stringid, 4);
res.SetEncodedMessage(GetEncodedStringWithArgs(stringid, params));
} else {
switch (cb_res) {
case 0x400: return res; // No error.
@ -490,14 +497,6 @@ CommandCost GetErrorMessageFromLocationCallbackResult(uint16_t cb_res, const GRF
}
}
/* If this error isn't for the local player then it won't be seen, so don't bother encoding anything. */
if (!IsLocalCompany()) return res;
/* Copy some parameters from the registers to the error message text ref. stack */
std::array<StringParameter, 20> params{};
res.UseTextRefStack(grffile, 4);
res.SetEncodedMessage(GetEncodedStringWithArgs(res.GetErrorMessage(), params));
return res;
}

View File

@ -17,6 +17,7 @@
#include "stdafx.h"
#include "debug.h"
#include "newgrf.h"
#include "strings_internal.h"
#include "newgrf_storage.h"
@ -685,12 +686,24 @@ void CleanUpStrings()
}
struct TextRefStack {
std::array<uint8_t, 0x30> stack;
uint8_t position;
const GRFFile *grffile;
bool used;
std::array<uint8_t, 0x30> stack{};
uint8_t position = 0;
const GRFFile *grffile = nullptr;
TextRefStack() : position(0), grffile(nullptr), used(false) {}
TextRefStack(const GRFFile *grffile, uint8_t num_entries) : grffile(grffile)
{
extern TemporaryStorageArray<int32_t, 0x110> _temp_store;
assert(num_entries < sizeof(uint32_t) * std::size(stack));
auto stack_it = this->stack.begin();
for (uint i = 0; i < num_entries; i++) {
uint32_t value = _temp_store.GetValue(0x100 + i);
for (uint j = 0; j < 32; j += 8) {
*stack_it++ = GB(value, j, 8);
}
}
}
uint8_t PopUnsignedByte() { assert(this->position < this->stack.size()); return this->stack[this->position++]; }
int8_t PopSignedByte() { return (int8_t)this->PopUnsignedByte(); }
@ -736,213 +749,95 @@ struct TextRefStack {
this->stack[this->position] = GB(word, 0, 8);
this->stack[this->position + 1] = GB(word, 8, 8);
}
void ResetStack(const GRFFile *grffile)
{
assert(grffile != nullptr);
this->position = 0;
this->grffile = grffile;
this->used = true;
}
};
/** The stack that is used for TTDP compatible string code parsing */
static TextRefStack _newgrf_textrefstack;
static void HandleNewGRFStringControlCodes(const char *str, TextRefStack &stack, std::vector<StringParameter> &params);
/**
* Check whether the NewGRF text stack is in use.
* @return True iff the NewGRF text stack is used.
* Process NewGRF string control code instructions.
* @param scc The string control code that has been read.
* @param str The string that we are reading from.
* @param stack The TextRefStack.
* @param[out] params Output parameters
*/
bool UsingNewGRFTextStack()
static void RemapNewGRFStringControlCode(char32_t scc, const char **str, TextRefStack &stack, std::vector<StringParameter> &params)
{
return _newgrf_textrefstack.used;
}
auto it = std::back_inserter(params);
/**
* Create a backup of the current NewGRF text stack.
* @return A copy of the current text stack.
*/
struct TextRefStack *CreateTextRefStackBackup()
{
return new TextRefStack(_newgrf_textrefstack);
}
/**
* Restore a copy of the text stack to the used stack.
* @param backup The copy to restore.
*/
void RestoreTextRefStackBackup(struct TextRefStack *backup)
{
_newgrf_textrefstack = *backup;
delete backup;
}
/**
* Start using the TTDP compatible string code parsing.
*
* On start a number of values is copied on the #TextRefStack.
* You can then use #GetString() and the normal string drawing functions,
* and they will use the #TextRefStack for NewGRF string codes.
*
* However, when you want to draw a string multiple times using the same stack,
* you have to call #RewindTextRefStack() between draws.
*
* After you are done with drawing, you must disable usage of the #TextRefStack
* by calling #StopTextRefStackUsage(), so NewGRF string codes operate on the
* normal string parameters again.
*
* @param grffile the NewGRF providing the stack data
* @param numEntries number of entries to copy from the registers
* @param values values to copy onto the stack; if nullptr the temporary NewGRF registers will be used instead
*/
void StartTextRefStackUsage(const GRFFile *grffile, uint8_t numEntries, const uint32_t *values)
{
extern TemporaryStorageArray<int32_t, 0x110> _temp_store;
_newgrf_textrefstack.ResetStack(grffile);
auto stack_it = _newgrf_textrefstack.stack.begin();
for (uint i = 0; i < numEntries; i++) {
uint32_t value = values != nullptr ? values[i] : _temp_store.GetValue(0x100 + i);
for (uint j = 0; j < 32; j += 8) {
*stack_it = GB(value, j, 8);
stack_it++;
}
}
}
/** 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 str the string that we need to write
* @param parameters the OpenTTD string formatting parameters
* @param modify_parameters When true, modify the OpenTTD string formatting parameters.
* @return the string control code to "execute" now
*/
char32_t RemapNewGRFStringControlCode(char32_t scc, const char **str, StringParameters &parameters, bool modify_parameters)
{
/* There is data on the NewGRF text stack, and we want to move them to OpenTTD's string stack.
* After this call, a new call is made with `modify_parameters` set to false when the string is finally formatted. */
switch (scc) {
default: break;
default: return;
case SCC_NEWGRF_PRINT_BYTE_SIGNED: *it = stack.PopSignedByte(); break;
case SCC_NEWGRF_PRINT_QWORD_CURRENCY: *it = stack.PopSignedQWord(); break;
/* These control codes take one string parameter, check there are at least that many available. */
case SCC_NEWGRF_PRINT_DWORD_SIGNED:
case SCC_NEWGRF_PRINT_WORD_SIGNED:
case SCC_NEWGRF_PRINT_BYTE_SIGNED:
case SCC_NEWGRF_PRINT_WORD_UNSIGNED:
case SCC_NEWGRF_PRINT_BYTE_HEX:
case SCC_NEWGRF_PRINT_WORD_HEX:
case SCC_NEWGRF_PRINT_DWORD_HEX:
case SCC_NEWGRF_PRINT_QWORD_HEX:
case SCC_NEWGRF_PRINT_DWORD_CURRENCY:
case SCC_NEWGRF_PRINT_QWORD_CURRENCY:
case SCC_NEWGRF_PRINT_WORD_STRING_ID:
case SCC_NEWGRF_PRINT_WORD_DATE_LONG:
case SCC_NEWGRF_PRINT_DWORD_DATE_LONG:
case SCC_NEWGRF_PRINT_WORD_DATE_SHORT:
case SCC_NEWGRF_PRINT_DWORD_DATE_SHORT:
case SCC_NEWGRF_PRINT_DWORD_SIGNED: *it = stack.PopSignedDWord(); break;
case SCC_NEWGRF_PRINT_BYTE_HEX: *it = stack.PopUnsignedByte(); break;
case SCC_NEWGRF_PRINT_QWORD_HEX: *it = stack.PopUnsignedQWord(); break;
case SCC_NEWGRF_PRINT_WORD_SPEED:
case SCC_NEWGRF_PRINT_WORD_VOLUME_LONG:
case SCC_NEWGRF_PRINT_WORD_VOLUME_SHORT:
case SCC_NEWGRF_PRINT_WORD_SIGNED: *it = stack.PopSignedWord(); break;
case SCC_NEWGRF_PRINT_WORD_HEX:
case SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG:
case SCC_NEWGRF_PRINT_WORD_WEIGHT_SHORT:
case SCC_NEWGRF_PRINT_WORD_POWER:
case SCC_NEWGRF_PRINT_DWORD_FORCE:
case SCC_NEWGRF_PRINT_WORD_STATION_NAME:
case SCC_NEWGRF_PRINT_WORD_CARGO_NAME:
if (parameters.GetDataLeft() < 1) {
Debug(misc, 0, "Too many NewGRF string parameters.");
return 0;
}
break;
case SCC_NEWGRF_PRINT_WORD_UNSIGNED: *it = stack.PopUnsignedWord(); break;
case SCC_NEWGRF_PRINT_DWORD_FORCE:
case SCC_NEWGRF_PRINT_DWORD_DATE_LONG:
case SCC_NEWGRF_PRINT_DWORD_DATE_SHORT:
case SCC_NEWGRF_PRINT_DWORD_HEX: *it = stack.PopUnsignedDWord(); break;
/* Dates from NewGRFs have 1920-01-01 as their zero point, convert it to OpenTTD's epoch. */
case SCC_NEWGRF_PRINT_WORD_DATE_LONG:
case SCC_NEWGRF_PRINT_WORD_DATE_SHORT: *it = CalendarTime::DAYS_TILL_ORIGINAL_BASE_YEAR + stack.PopUnsignedWord(); break;
case SCC_NEWGRF_DISCARD_WORD: stack.PopUnsignedWord(); break;
case SCC_NEWGRF_ROTATE_TOP_4_WORDS: stack.RotateTop4Words(); break;
case SCC_NEWGRF_PUSH_WORD: stack.PushWord(Utf8Consume(str)); break;
/* These string code take two string parameters, check there are at least that many available. */
case SCC_NEWGRF_PRINT_WORD_CARGO_LONG:
case SCC_NEWGRF_PRINT_WORD_CARGO_SHORT:
case SCC_NEWGRF_PRINT_WORD_CARGO_TINY:
if (parameters.GetDataLeft() < 2) {
Debug(misc, 0, "Too many NewGRF string parameters.");
return 0;
}
*it = GetCargoTranslation(stack.PopUnsignedWord(), stack.grffile);
*it = stack.PopUnsignedWord();
break;
}
if (_newgrf_textrefstack.used && modify_parameters) {
/* There is data on the NewGRF text stack, and we want to move them to OpenTTD's string stack.
* After this call, a new call is made with `modify_parameters` set to false when the string is finally formatted. */
switch (scc) {
default: NOT_REACHED();
case SCC_NEWGRF_PRINT_BYTE_SIGNED: parameters.SetParam(0, _newgrf_textrefstack.PopSignedByte()); break;
case SCC_NEWGRF_PRINT_QWORD_CURRENCY: parameters.SetParam(0, _newgrf_textrefstack.PopSignedQWord()); break;
case SCC_NEWGRF_PRINT_DWORD_CURRENCY:
case SCC_NEWGRF_PRINT_DWORD_SIGNED: parameters.SetParam(0, _newgrf_textrefstack.PopSignedDWord()); break;
case SCC_NEWGRF_PRINT_BYTE_HEX: parameters.SetParam(0, _newgrf_textrefstack.PopUnsignedByte()); break;
case SCC_NEWGRF_PRINT_QWORD_HEX: parameters.SetParam(0, _newgrf_textrefstack.PopUnsignedQWord()); break;
case SCC_NEWGRF_PRINT_WORD_SPEED:
case SCC_NEWGRF_PRINT_WORD_VOLUME_LONG:
case SCC_NEWGRF_PRINT_WORD_VOLUME_SHORT:
case SCC_NEWGRF_PRINT_WORD_SIGNED: parameters.SetParam(0, _newgrf_textrefstack.PopSignedWord()); break;
case SCC_NEWGRF_PRINT_WORD_HEX:
case SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG:
case SCC_NEWGRF_PRINT_WORD_WEIGHT_SHORT:
case SCC_NEWGRF_PRINT_WORD_POWER:
case SCC_NEWGRF_PRINT_WORD_STATION_NAME:
case SCC_NEWGRF_PRINT_WORD_UNSIGNED: parameters.SetParam(0, _newgrf_textrefstack.PopUnsignedWord()); break;
case SCC_NEWGRF_PRINT_DWORD_FORCE:
case SCC_NEWGRF_PRINT_DWORD_DATE_LONG:
case SCC_NEWGRF_PRINT_DWORD_DATE_SHORT:
case SCC_NEWGRF_PRINT_DWORD_HEX: parameters.SetParam(0, _newgrf_textrefstack.PopUnsignedDWord()); break;
/* Dates from NewGRFs have 1920-01-01 as their zero point, convert it to OpenTTD's epoch. */
case SCC_NEWGRF_PRINT_WORD_DATE_LONG:
case SCC_NEWGRF_PRINT_WORD_DATE_SHORT: parameters.SetParam(0, CalendarTime::DAYS_TILL_ORIGINAL_BASE_YEAR + _newgrf_textrefstack.PopUnsignedWord()); 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_PRINT_WORD_CARGO_LONG:
case SCC_NEWGRF_PRINT_WORD_CARGO_SHORT:
case SCC_NEWGRF_PRINT_WORD_CARGO_TINY:
parameters.SetParam(0, GetCargoTranslation(_newgrf_textrefstack.PopUnsignedWord(), _newgrf_textrefstack.grffile));
parameters.SetParam(1, _newgrf_textrefstack.PopUnsignedWord());
break;
case SCC_NEWGRF_PRINT_WORD_STRING_ID:
parameters.SetParam(0, MapGRFStringID(_newgrf_textrefstack.grffile->grfid, GRFStringID{_newgrf_textrefstack.PopUnsignedWord()}));
break;
case SCC_NEWGRF_PRINT_WORD_CARGO_NAME: {
CargoType cargo = GetCargoTranslation(_newgrf_textrefstack.PopUnsignedWord(), _newgrf_textrefstack.grffile);
parameters.SetParam(0, cargo < NUM_CARGO ? 1ULL << cargo : 0);
break;
}
case SCC_NEWGRF_PRINT_WORD_STRING_ID: {
StringID stringid = MapGRFStringID(stack.grffile->grfid, GRFStringID{stack.PopUnsignedWord()});
*it = stringid;
/* We also need to handle the substring's stack usage. */
HandleNewGRFStringControlCodes(GetStringPtr(stringid), stack, params);
break;
}
} else {
/* Consume additional parameter characters that follow the NewGRF string code. */
switch (scc) {
default: break;
case SCC_NEWGRF_PUSH_WORD:
Utf8Consume(str);
break;
case SCC_NEWGRF_PRINT_WORD_CARGO_NAME: {
CargoType cargo = GetCargoTranslation(stack.PopUnsignedWord(), stack.grffile);
*it = cargo < NUM_CARGO ? 1ULL << cargo : 0;
break;
}
}
}
/* Emit OpenTTD's internal string code for the different NewGRF variants. */
/**
* Emit OpenTTD's internal string code for the different NewGRF string codes.
* @param scc NewGRF string code.
* @param[in,out] str String iterator, moved forward if SCC_NEWGRF_PUSH_WORD is found.
* @returns String code to use.
*/
char32_t RemapNewGRFStringControlCode(char32_t scc, const char **str)
{
switch (scc) {
default: NOT_REACHED();
default:
return scc;
case SCC_NEWGRF_PRINT_DWORD_SIGNED:
case SCC_NEWGRF_PRINT_WORD_SIGNED:
case SCC_NEWGRF_PRINT_BYTE_SIGNED:
@ -1007,9 +902,66 @@ char32_t RemapNewGRFStringControlCode(char32_t scc, const char **str, StringPara
return SCC_STATION_NAME;
/* These NewGRF string codes modify the NewGRF stack or otherwise do not map to OpenTTD string codes. */
case SCC_NEWGRF_PUSH_WORD:
Utf8Consume(str);
return 0;
case SCC_NEWGRF_DISCARD_WORD:
case SCC_NEWGRF_ROTATE_TOP_4_WORDS:
case SCC_NEWGRF_PUSH_WORD:
return 0;
}
}
/**
* Handle control codes in a NewGRF string, processing the stack and filling parameters.
* @param str String to process.
* @param[in,out] stack Stack to use.
* @param[out] params Parameters to fill.
*/
static void HandleNewGRFStringControlCodes(const char *str, TextRefStack &stack, std::vector<StringParameter> &params)
{
if (str == nullptr) return;
for (const char *p = str; *p != '\0'; /* nothing */) {
char32_t scc;
p += Utf8Decode(&scc, p);
RemapNewGRFStringControlCode(scc, &p, stack, params);
}
}
/**
* Process the text ref stack for a GRF String and return its parameters.
* @param grffile GRFFile of string.
* @param stringid StringID of string.
* @param num_entries Number of temporary storage registers to import.
* @returns Parameters for GRF string.
*/
std::vector<StringParameter> GetGRFSringTextStackParameters(const GRFFile *grffile, StringID stringid, uint8_t num_entries)
{
if (stringid == INVALID_STRING_ID) return {};
const char *str = GetStringPtr(stringid);
if (str == nullptr) return {};
std::vector<StringParameter> params;
params.reserve(20);
TextRefStack stack{grffile, num_entries};
HandleNewGRFStringControlCodes(str, stack, params);
return params;
}
/**
* Format a GRF string using the text ref stack for parameters.
* @param grffile GRFFile of string.
* @param grfstringid GRFStringID of string.
* @param num_entries Number of temporary storage registers to import.
* @returns Formatted string.
*/
std::string GetGRFStringWithTextStack(const struct GRFFile *grffile, GRFStringID grfstringid, uint8_t num_entries)
{
StringID stringid = GetGRFStringID(grffile->grfid, grfstringid);
auto params = GetGRFSringTextStackParameters(grffile, stringid, num_entries);
return GetStringWithArgs(stringid, params);
}

View File

@ -28,10 +28,7 @@ void AddGRFTextToList(GRFTextWrapper &list, std::string_view text_to_add);
bool CheckGrfLangID(uint8_t lang_id, uint8_t grf_version);
void StartTextRefStackUsage(const struct GRFFile *grffile, uint8_t numEntries, const uint32_t *values = nullptr);
void StopTextRefStackUsage();
bool UsingNewGRFTextStack();
struct TextRefStack *CreateTextRefStackBackup();
void RestoreTextRefStackBackup(struct TextRefStack *backup);
std::vector<StringParameter> GetGRFSringTextStackParameters(const struct GRFFile *grffile, StringID stringid, uint8_t num_entries);
std::string GetGRFStringWithTextStack(const struct GRFFile *grffile, GRFStringID grfstringid, uint8_t num_entries);
#endif /* NEWGRF_TEXT_H */

View File

@ -245,11 +245,9 @@ public:
if (callback_res > 0x400) {
ErrorUnknownCallbackResult(spec->grf_prop.grfid, CBID_OBJECT_FUND_MORE_TEXT, callback_res);
} else {
StringID message = GetGRFStringID(spec->grf_prop.grfid, GRFSTR_MISC_GRF_TEXT + callback_res);
if (message != STR_NULL && message != STR_UNDEFINED) {
StartTextRefStackUsage(spec->grf_prop.grffile, 6);
tr.top = DrawStringMultiLine(tr, message, TC_ORANGE);
StopTextRefStackUsage();
std::string str = GetGRFStringWithTextStack(spec->grf_prop.grffile, GRFSTR_MISC_GRF_TEXT + callback_res, 6);
if (!str.empty()) {
tr.top = DrawStringMultiLine(tr, str, TC_ORANGE);
}
}
}

View File

@ -1143,19 +1143,7 @@ static void FormatString(StringBuilder &builder, const char *str_arg, StringPara
*/
std::string buffer;
StringBuilder dry_run_builder(buffer);
if (UsingNewGRFTextStack()) {
/* Values from the NewGRF text stack are only copied to the normal
* argv array at the time they are encountered. That means that if
* another string command references a value later in the string it
* would fail. We solve that by running FormatString twice. The first
* pass makes sure the argv array is correctly filled and the second
* pass can reference later values without problems. */
struct TextRefStack *backup = CreateTextRefStackBackup();
FormatString(dry_run_builder, str_arg, args, case_index, game_script, true);
RestoreTextRefStackBackup(backup);
} else {
FormatString(dry_run_builder, str_arg, args, case_index, game_script, true);
}
FormatString(dry_run_builder, str_arg, args, case_index, game_script, true);
/* We have to restore the original offset here to to read the correct values. */
args.SetOffset(orig_offset);
}
@ -1174,8 +1162,7 @@ static void FormatString(StringBuilder &builder, const char *str_arg, StringPara
if (SCC_NEWGRF_FIRST <= b && b <= SCC_NEWGRF_LAST) {
/* We need to pass some stuff as it might be modified. */
StringParameters remaining = args.GetRemainingParameters();
b = RemapNewGRFStringControlCode(b, &str, remaining, dry_run);
b = RemapNewGRFStringControlCode(b, &str);
if (b == 0) continue;
}
@ -1677,11 +1664,7 @@ static void FormatString(StringBuilder &builder, const char *str_arg, StringPara
const GRFFile *grffile = e->GetGRF();
assert(grffile != nullptr);
StartTextRefStackUsage(grffile, 6);
ArrayStringParameters<6> tmp_params;
GetStringWithArgs(builder, GetGRFStringID(grffile->grfid, GRFSTR_MISC_GRF_TEXT + callback), tmp_params);
StopTextRefStackUsage();
builder += GetGRFStringWithTextStack(grffile, GRFSTR_MISC_GRF_TEXT + callback, 6);
break;
}
}

View File

@ -338,6 +338,6 @@ void GenerateTownNameString(StringBuilder &builder, size_t lang, uint32_t seed);
void GetTownName(StringBuilder &builder, const struct Town *t);
void GRFTownNameGenerate(StringBuilder &builder, uint32_t grfid, uint16_t gen, uint32_t seed);
char32_t RemapNewGRFStringControlCode(char32_t scc, const char **str, StringParameters &parameters, bool modify_parameters);
char32_t RemapNewGRFStringControlCode(char32_t scc, const char **str);
#endif /* STRINGS_INTERNAL_H */