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 ScriptTextList seen_texts
;
167 auto output
= std::back_inserter(result
);
168 this->_FillParamList(params
, seen_texts
);
169 this->_GetEncodedText(output
, param_count
, params
, true);
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
, ScriptTextList
&seen_texts
)
176 if (std::find(seen_texts
.begin(), seen_texts
.end(), this) != seen_texts
.end()) throw Script_FatalError(fmt::format("{}: Circular reference detected", GetGameStringName(this->string
)));
177 seen_texts
.push_back(this);
179 for (int i
= 0; i
< this->paramc
; i
++) {
180 Param
*p
= &this->param
[i
];
181 params
.emplace_back(this->string
, i
, p
);
182 if (!std::holds_alternative
<ScriptTextRef
>(*p
)) continue;
183 std::get
<ScriptTextRef
>(*p
)->_FillParamList(params
, seen_texts
);
186 seen_texts
.pop_back();
188 /* Fill with dummy parameters to match FormatString() behaviour. */
189 if (seen_texts
.empty()) {
190 static Param dummy
= 0;
191 int nb_extra
= SCRIPT_TEXT_MAX_PARAMETERS
- (int)params
.size();
192 for (int i
= 0; i
< nb_extra
; i
++)
193 params
.emplace_back(-1, i
, &dummy
);
197 void ScriptText::ParamCheck::Encode(std::back_insert_iterator
<std::string
> &output
, const char *cmd
)
199 if (this->cmd
== nullptr) this->cmd
= cmd
;
200 if (this->used
) return;
201 if (std::holds_alternative
<std::string
>(*this->param
)) fmt::format_to(output
, ":\"{}\"", std::get
<std::string
>(*this->param
));
202 if (std::holds_alternative
<SQInteger
>(*this->param
)) fmt::format_to(output
, ":{:X}", std::get
<SQInteger
>(*this->param
));
203 if (std::holds_alternative
<ScriptTextRef
>(*this->param
)) {
204 fmt::format_to(output
, ":");
205 Utf8Encode(output
, SCC_ENCODED
);
206 fmt::format_to(output
, "{:X}", std::get
<ScriptTextRef
>(*this->param
)->string
);
211 void ScriptText::_GetEncodedText(std::back_insert_iterator
<std::string
> &output
, int ¶m_count
, ParamSpan args
, bool first
)
213 const std::string
&name
= GetGameStringName(this->string
);
216 Utf8Encode(output
, SCC_ENCODED
);
217 fmt::format_to(output
, "{:X}", this->string
);
220 const StringParams
¶ms
= GetGameStringParams(this->string
);
223 auto get_next_arg
= [&]() {
224 if (idx
>= args
.size()) throw Script_FatalError(fmt::format("{}({}): Not enough parameters", name
, param_count
+ 1));
225 ParamCheck
&pc
= args
[idx
++];
226 if (pc
.owner
!= this->string
) ScriptLog::Warning(fmt::format("{}({}): Consumes {}({})", name
, param_count
+ 1, GetGameStringName(pc
.owner
), pc
.idx
+ 1));
229 auto skip_args
= [&](size_t nb
) { idx
+= nb
; };
231 for (const StringParam
&cur_param
: params
) {
233 switch (cur_param
.type
) {
234 case StringParam::UNUSED
:
235 skip_args(cur_param
.consumes
);
238 case StringParam::RAW_STRING
:
240 ParamCheck
&p
= *get_next_arg();
241 p
.Encode(output
, cur_param
.cmd
);
242 if (p
.cmd
!= cur_param
.cmd
) throw 1;
243 if (!std::holds_alternative
<std::string
>(*p
.param
)) ScriptLog::Error(fmt::format("{}({}): {{{}}} expects a raw string", name
, param_count
+ 1, cur_param
.cmd
));
247 case StringParam::STRING
:
249 ParamCheck
&p
= *get_next_arg();
250 p
.Encode(output
, cur_param
.cmd
);
251 if (p
.cmd
!= cur_param
.cmd
) throw 1;
252 if (!std::holds_alternative
<ScriptTextRef
>(*p
.param
)) {
253 ScriptLog::Error(fmt::format("{}({}): {{{}}} expects a GSText", name
, param_count
+ 1, cur_param
.cmd
));
258 ScriptTextRef
&ref
= std::get
<ScriptTextRef
>(*p
.param
);
259 ref
->_GetEncodedText(output
, count
, args
.subspan(idx
), false);
260 if (++count
!= cur_param
.consumes
) {
261 ScriptLog::Warning(fmt::format("{}({}): {{{}}} expects {} to be consumed, but {} consumes {}", name
, param_count
+ 1, cur_param
.cmd
, cur_param
.consumes
- 1, GetGameStringName(ref
->string
), count
- 1));
262 /* Fill missing params if needed. */
263 for (int i
= count
; i
< cur_param
.consumes
; i
++) fmt::format_to(output
, ":0");
265 skip_args(cur_param
.consumes
- 1);
270 for (int i
= 0; i
< cur_param
.consumes
; i
++) {
271 ParamCheck
&p
= *get_next_arg();
272 p
.Encode(output
, i
== 0 ? cur_param
.cmd
: nullptr);
273 if (i
== 0 && p
.cmd
!= cur_param
.cmd
) throw 1;
274 if (!std::holds_alternative
<SQInteger
>(*p
.param
)) ScriptLog::Error(fmt::format("{}({}): {{{}}} expects an integer", name
, param_count
+ i
+ 1, cur_param
.cmd
));
278 param_count
+= cur_param
.consumes
;
281 ScriptLog::Warning(fmt::format("{}({}): Invalid parameter", name
, param_count
));
286 const std::string
Text::GetDecodedText()
288 ::SetDParamStr(0, this->GetEncodedText());
289 return ::GetString(STR_JUST_RAW_STRING
);