PageRenderTime 52ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/Ca3DE/Client/ClientWorld.cpp

https://bitbucket.org/lennonchan/cafu
C++ | 605 lines | 348 code | 127 blank | 130 comment | 73 complexity | aef3f5c3cb00d349004803f04dfc86ff 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 "ClientWorld.hpp"
  18. #include "../EngineEntity.hpp"
  19. #include "../NetConst.hpp"
  20. #include "ClipSys/CollisionModel_static.hpp"
  21. #include "ClipSys/CollisionModelMan.hpp"
  22. #include "ConsoleCommands/ConVar.hpp"
  23. #include "MaterialSystem/Renderer.hpp"
  24. #include "Math3D/Matrix.hpp"
  25. #include "Network/Network.hpp"
  26. #include "SceneGraph/Node.hpp"
  27. #include "SceneGraph/BspTreeNode.hpp"
  28. #include "SceneGraph/FaceNode.hpp"
  29. #include "Win32/Win32PrintHelp.hpp"
  30. #include "DebugLog.hpp"
  31. #include "../../Games/Game.hpp"
  32. #include <cassert>
  33. CaClientWorldT::CaClientWorldT(cf::GameSys::GameInfoI* GameInfo, cf::GameSys::GameI* Game, const char* FileName, ModelManagerT& ModelMan, cf::GuiSys::GuiResourcesT& GuiRes, WorldT::ProgressFunctionT ProgressFunction, unsigned long OurEntityID_) /*throw (WorldT::LoadErrorT)*/
  34. : Ca3DEWorldT(GameInfo, Game, FileName, ModelMan, GuiRes, true, ProgressFunction),
  35. OurEntityID(OurEntityID_),
  36. m_ServerFrameNr(0xDEADBEEF),
  37. MAX_FRAMES(16) /*MUST BE POWER OF 2*/,
  38. Frames(),
  39. m_PlayerCommands()
  40. {
  41. ProgressFunction(-1.0f, "InitDrawing()");
  42. m_World->BspTree->InitDrawing();
  43. for (unsigned long EntityNr=0; EntityNr<m_World->GameEntities.Size(); EntityNr++)
  44. m_World->GameEntities[EntityNr]->BspTree->InitDrawing();
  45. Frames.PushBackEmpty(MAX_FRAMES);
  46. m_PlayerCommands.PushBackEmpty(128); // The size MUST be a power of 2.
  47. ProgressFunction(-1.0f, "Loading Materials");
  48. MatSys::Renderer->PreCache();
  49. }
  50. unsigned long CaClientWorldT::CreateNewEntity(const std::map<std::string, std::string>& Properties, unsigned long CreationFrameNr, const VectorT& Origin)
  51. {
  52. return 0xFFFFFFFF;
  53. }
  54. void CaClientWorldT::RemoveEntity(unsigned long EntityID)
  55. {
  56. }
  57. bool CaClientWorldT::ReadEntityBaseLineMessage(NetDataT& InData)
  58. {
  59. unsigned long EntityID =InData.ReadLong();
  60. unsigned long EntityTypeID=InData.ReadLong();
  61. unsigned long EntityWFI =InData.ReadLong(); // Short for: EntityWorldFileIndex
  62. const std::map<std::string, std::string> EmptyMap;
  63. const unsigned long MFIndex =EntityWFI<m_World->GameEntities.Size() ? m_World->GameEntities[EntityWFI]->MFIndex : 0xFFFFFFFF;
  64. const std::map<std::string, std::string>& Props =EntityWFI<m_World->GameEntities.Size() ? m_World->GameEntities[EntityWFI]->Properties : EmptyMap;
  65. const cf::SceneGraph::GenericNodeT* RootNode=EntityWFI<m_World->GameEntities.Size() ? m_World->GameEntities[EntityWFI]->BspTree : NULL;
  66. const cf::ClipSys::CollisionModelT* CollMdl =EntityWFI<m_World->GameEntities.Size() ? m_World->GameEntities[EntityWFI]->CollModel : NULL;
  67. // Register CollMdl also with the cf::ClipSys::CollModelMan, so that both the owner (Ca3DEWorld.GameEntities[EntityWFI])
  68. // as well as the game code can free/delete it in their destructors (one by "delete", the other by cf::ClipSys::CollModelMan->FreeCM()).
  69. cf::ClipSys::CollModelMan->GetCM(CollMdl);
  70. // Es ist nicht sinnvoll, CreateGameEntityFromTypeID() in Parametern die geparsten InData-Inhalte zu übergeben (Origin, Velocity, ...),
  71. // denn spätestens bei der SequenceNr und FrameNr kommt es zu Problemen. Deshalb lieber erstmal ein BaseEntitiy mit "falschem" State erzeugen.
  72. IntrusivePtrT<GameEntityI> NewEntity=m_Game->CreateGameEntityFromTypeNr(EntityTypeID, Props, RootNode, CollMdl, EntityID, EntityWFI, MFIndex, this);
  73. // Dies kann nur passieren, wenn EntityTypeID ein unbekannter Typ ist! Ein solcher Fehler ist also fatal.
  74. // Andererseits sollte ein Disconnect dennoch nicht notwendig sein, der Fehler sollte ohnehin niemals auftreten.
  75. if (NewEntity.IsNull())
  76. {
  77. // Finish reading InData, so that we can gracefully continue despite the error.
  78. InData.ReadDMsg();
  79. EnqueueString("CLIENT ERROR: %s, L %u: Cannot create entity %u from SC1_EntityBaseLine msg: unknown type ID '%u' (WorldFileIndex %lu, MapFileIndex %lu)!\n", __FILE__, __LINE__, EntityID, EntityTypeID, EntityWFI, MFIndex);
  80. return false;
  81. }
  82. // Falls notwendig, Platz für die neue EntityID schaffen.
  83. while (m_EngineEntities.Size()<=EntityID) m_EngineEntities.PushBack(NULL);
  84. // Die EntityID könnte durchaus wiederverwendet werden - was immer der Server wünscht.
  85. delete m_EngineEntities[EntityID];
  86. // Neuen Entity tatsächlich erschaffen.
  87. m_EngineEntities[EntityID]=new EngineEntityT(NewEntity, InData);
  88. return true;
  89. }
  90. unsigned long CaClientWorldT::ReadServerFrameMessage(NetDataT& InData)
  91. {
  92. FrameT CurrentFrame;
  93. const FrameT* DeltaFrame;
  94. // Header der SC1_NewFrameInfo Message zu Ende lesen
  95. CurrentFrame.ServerFrameNr=InData.ReadLong(); // Frame for which the server is sending (delta) info
  96. CurrentFrame.DeltaFrameNr =InData.ReadLong(); // Frame to decompress against
  97. cf::LogDebug(net, " CurrentFrame.ServerFrameNr==%lu", CurrentFrame.ServerFrameNr);
  98. cf::LogDebug(net, " CurrentFrame.DeltaFrameNr ==%lu", CurrentFrame.DeltaFrameNr);
  99. m_ServerFrameNr=CurrentFrame.ServerFrameNr;
  100. if (CurrentFrame.DeltaFrameNr==0)
  101. {
  102. // Das Frame ist gegen die BaseLines komprimiert
  103. DeltaFrame=NULL;
  104. CurrentFrame.IsValid=true;
  105. }
  106. else
  107. {
  108. // Das Frame ist gegen ein anderes Frame (delta-)komprimiert
  109. DeltaFrame=&Frames[CurrentFrame.DeltaFrameNr & (MAX_FRAMES-1)];
  110. // Wir können nur dann richtig dekomprimieren, wenn das DeltaFrame damals gültig war (Ungültigkeit sollte hier niemals vorkommen!)
  111. // und es nicht zu alt ist (andernfalls wurde es schon mit einem jüngeren Frame überschrieben und ist daher nicht mehr verfügbar).
  112. if (DeltaFrame->IsValid && CurrentFrame.DeltaFrameNr==DeltaFrame->ServerFrameNr)
  113. {
  114. CurrentFrame.IsValid=true;
  115. }
  116. else
  117. {
  118. // Falls wir hier 'CurrentFrame.IsValid==false' haben, so heißt das, daß wir den Rest der Message lesen und ignorieren müssen,
  119. // denn er ist nicht verwertbar. Dazu arbeiten wir ganz normal mit dem ungültigen oder veralteten DeltaFrame, denn das CurrentFrame
  120. // ist ja eh ungültig. Danach müssen wir eine nicht-komprimierte (d.h. gegen die BaseLines komprimierte) Nachricht anfordern,
  121. // indem wir ganz am Ende dieser Funktion 0 anstatt 'm_ServerFrameNr' zurückgeben.
  122. EnqueueString("CLIENT WARNING: %s, L %u: %u %u %u!\n", __FILE__, __LINE__, DeltaFrame->IsValid, CurrentFrame.DeltaFrameNr, DeltaFrame->ServerFrameNr);
  123. }
  124. }
  125. unsigned long DeltaFrameIndex =0;
  126. unsigned long DeltaFrameEntityID=0;
  127. if (DeltaFrame==NULL) DeltaFrameEntityID=0x99999999;
  128. else
  129. {
  130. if (DeltaFrameIndex>=DeltaFrame->EntityIDsInPVS.Size()) DeltaFrameEntityID=0x99999999;
  131. else DeltaFrameEntityID=DeltaFrame->EntityIDsInPVS[DeltaFrameIndex];
  132. }
  133. // Read all 'SC1_EntityUpdate' and 'SC1_EntityRemove' messages.
  134. while (true)
  135. {
  136. if (InData.ReadPos>=InData.Data.Size()) break; // InBuffer ist zu Ende
  137. if (InData.Data[InData.ReadPos]!=SC1_EntityUpdate &&
  138. InData.Data[InData.ReadPos]!=SC1_EntityRemove) break; // Nächste Message ist keine 'SC1_EntityUpdate' oder 'SC1_EntityRemove' Message
  139. const char Msg =InData.ReadByte();
  140. const unsigned long NewEntityID=InData.ReadLong();
  141. cf::LogDebug(net, " %s: NewEntityID==%lu", Msg==SC1_EntityUpdate ? "SC1_EntityUpdate" : "SC1_EntityRemove", NewEntityID);
  142. while (DeltaFrameEntityID<NewEntityID)
  143. {
  144. // Ein oder mehrere Entities vom DeltaFrame finden sich unverändert im CurrentFrame wieder,
  145. // für diese Entities hat der Server aber überhaupt keine Bits geschickt, d.h. noch nichtmal den Header!
  146. CurrentFrame.EntityIDsInPVS.PushBack(DeltaFrameEntityID);
  147. // Es geht nun also ganz um den Entity mit der ID 'DeltaFrameEntityID', von dem wir wissen, daß er im DeltaFrame vorkam.
  148. // Notwendig ist es, den Zustand dieses Entities vom DeltaFrame (Nummer CurrentFrame.DeltaFrameNr) in den Zustand des
  149. // CurrentFrames (Nummer CurrentFrame.ServerFrameNr) zu kopieren.
  150. CurrentFrame.IsValid&= // Note that operator & doesn't short-circuit, like operator && does!
  151. ParseServerDeltaUpdateMessage(DeltaFrameEntityID, CurrentFrame.DeltaFrameNr, CurrentFrame.ServerFrameNr, NULL);
  152. DeltaFrameIndex++;
  153. if (DeltaFrameIndex>=DeltaFrame->EntityIDsInPVS.Size()) DeltaFrameEntityID=0x99999999;
  154. else DeltaFrameEntityID=DeltaFrame->EntityIDsInPVS[DeltaFrameIndex];
  155. }
  156. // Ab hier gilt 'DeltaFrameEntityID>=NewEntityID'
  157. if (Msg==SC1_EntityRemove)
  158. {
  159. // Der Entity im DeltaFrame kommt im CurrentFrame nicht mehr vor
  160. if (DeltaFrameEntityID==NewEntityID)
  161. {
  162. DeltaFrameIndex++;
  163. if (DeltaFrameIndex>=DeltaFrame->EntityIDsInPVS.Size()) DeltaFrameEntityID=0x99999999;
  164. else DeltaFrameEntityID=DeltaFrame->EntityIDsInPVS[DeltaFrameIndex];
  165. }
  166. else
  167. {
  168. // Cannot remove an entity from the delta frame that wasn't in the delta frame in the first place.
  169. EnqueueString("CLIENT WARNING: %s, L %u: DeltaFrameEntityID!=NewEntityID on removal!\n", __FILE__, __LINE__);
  170. }
  171. continue;
  172. }
  173. if (DeltaFrameEntityID==NewEntityID)
  174. {
  175. const ArrayT<uint8_t> DeltaMessage=InData.ReadDMsg();
  176. // Der Entity vom DeltaFrame kommt auch im CurrentFrame vor, Änderungen ergeben sich aus der Delta-Dekompression bzgl. des DeltaFrame
  177. CurrentFrame.EntityIDsInPVS.PushBack(NewEntityID);
  178. CurrentFrame.IsValid&= // Note that operator & doesn't short-circuit, like operator && does!
  179. ParseServerDeltaUpdateMessage(NewEntityID, CurrentFrame.DeltaFrameNr, CurrentFrame.ServerFrameNr, &DeltaMessage);
  180. DeltaFrameIndex++;
  181. if (DeltaFrameIndex>=DeltaFrame->EntityIDsInPVS.Size()) DeltaFrameEntityID=0x99999999;
  182. else DeltaFrameEntityID=DeltaFrame->EntityIDsInPVS[DeltaFrameIndex];
  183. continue;
  184. }
  185. if (DeltaFrameEntityID>NewEntityID)
  186. {
  187. const ArrayT<uint8_t> DeltaMessage=InData.ReadDMsg();
  188. // Der Entity kommt im CurrentFrame neu dazu, delta'en bzgl. der BaseLine
  189. CurrentFrame.EntityIDsInPVS.PushBack(NewEntityID);
  190. // EnqueueString("Frame %lu, Entity mit ID %i kam hinzu.\n", CurrentFrame.ServerFrameNr, NewEntityID);
  191. CurrentFrame.IsValid&= // Note that operator & doesn't short-circuit, like operator && does!
  192. ParseServerDeltaUpdateMessage(NewEntityID, 0, CurrentFrame.ServerFrameNr, &DeltaMessage);
  193. continue;
  194. }
  195. }
  196. // Entities, die im DeltaFrame noch 'übrig' sind, werden ins CurrentFrame übernommen
  197. while (DeltaFrameEntityID!=0x99999999)
  198. {
  199. // Gleicher Fall wie oben:
  200. // Ein oder mehrere Entities vom DeltaFrame finden sich unverändert im CurrentFrame wieder,
  201. // für diese Entities hat der Server aber überhaupt keine Bits geschickt, d.h. noch nichtmal den Header!
  202. CurrentFrame.EntityIDsInPVS.PushBack(DeltaFrameEntityID);
  203. CurrentFrame.IsValid&= // Note that operator & doesn't short-circuit, like operator && does!
  204. ParseServerDeltaUpdateMessage(DeltaFrameEntityID, CurrentFrame.DeltaFrameNr, CurrentFrame.ServerFrameNr, NULL);
  205. DeltaFrameIndex++;
  206. if (DeltaFrameIndex>=DeltaFrame->EntityIDsInPVS.Size()) DeltaFrameEntityID=0x99999999;
  207. else DeltaFrameEntityID=DeltaFrame->EntityIDsInPVS[DeltaFrameIndex];
  208. }
  209. // CurrentFrame speichern für die spätere Wiederverwendung
  210. Frames[m_ServerFrameNr & (MAX_FRAMES-1)]=CurrentFrame;
  211. // Falls das CurrentFrame die ganze Zeit nicht gültig war, müssen wir 0 zurückgeben,
  212. // um vom Server gegen die BaseLines komprimierte Messages zu bekommen (siehe oben)!
  213. if (!CurrentFrame.IsValid)
  214. EnqueueString("CLIENT INFO: CurrentFrame (%lu %lu) invalid, requesting baseline message.\n", CurrentFrame.ServerFrameNr, CurrentFrame.DeltaFrameNr);
  215. return CurrentFrame.IsValid ? m_ServerFrameNr : 0;
  216. }
  217. bool CaClientWorldT::OurEntity_Repredict(unsigned long RemoteLastIncomingSequenceNr, unsigned long LastOutgoingSequenceNr)
  218. {
  219. if (OurEntityID<m_EngineEntities.Size())
  220. if (m_EngineEntities[OurEntityID]!=NULL)
  221. return m_EngineEntities[OurEntityID]->Repredict(m_PlayerCommands, RemoteLastIncomingSequenceNr, LastOutgoingSequenceNr);
  222. return false;
  223. }
  224. void CaClientWorldT::OurEntity_Predict(const PlayerCommandT& PlayerCommand, unsigned long OutgoingSequenceNr)
  225. {
  226. // Store the PlayerCommand for the reprediction.
  227. m_PlayerCommands[OutgoingSequenceNr & (m_PlayerCommands.Size()-1)] = PlayerCommand;
  228. if (OurEntityID<m_EngineEntities.Size())
  229. if (m_EngineEntities[OurEntityID]!=NULL)
  230. m_EngineEntities[OurEntityID]->Predict(PlayerCommand, OutgoingSequenceNr);
  231. }
  232. bool CaClientWorldT::OurEntity_GetCamera(Vector3dT& Origin, unsigned short& Heading, unsigned short& Pitch, unsigned short& Bank) const
  233. {
  234. if (OurEntityID<m_EngineEntities.Size())
  235. if (m_EngineEntities[OurEntityID]!=NULL)
  236. {
  237. m_EngineEntities[OurEntityID]->GetCamera(Origin, Heading, Pitch, Bank);
  238. return true;
  239. }
  240. return false;
  241. }
  242. void CaClientWorldT::ComputeBFSPath(const VectorT& Start, const VectorT& End)
  243. {
  244. #if 0 // TODO: Move this into the scene graph.
  245. if (Start.IsEqual(End, MapT::RoundEpsilon)) { BFS_TreePoints.Clear(); return; }
  246. unsigned long LeafNr;
  247. BFS_Tree.Clear(); for (LeafNr=0; LeafNr<Ca3DEWorld.Map.Leaves.Size(); LeafNr++) BFS_Tree.PushBack((unsigned long)-1);
  248. BFS_TreePoints.Clear(); for (LeafNr=0; LeafNr<Ca3DEWorld.Map.Leaves.Size(); LeafNr++) BFS_TreePoints.PushBack(VectorT());
  249. ArrayT<bool> BFS_Visited; for (LeafNr=0; LeafNr<Ca3DEWorld.Map.Leaves.Size(); LeafNr++) BFS_Visited.PushBack(false);
  250. ArrayT<unsigned long> ToDoList;
  251. ToDoList.PushBack(Ca3DEWorld.Map.WhatLeaf(Start));
  252. BFS_Visited[ToDoList[0]]=true;
  253. while (ToDoList.Size())
  254. {
  255. // Nimm das erste Element aus der ToDoList...
  256. unsigned long CurrentLeafNr=ToDoList[0];
  257. // ...und rücke alles eins runter
  258. for (LeafNr=0; LeafNr+1<ToDoList.Size(); LeafNr++) ToDoList[LeafNr]=ToDoList[LeafNr+1];
  259. ToDoList.DeleteBack();
  260. // Alle Nachbarn betrachten
  261. // OPTIMIZE: Das geht natürlich besser, wenn man einen Adjaceny-Graph hat!
  262. for (LeafNr=0; LeafNr<Ca3DEWorld.Map.Leaves.Size(); LeafNr++)
  263. {
  264. if (BFS_Visited[LeafNr] || !Ca3DEWorld.Map.Leaves[CurrentLeafNr].BB.GetEpsilonBox(MapT::RoundEpsilon).Intersects(Ca3DEWorld.Map.Leaves[LeafNr].BB)) continue;
  265. for (unsigned long Portal1Nr=0; Portal1Nr<Ca3DEWorld.Map.Leaves[CurrentLeafNr].Portals.Size(); Portal1Nr++)
  266. for (unsigned long Portal2Nr=0; Portal2Nr<Ca3DEWorld.Map.Leaves[LeafNr].Portals.Size(); Portal2Nr++)
  267. if (Ca3DEWorld.Map.Leaves[CurrentLeafNr].Portals[Portal1Nr].Overlaps(Ca3DEWorld.Map.Leaves[LeafNr].Portals[Portal2Nr], false, MapT::RoundEpsilon))
  268. {
  269. BFS_Visited[LeafNr]=true; // Nachbarn 'markieren',
  270. BFS_Tree [LeafNr]=CurrentLeafNr; // Vorgänger von -1 auf CurrentLeaf setzen
  271. ToDoList.PushBack(LeafNr); // und in ToDoList aufnehmen.
  272. // Als Zugabe wollen wir noch den Eintrittspunkt festhalten
  273. ArrayT< Polygon3T<double> > NewPolys;
  274. Ca3DEWorld.Map.Leaves[LeafNr].Portals[Portal2Nr].GetChoppedUpAlong(Ca3DEWorld.Map.Leaves[CurrentLeafNr].Portals[Portal1Nr], MapT::RoundEpsilon, NewPolys);
  275. VectorT Center;
  276. for (unsigned long VertexNr=0; VertexNr<NewPolys[NewPolys.Size()-1].Vertices.Size(); VertexNr++)
  277. Center=Center+NewPolys[NewPolys.Size()-1].Vertices[VertexNr];
  278. BFS_TreePoints[LeafNr]=scale(Center, 1.0/double(NewPolys[NewPolys.Size()-1].Vertices.Size()));
  279. // Es wäre nicht schlimm, wenn ein Leaf mehrfach in der ToDoListe landet, aber sinnvoll ist es auch nicht
  280. Portal1Nr=Ca3DEWorld.Map.Leaves[CurrentLeafNr].Portals.Size();
  281. break;
  282. }
  283. }
  284. }
  285. BFS_EndLeafNr=Ca3DEWorld.Map.WhatLeaf(End);
  286. EnqueueString("Path from (%f %f %f) to (%f %f %f) calculated.", Start.x, Start.y, Start.z, End.x, End.y, End.z);
  287. #endif
  288. }
  289. void CaClientWorldT::Draw(float FrameTime, const Vector3dT& DrawOrigin, unsigned short DrawHeading, unsigned short DrawPitch, unsigned short DrawBank) const
  290. {
  291. MatSys::Renderer->SetMatrix(MatSys::RendererI::MODEL_TO_WORLD, MatrixT());
  292. MatSys::Renderer->SetMatrix(MatSys::RendererI::WORLD_TO_VIEW, MatrixT::GetRotateXMatrix(-90.0f)); // Start with the global Ca3DE coordinate system (not the OpenGL coordinate system).
  293. MatSys::Renderer->RotateY (MatSys::RendererI::WORLD_TO_VIEW, -float(DrawBank )*45.0f/8192.0f); // *360/2^16
  294. MatSys::Renderer->RotateX (MatSys::RendererI::WORLD_TO_VIEW, float(DrawPitch )*45.0f/8192.0f);
  295. MatSys::Renderer->RotateZ (MatSys::RendererI::WORLD_TO_VIEW, float(DrawHeading)*45.0f/8192.0f);
  296. // OBSOLETE: DrawableSkyDome.Draw();
  297. #if SHL_ENABLED
  298. MoveSHLSun(FrameTime);
  299. #endif
  300. MatSys::Renderer->Translate(MatSys::RendererI::WORLD_TO_VIEW, -float(DrawOrigin.x), -float(DrawOrigin.y), -float(DrawOrigin.z));
  301. #if 0 // TODO: Move this into the scene graph.
  302. #ifdef DEBUG
  303. if (BFS_TreePoints.Size())
  304. {
  305. /* unsigned long CurrentLeaf=BFS_EndLeafNr;
  306. glDisable(GL_TEXTURE_2D);
  307. glColor3f(1.0, 0.0, 0.0);
  308. glBegin(GL_LINE_STRIP);
  309. while (CurrentLeaf!=(unsigned long)-1)
  310. {
  311. glVertex3d(BFS_TreePoints[CurrentLeaf].x, BFS_TreePoints[CurrentLeaf].y, BFS_TreePoints[CurrentLeaf].z);
  312. CurrentLeaf=BFS_Tree[CurrentLeaf];
  313. }
  314. glEnd();
  315. glEnable(GL_TEXTURE_2D); */
  316. }
  317. #endif
  318. #endif
  319. // Es gibt zwei Möglichkeiten, das PVS zu "disablen":
  320. // Entweder DrawEntities() veranlassen, alle Entities des m_EngineEntities-Arrays zu zeichnen
  321. // (z.B. durch einen Trick, oder explizit ein Array der Größe m_EngineEntities.Size() übergeben, das an der Stelle i der Wert i hat),
  322. // oder indem die Beachtung des PVS auf Server-Seite (!) ausgeschaltet wird! Die Effekte sind jeweils verschieden!
  323. const FrameT& CurrentFrame=Frames[m_ServerFrameNr & (MAX_FRAMES-1)];
  324. static float TotalTime=0.0;
  325. TotalTime+=FrameTime;
  326. // Add a small offset to the z-component of the eye position, which adds a mild nice moving effect to the specular highlights.
  327. const float EyeOffsetZ=200.0f*sinf(TotalTime);
  328. MatSys::Renderer->SetCurrentRenderAction(MatSys::RendererI::AMBIENT);
  329. MatSys::Renderer->SetCurrentEyePosition(float(DrawOrigin.x), float(DrawOrigin.y), float(DrawOrigin.z)+EyeOffsetZ); // Also required in some ambient shaders.
  330. m_World->BspTree->DrawAmbientContrib(DrawOrigin);
  331. if (!CurrentFrame.IsValid)
  332. {
  333. // Eine Möglichkeit, wie man zu diesem Fehler kommt:
  334. // Bei einer World mit sehr vielen Entities, die auch alle vom Startpunkt aus sichtbar (d.h. im PVS) sind,
  335. // schickt uns der Server nach dem Join-Request die WorldInfo, die BaseLines, und auch die erste FrameInfo-Message.
  336. // Die FrameInfo-Message wird jedoch nur "unreliable" zu übertragen versucht, und daher vom Protokoll weggelassen,
  337. // wenn die max. Größe des Netzwerkpakets überschritten wird.
  338. // Somit können wir hierherkommen, ohne jemals eine FrameInfo-Message vom Server gesehen zu haben.
  339. // Erkennen kann man diesen Fall daran, daß 'm_ServerFrameNr' noch den Initialisierungswert 0xDEADBEEF enthält.
  340. // Das Auftreten dieses Fehlers ist nicht schön, aber auch nicht sehr schlimm, solange es keine sauberere Lösung gibt.
  341. #ifdef DEBUG
  342. EnqueueString("CLIENT WARNING: %s, L %u: Frame %lu was invalid on entity draw attempt!", __FILE__, __LINE__, m_ServerFrameNr);
  343. #endif
  344. return;
  345. }
  346. // Draw the ambient contribution of the entities.
  347. DrawEntities(OurEntityID, false, DrawOrigin, CurrentFrame.EntityIDsInPVS);
  348. // Render the contribution of the point light sources (shadows, normal-maps, specular-maps).
  349. int LightSourceCount=0;
  350. for (unsigned long EntityIDNr=0; EntityIDNr<CurrentFrame.EntityIDsInPVS.Size(); EntityIDNr++)
  351. {
  352. unsigned long LightColorDiffuse=0;
  353. unsigned long LightColorSpecular=0;
  354. VectorT LightPosition;
  355. float LightRadius;
  356. bool LightCastsShadows;
  357. const IntrusivePtrT<GameEntityI> BaseEntity=GetGameEntityByID(CurrentFrame.EntityIDsInPVS[EntityIDNr]);
  358. // The light source info is not taken from the BaseEntity directly because it yields the unpredicted light source position.
  359. // If once human player entities have no light source any more, we might get rid of the CaClientWorldT::GetLightSourceInfo()
  360. // function chain again altogether.
  361. if (BaseEntity.IsNull()) continue;
  362. // if (!BaseEntity->GetLightSourceInfo(LightColorDiffuse, LightColorSpecular, LightPosition, LightRadius)) continue;
  363. if (!GetLightSourceInfo(BaseEntity->GetID(), LightColorDiffuse, LightColorSpecular, LightPosition, LightRadius, LightCastsShadows)) continue;
  364. if (!LightColorDiffuse && !LightColorSpecular) continue;
  365. // THIS IS *TEMPORARY* ONLY!
  366. // The purpose of limiting the number of light sources here is to compensate for the
  367. // severe problems with the stencil shadow code (fill-rate).
  368. static ConVarT MaxLights("cl_maxLights", 8, ConVarT::FLAG_MAIN_EXE, "Limits the number of simultaneously active dynamic lights.", 0, 255);
  369. if (LightSourceCount>=MaxLights.GetValueInt()) break;
  370. LightSourceCount++;
  371. MatSys::Renderer->SetCurrentLightSourcePosition(float(LightPosition.x), float(LightPosition.y), float(LightPosition.z));
  372. MatSys::Renderer->SetCurrentLightSourceRadius(LightRadius);
  373. MatSys::Renderer->SetCurrentLightSourceDiffuseColor (float(LightColorDiffuse & 0xFF)/255.0f, float((LightColorDiffuse >> 8) & 0xFF)/255.0f, float((LightColorDiffuse >> 16) & 0xFF)/255.0f);
  374. MatSys::Renderer->SetCurrentLightSourceSpecularColor(float(LightColorSpecular & 0xFF)/255.0f, float((LightColorSpecular >> 8) & 0xFF)/255.0f, float((LightColorSpecular >> 16) & 0xFF)/255.0f);
  375. // MatSys::Renderer->SetCurrentEyePosition(...); was already called above!
  376. // Render the stencil shadows.
  377. MatSys::Renderer->SetCurrentRenderAction(MatSys::RendererI::STENCILSHADOW); // Make sure thet stencil buffer is cleared.
  378. if (LightCastsShadows)
  379. {
  380. m_World->BspTree->DrawStencilShadowVolumes(LightPosition, LightRadius);
  381. // static ConVarT LocalPlayerStencilShadows("cl_LocalPlayerStencilShadows", false, ConVarT::FLAG_MAIN_EXE, "Whether the local player casts stencil shadows.");
  382. // if (LocalPlayerStencilShadows.GetValueBool())
  383. // {
  384. // Our entity casts shadows, except when the light source is he himself.
  385. DrawEntities(OurEntityID==BaseEntity->GetID() ? OurEntityID : 0xFFFFFFFF /* an ugly, dirty, kaum nachvollziehbarer hack */,
  386. OurEntityID==BaseEntity->GetID(),
  387. DrawOrigin,
  388. CurrentFrame.EntityIDsInPVS);
  389. // }
  390. // else
  391. // {
  392. // // Our entity does not cast shadows at all, no matter if he himself or another entity is the light source.
  393. // // ### In my last test, I did not observe any performance improvements with this, in comparison with the case above... ###
  394. // DrawEntities(OurEntityID,
  395. // true,
  396. // DrawOrigin,
  397. // CurrentFrame.EntityIDsInPVS);
  398. // }
  399. }
  400. // Render the light-source dependent terms.
  401. MatSys::Renderer->SetCurrentRenderAction(MatSys::RendererI::LIGHTING);
  402. m_World->BspTree->DrawLightSourceContrib(DrawOrigin, LightPosition);
  403. DrawEntities(OurEntityID,
  404. false,
  405. DrawOrigin,
  406. CurrentFrame.EntityIDsInPVS);
  407. }
  408. MatSys::Renderer->SetCurrentRenderAction(MatSys::RendererI::AMBIENT);
  409. // Render translucent nodes back-to-front.
  410. m_World->BspTree->DrawTranslucentContrib(DrawOrigin);
  411. // Zuletzt halbtransparente HUD-Elemente, Fonts usw. zeichnen.
  412. PostDrawEntities(FrameTime, CurrentFrame.EntityIDsInPVS);
  413. }
  414. bool CaClientWorldT::ParseServerDeltaUpdateMessage(unsigned long EntityID, unsigned long DeltaFrameNr, unsigned long ServerFrameNr, const ArrayT<uint8_t>* DeltaMessage)
  415. {
  416. bool EntityIDIsOK=false;
  417. if (EntityID<m_EngineEntities.Size())
  418. if (m_EngineEntities[EntityID]!=NULL)
  419. EntityIDIsOK=true;
  420. if (!EntityIDIsOK)
  421. {
  422. // Gib Warnung aus. Aber nur, weil wir mit einer SC1_EntityUpdate Message nichts anfangen können, brauchen wir noch lange nicht zu disconnecten.
  423. // ONE reason for getting EntityID>=m_EngineEntities.Size() here is the way how baselines are sent:
  424. // When a client joins a level, there can be a LOT of entities. Usually, not all baselines of all entities fit into a single
  425. // realiable message at once, and thus the server sends them in batches, contained in subsequent realiable messages.
  426. // Between realiable messages however, the server sends also SC1_EntityUpdate messages.
  427. // These messages can already refer to entities that the client knows nothing about, because it has not yet seen the (reliable)
  428. // introductory baseline message, and so we get here.
  429. // I turn the "WARNING" into an "INFO", so that ordinary users get a better impression. ;)
  430. if (EntityID>=m_EngineEntities.Size()) EnqueueString("CLIENT INFO: %s, L %u: EntityID>=m_EngineEntities.Size()\n", __FILE__, __LINE__);
  431. else EnqueueString("CLIENT WARNING: %s, L %u: m_EngineEntities[EntityID]==NULL \n", __FILE__, __LINE__);
  432. EnqueueString("(EntityID==%u, m_EngineEntities.Size()==%u)\n", EntityID, m_EngineEntities.Size());
  433. return false;
  434. }
  435. // Gibt bei Scheitern Diagnose-Nachricht aus. Häufigster Grund für Scheitern dürfe eine zu alte DeltaFrameNr sein.
  436. // Der Calling-Code muß das erkennen und reagieren (durch Anfordern von nichtkomprimierten (gegen die BaseLine komprimierten) Messages).
  437. // Jedenfalls nicht Grund genug für ein Client-Disconnect.
  438. return m_EngineEntities[EntityID]->ParseServerDeltaUpdateMessage(DeltaFrameNr, ServerFrameNr, DeltaMessage);
  439. }
  440. bool CaClientWorldT::GetLightSourceInfo(unsigned long EntityID, unsigned long& DiffuseColor, unsigned long& SpecularColor, VectorT& Position, float& Radius, bool& CastsShadows) const
  441. {
  442. if (EntityID<m_EngineEntities.Size())
  443. if (m_EngineEntities[EntityID]!=NULL)
  444. return m_EngineEntities[EntityID]->GetLightSourceInfo(DiffuseColor, SpecularColor, Position, Radius, CastsShadows);
  445. return false;
  446. }
  447. void CaClientWorldT::DrawEntities(unsigned long OurEntityID, bool SkipOurEntity, const VectorT& ViewerPos, const ArrayT<unsigned long>& EntityIDs) const
  448. {
  449. for (unsigned long IDNr=0; IDNr<EntityIDs.Size(); IDNr++)
  450. {
  451. const unsigned long EntityID=EntityIDs[IDNr];
  452. if (EntityID<m_EngineEntities.Size())
  453. if (m_EngineEntities[EntityID]!=NULL)
  454. {
  455. const bool FirstPersonView = (EntityID==OurEntityID);
  456. if (EntityID==OurEntityID && SkipOurEntity) continue;
  457. m_EngineEntities[EntityID]->Draw(FirstPersonView, ViewerPos);
  458. }
  459. }
  460. }
  461. void CaClientWorldT::PostDrawEntities(float FrameTime, const ArrayT<unsigned long>& EntityIDs) const
  462. {
  463. for (unsigned long IDNr=0; IDNr<EntityIDs.Size(); IDNr++)
  464. {
  465. const unsigned long EntityID=EntityIDs[IDNr];
  466. if (EntityID!=OurEntityID && EntityID<m_EngineEntities.Size())
  467. if (m_EngineEntities[EntityID]!=NULL)
  468. m_EngineEntities[EntityID]->PostDraw(FrameTime, false);
  469. }
  470. if (OurEntityID<m_EngineEntities.Size())
  471. if (m_EngineEntities[OurEntityID]!=NULL)
  472. m_EngineEntities[OurEntityID]->PostDraw(FrameTime, true);
  473. }