2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file script_text.cpp Implementation of ScriptText. */
10 #include "../../stdafx.h"
11 #include "../../string_func.h"
12 #include "../../strings_func.h"
13 #include "../../game/game_text.hpp"
14 #include "script_text.hpp"
15 #include "script_log.hpp"
16 #include "../script_fatalerror.hpp"
17 #include "../../table/control_codes.h"
19 #include "table/strings.h"
21 #include "../../safeguards.h"
23 RawText::RawText(const std::string
&text
) : text(text
)
28 ScriptText::ScriptText(HSQUIRRELVM vm
) :
29 ZeroedMemoryAllocator()
31 int nparam
= sq_gettop(vm
) - 1;
33 throw sq_throwerror(vm
, "You need to pass at least a StringID to the constructor");
36 /* First resolve the StringID. */
38 if (SQ_FAILED(sq_getinteger(vm
, 2, &sqstring
))) {
39 throw sq_throwerror(vm
, "First argument must be a valid StringID");
41 this->string
= sqstring
;
43 /* The rest of the parameters must be arguments. */
44 for (int i
= 0; i
< nparam
- 1; i
++) {
45 /* Push the parameter to the top of the stack. */
48 if (SQ_FAILED(this->_SetParam(i
, vm
))) {
50 throw sq_throwerror(vm
, "Invalid parameter");
53 /* Pop the parameter again. */
58 SQInteger
ScriptText::_SetParam(int parameter
, HSQUIRRELVM vm
)
60 if (parameter
>= SCRIPT_TEXT_MAX_PARAMETERS
) return SQ_ERROR
;
62 switch (sq_gettype(vm
, -1)) {
65 sq_getstring(vm
, -1, &value
);
67 this->param
[parameter
] = StrMakeValid(value
);
73 sq_getinteger(vm
, -1, &value
);
75 this->param
[parameter
] = value
;
80 SQUserPointer real_instance
= nullptr;
83 sq_getstackobj(vm
, -1, &instance
);
85 /* Validate if it is a GSText instance */
87 sq_pushstring(vm
, "GSText", -1);
89 sq_pushobject(vm
, instance
);
90 if (sq_instanceof(vm
) != SQTrue
) return SQ_ERROR
;
93 /* Get the 'real' instance of this class */
94 sq_getinstanceup(vm
, -1, &real_instance
, nullptr);
95 if (real_instance
== nullptr) return SQ_ERROR
;
97 ScriptText
*value
= static_cast<ScriptText
*>(real_instance
);
98 this->param
[parameter
] = ScriptTextRef(value
);
102 default: return SQ_ERROR
;
105 if (this->paramc
<= parameter
) this->paramc
= parameter
+ 1;
109 SQInteger
ScriptText::SetParam(HSQUIRRELVM vm
)
111 if (sq_gettype(vm
, 2) != OT_INTEGER
) return SQ_ERROR
;
114 sq_getinteger(vm
, 2, &k
);
116 if (k
> SCRIPT_TEXT_MAX_PARAMETERS
) return SQ_ERROR
;
117 if (k
< 1) return SQ_ERROR
;
120 return this->_SetParam(k
, vm
);
123 SQInteger
ScriptText::AddParam(HSQUIRRELVM vm
)
126 res
= this->_SetParam(this->paramc
, vm
);
127 if (res
!= 0) return res
;
129 /* Push our own instance back on top of the stack */
134 SQInteger
ScriptText::_set(HSQUIRRELVM vm
)
138 if (sq_gettype(vm
, 2) == OT_STRING
) {
139 const SQChar
*key_string
;
140 sq_getstring(vm
, 2, &key_string
);
142 std::string str
= StrMakeValid(key_string
);
143 if (!str
.starts_with("param_") || str
.size() > 8) return SQ_ERROR
;
145 k
= stoi(str
.substr(6));
146 } else if (sq_gettype(vm
, 2) == OT_INTEGER
) {
148 sq_getinteger(vm
, 2, &key
);
154 if (k
> SCRIPT_TEXT_MAX_PARAMETERS
) return SQ_ERROR
;
155 if (k
< 1) return SQ_ERROR
;
158 return this->_SetParam(k
, vm
);
161 std::string
ScriptText::GetEncodedText()
163 StringIDList seen_ids
;
167 auto output
= std::back_inserter(result
);
168 this->_FillParamList(params
);
169 this->_GetEncodedText(output
, param_count
, seen_ids
, params
);
170 if (param_count
> SCRIPT_TEXT_MAX_PARAMETERS
) throw Script_FatalError(fmt::format("{}: Too many parameters", GetGameStringName(this->string
)));
174 void ScriptText::_FillParamList(ParamList
¶ms
)
176 for (int i
= 0; i
< this->paramc
; i
++) {
177 Param
*p
= &this->param
[i
];
178 params
.emplace_back(this->string
, i
, p
);
179 if (!std::holds_alternative
<ScriptTextRef
>(*p
)) continue;
180 std::get
<ScriptTextRef
>(*p
)->_FillParamList(params
);
184 void ScriptText::ParamCheck::Encode(std::back_insert_iterator
<std::string
> &output
)
186 if (this->used
) return;
187 if (std::holds_alternative
<std::string
>(*this->param
)) fmt::format_to(output
, ":\"{}\"", std::get
<std::string
>(*this->param
));
188 if (std::holds_alternative
<SQInteger
>(*this->param
)) fmt::format_to(output
, ":{:X}", std::get
<SQInteger
>(*this->param
));
189 if (std::holds_alternative
<ScriptTextRef
>(*this->param
)) fmt::format_to(output
, ":{:X}", this->owner
);
193 void ScriptText::_GetEncodedText(std::back_insert_iterator
<std::string
> &output
, int ¶m_count
, StringIDList
&seen_ids
, ParamSpan args
)
195 const std::string
&name
= GetGameStringName(this->string
);
197 if (std::find(seen_ids
.begin(), seen_ids
.end(), this->string
) != seen_ids
.end()) throw Script_FatalError(fmt::format("{}: Circular reference detected", name
));
198 seen_ids
.push_back(this->string
);
200 Utf8Encode(output
, SCC_ENCODED
);
201 fmt::format_to(output
, "{:X}", this->string
);
203 const StringParams
¶ms
= GetGameStringParams(this->string
);
206 auto get_next_arg
= [&]() {
207 if (idx
>= args
.size()) throw Script_FatalError(fmt::format("{}({}): Not enough parameters", name
, param_count
+ 1));
208 ParamCheck
&pc
= args
[idx
++];
209 if (pc
.owner
!= this->string
) ScriptLog::Warning(fmt::format("{}({}): Consumes {}({})", name
, param_count
+ 1, GetGameStringName(pc
.owner
), pc
.idx
+ 1));
212 auto skip_args
= [&](size_t nb
) { idx
+= nb
; };
214 for (const StringParam
&cur_param
: params
) {
215 switch (cur_param
.type
) {
216 case StringParam::UNUSED
:
217 skip_args(cur_param
.consumes
);
220 case StringParam::RAW_STRING
: {
221 ParamCheck
&p
= *get_next_arg();
222 if (!std::holds_alternative
<std::string
>(*p
.param
)) ScriptLog::Error(fmt::format("{}({}): {{{}}} expects a raw string", name
, param_count
+ 1, cur_param
.cmd
));
227 case StringParam::STRING
: {
228 ParamCheck
&p
= *get_next_arg();
229 if (!std::holds_alternative
<ScriptTextRef
>(*p
.param
)){
230 ScriptLog::Error(fmt::format("{}({}): {{{}}} expects a GSText", name
, param_count
+ 1, cur_param
.cmd
));
235 fmt::format_to(output
, ":");
236 ScriptTextRef
&ref
= std::get
<ScriptTextRef
>(*p
.param
);
237 ref
->_GetEncodedText(output
, count
, seen_ids
, args
.subspan(idx
));
239 if (++count
!= cur_param
.consumes
) {
240 ScriptLog::Error(fmt::format("{}({}): {{{}}} expects {} to be consumed, but {} consumes {}", name
, param_count
+ 1, cur_param
.cmd
, cur_param
.consumes
- 1, GetGameStringName(ref
->string
), count
- 1));
241 /* Fill missing params if needed. */
242 for (int i
= count
; i
< cur_param
.consumes
; i
++) fmt::format_to(output
, ":0");
244 skip_args(cur_param
.consumes
- 1);
249 for (int i
= 0; i
< cur_param
.consumes
; i
++) {
250 ParamCheck
&p
= *get_next_arg();
251 if (!std::holds_alternative
<SQInteger
>(*p
.param
)) ScriptLog::Error(fmt::format("{}({}): {{{}}} expects an integer", name
, param_count
+ i
+ 1, cur_param
.cmd
));
256 param_count
+= cur_param
.consumes
;
262 const std::string
Text::GetDecodedText()
264 ::SetDParamStr(0, this->GetEncodedText());
265 return ::GetString(STR_JUST_RAW_STRING
);