Fix issue in Rocket.lua script.
[cafu-Engine.git] / Ca3DE / Client / ClientStateInGame.cpp
blob762d37aee2e7b1e54d10fc1e9e5deb753e837062
1 /*
2 Cafu Engine, http://www.cafu.de/
3 Copyright (c) Carsten Fuchs and other contributors.
4 This project is licensed under the terms of the MIT license.
5 */
7 #include "../GameInfo.hpp"
8 #include "../NetConst.hpp"
10 #include "ClientStateInGame.hpp"
11 #include "Client.hpp"
12 #include "ClientWorld.hpp"
13 #include "PathRecorder.hpp"
15 #include "GuiSys/GuiImpl.hpp"
16 #include "GuiSys/GuiManImpl.hpp"
17 #include "GuiSys/Window.hpp"
18 #include "MaterialSystem/MaterialManager.hpp"
19 #include "MaterialSystem/Mesh.hpp"
20 #include "MaterialSystem/Renderer.hpp"
21 #include "MaterialSystem/TextureMap.hpp"
22 #include "SoundSystem/SoundSys.hpp"
23 #include "MainWindow/MainWindow.hpp"
24 #include "Math3D/Angles.hpp"
25 #include "Math3D/Matrix.hpp"
26 #include "Network/Network.hpp"
27 #include "OpenGL/OpenGLWindow.hpp"
28 #include "ConsoleCommands/Console.hpp"
29 #include "ConsoleCommands/ConsoleInterpreter.hpp"
30 #include "ConsoleCommands/ConVar.hpp"
31 #include "ConsoleCommands/ConFunc.hpp"
32 #include "SceneGraph/Node.hpp"
33 #include "Win32/Win32PrintHelp.hpp"
34 #include "DebugLog.hpp"
36 extern "C"
38 #include <lua.h>
39 #include <lualib.h>
40 #include <lauxlib.h>
43 #include <assert.h>
44 #include <stdio.h>
45 #include <string.h>
47 #ifdef _WIN32
48 #define WIN32_LEAN_AND_MEAN
49 #include <windows.h>
50 #else
51 #include <errno.h>
52 #define WSAECONNRESET ECONNRESET
53 #define WSAEMSGSIZE EMSGSIZE
54 #define WSAEWOULDBLOCK EWOULDBLOCK
55 #endif
58 static ClientStateInGameT* ClientIGSPtr=NULL;
61 int ClientStateInGameT::ConFunc_say_Callback(lua_State* LuaState)
63 if (!ClientIGSPtr) return 0;
65 NetDataT NewReliableMsg;
67 NewReliableMsg.WriteByte (CS1_SayToAll);
68 NewReliableMsg.WriteString(luaL_checkstring(LuaState, 1));
70 ClientIGSPtr->ReliableDatas.PushBack(NewReliableMsg.Data);
71 return 0;
74 static ConFuncT ConFunc_say("say", ClientStateInGameT::ConFunc_say_Callback, ConFuncT::FLAG_MAIN_EXE, "Sends the given string to all connected clients.");
77 #if 0
78 // This is a nice auxiliary function for recording demo movies (e.g. using Fraps),
79 // as we can easily script fake-chats with it, see example in ChatInput.cgui.
80 int ClientStateInGameT::ConFunc_chatPrint_Callback(lua_State* LuaState)
82 if (!ClientIGSPtr) return 0;
84 ClientIGSPtr->ChatScrollInfo.Print(luaL_checkstring(LuaState, 1));
85 return 0;
88 static ConFuncT ConFunc_chatPrint("chatPrint", ClientStateInGameT::ConFunc_chatPrint_Callback, ConFuncT::FLAG_MAIN_EXE, "Prints the given string as a chat message area.");
89 #endif
92 int ClientStateInGameT::ConFunc_showPath_Callback(lua_State* LuaState)
94 if (!ClientIGSPtr) return 0;
95 if (lua_gettop(LuaState)!=6) return luaL_error(LuaState, "Usage: showPath(Ax, Ay, Az, Bx, By, Bz) (shows the path from A to B).\n");
97 VectorT Start(luaL_checknumber(LuaState, 1), luaL_checknumber(LuaState, 2), luaL_checknumber(LuaState, 3));
98 VectorT End (luaL_checknumber(LuaState, 4), luaL_checknumber(LuaState, 5), luaL_checknumber(LuaState, 6));
100 ClientIGSPtr->World->ComputeBFSPath(Start, End);
101 return 0;
104 static ConFuncT ConFunc_showPath("showPath", ClientStateInGameT::ConFunc_showPath_Callback, ConFuncT::FLAG_MAIN_EXE, "Shows the \"shortest\" path from one point in space to another.");
107 int ClientStateInGameT::ConFunc_recordPath_Callback(lua_State* LuaState)
109 if (!ClientIGSPtr) return 0;
111 PathRecorderT*& Recorder=ClientIGSPtr->m_PathRecorder;
112 const char* FileName=lua_tostring(LuaState, 1);
114 if (FileName)
116 // Already recording to the file with the given name? Continue recording.
117 // If we were not recording before, or are recording to a different file, start recording.
118 if (!Recorder || Recorder->GetFileName()!=FileName)
120 delete Recorder;
121 Recorder=new PathRecorderT(FileName);
124 else
126 // Stop recording.
127 delete Recorder;
128 Recorder=NULL;
131 return 0;
134 static ConFuncT ConFunc_recordPath("recordPath", ClientStateInGameT::ConFunc_recordPath_Callback, ConFuncT::FLAG_MAIN_EXE, "Records the players path into a pointfile (load into CaWE to view).");
137 ClientStateInGameT::ClientStateInGameT(ClientT& Client_)
138 : Client(Client_),
139 Font_v("Fonts/Arial"),
140 Font_f("Fonts/FixedWidth"),
141 World(NULL),
142 IsLoadingWorld(false),
143 ClientFrameNr(0),
144 m_PlayerCommand(),
145 m_PlayerCommandCount(1), // In each newly loaded world, player command numbering restarts at 1.
146 m_PathRecorder(NULL)
148 assert(Client.Socket!=INVALID_SOCKET);
150 assert(ClientIGSPtr==NULL);
151 ClientIGSPtr=this;
155 ClientStateInGameT::~ClientStateInGameT()
157 delete m_PathRecorder;
158 m_PathRecorder=NULL;
160 if (Client.MainMenuGui!=NULL)
162 Client.MainMenuGui->Activate();
164 // Bringing the main menu to front here is not good:
165 // It should have been in front of the client window anyway, and if
166 // we get here because "disconnect()" was typed into the console,
167 // the console is suddenly "behind" the main menu, and thus inaccessible.
168 // cf::GuiSys::GuiMan->BringToFront(Client.MainMenuGui);
173 const ArrayT< ArrayT<char> > EmptyReliableMessage;
174 NetDataT UnreliableMessage;
176 // Disconnect Msg senden, es gibt kein ACK
177 UnreliableMessage.WriteByte(CS1_Disconnect);
178 GameProtocol.GetTransmitData(EmptyReliableMessage, UnreliableMessage.Data).Send(Client.Socket, Client.ServerAddress);
180 catch (const NetworkError& /*E*/) {} // Ignoriere mögliche Network-Errors.
182 delete World;
183 World=NULL;
185 assert(ClientIGSPtr==this);
186 ClientIGSPtr=NULL;
190 int ClientStateInGameT::GetID() const
192 return ClientT::INGAME;
196 bool ClientStateInGameT::ProcessInputEvent(const CaKeyboardEventT& KE)
198 const bool s = (KE.Type == CaKeyboardEventT::CKE_KEYDOWN);
200 switch (KE.Key)
202 case CaKeyboardEventT::CK_ESCAPE:
204 if (s && Client.MainMenuGui != NULL)
206 Client.MainMenuGui->Activate();
207 cf::GuiSys::GuiMan->BringToFront(Client.MainMenuGui);
209 break;
212 case CaKeyboardEventT::CK_T:
213 case CaKeyboardEventT::CK_Y:
215 IntrusivePtrT<cf::GuiSys::GuiImplT> ChatInputGui = cf::GuiSys::GuiMan->Find(std::string("Games/") + Client.m_GameInfo.GetName() + "/GUIs/ChatInput_main.cgui", true);
217 // Could be NULL on file not found, parse error, etc.
218 if (s && ChatInputGui != NULL)
220 ChatInputGui->Activate();
221 cf::GuiSys::GuiMan->BringToFront(ChatInputGui);
223 break;
226 case CaKeyboardEventT::CK_SPACE:
227 m_PlayerCommand.Set(PCK_Jump, s);
228 break;
230 case CaKeyboardEventT::CK_LSHIFT:
231 case CaKeyboardEventT::CK_RSHIFT:
232 m_PlayerCommand.Set(PCK_Walk, s);
233 break;
235 case CaKeyboardEventT::CK_UP:
236 case CaKeyboardEventT::CK_W:
237 m_PlayerCommand.Set(PCK_MoveForward, s);
238 break;
240 case CaKeyboardEventT::CK_DOWN:
241 case CaKeyboardEventT::CK_S:
242 m_PlayerCommand.Set(PCK_MoveBackward, s);
243 break;
245 case CaKeyboardEventT::CK_A:
246 case CaKeyboardEventT::CK_COMMA:
247 m_PlayerCommand.Set(PCK_StrafeLeft, s);
248 break;
250 case CaKeyboardEventT::CK_D:
251 case CaKeyboardEventT::CK_PERIOD:
252 m_PlayerCommand.Set(PCK_StrafeRight, s);
253 break;
255 case CaKeyboardEventT::CK_R:
256 m_PlayerCommand.Set(PCK_Fire1, s);
257 break;
259 case CaKeyboardEventT::CK_RETURN:
260 case CaKeyboardEventT::CK_NUMPADENTER:
261 m_PlayerCommand.Set(PCK_Use, s);
262 break;
264 case CaKeyboardEventT::CK_LEFT:
265 m_PlayerCommand.Set(PCK_TurnLeft, s);
266 break;
268 case CaKeyboardEventT::CK_RIGHT:
269 m_PlayerCommand.Set(PCK_TurnRight, s);
270 break;
272 case CaKeyboardEventT::CK_PGDN:
273 m_PlayerCommand.Set(PCK_LookUp, s);
274 break;
276 case CaKeyboardEventT::CK_PGUP:
277 m_PlayerCommand.Set(PCK_LookDown, s);
278 break;
280 case CaKeyboardEventT::CK_END:
281 m_PlayerCommand.Set(PCK_CenterView, s);
282 break;
284 case CaKeyboardEventT::CK_1:
285 case CaKeyboardEventT::CK_NUMPAD1:
286 m_PlayerCommand.SetNumber(s ? 1 : 0);
287 break;
289 case CaKeyboardEventT::CK_2:
290 case CaKeyboardEventT::CK_NUMPAD2:
291 m_PlayerCommand.SetNumber(s ? 2 : 0);
292 break;
294 case CaKeyboardEventT::CK_3:
295 case CaKeyboardEventT::CK_NUMPAD3:
296 m_PlayerCommand.SetNumber(s ? 3 : 0);
297 break;
299 case CaKeyboardEventT::CK_4:
300 case CaKeyboardEventT::CK_NUMPAD4:
301 m_PlayerCommand.SetNumber(s ? 4 : 0);
302 break;
304 case CaKeyboardEventT::CK_5:
305 case CaKeyboardEventT::CK_NUMPAD5:
306 m_PlayerCommand.SetNumber(s ? 5 : 0);
307 break;
309 case CaKeyboardEventT::CK_6:
310 case CaKeyboardEventT::CK_NUMPAD6:
311 m_PlayerCommand.SetNumber(s ? 6 : 0);
312 break;
314 case CaKeyboardEventT::CK_7:
315 case CaKeyboardEventT::CK_NUMPAD7:
316 m_PlayerCommand.SetNumber(s ? 7 : 0);
317 break;
319 case CaKeyboardEventT::CK_8:
320 case CaKeyboardEventT::CK_NUMPAD8:
321 m_PlayerCommand.SetNumber(s ? 8 : 0);
322 break;
324 case CaKeyboardEventT::CK_9:
325 case CaKeyboardEventT::CK_NUMPAD9:
326 m_PlayerCommand.SetNumber(s ? 9 : 0);
327 break;
329 case CaKeyboardEventT::CK_0:
330 case CaKeyboardEventT::CK_NUMPAD0:
331 m_PlayerCommand.SetNumber(s ? 10 : 0);
332 break;
334 default:
335 break;
338 return true;
342 // This convar is declared here rather than (statically local) in the CM_MOVE_Y case as indicated below
343 // so that it is registered with the console interpreter early during initialization, and not only when
344 // the program flow first enters the CM_MOVE_Y case.
345 // Otherwise, when the Main Menu GUI tried to access this variable (through the console interpreter),
346 // it might not be available yet, which may make the "Input Controls" dialog look broken to the user.
347 static ConVarT MouseReverseY("mouseRevY", false, ConVarT::FLAG_MAIN_EXE | ConVarT::FLAG_PERSISTENT, "Flips the interpretation of the mouse up/down axis (normal vs. aircraft-style).");
349 bool ClientStateInGameT::ProcessInputEvent(const CaMouseEventT& ME)
351 switch (ME.Type)
353 case CaMouseEventT::CM_BUTTON0:
354 m_PlayerCommand.Set(PCK_Fire1, ME.Amount == 1);
355 break;
357 case CaMouseEventT::CM_BUTTON1:
358 case CaMouseEventT::CM_BUTTON2:
359 case CaMouseEventT::CM_BUTTON3:
360 m_PlayerCommand.Set(PCK_Fire2, ME.Amount == 1);
361 break;
363 case CaMouseEventT::CM_MOVE_X: // X-Axis.
364 m_PlayerCommand.DeltaHeading+=(unsigned short)(ME.Amount*30);
365 break;
367 case CaMouseEventT::CM_MOVE_Y: // Y-Axis.
369 // static ConVarT MouseReverseY(...);
371 if (MouseReverseY.GetValueBool()) m_PlayerCommand.DeltaPitch-=(unsigned short)(ME.Amount*30);
372 else m_PlayerCommand.DeltaPitch+=(unsigned short)(ME.Amount*30);
373 break;
376 default:
377 // Ignore other ME types.
378 break;
381 return true;
385 static MatSys::RenderMaterialT* LogoRenderMat =NULL;
386 static MatSys::RenderMaterialT* LoadingBarLMat=NULL;
387 static MatSys::RenderMaterialT* LoadingBarRMat=NULL;
388 static MatSys::RenderMaterialT* LoadingBar0Mat=NULL;
389 static MatSys::RenderMaterialT* LoadingBar1Mat=NULL;
390 static FontT* LoadingFont =NULL;
391 static float LoadingProgressPercent=0.0f;
392 static std::string LoadingProgressText="";
395 void ClientStateInGameT::Render(float FrameTime)
397 const unsigned int fbWidth = Client.m_MainWin.GetFrameBufferWidth();
398 const unsigned int fbHeight = Client.m_MainWin.GetFrameBufferHeight();
399 const float ffbWidth = float(fbWidth);
400 const float ffbHeight = float(fbHeight);
402 Graphs.ClearForFrame(ClientFrameNr);
404 // Bestimme die FrameTime des letzten Frames
405 if (FrameTime<0.0001f) FrameTime=0.0001f;
407 Graphs.FPS[ClientFrameNr & (512-1)]=1.0f/FrameTime;
410 if (World)
412 IntrusivePtrT<const cf::GameSys::ComponentTransformT> CameraTrafo = World->OurEntity_GetCamera();
414 if (CameraTrafo != NULL)
416 // Graphs.Heading[ClientFrameNr & (512-1)]=(Current_Heading >> 5) & 511;
417 Graphs.PosY [ClientFrameNr & (512-1)]=((unsigned short)CameraTrafo->GetOriginWS().y) & 511;
418 Graphs.PosZ [ClientFrameNr & (512-1)]=((unsigned short)CameraTrafo->GetOriginWS().z) & 511;
420 MatSys::Renderer->PushMatrix(MatSys::RendererI::PROJECTION );
421 MatSys::Renderer->PushMatrix(MatSys::RendererI::MODEL_TO_WORLD);
422 MatSys::Renderer->PushMatrix(MatSys::RendererI::WORLD_TO_VIEW );
424 MatSys::Renderer->SetMatrix(MatSys::RendererI::PROJECTION,
425 // Note that the far plane is located at infinity for our stencil shadows implementation!
426 // A fovY of 67.5 corresponds to a fovX of 90.0 when the aspect ratio is 4:3.
427 MatrixT::GetProjPerspectiveMatrix(67.5f, ffbWidth / ffbHeight, 4.0f, -1.0f));
429 // MatSys::Renderer->SetMatrix(MatSys::RendererI::MODEL_TO_WORLD, MatrixT()); // Setup by World->Draw().
430 // MatSys::Renderer->SetMatrix(MatSys::RendererI::WORLD_TO_VIEW, MatrixT());
432 World->Draw(FrameTime);
434 MatSys::Renderer->PopMatrix(MatSys::RendererI::PROJECTION );
435 MatSys::Renderer->PopMatrix(MatSys::RendererI::MODEL_TO_WORLD);
436 MatSys::Renderer->PopMatrix(MatSys::RendererI::WORLD_TO_VIEW );
438 // Die Framerate ist zwar World-unabhängig, ihre Anzeige hier aber besser aufgehoben (aus rein kosmetischen Gründen).
439 static ConVarT ShowFrameRate("showFPS", false, ConVarT::FLAG_MAIN_EXE, "Toggles whether the frames-per-second number is shown.");
440 static ConVarT ShowPosition("showPos", false, ConVarT::FLAG_MAIN_EXE, "Toggles whether the current players position is shown.");
442 if (ShowFrameRate.GetValueBool()) Font_f.Print(fbWidth-100, fbHeight-16, ffbWidth, ffbHeight, 0x00FFFFFF, cf::va("FPS %5.1f", 1.0f/FrameTime));
444 if (ShowPosition.GetValueBool())
446 // unsigned long LeafNr =World->GetCa3DEWorldP()->Map.WhatLeaf(Current_Origin);
447 // char LeafContents='o';
449 // if (World->GetCa3DEWorldP()->Map.Leaves[LeafNr].IsInnerLeaf)
450 // LeafContents=World->GetCa3DEWorldP()->Map.Leaves[LeafNr].IsWaterLeaf ? 'w' : 'i';
452 const Vector3fT Origin = CameraTrafo->GetOriginWS();
454 Font_f.Print(fbWidth-130, 15, ffbWidth, ffbHeight, 0x00FFFFFF, cf::va("X %10.1f", Origin.x));
455 Font_f.Print(fbWidth-130, 35, ffbWidth, ffbHeight, 0x00FFFFFF, cf::va("Y %10.1f", Origin.y));
456 Font_f.Print(fbWidth-130, 55, ffbWidth, ffbHeight, 0x00FFFFFF, cf::va("Z %10.1f", Origin.z));
457 Font_f.Print(fbWidth-130, 75, ffbWidth, ffbHeight, 0x00FFFFFF, cf::va("Hdg %8.1f", cf::math::AnglesfT(CameraTrafo->GetQuatWS()).yaw()));
458 // Font_f.Print(fbWidth-100, fbHeight-32, ffbWidth, ffbHeight, 0x00FFFFFF, cf::va("L %4u %c", LeafNr, LeafContents));
461 /*if (ShowLeaf)
466 if (ShowPointPath)
471 else
473 Font_f.Print(30, fbHeight*2/3, ffbWidth, ffbHeight, 0x00004080, "Receiving entity baselines...");
476 else
478 // Falls wir noch keine World haben (weil nach dem Starten vom Server bisher nichts kam), können wir nicht viel tun.
479 if (IsLoadingWorld)
481 // MatSys::Renderer->PushMatrix(MatSys::RendererI::MODEL_TO_WORLD);
482 // MatSys::Renderer->PushMatrix(MatSys::RendererI::WORLD_TO_VIEW );
483 MatSys::Renderer->PushMatrix(MatSys::RendererI::PROJECTION );
485 MatSys::Renderer->SetMatrix(MatSys::RendererI::MODEL_TO_WORLD, MatrixT());
486 MatSys::Renderer->SetMatrix(MatSys::RendererI::WORLD_TO_VIEW, MatrixT());
487 MatSys::Renderer->SetMatrix(MatSys::RendererI::PROJECTION, MatrixT::GetProjOrthoMatrix(0.0f, ffbWidth, ffbHeight, 0.0f, -1.0f, 1.0f));
489 MatSys::Renderer->SetCurrentRenderAction(MatSys::RendererI::AMBIENT);
491 MatSys::MeshT M(MatSys::MeshT::Quads);
492 M.Vertices.PushBackEmpty(4);
493 M.Vertices[0].SetTextureCoord(0, 0);
494 M.Vertices[1].SetTextureCoord(1, 0);
495 M.Vertices[2].SetTextureCoord(1, 1);
496 M.Vertices[3].SetTextureCoord(0, 1);
498 // Render the big logo.
500 // Do floating-point (rather than unsigned long) math for the origin, or else we get into trouble with negative numbers.
501 // The coordinates have also been tested with a hor. and ver. stripe pattern texture for making sure that there is no
502 // inadvertent scaling by plus or minus one pixel.
503 M.Vertices[0].SetOrigin(fbWidth/2.0f-512.0f, fbHeight/2.0f-256.0f-20.0f); // links oben
504 M.Vertices[1].SetOrigin(fbWidth/2.0f+512.0f, fbHeight/2.0f-256.0f-20.0f); // rechts oben
505 M.Vertices[2].SetOrigin(fbWidth/2.0f+512.0f, fbHeight/2.0f+256.0f-20.0f); // rechts unten
506 M.Vertices[3].SetOrigin(fbWidth/2.0f-512.0f, fbHeight/2.0f+256.0f-20.0f); // links unten
508 MatSys::Renderer->SetCurrentMaterial(LogoRenderMat);
509 MatSys::Renderer->RenderMesh(M);
513 #if 0
514 const unsigned long BarHalfWidth=fbWidth/2-40;
516 // Render the left end of the loading bar.
518 M.Vertices[0].SetOrigin(fbWidth/2-BarHalfWidth-16, fbHeight*9/10-20 );
519 M.Vertices[1].SetOrigin(fbWidth/2-BarHalfWidth , fbHeight*9/10-20 );
520 M.Vertices[2].SetOrigin(fbWidth/2-BarHalfWidth , fbHeight*9/10-20+32);
521 M.Vertices[3].SetOrigin(fbWidth/2-BarHalfWidth-16, fbHeight*9/10-20+32);
523 MatSys::Renderer->SetCurrentMaterial(LoadingBarLMat);
524 MatSys::Renderer->RenderMesh(M);
527 // Render the center left (filled) part of the loading bar.
529 M.Vertices[0].SetOrigin(fbWidth/2-BarHalfWidth , fbHeight*9/10-20 );
530 M.Vertices[1].SetOrigin(fbWidth/2-BarHalfWidth+2*BarHalfWidth*ProgressPercent, fbHeight*9/10-20 );
531 M.Vertices[2].SetOrigin(fbWidth/2-BarHalfWidth+2*BarHalfWidth*ProgressPercent, fbHeight*9/10-20+32);
532 M.Vertices[3].SetOrigin(fbWidth/2-BarHalfWidth , fbHeight*9/10-20+32);
534 MatSys::Renderer->SetCurrentMaterial(LoadingBar1Mat);
535 MatSys::Renderer->RenderMesh(M);
538 // Render the center right (not yet filled) part of the loading bar.
540 M.Vertices[0].SetOrigin(fbWidth/2-BarHalfWidth+2*BarHalfWidth*ProgressPercent, fbHeight*9/10-20 );
541 M.Vertices[1].SetOrigin(fbWidth/2+BarHalfWidth , fbHeight*9/10-20 );
542 M.Vertices[2].SetOrigin(fbWidth/2+BarHalfWidth , fbHeight*9/10-20+32);
543 M.Vertices[3].SetOrigin(fbWidth/2-BarHalfWidth+2*BarHalfWidth*ProgressPercent, fbHeight*9/10-20+32);
545 MatSys::Renderer->SetCurrentMaterial(LoadingBar0Mat);
546 MatSys::Renderer->RenderMesh(M);
549 // Render the right end of the loading bar.
551 M.Vertices[0].SetOrigin(fbWidth/2+BarHalfWidth , fbHeight*9/10-20 );
552 M.Vertices[1].SetOrigin(fbWidth/2+BarHalfWidth+16, fbHeight*9/10-20 );
553 M.Vertices[2].SetOrigin(fbWidth/2+BarHalfWidth+16, fbHeight*9/10-20+32);
554 M.Vertices[3].SetOrigin(fbWidth/2+BarHalfWidth , fbHeight*9/10-20+32);
556 MatSys::Renderer->SetCurrentMaterial(LoadingBarRMat);
557 MatSys::Renderer->RenderMesh(M);
559 #endif
561 MatSys::Renderer->PopMatrix(MatSys::RendererI::PROJECTION);
562 // Don't bother to restore the model-to-world or world-to-view matrices: Whatever code follows should write its own setup into them.
565 const unsigned long CharWidth=10;
567 #ifdef DEBUG
568 LoadingFont->Print(fbWidth/2-34*CharWidth/2, fbHeight*9/10+12, ffbWidth, ffbHeight, 0x00800000, "Version: " __DATE__" [Debug build], "+LoadingProgressText);
569 #else
570 LoadingFont->Print(fbWidth/2-20*CharWidth/2, fbHeight*9/10+12, ffbWidth, ffbHeight, 0x00800000, "Version: " __DATE__);
571 #endif
573 if (LoadingProgressPercent>0)
574 LoadingFont->Print(fbWidth/2-10*CharWidth/2, fbHeight*9/10+30, ffbWidth, ffbHeight, 0x00800000, cf::va("Loading... %.0f%%", LoadingProgressPercent*100.0f));
575 else
576 LoadingFont->Print(fbWidth/2-10*CharWidth/2, fbHeight*9/10+30, ffbWidth, ffbHeight, 0x00800000, "Loading...");
578 else
580 // We have no world yet, and are currently not loading one,
581 // which means that we're still waiting for a SC1_WorldInfo message.
582 Font_f.Print(5, 200, ffbWidth, ffbHeight, 0x00DDFFBB, "Waiting for SC1_WorldInfo message...");
586 // Zeichne die restlichen Dinge, die unabhängig von der World sind.
587 static ConVarT ShowGraphs("showGraphs", false, ConVarT::FLAG_MAIN_EXE, "Toggles whether some FPS graphs are shown.");
589 if (ShowGraphs.GetValueBool()) Graphs.Draw(ClientFrameNr, fbWidth, fbHeight);
591 for (const char* s=DequeueString(); s!=NULL; s=DequeueString())
593 Console->Print(std::string(s)+"\n");
594 SystemScrollInfo.Print(s);
597 ChatScrollInfo .Draw(Font_v, 5, fbHeight-10-140, ffbWidth, ffbHeight);
598 ChatScrollInfo .AdvanceTime(FrameTime);
599 SystemScrollInfo.Draw(Font_v, 5, 15, ffbWidth, ffbHeight);
600 SystemScrollInfo.AdvanceTime(FrameTime);
603 ClientFrameNr++;
607 void ClientStateInGameT::ProcessConnectionLessPacket(NetDataT& InData, const NetAddressT& SenderAddress)
609 InData.ReadLong(); // const unsigned long PacketID=InData.ReadLong();
611 /* if (PacketID!=ExpectedPacketID)
613 Console->Warning(...);
614 return;
615 } */
617 switch (InData.ReadByte())
619 case SC0_RccReply:
620 // This is the reply to a CS0_RemoteConsoleCommand message.
621 // Setting a non-default color would be nice...
622 Console->Print(InData.ReadString());
623 break;
625 case SC0_ACK:
626 case SC0_NACK:
627 default:
628 // Unexpected message type...
629 break;
634 static MainWindowT* globalMainWinPtr = NULL; // A hack...
636 static void WorldLoadingProgressFunction(float ProgressPercent, const char* ProgressText)
638 LoadingProgressPercent=ProgressPercent;
639 LoadingProgressText =ProgressText ? ProgressText : "";
641 // TODO 1: Should pass the total, global system time to BeginFrame().
642 // TODO 2: This should be in a "Yield()" method. (That additionally makes sure that it is not called recursively.)
644 // Also note that we (==Yield()) need our *own* Renderer->BeginFrame() / EndFrame() pair,
645 // because cf::GuiSys::GuiMan->RenderAll(); doesn't clear the screen and thus cannot be called twice inside this pair.
646 // The pair however cannot be nested either! Thus calling Yield() from within cf::GuiSys::GuiMan->RenderAll(); must not be allowed at all!
647 MatSys::Renderer->BeginFrame(0.0);
649 cf::GuiSys::GuiMan->RenderAll();
651 MatSys::Renderer->EndFrame();
653 assert(globalMainWinPtr);
654 globalMainWinPtr->SwapBuffers();
656 SoundSystem->Update();
660 void ClientStateInGameT::ParseServerPacket(NetDataT& InData)
662 cf::LogDebug(net, "SC1_*: BEGIN parsing InData of size %lu", InData.Data.Size());
664 while (!InData.ReadOfl && !(InData.ReadPos>=InData.Data.Size()))
666 char MessageType=InData.ReadByte();
668 switch (MessageType)
670 case SC1_WorldInfo:
672 const std::string SvGameName = InData.ReadString();
673 const char* WorldName = InData.ReadString();
674 unsigned long OurEntityID = InData.ReadLong();
676 cf::LogDebug(net, "SC1_WorldInfo: %s %s %lu", SvGameName.c_str(), WorldName, OurEntityID);
677 // printf(" Client: Got MapInfo");
679 if (SvGameName != Client.m_GameInfo.GetName())
681 const std::string msg = "Client is running game '" + Client.m_GameInfo.GetName() + "', but server sent SC1_WorldInfo message for game '" + SvGameName + "' -- ignored.";
683 cf::LogDebug(net, msg);
684 Console->Print(msg + "\n");
685 break;
688 // This must be here, because if we really wanted to re-register materials here after a game change
689 // (which servers can't do anyway), we should first get rid of the old world before re-initing the MaterialManager.
690 delete World;
691 World=NULL;
693 // If we really wanted to change the game here, the following would suffice.
694 // See my svn commit message to revision #9 for a detailed discussion about why this doesn't conflict with
695 // a simultaneously running server!
697 // MaterialManager->ClearAllMaterials();
698 // MaterialManager->RegisterMaterialScriptsInDir(std::string("Games/")+GameName+"/Materials", std::string("Games/")+GameName+"/");
700 // // Re-assign fonts now that we have read the material scripts (which are all game-specific).
701 // Font_v=FontT("Fonts/Arial");
702 // Font_f=FontT("Fonts/FixedWidth");
704 // Welcome-Screen anzeigen.
705 LogoRenderMat =MatSys::Renderer->RegisterMaterial(MaterialManager->GetMaterial("MainSplashLogo" ));
706 LoadingBarLMat=MatSys::Renderer->RegisterMaterial(MaterialManager->GetMaterial("LoadingBar_Left" ));
707 LoadingBarRMat=MatSys::Renderer->RegisterMaterial(MaterialManager->GetMaterial("LoadingBar_Right" ));
708 LoadingBar0Mat=MatSys::Renderer->RegisterMaterial(MaterialManager->GetMaterial("LoadingBar_Center0"));
709 LoadingBar1Mat=MatSys::Renderer->RegisterMaterial(MaterialManager->GetMaterial("LoadingBar_Center1"));
710 LoadingFont =&Font_v;
712 // BEGIN Load Map
713 cf::GuiSys::GuiMan->Find("Games/" + Client.m_GameInfo.GetName() + "/GUIs/Console_main.cgui", true)->Activate(false); // Close console on map change.
714 Console->Print(std::string("Load World \"")+WorldName+"\".\n");
716 char PathName[512];
717 sprintf(PathName, "Games/%.200s/Worlds/%.200s.cw", Client.m_GameInfo.GetName().c_str(), WorldName);
721 // World is deleted above.
722 IsLoadingWorld=true;
723 globalMainWinPtr = &Client.m_MainWin;
724 World=new CaClientWorldT(PathName, Client.m_ModelMan, Client.m_GuiRes, WorldLoadingProgressFunction, OurEntityID);
725 globalMainWinPtr = NULL;
726 IsLoadingWorld=false;
728 ConsoleInterpreter->RunCommand("StartLevelIntroMusic()"); // This function must be provided in "config.lua".
730 // In a newly loaded world, start with all keys up, i.e. m_PlayerCommand.Keys == 0.
731 // This is to make sure that even if the player holds a key physically down all the time,
732 // e.g. the LMB that is still down from the click in another GUI that brought us here,
733 // it must be released and pressed again in order to create the first "down" event.
734 // (This is somewhat counteracted if we receive "key repeat" events from keyboard keys.)
735 m_PlayerCommand = PlayerCommandT(0);
736 m_PlayerCommandCount = 1;
738 catch (const WorldT::LoadErrorT& E)
740 Console->Warning(E.Msg);
741 Client.NextState=ClientT::IDLE;
743 // END Load Map
745 MatSys::Renderer->FreeMaterial(LogoRenderMat ); LogoRenderMat =NULL;
746 MatSys::Renderer->FreeMaterial(LoadingBarLMat); LoadingBarLMat=NULL;
747 MatSys::Renderer->FreeMaterial(LoadingBarRMat); LoadingBarRMat=NULL;
748 MatSys::Renderer->FreeMaterial(LoadingBar0Mat); LoadingBar0Mat=NULL;
749 MatSys::Renderer->FreeMaterial(LoadingBar1Mat); LoadingBar1Mat=NULL;
750 LoadingFont=NULL;
753 // Wir bestätigen explizit, daß wir in der neuen Map sind, damit der Server uns von
754 // MapTransition nach Dead setzt und uns wieder unreliable Data schickt.
755 // Dies ist notwendig, falls der MapChange gerade erfolgt, während der Server noch auf ein
756 // ACK von uns wartet. Erst das danach folgende ACK wäre das ACK zur MapInfo, also bestätigen
757 // wir es einfach explizit, und alles ist OK!
758 NetDataT NewReliableMsg;
760 NewReliableMsg.WriteByte(CS1_WorldInfoACK);
761 // if (Worldchange hat geklappt, kein Fehler) ReliableBuffer.WriteString(WorldName);
762 // else ReliableBuffer.WriteString(""); // Hat nicht geklappt
763 NewReliableMsg.WriteString(WorldName);
765 ReliableDatas.PushBack(NewReliableMsg.Data);
766 break;
769 case SC1_ChatMsg:
771 const char* ChatMessage=InData.ReadString();
773 cf::LogDebug(net, "SC1_ChatMsg: %s", ChatMessage);
774 Console->Print(std::string(ChatMessage)+"\n");
775 ChatScrollInfo.Print(ChatMessage);
776 break;
779 case SC1_EntityBaseLine:
780 cf::LogDebug(net, "SC1_EntityBaseLine: World==%p", World);
781 if (!World) return;
782 World->ReadEntityBaseLineMessage(InData);
783 // Console->Print("Received BaseLine\n");
784 break;
786 case SC1_FrameInfo:
787 cf::LogDebug(net, "SC1_FrameInfo: World==%p", World);
788 if (!World) return;
789 // Sende hier direkt eine CS1_FrameInfoACK-Message zurück.
790 // Beachte, daß dies nicht in unserer Hauptschleife geschehen muß, selbst bei Packet-Loss nicht!
791 UnreliableData.WriteByte(CS1_FrameInfoACK);
792 UnreliableData.WriteLong(World->ReadServerFrameMessage(InData));
793 break;
795 case SC1_DropClient:
797 unsigned long EntityID=InData.ReadLong();
798 const char* Reason =InData.ReadString();
800 cf::LogDebug(net, "SC1_DropClient: EntityID==%lu%s, Reason==%s, World==%p", EntityID,
801 World ? (EntityID==World->GetOurEntityID() ? " (our)" : " (not our)") : "", Reason, World);
803 if (World)
804 if (EntityID==World->GetOurEntityID())
805 Client.NextState=ClientT::IDLE;
807 const std::string msg=cf::va("Client with EntityID %u has left the game. Reason: ", EntityID)+Reason+"\n";
809 Console->Print(msg);
810 SystemScrollInfo.Print(msg);
811 break;
814 default:
816 // Alle SC1_EntityUpdate-Messages sollten nach SC1_FrameInfo schon gelesen worden sein!
817 cf::LogDebug(net, "SC1_???: WARNING: Unknown SC1_* in-game message type '%u' received!\n", MessageType);
819 const std::string msg=cf::va("WARNING: Unknown in-game message type '%3u' received!\n", MessageType);
821 Console->Print(msg);
822 SystemScrollInfo.Print(msg);
823 // assert(false);
828 cf::LogDebug(net, "SC1_*: END parsing InData\n");
832 void ClientStateInGameT::ParseServerPacketHelper(NetDataT& InData, unsigned long /*LastIncomingSequenceNr*/)
834 ClientIGSPtr->ParseServerPacket(InData);
838 void ClientStateInGameT::MainLoop(float FrameTime)
840 // Prüfe auf Server-Antwort(en) und verarbeite diese. Wir holen in einer Schleife die Packets ab, bis keine mehr da sind.
841 // Dies ist insbesondere wichtig, wenn wir auf einem langsamen Computer schneller Server-Packets erhalten als wir Frames generieren können!
842 // (Würde pro Frame nur ein Packet bearbeitet werden, gäbe es in einem solchen Fall Buffer-Overflows im OS und folglich packet-loss!)
843 unsigned long MaxPacketsCount=20;
845 while (MaxPacketsCount--)
849 NetDataT InData;
850 NetAddressT SenderAddress=InData.Receive(Client.Socket);
852 if (GameProtocol1T::IsIncomingMessageOutOfBand(InData))
854 // It's well possible that we receive connection-less messages,
855 // e.g. in reply to the connection-less messages that we have sent.
856 ProcessConnectionLessPacket(InData, SenderAddress);
858 else
860 assert(ClientIGSPtr==this);
862 // ProcessIncomingMessage() returns the last sequence number that the remote
863 // party has seen from us, but we have no longer need for this here.
864 GameProtocol.ProcessIncomingMessage(InData, ParseServerPacketHelper);
867 catch (const NetDataT::WinSockAPIError& E)
869 if (E.Error==WSAEWOULDBLOCK) break;
871 const char* ErrorString="not yet looked-up";
873 switch (E.Error)
875 // case WSAEWOULDBLOCK: ErrorString="WSAEWOULDBLOCK"; break; // WSAEWOULDBLOCK is silently handled above
876 case WSAEMSGSIZE : ErrorString="WSAEMSGSIZE" ; break;
877 case WSAECONNRESET : ErrorString="WSAECONNRESET" ; break;
880 Console->Warning(cf::va("InData.Receive() returned WSA fail code %u (%s). Packet ignored.\n", E.Error, ErrorString));
884 if (World)
886 IntrusivePtrT<cf::GuiSys::GuiImplT> ActiveGui = cf::GuiSys::GuiMan->GetTopmostActiveAndInteractive();
888 // Checking (ActiveGui->GetFocusWindow() == our_window_instance) would be better,
889 // but at this time, we don't have our_window_instance available.
890 if (ActiveGui.IsNull() ||
891 ActiveGui->GetFocusWindow().IsNull() ||
892 ActiveGui->GetFocusWindow()->GetBasics()->GetWindowName() != "Client")
894 // If we hold the fire button, e.g. for repeat fire with the machine gun, and simultaneously
895 // press another key such as F1 for opening the console, ESC for the main menu or T for the
896 // chat window, our client GUI/window/component loses the input focus and we never receive
897 // the "fire button up" event.
898 // Thus, we have to force the release of all keys whenever we don't have the input focus.
899 m_PlayerCommand.Keys = 0;
902 m_PlayerCommand.FrameTime = FrameTime;
904 // m_PlayerCommand an Server senden
905 UnreliableData.WriteByte (CS1_PlayerCommand);
906 UnreliableData.WriteLong (m_PlayerCommandCount);
907 UnreliableData.WriteFloat(FrameTime);
908 UnreliableData.WriteLong (m_PlayerCommand.Keys);
909 UnreliableData.WriteWord (m_PlayerCommand.DeltaHeading);
910 UnreliableData.WriteWord (m_PlayerCommand.DeltaPitch);
911 // UnreliableData.WriteWord (m_PlayerCommand.DeltaBank);
913 // Führe für unseren Entity die Prediction durch
914 World->OurEntity_Predict(m_PlayerCommand, m_PlayerCommandCount);
916 if (m_PathRecorder)
918 IntrusivePtrT<const cf::GameSys::ComponentTransformT> CameraTrafo = World->OurEntity_GetCamera();
920 if (CameraTrafo != NULL)
921 m_PathRecorder->WritePath(CameraTrafo->GetOriginWS().AsVectorOfDouble(), 0 /*Fixme: Heading*/, FrameTime);
924 // Keep the key state for the next frame, clear everything else.
925 m_PlayerCommand = PlayerCommandT(m_PlayerCommand.Keys);
926 m_PlayerCommandCount++;
932 GameProtocol.GetTransmitData(ReliableDatas, UnreliableData.Data).Send(Client.Socket, Client.ServerAddress);
934 catch (const GameProtocol1T::MaxMsgSizeExceeded& /*E*/) { EnqueueString("FATAL WARNING: caught a GameProtocol1T::MaxMsgSizeExceeded exception!\n"); }
935 catch (const NetDataT::WinSockAPIError& E ) { EnqueueString("FATAL WARNING: caught a NetDataT::WinSockAPIError exception (error %u)!\n", E.Error); }
936 catch (const NetDataT::MessageLength& E ) { EnqueueString("FATAL WARNING: caught a NetDataT::MessageLength exception (wanted %u, actual %u)!\n", E.Wanted, E.Actual); }
938 ReliableDatas.Clear();
939 UnreliableData=NetDataT();