Fix issue in Rocket.lua script.
[cafu-Engine.git] / Ca3DE / EngineEntity.cpp
blob8389ffce051bc10c1a7939289d16453855a9ff50
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 "EngineEntity.hpp"
8 #include "NetConst.hpp"
9 #include "ConsoleCommands/ConVar.hpp"
10 #include "MaterialSystem/Renderer.hpp"
11 #include "Math3D/Matrix.hpp"
12 #include "Network/Network.hpp"
13 #include "Win32/Win32PrintHelp.hpp"
14 #include "Ca3DEWorld.hpp"
15 #include "GameSys/CompHumanPlayer.hpp"
16 #include "GameSys/Entity.hpp"
17 #include "GameSys/Interpolator.hpp"
20 namespace
22 ConVarT UsePrediction("usePrediction", true, ConVarT::FLAG_MAIN_EXE,
23 "Toggles whether client prediction is used (recommended!).");
25 ConVarT clientApproxNPCs("clientApproxNPCs", true, ConVarT::FLAG_MAIN_EXE,
26 "Toggles whether origins and other values are interpolated over client "
27 "frames in order to bridge the larger intervals between server frames.");
31 /******************/
32 /*** Both Sides ***/
33 /******************/
36 cf::Network::StateT EngineEntityT::GetState() const
38 cf::Network::StateT State;
39 cf::Network::OutStreamT Stream(State);
41 m_Entity->Serialize(Stream);
43 return State;
47 void EngineEntityT::SetState(const cf::Network::StateT& State, bool IsIniting) const
49 cf::Network::InStreamT Stream(State);
51 m_Entity->Deserialize(Stream, IsIniting);
53 // Deserialization has brought new reference values for interpolated values.
54 for (unsigned int CompNr = 0; true; CompNr++)
56 IntrusivePtrT<cf::GameSys::ComponentBaseT> Comp = m_Entity->GetComponent(CompNr);
58 if (Comp == NULL) break;
60 for (unsigned int i = 0; i < Comp->GetInterpolators().Size(); i++)
62 if (IsIniting || !clientApproxNPCs.GetValueBool())
64 Comp->GetInterpolators()[i]->ReInit();
66 else
68 Comp->GetInterpolators()[i]->UpdateTargetValue();
75 /*******************/
76 /*** Server Side ***/
77 /*******************/
80 EngineEntityT::EngineEntityT(IntrusivePtrT<cf::GameSys::EntityT> Ent, unsigned long CreationFrameNr)
81 : m_Entity(Ent),
82 EntityStateFrameNr(CreationFrameNr),
83 m_BaseLine(),
84 m_BaseLineFrameNr(CreationFrameNr),
85 m_OldStates()
87 m_BaseLine=GetState();
89 for (unsigned long OldStateNr=0; OldStateNr<16 /*MUST be a power of 2*/; OldStateNr++)
90 m_OldStates.PushBack(m_BaseLine);
94 void EngineEntityT::PreThink(unsigned long ServerFrameNr)
96 // The goal of the Think() methods is to compute the state for frame ServerFrameNr.
97 // Thus, here, before the thinking starts, record the state of the previous frame.
99 // Note that if this entity has only been created in ServerFrameNr, there is no previous state to record.
100 // As our earliest state is `m_BaseLineFrameNr`, we must have `m_BaseLineFrameNr <= ServerFrameNr - 1`,
101 // thus `m_BaseLineFrameNr < ServerFrameNr`. (If `m_BaseLineFrameNr == ServerFrameNr` was allowed, this
102 // would imply that we had an old state at `m_OldStates[m_BaseLineFrameNr - 1]` and result in
103 // `m_BaseLine != m_OldStates[m_BaseLineFrameNr]`.)
104 if (m_BaseLineFrameNr >= ServerFrameNr) return;
106 m_OldStates[(ServerFrameNr-1) & (m_OldStates.Size()-1)] = GetState();
110 void EngineEntityT::Think(float FrameTime, unsigned long ServerFrameNr)
112 // As detailed in PreThink().
113 if (m_BaseLineFrameNr >= ServerFrameNr) return;
115 // This computes the state of the entity for frame ServerFrameNr.
116 m_Entity->OnServerFrame(FrameTime);
118 EntityStateFrameNr=ServerFrameNr;
122 void EngineEntityT::WriteNewBaseLine(unsigned long SentClientBaseLineFrameNr, ArrayT< ArrayT<char> >& OutDatas) const
124 // Nur dann etwas tun, wenn unsere 'BaseLineFrameNr' größer (d.h. jünger) als 'SentClientBaseLineFrameNr' ist,
125 // d.h. unsere 'BaseLineFrameNr' noch nie / noch nicht an den Client gesendet wurde.
126 if (SentClientBaseLineFrameNr >= m_BaseLineFrameNr) return;
128 NetDataT NewBaseLineMsg;
130 NewBaseLineMsg.WriteByte(SC1_EntityBaseLine);
131 NewBaseLineMsg.WriteLong(m_Entity->GetID());
132 NewBaseLineMsg.WriteLong(m_Entity->GetParent().IsNull() ? UINT_MAX : m_Entity->GetParent()->GetID());
133 NewBaseLineMsg.WriteDMsg(m_BaseLine.GetDeltaMessage(cf::Network::StateT() /*::ALL_ZEROS*/));
135 OutDatas.PushBack(NewBaseLineMsg.Data);
139 bool EngineEntityT::WriteDeltaEntity(bool SendFromBaseLine, unsigned long ClientFrameNr, NetDataT& OutData, bool ForceInfo) const
141 // Prüfe, ob die Voraussetzungen für die Parameter (insb. 'ClientFrameNr') eingehalten werden.
142 if (!SendFromBaseLine)
144 // EntityStateFrameNr wird in Think() gesetzt und ist gleich der ServerFrameNr!
145 // Beachte: OldStates speichert die alten Zustände von ServerFrameNr-1 bis ServerFrameNr-16.
146 const unsigned long FrameDiff = EntityStateFrameNr - ClientFrameNr;
148 if (FrameDiff < 1 || FrameDiff > m_OldStates.Size()) return false;
152 const cf::Network::StateT CurrentState = GetState();
153 const ArrayT<uint8_t> DeltaMsg = CurrentState.GetDeltaMessage(SendFromBaseLine ? m_BaseLine : m_OldStates[ClientFrameNr & (m_OldStates.Size()-1)]);
155 if (cf::Network::StateT::IsDeltaMessageEmpty(DeltaMsg) && !ForceInfo) return true;
157 // Write the SC1_EntityUpdate message
158 OutData.WriteByte(SC1_EntityUpdate);
159 OutData.WriteLong(m_Entity->GetID());
160 OutData.WriteDMsg(DeltaMsg);
162 return true;
166 #if !DEDICATED
168 /*******************/
169 /*** Client Side ***/
170 /*******************/
173 EngineEntityT::EngineEntityT(IntrusivePtrT<cf::GameSys::EntityT> Ent, NetDataT& InData)
174 : m_Entity(Ent),
175 EntityStateFrameNr(0),
176 m_BaseLine(),
177 m_BaseLineFrameNr(1234), // The m_BaseLineFrameNr is unused on the client.
178 m_OldStates()
180 const cf::Network::StateT CurrentState(cf::Network::StateT() /*::ALL_ZEROS*/, InData.ReadDMsg());
182 // Pass true for the IsInited parameter in order to indicate that we're constructing the entity.
183 // This is done in order to have it not wrongly process the event counters.
184 SetState(CurrentState, true);
186 for (unsigned long OldStateNr=0; OldStateNr<32 /*MUST be a power of 2*/; OldStateNr++)
187 m_OldStates.PushBack(CurrentState);
189 m_BaseLine=CurrentState;
193 bool EngineEntityT::ParseServerDeltaUpdateMessage(unsigned long DeltaFrameNr, unsigned long ServerFrameNr, const ArrayT<uint8_t>* DeltaMessage)
195 // Sanity-Check: Wir wollen, daß 'DeltaFrameNr<=EntityStateFrameNr<ServerFrameNr' gilt.
196 // Wäre 'DeltaFrameNr>EntityStateFrameNr', so sollten wir gegen einen State dekomprimieren, der in der Zukunft liegt.
197 // Wäre 'EntityStateFrameNr>=ServerFrameNr', so sollten wir uns in einen State begeben, der schon Vergangenheit ist.
198 // Dies hält auch für den Spezialfall 'DeltaFrameNr==0' (Delta-Dekompression gegen die BaseLine).
199 // Im Normalfall 'DeltaFrameNr>0' müssen wir unten außerdem noch sicherstellen, daß der DeltaState nicht zu weit in der Vergangenheit liegt.
201 // ONE possible reason for DeltaFrameNr>EntityStateFrameNr is related to the way how baselines are sent,
202 // see EntityManager.cpp, EntityManagerT::ParseServerDeltaUpdateMessage() for a description, which is essentially repeated here:
203 // When a client joins a level, there can be a LOT of entities. Usually, not all baselines of all entities fit into a single
204 // realiable message at once, and thus the server sends them in batches, contained in subsequent realiable messages.
205 // Between realiable messages however, the server sends also SC1_EntityUpdate messages.
206 // These messages can already refer to entities that the client knows nothing about, because it has not yet seen the (reliable)
207 // introductory baseline message.
208 // Then, the entities that the client already knows about normally receive and process delta updates here in this function,
209 // the others don't (because their non-presence is already detected in EntityManagerT::ParseServerDeltaUpdateMessage()).
210 // However, the frame counters increase normally, as if all entities were present. When finally the remaining entities
211 // arrive (because their baseline got finally through), these entities are likely to have DeltaFrameNr>EntityStateFrameNr.
212 // I turn the "WARNING" into an "INFO", so that ordinary users get a better impression. ;)
213 if (DeltaFrameNr>EntityStateFrameNr) { EnqueueString("CLIENT INFO: %s, L %u: DeltaFrameNr>EntityStateFrameNr (%lu>%lu)\n" , __FILE__, __LINE__, DeltaFrameNr, EntityStateFrameNr); return false; }
214 if (EntityStateFrameNr>=ServerFrameNr) { EnqueueString("CLIENT WARNING: %s, L %u: EntityStateFrameNr>=ServerFrameNr (%lu>%lu)\n", __FILE__, __LINE__, EntityStateFrameNr, ServerFrameNr); return false; }
217 // Determine the source state to delta-decompress against (an old state or the baseline).
218 const cf::Network::StateT* DeltaState=NULL;
220 if (DeltaFrameNr>0)
222 // Der oben angekündigte Test, ob der DeltaState nicht schon zu weit in der Vergangenheit liegt.
223 // Einen gültigen State können wir dann nicht mehr produzieren, und dem Calling-Code muß klar sein oder klar werden,
224 // daß er gegen die BaseLines komprimierte Messages anfordern muß.
225 if (EntityStateFrameNr-DeltaFrameNr >= m_OldStates.Size())
227 EnqueueString("CLIENT WARNING: %s, L %u: Delta state too old!\n", __FILE__, __LINE__);
228 return false;
231 DeltaState = &m_OldStates[DeltaFrameNr & (m_OldStates.Size()-1)];
233 else
235 DeltaState = &m_BaseLine;
238 // Set the result as the new entity state, and record it in the m_OldStates for future reference.
239 EntityStateFrameNr = ServerFrameNr;
241 const cf::Network::StateT NewState = DeltaMessage ? cf::Network::StateT(*DeltaState, *DeltaMessage) : *DeltaState;
243 m_OldStates[EntityStateFrameNr & (m_OldStates.Size()-1)] = NewState;
244 SetState(NewState, DeltaFrameNr == 0); // Don't process events and don't interpolate if we're delta'ing from the baseline (e.g. when re-entering the PVS).
245 return true;
249 void EngineEntityT::Predict(const PlayerCommandT& PrevPlayerCommand, const PlayerCommandT& PlayerCommand)
251 if (!UsePrediction.GetValueBool())
252 return;
254 IntrusivePtrT<cf::GameSys::ComponentHumanPlayerT> CompHP =
255 dynamic_pointer_cast<cf::GameSys::ComponentHumanPlayerT>(m_Entity->GetComponent("HumanPlayer"));
257 if (CompHP == NULL)
259 EnqueueString("WARNING - Prediction impossible: HumanPlayer component not found in human player entity!\n");
260 return;
263 // Note that components other than CompHP should *not* Think/Repredict,
264 // e.g. the player's CollisionModel component must not cause OnTrigger() callbacks!
265 CompHP->Think(PrevPlayerCommand, PlayerCommand, false /*ThinkingOnServerSide*/);
268 #endif /* !DEDICATED */