/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 1998-2000, Matthes Bender * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ * Copyright (c) 2009-2016, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ /* Handles script file components (calls, inheritance, function maps) */ #include "C4Include.h" #include "script/C4ScriptHost.h" #include "object/C4Def.h" #include "script/C4AulCompiler.h" #include "script/C4AulParse.h" #include "script/C4AulScriptFunc.h" #include "script/C4AulScriptFunc.h" #include "script/C4Effect.h" /*--- C4ScriptHost ---*/ C4ScriptHost::C4ScriptHost() { Script = nullptr; stringTable = nullptr; SourceScripts.push_back(this); // prepare include list IncludesResolved = false; Resolving=false; Includes.clear(); Appends.clear(); } C4ScriptHost::~C4ScriptHost() { Unreg(); Clear(); } void C4ScriptHost::Clear() { UnlinkOwnedFunctions(); C4ComponentHost::Clear(); ast.reset(); Script.Clear(); LocalValues.Clear(); DeleteOwnedPropLists(); SourceScripts.clear(); SourceScripts.push_back(this); if (stringTable) { stringTable->DelRef(); stringTable = nullptr; } // remove includes Includes.clear(); Appends.clear(); // reset flags State = ASS_NONE; enabledWarnings.clear(); } void C4ScriptHost::UnlinkOwnedFunctions() { // Remove owned functions from their parents. This solves a problem // where overloading a definition would unload the C4ScriptHost, but // keep around global functions, which then contained dangling pointers. for (const auto &box : ownedFunctions) { assert(box.GetType() == C4V_Function); C4AulScriptFunc *func = box._getFunction()->SFunc(); C4PropList *parent = func->Parent; if (parent == GetPropList()) continue; assert(parent == &::ScriptEngine); C4Value v; parent->GetPropertyByS(func->Name, &v); if (v.getFunction() == func) { // If the function we're deleting is the top-level function in // the inheritance chain, promote the next one in its stead; // if there is no overloaded function, remove the property. parent->Thaw(); if (func->OwnerOverloaded) parent->SetPropertyByS(func->Name, C4VFunction(func->OwnerOverloaded)); else parent->ResetProperty(func->Name); } else { C4AulScriptFunc *func_chain = v.getFunction()->SFunc(); assert(func_chain != func); while (func_chain) { // Unlink the removed function from the inheritance chain if (func_chain->OwnerOverloaded == func) { func_chain->OwnerOverloaded = func->OwnerOverloaded; break; } assert(func_chain->OwnerOverloaded && "Removed function not found in inheritance chain"); func_chain = func_chain->OwnerOverloaded->SFunc(); } } } ownedFunctions.clear(); } void C4ScriptHost::DeleteOwnedPropLists() { // delete all static proplists associated to this script host. // Note that just clearing the vector is not enough in case of // cyclic references. for (C4Value& value: ownedPropLists) { C4PropList* plist = value.getPropList(); if (plist) { if (plist->Delete()) delete plist; else plist->Clear(); } } ownedPropLists.clear(); } void C4ScriptHost::Unreg() { // remove from list if (Prev) Prev->Next = Next; else if (Engine) Engine->Child0 = Next; if (Next) Next->Prev = Prev; else if (Engine) Engine->ChildL = Prev; Prev = Next = nullptr; Engine = nullptr; } void C4ScriptHost::Reg2List(C4AulScriptEngine *pEngine) { // already regged? (def reloaded) if (Engine) return; // reg to list if ((Engine = pEngine)) { if ((Prev = Engine->ChildL)) Prev->Next = this; else Engine->Child0 = this; Engine->ChildL = this; } else Prev = nullptr; Next = nullptr; } bool C4ScriptHost::Load(C4Group &hGroup, const char *szFilename, const char *szLanguage, class C4LangStringTable *pLocalTable) { // Base load bool fSuccess = C4ComponentHost::Load(hGroup,szFilename,szLanguage); // String Table if (stringTable != pLocalTable) { if (stringTable) stringTable->DelRef(); stringTable = pLocalTable; if (stringTable) stringTable->AddRef(); } // set name ScriptName.Ref(GetFilePath()); // preparse script MakeScript(); // Success return fSuccess; } bool C4ScriptHost::LoadData(const char *szFilename, const char *szData, class C4LangStringTable *pLocalTable) { // String Table if (stringTable != pLocalTable) { if (stringTable) stringTable->DelRef(); stringTable = pLocalTable; if (stringTable) stringTable->AddRef(); } ScriptName.Copy(szFilename); StdStrBuf tempScript; tempScript.Copy(szData); Script.Clear(); if(stringTable) stringTable->ReplaceStrings(tempScript, Script); else Script.Take(tempScript); Preparse(); return true; } void C4ScriptHost::MakeScript() { // clear prev Script.Clear(); // create script if (stringTable) { stringTable->ReplaceStrings(GetDataBuf(), Script); } else { Script.Ref(GetDataBuf()); } // preparse script Preparse(); } bool C4ScriptHost::ReloadScript(const char *szPath, const char *szLanguage) { // this? if (SEqualNoCase(szPath, GetFilePath()) || (stringTable && SEqualNoCase(szPath, stringTable->GetFilePath()))) { // try reload char szParentPath[_MAX_PATH + 1]; C4Group ParentGrp; if (GetParentPath(szPath, szParentPath)) if (ParentGrp.Open(szParentPath)) if (Load(ParentGrp, nullptr, szLanguage, stringTable)) return true; } return false; } std::string C4ScriptHost::Translate(const std::string &text) const { if (stringTable) return stringTable->Translate(text); throw C4LangStringTable::NoSuchTranslation(text); } /*--- C4ExtraScriptHost ---*/ C4ExtraScriptHost::C4ExtraScriptHost(C4String *parent_key_name): ParserPropList(C4PropList::NewStatic(nullptr, nullptr, parent_key_name)) { } C4ExtraScriptHost::~C4ExtraScriptHost() { Clear(); } void C4ExtraScriptHost::Clear() { C4ScriptHost::Clear(); ParserPropList._getPropList()->Clear(); } C4PropListStatic * C4ExtraScriptHost::GetPropList() { return ParserPropList._getPropList()->IsStatic(); } /*--- C4ScenarioObjectsScriptHost ---*/ C4ScenarioObjectsScriptHost::C4ScenarioObjectsScriptHost() : C4ExtraScriptHost(::Strings.RegString("ScenarioObjects")) { // Note that "ScenarioObjects" is a fake key name under which you cannot access this prop list from script. // It's just given to have a proper name when script errors are reported. } /*--- C4DefScriptHost ---*/ bool C4DefScriptHost::Parse() { bool r = C4ScriptHost::Parse(); assert(Def); // Check category if (!Def->GetPlane() && Def->Category & C4D_SortLimit) { int Plane; bool gotplane = true; switch (Def->Category & C4D_SortLimit) { case C4D_StaticBack: Plane = 100; break; case C4D_Structure: Plane = C4Plane_Structure; break; case C4D_Vehicle: Plane = 300; break; case C4D_Living: Plane = 400; break; case C4D_Object: Plane = 500; break; case C4D_StaticBack | C4D_Background: Plane = -500; break; case C4D_Structure | C4D_Background: Plane = -400; break; case C4D_Vehicle | C4D_Background: Plane = -300; break; case C4D_Living | C4D_Background: Plane = -200; break; case C4D_Object | C4D_Background: Plane = -100; break; case C4D_StaticBack | C4D_Foreground: Plane = 1100; break; case C4D_Structure | C4D_Foreground: Plane = 1200; break; case C4D_Vehicle | C4D_Foreground: Plane = 1300; break; case C4D_Living | C4D_Foreground: Plane = 1400; break; case C4D_Object | C4D_Foreground: Plane = 1500; break; default: Warn("Def %s (%s) has invalid category", Def->GetName(), Def->GetDataString().getData()); gotplane = false; break; } if (gotplane) Def->SetProperty(P_Plane, C4VInt(Plane)); } if (!Def->GetPlane()) { Warn("Def %s (%s) has invalid Plane", Def->GetName(), Def->GetDataString().getData()); Def->SetProperty(P_Plane, C4VInt(1)); } return r; } C4PropListStatic * C4DefScriptHost::GetPropList() { return Def; } class C4PropListScen: public C4PropListStatic { public: C4PropListScen(const C4PropListStatic * parent, C4String * key): C4PropListStatic(nullptr, parent, key) { C4PropList * proto = C4PropList::NewStatic(ScriptEngine.GetPropList(), this, &::Strings.P[P_Prototype]); C4PropListStatic::SetPropertyByS(&::Strings.P[P_Prototype], C4VPropList(proto)); } void SetPropertyByS(C4String * k, const C4Value & to) override { if (k == &Strings.P[P_Prototype]) { DebugLog("ERROR: Attempt to set Scenario.Prototype."); return; } C4PropListStatic::SetPropertyByS(k, to); } }; /*--- C4GameScriptHost ---*/ C4GameScriptHost::C4GameScriptHost(): ScenPrototype(0), ScenPropList(0) { } C4GameScriptHost::~C4GameScriptHost() = default; bool C4GameScriptHost::Load(C4Group & g, const char * f, const char * l, C4LangStringTable * t) { assert(ScriptEngine.GetPropList()); C4PropListStatic * pScen = new C4PropListScen(nullptr, &::Strings.P[P_Scenario]); ScenPropList.SetPropList(pScen); ::ScriptEngine.RegisterGlobalConstant("Scenario", ScenPropList); ScenPrototype.SetPropList(pScen->GetPrototype()); Reg2List(&ScriptEngine); return C4ScriptHost::Load(g, f, l, t); } bool C4GameScriptHost::LoadData(const char * f, const char * d, C4LangStringTable * t) { assert(ScriptEngine.GetPropList()); C4PropListStatic * pScen = new C4PropListScen(nullptr, &::Strings.P[P_Scenario]); ScenPropList.SetPropList(pScen); ::ScriptEngine.RegisterGlobalConstant("Scenario", ScenPropList); ScenPrototype.SetPropList(pScen->GetPrototype()); Reg2List(&ScriptEngine); return C4ScriptHost::LoadData(f, d, t); } void C4GameScriptHost::Clear() { C4ScriptHost::Clear(); ScenPropList.Set0(); ScenPrototype.Set0(); delete pScenarioEffects; pScenarioEffects=nullptr; } C4PropListStatic * C4GameScriptHost::GetPropList() { C4PropList * p = ScenPrototype._getPropList(); return p ? p->IsStatic() : nullptr; } void C4GameScriptHost::Denumerate(C4ValueNumbers * numbers) { ScenPropList.Denumerate(numbers); if (pScenarioEffects) pScenarioEffects->Denumerate(numbers); } C4Value C4GameScriptHost::Call(const char *szFunction, C4AulParSet *Pars, bool fPassError) { return ScenPropList._getPropList()->Call(szFunction, Pars, fPassError); } C4GameScriptHost GameScript;