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.
7 #include "../GameInfo.hpp"
8 #include "../NetConst.hpp"
10 #include "ClientStateInGame.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"
48 #define WIN32_LEAN_AND_MEAN
52 #define WSAECONNRESET ECONNRESET
53 #define WSAEMSGSIZE EMSGSIZE
54 #define WSAEWOULDBLOCK EWOULDBLOCK
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
);
74 static ConFuncT
ConFunc_say("say", ClientStateInGameT::ConFunc_say_Callback
, ConFuncT::FLAG_MAIN_EXE
, "Sends the given string to all connected clients.");
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));
88 static ConFuncT
ConFunc_chatPrint("chatPrint", ClientStateInGameT::ConFunc_chatPrint_Callback
, ConFuncT::FLAG_MAIN_EXE
, "Prints the given string as a chat message area.");
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
);
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);
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
)
121 Recorder
=new PathRecorderT(FileName
);
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_
)
139 Font_v("Fonts/Arial"),
140 Font_f("Fonts/FixedWidth"),
142 IsLoadingWorld(false),
145 m_PlayerCommandCount(1), // In each newly loaded world, player command numbering restarts at 1.
148 assert(Client
.Socket
!=INVALID_SOCKET
);
150 assert(ClientIGSPtr
==NULL
);
155 ClientStateInGameT::~ClientStateInGameT()
157 delete m_PathRecorder
;
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.
185 assert(ClientIGSPtr
==this);
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
);
202 case CaKeyboardEventT::CK_ESCAPE
:
204 if (s
&& Client
.MainMenuGui
!= NULL
)
206 Client
.MainMenuGui
->Activate();
207 cf::GuiSys::GuiMan
->BringToFront(Client
.MainMenuGui
);
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
);
226 case CaKeyboardEventT::CK_SPACE
:
227 m_PlayerCommand
.Set(PCK_Jump
, s
);
230 case CaKeyboardEventT::CK_LSHIFT
:
231 case CaKeyboardEventT::CK_RSHIFT
:
232 m_PlayerCommand
.Set(PCK_Walk
, s
);
235 case CaKeyboardEventT::CK_UP
:
236 case CaKeyboardEventT::CK_W
:
237 m_PlayerCommand
.Set(PCK_MoveForward
, s
);
240 case CaKeyboardEventT::CK_DOWN
:
241 case CaKeyboardEventT::CK_S
:
242 m_PlayerCommand
.Set(PCK_MoveBackward
, s
);
245 case CaKeyboardEventT::CK_A
:
246 case CaKeyboardEventT::CK_COMMA
:
247 m_PlayerCommand
.Set(PCK_StrafeLeft
, s
);
250 case CaKeyboardEventT::CK_D
:
251 case CaKeyboardEventT::CK_PERIOD
:
252 m_PlayerCommand
.Set(PCK_StrafeRight
, s
);
255 case CaKeyboardEventT::CK_R
:
256 m_PlayerCommand
.Set(PCK_Fire1
, s
);
259 case CaKeyboardEventT::CK_RETURN
:
260 case CaKeyboardEventT::CK_NUMPADENTER
:
261 m_PlayerCommand
.Set(PCK_Use
, s
);
264 case CaKeyboardEventT::CK_LEFT
:
265 m_PlayerCommand
.Set(PCK_TurnLeft
, s
);
268 case CaKeyboardEventT::CK_RIGHT
:
269 m_PlayerCommand
.Set(PCK_TurnRight
, s
);
272 case CaKeyboardEventT::CK_PGDN
:
273 m_PlayerCommand
.Set(PCK_LookUp
, s
);
276 case CaKeyboardEventT::CK_PGUP
:
277 m_PlayerCommand
.Set(PCK_LookDown
, s
);
280 case CaKeyboardEventT::CK_END
:
281 m_PlayerCommand
.Set(PCK_CenterView
, s
);
284 case CaKeyboardEventT::CK_1
:
285 case CaKeyboardEventT::CK_NUMPAD1
:
286 m_PlayerCommand
.SetNumber(s
? 1 : 0);
289 case CaKeyboardEventT::CK_2
:
290 case CaKeyboardEventT::CK_NUMPAD2
:
291 m_PlayerCommand
.SetNumber(s
? 2 : 0);
294 case CaKeyboardEventT::CK_3
:
295 case CaKeyboardEventT::CK_NUMPAD3
:
296 m_PlayerCommand
.SetNumber(s
? 3 : 0);
299 case CaKeyboardEventT::CK_4
:
300 case CaKeyboardEventT::CK_NUMPAD4
:
301 m_PlayerCommand
.SetNumber(s
? 4 : 0);
304 case CaKeyboardEventT::CK_5
:
305 case CaKeyboardEventT::CK_NUMPAD5
:
306 m_PlayerCommand
.SetNumber(s
? 5 : 0);
309 case CaKeyboardEventT::CK_6
:
310 case CaKeyboardEventT::CK_NUMPAD6
:
311 m_PlayerCommand
.SetNumber(s
? 6 : 0);
314 case CaKeyboardEventT::CK_7
:
315 case CaKeyboardEventT::CK_NUMPAD7
:
316 m_PlayerCommand
.SetNumber(s
? 7 : 0);
319 case CaKeyboardEventT::CK_8
:
320 case CaKeyboardEventT::CK_NUMPAD8
:
321 m_PlayerCommand
.SetNumber(s
? 8 : 0);
324 case CaKeyboardEventT::CK_9
:
325 case CaKeyboardEventT::CK_NUMPAD9
:
326 m_PlayerCommand
.SetNumber(s
? 9 : 0);
329 case CaKeyboardEventT::CK_0
:
330 case CaKeyboardEventT::CK_NUMPAD0
:
331 m_PlayerCommand
.SetNumber(s
? 10 : 0);
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
)
353 case CaMouseEventT::CM_BUTTON0
:
354 m_PlayerCommand
.Set(PCK_Fire1
, ME
.Amount
== 1);
357 case CaMouseEventT::CM_BUTTON1
:
358 case CaMouseEventT::CM_BUTTON2
:
359 case CaMouseEventT::CM_BUTTON3
:
360 m_PlayerCommand
.Set(PCK_Fire2
, ME
.Amount
== 1);
363 case CaMouseEventT::CM_MOVE_X
: // X-Axis.
364 m_PlayerCommand
.DeltaHeading
+=(unsigned short)(ME
.Amount
*30);
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);
377 // Ignore other ME types.
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
;
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));
473 Font_f
.Print(30, fbHeight
*2/3, ffbWidth
, ffbHeight
, 0x00004080, "Receiving entity baselines...");
478 // Falls wir noch keine World haben (weil nach dem Starten vom Server bisher nichts kam), können wir nicht viel tun.
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
);
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
);
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;
568 LoadingFont
->Print(fbWidth
/2-34*CharWidth
/2, fbHeight
*9/10+12, ffbWidth
, ffbHeight
, 0x00800000, "Version: " __DATE__
" [Debug build], "+LoadingProgressText
);
570 LoadingFont
->Print(fbWidth
/2-20*CharWidth
/2, fbHeight
*9/10+12, ffbWidth
, ffbHeight
, 0x00800000, "Version: " __DATE__
);
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
));
576 LoadingFont
->Print(fbWidth
/2-10*CharWidth
/2, fbHeight
*9/10+30, ffbWidth
, ffbHeight
, 0x00800000, "Loading...");
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
);
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(...);
617 switch (InData
.ReadByte())
620 // This is the reply to a CS0_RemoteConsoleCommand message.
621 // Setting a non-default color would be nice...
622 Console
->Print(InData
.ReadString());
628 // Unexpected message type...
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();
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");
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.
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
;
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");
717 sprintf(PathName
, "Games/%.200s/Worlds/%.200s.cw", Client
.m_GameInfo
.GetName().c_str(), WorldName
);
721 // World is deleted above.
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
;
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
;
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
);
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
);
779 case SC1_EntityBaseLine
:
780 cf::LogDebug(net
, "SC1_EntityBaseLine: World==%p", World
);
782 World
->ReadEntityBaseLineMessage(InData
);
783 // Console->Print("Received BaseLine\n");
787 cf::LogDebug(net
, "SC1_FrameInfo: World==%p", World
);
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
));
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
);
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";
810 SystemScrollInfo
.Print(msg
);
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
);
822 SystemScrollInfo
.Print(msg
);
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
--)
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
);
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";
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
));
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
);
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();