PageRenderTime 28ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/Ca3DE/Server/ServerWorld.cpp

https://bitbucket.org/lennonchan/cafu
C++ | 432 lines | 235 code | 94 blank | 103 comment | 41 complexity | 6ad1f64b1d86c96cc50f86bf595d914b MD5 | raw file
  1. /*
  2. =================================================================================
  3. This file is part of Cafu, the open-source game engine and graphics engine
  4. for multiplayer, cross-platform, real-time 3D action.
  5. Copyright (C) 2002-2013 Carsten Fuchs Software.
  6. Cafu is free software: you can redistribute it and/or modify it under the terms
  7. of the GNU General Public License as published by the Free Software Foundation,
  8. either version 3 of the License, or (at your option) any later version.
  9. Cafu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  10. without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
  11. PURPOSE. See the GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with Cafu. If not, see <http://www.gnu.org/licenses/>.
  14. For support and more information about Cafu, visit us at <http://www.cafu.de>.
  15. =================================================================================
  16. */
  17. #include "wx/msgdlg.h"
  18. #include "ServerWorld.hpp"
  19. #include "../EngineEntity.hpp"
  20. #include "ClipSys/CollisionModel_static.hpp"
  21. #include "ClipSys/CollisionModelMan.hpp"
  22. #include "ConsoleCommands/ConVar.hpp"
  23. #include "ConsoleCommands/Console.hpp" // For cf::va().
  24. #include "Network/Network.hpp"
  25. #include "SceneGraph/BspTreeNode.hpp"
  26. #include "Win32/Win32PrintHelp.hpp"
  27. #include "TypeSys.hpp"
  28. #include "../NetConst.hpp"
  29. #include "../../Games/Game.hpp"
  30. static ConVarT AutoAddCompanyBot("sv_AutoAddCompanyBot", false, ConVarT::FLAG_MAIN_EXE, "If true, auto-inserts an entity of type \"Company Bot\" at the player start position into each newly loaded map.");
  31. CaServerWorldT::CaServerWorldT(cf::GameSys::GameInfoI* GameInfo, cf::GameSys::GameI* Game, const char* FileName, ModelManagerT& ModelMan, cf::GuiSys::GuiResourcesT& GuiRes)
  32. : Ca3DEWorldT(GameInfo, Game, FileName, ModelMan, GuiRes, false, NULL),
  33. m_ServerFrameNr(1), // 0 geht nicht, denn die ClientInfoT::LastKnownFrameReceived werden mit 0 initialisiert!
  34. m_IsThinking(false),
  35. m_EntityRemoveList()
  36. {
  37. // Gehe alle GameEntities der Ca3DEWorld durch und erstelle dafür "echte" Entities.
  38. for (unsigned long GENr=0; GENr<m_World->GameEntities.Size(); GENr++)
  39. {
  40. const GameEntityT* GE=m_World->GameEntities[GENr];
  41. // Register GE->CollModel also with the cf::ClipSys::CollModelMan, so that both the owner (the game entity GE)
  42. // as well as the game code can free/delete it in their destructors (one by "delete", the other by cf::ClipSys::CollModelMan->FreeCM()).
  43. cf::ClipSys::CollModelMan->GetCM(GE->CollModel);
  44. CreateNewEntityFromBasicInfo(GE->Properties, GE->BspTree, GE->CollModel, GENr, GE->MFIndex, m_ServerFrameNr, GE->Origin);
  45. }
  46. // Gehe alle InfoPlayerStarts der Ca3DEWorld durch und erstelle dafür "echte" Entities.
  47. for (unsigned long IPSNr=0; IPSNr<m_World->InfoPlayerStarts.Size(); IPSNr++)
  48. {
  49. std::map<std::string, std::string> Props;
  50. Props["classname"]="info_player_start";
  51. Props["angles"] =cf::va("0 %lu 0", (unsigned long)(m_World->InfoPlayerStarts[IPSNr].Heading/8192.0*45.0));
  52. CreateNewEntityFromBasicInfo(Props, NULL, NULL, (unsigned long)-1, (unsigned long)-1, m_ServerFrameNr, m_World->InfoPlayerStarts[IPSNr].Origin);
  53. }
  54. // Zu Demonstrationszwecken fügen wir auch noch einen MonsterMaker vom Typ CompanyBot in die World ein.
  55. // TODO! Dies sollte ins Map/Entity-Script wandern!!!
  56. if (AutoAddCompanyBot.GetValueBool())
  57. {
  58. std::map<std::string, std::string> Props;
  59. Props["classname"] ="LifeFormMaker";
  60. Props["angles"] =cf::va("0 %lu 0", (unsigned long)(m_World->InfoPlayerStarts[0].Heading/8192.0*45.0));
  61. Props["monstertype"] ="CompanyBot";
  62. Props["monstercount"] ="1";
  63. Props["m_imaxlivechildren"]="1";
  64. CreateNewEntityFromBasicInfo(Props, NULL, NULL, (unsigned long)-1, (unsigned long)-1, m_ServerFrameNr, m_World->InfoPlayerStarts[0].Origin);
  65. }
  66. /// Finished calling CreateGameEntityFromMapFile() for all entities in the world file.
  67. /// Now load/insert the user provided map script (e.g. "TechDemo.lua") to the script state.
  68. std::string LuaScriptName=FileName;
  69. const size_t SuffixPos =LuaScriptName.rfind(".cw");
  70. if (SuffixPos==std::string::npos) LuaScriptName+=".lua";
  71. else LuaScriptName.replace(SuffixPos, 3, ".lua");
  72. m_ScriptState.GetScriptState().DoFile(LuaScriptName.c_str());
  73. // Call each entities OnInit() script method here???
  74. // Finally call the Lua OnInit() method of each entity.
  75. //for (unsigned long ChildNr=0; ChildNr<AllChildren.Size(); ChildNr++)
  76. //{
  77. // AllChildren[ChildNr]->OnLuaEventHandler(LuaState, "OnInit");
  78. //}
  79. }
  80. CaServerWorldT::~CaServerWorldT()
  81. {
  82. }
  83. unsigned long CaServerWorldT::CreateNewEntity(const std::map<std::string, std::string>& Properties, unsigned long CreationFrameNr, const VectorT& Origin)
  84. {
  85. return CreateNewEntityFromBasicInfo(Properties, NULL, NULL, (unsigned long)(-1), (unsigned long)(-1), CreationFrameNr, Origin);
  86. }
  87. // Die Clients bekommen unabhängig hiervon in einer SC1_DropClient Message explizit mitgeteilt, wenn ein Client (warum auch immer) den Server verläßt.
  88. // Den dazugehörigen Entity muß der Client deswegen aber nicht unbedingt sofort und komplett aus seiner World entfernen,
  89. // dies sollte vielmehr durch Wiederverwendung von EntityIDs durch den Server geschehen!
  90. void CaServerWorldT::RemoveEntity(unsigned long EntityID)
  91. {
  92. if (m_IsThinking)
  93. {
  94. // We're currently thinking, and EntityID might be the ID of the entity that currently thinks.
  95. // (That is, this entity is removing itself, as for example an exploded grenade.)
  96. // Thus, schedule this entity for removal until the thinking is finished.
  97. m_EntityRemoveList.PushBack(EntityID);
  98. }
  99. else
  100. {
  101. // Currently not thinking, so it should be safe to remove the entity immediately.
  102. if (EntityID < m_EngineEntities.Size())
  103. {
  104. delete m_EngineEntities[EntityID];
  105. m_EngineEntities[EntityID]=NULL;
  106. }
  107. }
  108. }
  109. unsigned long CaServerWorldT::InsertHumanPlayerEntityForNextFrame(const char* PlayerName, const char* ModelName, unsigned long ClientInfoNr)
  110. {
  111. std::map<std::string, std::string> Props;
  112. Props["classname"]="HumanPlayer";
  113. Props["name"] =cf::va("Player%lu", ClientInfoNr+1); // Setting the name is needed so that player entities can have a corresponding script instance.
  114. Props["angles"] =cf::va("0 %lu 0", (unsigned long)(m_World->InfoPlayerStarts[0].Heading/8192.0*45.0));
  115. return CreateNewEntityFromBasicInfo(Props, NULL, NULL, (unsigned long)-1, (unsigned long)-1, m_ServerFrameNr+1, m_World->InfoPlayerStarts[0].Origin+VectorT(0, 0, 1000), PlayerName, ModelName);
  116. }
  117. void CaServerWorldT::NotifyHumanPlayerEntityOfClientCommand(unsigned long HumanPlayerEntityID, const PlayerCommandT& PlayerCommand)
  118. {
  119. if (HumanPlayerEntityID < m_EngineEntities.Size())
  120. if (m_EngineEntities[HumanPlayerEntityID]!=NULL)
  121. m_EngineEntities[HumanPlayerEntityID]->ProcessConfigString(&PlayerCommand, "PlayerCommand");
  122. }
  123. void CaServerWorldT::Think(float FrameTime)
  124. {
  125. // Zuerst die Nummer des nächsten Frames 'errechnen'.
  126. // Die Reihenfolge ist wichtig, denn wenn ein neuer Entity geschaffen wird,
  127. // muß dieser korrekt wissen, zu welchem Frame er ins Leben gerufen wurde.
  128. m_ServerFrameNr++;
  129. // Jetzt das eigentliche Denken durchführen.
  130. // Heraus kommt eine Aussage der Form: "Zum Frame Nummer 'm_ServerFrameNr' ist die World in diesem Zustand!"
  131. if (m_IsThinking) return;
  132. m_IsThinking=true;
  133. // Beachte:
  134. // - Neu geschaffene Entities sollen nicht gleich 'Think()'en!
  135. // Zur Erreichung vergleiche dazu die Implementation von EngineEntityT::Think().
  136. // DÜRFTEN sie trotzdem gleich Think()en??? (JA!) Die OldStates kämen dann evtl. durcheinander!? (NEIN!)
  137. // Allerdings übertragen wir mit BaseLines grundsätzlich KEINE Events (??? PRÜFEN!), Think()en macht insofern also nur eingeschränkt Sinn.
  138. // - EntityIDs sollten wohl besser NICHT wiederverwendet werden, da z.B. Parents die IDs ihrer Children speichern usw.
  139. // - Letzteres führt aber zu zunehmend vielen NULL-Pointern im m_EngineEntities-Array.
  140. // - Dies könnte sich evtl. mit einem weiteren Array von 'active EntityIDs' lösen lassen.
  141. for (unsigned long EntityNr=0; EntityNr<m_EngineEntities.Size(); EntityNr++)
  142. if (m_EngineEntities[EntityNr]!=NULL)
  143. m_EngineEntities[EntityNr]->PreThink(m_ServerFrameNr);
  144. // Must never move this above the PreThink() calls above, because ...(?)
  145. m_PhysicsWorld.Think(FrameTime);
  146. m_ScriptState.GetScriptState().RunPendingCoroutines(FrameTime); // Should do this early: new coroutines are usually added "during" thinking.
  147. for (unsigned long EntityNr=0; EntityNr<m_EngineEntities.Size(); EntityNr++)
  148. if (m_EngineEntities[EntityNr]!=NULL)
  149. m_EngineEntities[EntityNr]->Think(FrameTime, m_ServerFrameNr);
  150. m_IsThinking=false;
  151. // If entities removed other entities (or even themselves!) while thinking, remove them now.
  152. for (unsigned long RemoveNr=0; RemoveNr<m_EntityRemoveList.Size(); RemoveNr++)
  153. {
  154. const unsigned long EntityID=m_EntityRemoveList[RemoveNr];
  155. delete m_EngineEntities[EntityID];
  156. m_EngineEntities[EntityID]=NULL;
  157. }
  158. m_EntityRemoveList.Overwrite();
  159. }
  160. unsigned long CaServerWorldT::WriteClientNewBaseLines(unsigned long OldBaseLineFrameNr, ArrayT< ArrayT<char> >& OutDatas) const
  161. {
  162. const unsigned long SentClientBaseLineFrameNr = OldBaseLineFrameNr;
  163. for (unsigned long EntityNr=0; EntityNr < m_EngineEntities.Size(); EntityNr++)
  164. if (m_EngineEntities[EntityNr]!=NULL)
  165. m_EngineEntities[EntityNr]->WriteNewBaseLine(SentClientBaseLineFrameNr, OutDatas);
  166. return m_ServerFrameNr;
  167. }
  168. void CaServerWorldT::WriteClientDeltaUpdateMessages(unsigned long ClientEntityID, unsigned long ClientFrameNr, ArrayT< ArrayT<unsigned long> >& ClientOldStatesPVSEntityIDs, unsigned long& ClientCurrentStateIndex, NetDataT& OutData) const
  169. {
  170. // Wenn dies hier aufgerufen wird, befinden sich sämtliche m_EngineEntities schon im Zustand ('Entity->State') zum Frame 'ServerFrameNr'.
  171. // Der Client, von dem obige Parameter stammen, ist aber noch nicht soweit (sondern noch im vorherigen Zustand).
  172. // Update daher zuerst die PVS-EntityID Infos dieses Clients.
  173. const char TEMP_MAX_OLDSTATES=16+1; // (?)
  174. if (ClientOldStatesPVSEntityIDs.Size()<TEMP_MAX_OLDSTATES)
  175. {
  176. ClientOldStatesPVSEntityIDs.PushBackEmpty();
  177. ClientCurrentStateIndex=ClientOldStatesPVSEntityIDs.Size()-1;
  178. }
  179. else
  180. {
  181. ClientCurrentStateIndex++;
  182. if (ClientCurrentStateIndex>=TEMP_MAX_OLDSTATES) ClientCurrentStateIndex=0;
  183. ClientOldStatesPVSEntityIDs[ClientCurrentStateIndex].Clear();
  184. }
  185. ArrayT<unsigned long>* NewStatePVSEntityIDs=&ClientOldStatesPVSEntityIDs[ClientCurrentStateIndex];
  186. ArrayT<unsigned long>* OldStatePVSEntityIDs=NULL;
  187. unsigned long ClientLeafNr =(m_EngineEntities[ClientEntityID]!=NULL) ? m_World->BspTree->WhatLeaf(m_EngineEntities[ClientEntityID]->GetGameEntity()->GetOrigin()) : 0;
  188. // Finde heraus, welche Entities im PVS von diesem Client liegen. Erhalte ein Array von EntityIDs.
  189. for (unsigned long EntityNr=0; EntityNr<m_EngineEntities.Size(); EntityNr++)
  190. if (m_EngineEntities[EntityNr]!=NULL)
  191. {
  192. const Vector3dT& EntityOrigin=m_EngineEntities[EntityNr]->GetGameEntity()->GetOrigin();
  193. BoundingBox3T<double> EntityBB =m_EngineEntities[EntityNr]->GetGameEntity()->GetDimensions();
  194. EntityBB.Min += EntityOrigin;
  195. EntityBB.Max += EntityOrigin;
  196. if (m_World->BspTree->IsInPVS(EntityBB, ClientLeafNr)) NewStatePVSEntityIDs->PushBack(EntityNr);
  197. }
  198. unsigned long DeltaFrameNr; // Kann dies entfernen, indem der Packet-Header direkt im if-else-Teil geschrieben wird!
  199. if (ClientFrameNr==0 || ClientFrameNr>=m_ServerFrameNr || ClientFrameNr+ClientOldStatesPVSEntityIDs.Size()-1<m_ServerFrameNr)
  200. {
  201. // Erläuterung der obigen if-Bedingung:
  202. // a) Der erste Teil 'ClientFrameNr==0' ist klar!
  203. // b) Der zweite Teil 'ClientFrameNr>=ServerFrameNr' ist nur zur Sicherheit und sollte NIEMALS anspringen!
  204. // c) Der dritte Teil ist äquivalent zu 'ServerFrameNr-ClientFrameNr>=ClientOldStatesPVSEntityIDs.Size()'!
  205. static ArrayT<unsigned long> EmptyArray;
  206. // Entweder will der Client explizit ein retransmit haben (bei neuer World oder auf User-Wunsch (no-delta mode) oder nach Problemen),
  207. // oder beim Client ist schon länger keine verwertbare Nachricht mehr angekommen. Daher delta'en wir bzgl. der BaseLine!
  208. DeltaFrameNr =0;
  209. OldStatePVSEntityIDs=&EmptyArray;
  210. }
  211. else
  212. {
  213. // Nach obiger if-Bedingung ist FrameDiff auf jeden Fall in [1 .. ClientOldStatesPVSEntityIDs.Size()-1].
  214. unsigned long FrameDiff=m_ServerFrameNr-ClientFrameNr;
  215. DeltaFrameNr =ClientFrameNr;
  216. OldStatePVSEntityIDs=&ClientOldStatesPVSEntityIDs[FrameDiff<=ClientCurrentStateIndex ? ClientCurrentStateIndex-FrameDiff : ClientOldStatesPVSEntityIDs.Size()+ClientCurrentStateIndex-FrameDiff];
  217. }
  218. OutData.WriteByte(SC1_FrameInfo);
  219. OutData.WriteLong(m_ServerFrameNr); // What we are delta'ing to (Frame, für das wir Informationen schicken)
  220. OutData.WriteLong(DeltaFrameNr); // What we are delta'ing from (Frame, auf das wir uns beziehen (0 für BaseLine))
  221. unsigned long OldIndex=0;
  222. unsigned long NewIndex=0;
  223. while (OldIndex<OldStatePVSEntityIDs->Size() || NewIndex<NewStatePVSEntityIDs->Size())
  224. {
  225. unsigned long OldEntityID=OldIndex<OldStatePVSEntityIDs->Size() ? (*OldStatePVSEntityIDs)[OldIndex] : 0x99999999;
  226. unsigned long NewEntityID=NewIndex<NewStatePVSEntityIDs->Size() ? (*NewStatePVSEntityIDs)[NewIndex] : 0x99999999;
  227. // Consider the following situation:
  228. // There are (or were) A REALLY BIG NUMBER of entities in the PVS of the current client entity ('m_EngineEntities[ClientEntityID]').
  229. // Each of them would cause data to be written into 'OutData'.
  230. // Unfortunately, if the network protocol detects later that 'OutData.Data.Size()' exceeds the maximum possible size,
  231. // the entire content is dropped, as it is only considered "unreliable data".
  232. // That's not inherently dangerous, BUT it tends to happen repeatedly (assuming the client does not move into another leaf with fewer entities).
  233. // Therefore, the client gets *no* updates anymore, and the truly problematic fact is that its *own* update is among those that get dropped!
  234. // The client-side prediction continues to let the client move for a while, but eventually it gets stuck.
  235. // (Theoretically, the client could try to move "blindly" into a leaf with few enough entities such that updates get through again
  236. // (server operation in not tampered by this condition!), but until then, the screen seems to indicate that it is stuck/frozen.)
  237. // Okay, but what can we do? Carefully think about it, and you will find that NOTHING can be done without introducing NEW problems!
  238. // Why? The spec. / code simply requires the consistency of this function! ANY changes here cause trouble with delta updates later!
  239. // It seems that the only reasonable solution is to omit update messages of "other" entities:
  240. // Better risk not-updated fields of other entities, than getting stuck with the own entity!
  241. // In order to achieve this, the introduction of the 'SkipEntity' variable is the best solution I could think of.
  242. bool SkipEntity=OutData.Data.Size()>1250 && NewEntityID!=ClientEntityID;
  243. if (OldEntityID==NewEntityID)
  244. {
  245. // Diesen Entity gab es schon im alten Frame.
  246. // Hierhin kommen wir nur, wenn 'OldStatePVSEntityIDs' nicht leer ist, vergleiche mit obigem Code!
  247. // PRINZIPIELL geht dann die Differenz 'ServerFrameNr-ClientFrameNr' in Ordnung, ebenfalls nach obigem Code.
  248. // TATSÄCHLICH wäre noch denkbar, daß der Entity mit ID 'NewEntityID' erst neu erschaffen wurde,
  249. // d.h. jünger ist als der Client, und deshalb die Differenz doch zu groß ist!
  250. // Dies wird aber von der Logik hier vermieden, denn ein solcher neuer Entity kann ja nicht schon im alten Frame vorgekommen sein!
  251. // Mit anderen Worten: Der folgende Aufruf sollte NIEMALS scheitern, falls doch, ist das ein fataler Fehler, der intensives Debugging erfordert.
  252. // Dennoch ist es wahrscheinlich (??) nicht notwendig, den Client bei Auftreten dieses Fehler zu disconnecten.
  253. if (!SkipEntity)
  254. if (!m_EngineEntities[NewEntityID]->WriteDeltaEntity(false /* send from baseline? */, ClientFrameNr, OutData, false))
  255. EnqueueString("SERVER ERROR: %s, L %u: NewEntityID %u, ServerFrameNr %u, ClientFrameNr %u\n", __FILE__, __LINE__, NewEntityID, m_ServerFrameNr, ClientFrameNr);
  256. OldIndex++;
  257. NewIndex++;
  258. continue;
  259. }
  260. if (OldEntityID>NewEntityID)
  261. {
  262. // Dies ist ein neuer Entity, sende ihn von der BaseLine aus.
  263. // Deswegen kann der folgende Aufruf (gemäß der Spezifikation von WriteDeltaEntity()) auch nicht scheitern!
  264. if (!SkipEntity)
  265. m_EngineEntities[NewEntityID]->WriteDeltaEntity(true /* send from baseline? */, 0, OutData, true);
  266. NewIndex++;
  267. continue;
  268. }
  269. if (OldEntityID<NewEntityID)
  270. {
  271. // Diesen Entity gibt es im neuen Frame nicht mehr.
  272. if (!SkipEntity)
  273. {
  274. OutData.WriteByte(SC1_EntityRemove);
  275. OutData.WriteLong(OldEntityID);
  276. }
  277. OldIndex++;
  278. continue;
  279. }
  280. }
  281. }
  282. unsigned long CaServerWorldT::CreateNewEntityFromBasicInfo(const std::map<std::string, std::string>& Properties,
  283. const cf::SceneGraph::GenericNodeT* RootNode, const cf::ClipSys::CollisionModelT* CollisionModel,
  284. unsigned long WorldFileIndex, unsigned long MapFileIndex, unsigned long CreationFrameNr, const VectorT& Origin, const char* PlayerName, const char* ModelName)
  285. {
  286. try
  287. {
  288. // 1. Determine from the entity class name (e.g. "monster_argrenade") the C++ class name (e.g. "EntARGrenadeT").
  289. std::map<std::string, std::string>::const_iterator EntClassNamePair=Properties.find("classname");
  290. if (EntClassNamePair==Properties.end())
  291. throw std::runtime_error("\"classname\" property not found.\n");
  292. const std::string EntClassName=EntClassNamePair->second;
  293. const std::string CppClassName=m_ScriptState.GetCppClassNameFromEntityClassName(EntClassName);
  294. if (CppClassName=="")
  295. throw std::runtime_error("C++ class name for entity class name \""+EntClassName+"\" not found.\n");
  296. // 2. Find the related type info.
  297. const cf::TypeSys::TypeInfoT* TI=m_Game->GetEntityTIM().FindTypeInfoByName(CppClassName.c_str());
  298. if (TI==NULL)
  299. {
  300. wxMessageBox("Entity with C++ class name \"" + CppClassName + "\" could not be instantiated.\n\n" +
  301. "No type info for entity class \"" + EntClassName + "\" with C++ class name \"" + CppClassName +
  302. "\" was found.\n\n" +
  303. "If you are developing a new C++ entity class, did you update the AllTypeInfos[] list in file " +
  304. "GameImpl.cpp in your game directory?\n\n" +
  305. "If in doubt, please post at the Cafu forums for help.",
  306. "Create new entity", wxOK | wxICON_EXCLAMATION);
  307. throw std::runtime_error("No type info found for entity class \""+EntClassName+"\" with C++ class name \""+CppClassName+"\".\n");
  308. }
  309. // 3. Create an instance of the desired entity type.
  310. const unsigned long NewEntityID = m_EngineEntities.Size();
  311. IntrusivePtrT<GameEntityI> NewEntity = m_Game->CreateGameEntityFromMapFile(
  312. TI, Properties, RootNode, CollisionModel, NewEntityID,
  313. WorldFileIndex, MapFileIndex, this, Origin);
  314. if (NewEntity.IsNull())
  315. throw std::runtime_error("Could not create entity of class \""+EntClassName+"\" with C++ class name \""+CppClassName+"\".\n");
  316. // OPEN QUESTION:
  317. // Should we copy the Properties into the Lua entity instance, into the C++ entity instance, or nowhere (just keep the std::map<> pointer around)?
  318. // See svn log -r 301 for one argument for the C++ instance.
  319. // Muß dies VOR dem Erzeugen des EngineEntitys tun, denn sonst stimmt dessen BaseLine nicht!
  320. if (PlayerName!=NULL) NewEntity->ProcessConfigString(PlayerName, "PlayerName");
  321. if (ModelName !=NULL) NewEntity->ProcessConfigString(ModelName , "ModelName" );
  322. m_EngineEntities.PushBack(new EngineEntityT(NewEntity, CreationFrameNr));
  323. return NewEntityID;
  324. }
  325. catch (const std::runtime_error& RE)
  326. {
  327. Console->Warning(RE.what());
  328. // Free the collision model in place of the (never instantiated) entity destructor,
  329. // so that the reference count of the CollModelMan gets right.
  330. cf::ClipSys::CollModelMan->FreeCM(CollisionModel);
  331. }
  332. // Return error code.
  333. return 0xFFFFFFFF;
  334. }