1/*
2 * LegacyClonk
3 *
4 * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design)
5 * Copyright (c) 2017-2022, The LegacyClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16
17/* Handles script file components (calls, inheritance, function maps) */
18
19#include <C4Include.h>
20#include <C4ScriptHost.h>
21
22#include <C4Console.h>
23#include <C4ObjectCom.h>
24#include <C4Object.h>
25#include <C4Wrappers.h>
26
27// C4ScriptHost
28
29C4ScriptHost::C4ScriptHost() { Default(); }
30C4ScriptHost::~C4ScriptHost() { Clear(); }
31
32void C4ScriptHost::Default()
33{
34 C4AulScript::Default();
35 C4ComponentHost::Default();
36 pStringTable = nullptr;
37}
38
39void C4ScriptHost::Clear()
40{
41 C4AulScript::Clear();
42 C4ComponentHost::Clear();
43 pStringTable = nullptr;
44}
45
46bool C4ScriptHost::Load(const char *szName, C4Group &hGroup, const char *szFilename,
47 const char *szLanguage, C4Def *pDef, class C4LangStringTable *pLocalTable, bool fLoadTable)
48{
49 // Set definition and id
50 Def = pDef; idDef = Def ? Def->id : 0;
51 // Base load
52 bool fSuccess = C4ComponentHost::LoadAppend(szName, hGroup, szFilename, szLanguage);
53 // String Table
54 pStringTable = pLocalTable;
55 // load it if specified
56 if (pStringTable && fLoadTable)
57 pStringTable->LoadEx(szName: "StringTbl", hGroup, C4CFN_ScriptStringTbl, szLanguage);
58 // set name
59 ScriptName = std::format(fmt: "{}" DirSep "{}", args: hGroup.GetFullName().getData(), args: +Filename);
60 // preparse script
61 MakeScript();
62 // Success
63 return fSuccess;
64}
65
66void C4ScriptHost::MakeScript()
67{
68 // clear prev
69 Script.Clear();
70
71 // create script
72 if (pStringTable)
73 {
74 pStringTable->ReplaceStrings(rBuf: Data, rTarget&: Script, szParentFilePath: FilePath);
75 }
76 else
77 {
78 Script.Ref(Buf2: Data);
79 }
80
81 // preparse script
82 Preparse();
83}
84
85void C4ScriptHost::Close()
86{
87 // Base close
88 C4ComponentHost::Close();
89 // Make executable script
90 MakeScript();
91 // Update console
92 Console.UpdateInputCtrl();
93}
94
95int32_t C4ScriptHost::GetControlMethod(int32_t com, int32_t first, int32_t second)
96{
97 return ((first >> com) & 0x01) | (((second >> com) & 0x01) << 1);
98}
99
100void C4ScriptHost::GetControlMethodMask(const std::format_string<const char *> functionFormat, int32_t &first, int32_t &second)
101{
102 first = second = 0;
103
104 if (!Script) return;
105
106 // Scan for com defined control functions
107 int32_t iCom;
108 char szFunction[256 + 1];
109 for (iCom = 0; iCom < ComOrderNum; iCom++)
110 {
111 FormatWithNull(buf&: szFunction, fmt: functionFormat, args: ComName(iCom: ComOrder(iCom)));
112 C4AulScriptFunc *func = GetSFunc(pIdtf: szFunction);
113
114 if (func)
115 {
116 first |= ((func->ControlMethod) & 0x01) << iCom;
117 second |= ((func->ControlMethod >> 1) & 0x01) << iCom;
118 }
119 }
120}
121
122C4Value C4ScriptHost::FunctionCall(C4Object *pCaller, const char *szFunction, C4Object *pObj, const C4AulParSet &Pars, bool fPrivateCall, bool fPassError, bool convertNilToIntBool)
123{
124 // get needed access
125 C4AulAccess Acc = AA_PRIVATE;
126 if (pObj && (pObj != pCaller) && !fPrivateCall)
127 if (pCaller) Acc = AA_PUBLIC; else Acc = AA_PROTECTED;
128 // get function
129 C4AulScriptFunc *pFn;
130 if (!(pFn = GetSFunc(pIdtf: szFunction, AccNeeded: Acc))) return C4VNull;
131 // Call code
132 return pFn->Exec(pObj, pPars: Pars, fPassErrors: fPassError, nonStrict3WarnConversionOnly: true, convertNilToIntBool);
133}
134
135bool C4ScriptHost::ReloadScript(const char *szPath)
136{
137 // this?
138 if (SEqualNoCase(szStr1: szPath, szStr2: FilePath) || (pStringTable && SEqualNoCase(szStr1: szPath, szStr2: pStringTable->GetFilePath())))
139 {
140 // try reload
141 char szParentPath[_MAX_PATH + 1]; C4Group ParentGrp;
142 if (GetParentPath(szFilename: szPath, szBuffer: szParentPath))
143 if (ParentGrp.Open(szGroupName: szParentPath))
144 if (Load(szName: Name, hGroup&: ParentGrp, szFilename: Filename, szLanguage: Config.General.Language, pDef: nullptr, pLocalTable: pStringTable))
145 return true;
146 }
147 // call for childs
148 return C4AulScript::ReloadScript(szPath);
149}
150
151const char *C4ScriptHost::GetControlDesc(const char *szFunctionFormat, int32_t iCom, C4ID *pidImage, int32_t *piImagePhase)
152{
153 // Compose script function
154 const char *const comName{ComName(iCom)};
155 std::string function{std::vformat(fmt: szFunctionFormat, args: std::make_format_args(fmt_args: comName))};
156 // Remove failsafe indicator
157 if (function.starts_with(x: '~'))
158 {
159 function.erase(position: function.begin());
160 }
161 // Find function reference
162 C4AulScriptFunc *pFn = GetSFunc(pIdtf: function.c_str());
163 // Get image id
164 if (pidImage) { *pidImage = idDef; if (pFn) *pidImage = pFn->idImage; }
165 // Get image phase
166 if (piImagePhase) { *piImagePhase = 0; if (pFn) *piImagePhase = pFn->iImagePhase; }
167 // Return function desc
168 if (pFn && pFn->Desc.getLength()) return pFn->DescText.getData();
169 // No function
170 return nullptr;
171}
172
173// C4DefScriptHost
174
175void C4DefScriptHost::Default()
176{
177 C4ScriptHost::Default();
178 SFn_CalcValue = SFn_SellTo = SFn_ControlTransfer = SFn_CustomComponents = nullptr;
179 ControlMethod[0] = ControlMethod[1] = ContainedControlMethod[0] = ContainedControlMethod[1] = ActivationControlMethod[0] = ActivationControlMethod[1] = 0;
180}
181
182void C4DefScriptHost::AfterLink()
183{
184 C4AulScript::AfterLink();
185 // Search cached functions
186 char WhereStr[C4MaxName + 18];
187 SFn_CalcValue = GetSFunc(PSF_CalcValue, AccNeeded: AA_PROTECTED);
188 SFn_SellTo = GetSFunc(PSF_SellTo, AccNeeded: AA_PROTECTED);
189 SFn_ControlTransfer = GetSFunc(PSF_ControlTransfer, AccNeeded: AA_PROTECTED);
190 SFn_CustomComponents = GetSFunc(PSF_GetCustomComponents, AccNeeded: AA_PROTECTED);
191 if (Def)
192 {
193 C4AulAccess CallAccess = AA_PRIVATE;
194 for (int32_t cnt = 0; cnt < Def->ActNum; cnt++)
195 {
196 C4ActionDef *pad = &Def->ActMap[cnt];
197 FormatWithNull(buf&: WhereStr, fmt: "Action {}: StartCall", args: +pad->Name); pad->StartCall = GetSFuncWarn(pIdtf: pad->SStartCall, AccNeeded: CallAccess, WarnStr: WhereStr);
198 FormatWithNull(buf&: WhereStr, fmt: "Action {}: PhaseCall", args: +pad->Name); pad->PhaseCall = GetSFuncWarn(pIdtf: pad->SPhaseCall, AccNeeded: CallAccess, WarnStr: WhereStr);
199 FormatWithNull(buf&: WhereStr, fmt: "Action {}: EndCall", args: +pad->Name); pad->EndCall = GetSFuncWarn(pIdtf: pad->SEndCall, AccNeeded: CallAccess, WarnStr: WhereStr);
200 FormatWithNull(buf&: WhereStr, fmt: "Action {}: AbortCall", args: +pad->Name); pad->AbortCall = GetSFuncWarn(pIdtf: pad->SAbortCall, AccNeeded: CallAccess, WarnStr: WhereStr);
201 }
202 Def->TimerCall = GetSFuncWarn(pIdtf: Def->STimerCall, AccNeeded: CallAccess, WarnStr: "TimerCall");
203 }
204 // Check if there are any Control/Contained/Activation script functions
205 GetControlMethodMask(PSF_Control, first&: ControlMethod[0], second&: ControlMethod[1]);
206 GetControlMethodMask(PSF_ContainedControl, first&: ContainedControlMethod[0], second&: ContainedControlMethod[1]);
207 GetControlMethodMask(PSF_Activate, first&: ActivationControlMethod[0], second&: ActivationControlMethod[1]);
208}
209
210// C4GameScriptHost
211
212C4GameScriptHost::C4GameScriptHost() : Counter(0), Go(false) {}
213C4GameScriptHost::~C4GameScriptHost() {}
214
215void C4GameScriptHost::Default()
216{
217 C4ScriptHost::Default();
218 Counter = 0;
219 Go = false;
220}
221
222bool C4GameScriptHost::Execute()
223{
224 if (!Script) return false;
225 char buffer[500];
226 if (Go && !Tick10)
227 {
228 FormatWithNull(buf&: buffer, PSF_Script, args: Counter++);
229 return static_cast<bool>(Call(szFunction: buffer));
230 }
231 return false;
232}
233
234C4Value C4GameScriptHost::GRBroadcast(const char *szFunction, const C4AulParSet &pPars, bool fPassError, bool fRejectTest, bool convertNilToIntBool)
235{
236 // call objects first - scenario script might overwrite hostility, etc...
237 C4Object *pObj;
238 for (C4ObjectLink *clnk = Game.Objects.ObjectsInt().First; clnk; clnk = clnk->Next) if (pObj = clnk->Obj)
239 if (pObj->Category & (C4D_Goal | C4D_Rule | C4D_Environment))
240 if (pObj->Status)
241 {
242 C4Value vResult(pObj->Call(szFunctionCall: szFunction, pPars, fPassError, convertNilToIntBool));
243 // rejection tests abort on first nonzero result
244 if (fRejectTest) if (vResult) return vResult;
245 }
246 // scenario script call
247 return Call(szFunction, pPars, fPassError, convertNilToIntBool);
248}
249
250void C4GameScriptHost::CompileFunc(StdCompiler *pComp)
251{
252 pComp->Value(rStruct: mkNamingAdapt(rValue&: Go, szName: "Go", rDefault: false));
253 pComp->Value(rStruct: mkNamingAdapt(rValue&: Counter, szName: "Counter", rDefault: 0));
254}
255