Update: Translations from eints
[openttd-github.git] / src / script / api / script_text.cpp
blobcff3d4dfd3ed4dad2a2eeb614a1595c98297ea76
1 /*
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/>.
6 */
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;
32 if (nparam < 1) {
33 throw sq_throwerror(vm, "You need to pass at least a StringID to the constructor");
36 /* First resolve the StringID. */
37 SQInteger sqstring;
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. */
46 sq_push(vm, i + 3);
48 if (SQ_FAILED(this->_SetParam(i, vm))) {
49 this->~ScriptText();
50 throw sq_throwerror(vm, "Invalid parameter");
53 /* Pop the parameter again. */
54 sq_pop(vm, 1);
58 SQInteger ScriptText::_SetParam(int parameter, HSQUIRRELVM vm)
60 if (parameter >= SCRIPT_TEXT_MAX_PARAMETERS) return SQ_ERROR;
62 switch (sq_gettype(vm, -1)) {
63 case OT_STRING: {
64 const SQChar *value;
65 sq_getstring(vm, -1, &value);
67 this->param[parameter] = StrMakeValid(value);
68 break;
71 case OT_INTEGER: {
72 SQInteger value;
73 sq_getinteger(vm, -1, &value);
75 this->param[parameter] = value;
76 break;
79 case OT_INSTANCE: {
80 SQUserPointer real_instance = nullptr;
81 HSQOBJECT instance;
83 sq_getstackobj(vm, -1, &instance);
85 /* Validate if it is a GSText instance */
86 sq_pushroottable(vm);
87 sq_pushstring(vm, "GSText", -1);
88 sq_get(vm, -2);
89 sq_pushobject(vm, instance);
90 if (sq_instanceof(vm) != SQTrue) return SQ_ERROR;
91 sq_pop(vm, 3);
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);
99 break;
102 default: return SQ_ERROR;
105 if (this->paramc <= parameter) this->paramc = parameter + 1;
106 return 0;
109 SQInteger ScriptText::SetParam(HSQUIRRELVM vm)
111 if (sq_gettype(vm, 2) != OT_INTEGER) return SQ_ERROR;
113 SQInteger k;
114 sq_getinteger(vm, 2, &k);
116 if (k > SCRIPT_TEXT_MAX_PARAMETERS) return SQ_ERROR;
117 if (k < 1) return SQ_ERROR;
118 k--;
120 return this->_SetParam(k, vm);
123 SQInteger ScriptText::AddParam(HSQUIRRELVM vm)
125 SQInteger res;
126 res = this->_SetParam(this->paramc, vm);
127 if (res != 0) return res;
129 /* Push our own instance back on top of the stack */
130 sq_push(vm, 1);
131 return 1;
134 SQInteger ScriptText::_set(HSQUIRRELVM vm)
136 int32_t k;
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) {
147 SQInteger key;
148 sq_getinteger(vm, 2, &key);
149 k = (int32_t)key;
150 } else {
151 return SQ_ERROR;
154 if (k > SCRIPT_TEXT_MAX_PARAMETERS) return SQ_ERROR;
155 if (k < 1) return SQ_ERROR;
156 k--;
158 return this->_SetParam(k, vm);
161 std::string ScriptText::GetEncodedText()
163 ScriptTextList seen_texts;
164 ParamList params;
165 int param_count = 0;
166 std::string result;
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)));
171 return result;
174 void ScriptText::_FillParamList(ParamList &params, 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);
208 this->used = true;
211 void ScriptText::_GetEncodedText(std::back_insert_iterator<std::string> &output, int &param_count, ParamSpan args, bool first)
213 const std::string &name = GetGameStringName(this->string);
215 if (first) {
216 Utf8Encode(output, SCC_ENCODED);
217 fmt::format_to(output, "{:X}", this->string);
220 const StringParams &params = GetGameStringParams(this->string);
222 size_t idx = 0;
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));
227 return &pc;
229 auto skip_args = [&](size_t nb) { idx += nb; };
231 for (const StringParam &cur_param : params) {
232 try {
233 switch (cur_param.type) {
234 case StringParam::UNUSED:
235 skip_args(cur_param.consumes);
236 break;
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));
244 break;
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));
254 param_count++;
255 continue;
257 int count = 0;
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);
266 break;
269 default:
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;
279 } catch (int nb) {
280 param_count += nb;
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);