forked from hitmen047/Source-PlusPlus
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathc_fish.cpp
352 lines (265 loc) · 8.71 KB
/
c_fish.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// c_fish.cpp
// Simple fish client-side logic
// Author: Michael S. Booth, April 2005
#include "cbase.h"
#include <bitbuf.h>
#include "engine/ivdebugoverlay.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern float UTIL_WaterLevel( const Vector &position, float minz, float maxz );
ConVar FishDebug( "fish_debug", "0", FCVAR_CHEAT, "Show debug info for fish" );
//-----------------------------------------------------------------------------
/**
* Client-side fish entity
*/
class C_Fish : public C_BaseAnimating
{
public:
DECLARE_CLASS( C_Fish, C_BaseAnimating );
DECLARE_CLIENTCLASS();
virtual void Spawn( void );
virtual void ClientThink();
virtual void OnDataChanged( DataUpdateType_t type );
private:
friend void RecvProxy_FishOriginX( const CRecvProxyData *pData, void *pStruct, void *pOut );
friend void RecvProxy_FishOriginY( const CRecvProxyData *pData, void *pStruct, void *pOut );
Vector m_pos; ///< local position
Vector m_vel; ///< local velocity
QAngle m_angles; ///< local angles
int m_localLifeState; ///< our version of m_lifeState
float m_deathDepth; ///< water depth when we died
float m_deathAngle; ///< angle to float at when dead
float m_buoyancy; ///< so each fish floats at a different rate when dead
CountdownTimer m_wiggleTimer; ///< for simulating swimming motions
float m_wigglePhase; ///< where in the wiggle sinusoid we are
float m_wiggleRate; ///< the speed of our wiggling
Vector m_actualPos; ///< position from server
QAngle m_actualAngles; ///< angles from server
Vector m_poolOrigin;
float m_waterLevel; ///< Z coordinate of water surface
bool m_gotUpdate; ///< true after we have received a network update
enum { MAX_ERROR_HISTORY = 20 };
float m_errorHistory[ MAX_ERROR_HISTORY ]; ///< error history samples
int m_errorHistoryIndex;
int m_errorHistoryCount;
float m_averageError;
};
//-----------------------------------------------------------------------------
void RecvProxy_FishOriginX( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
C_Fish *fish = (C_Fish *)pStruct;
float *out = (float *)pOut;
*out = pData->m_Value.m_Float + fish->m_poolOrigin.x;
}
void RecvProxy_FishOriginY( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
C_Fish *fish = (C_Fish *)pStruct;
float *out = (float *)pOut;
*out = pData->m_Value.m_Float + fish->m_poolOrigin.y;
}
IMPLEMENT_CLIENTCLASS_DT_NOBASE( C_Fish, DT_CFish, CFish )
RecvPropVector( RECVINFO(m_poolOrigin) ),
RecvPropFloat( RECVINFO_NAME( m_actualPos.x, m_x ), 0, RecvProxy_FishOriginX ),
RecvPropFloat( RECVINFO_NAME( m_actualPos.y, m_y ), 0, RecvProxy_FishOriginY ),
RecvPropFloat( RECVINFO_NAME( m_actualPos.z, m_z ) ),
RecvPropFloat( RECVINFO_NAME( m_actualAngles.y, m_angle ) ),
RecvPropInt( RECVINFO(m_nModelIndex) ),
RecvPropInt( RECVINFO(m_lifeState) ),
RecvPropFloat( RECVINFO(m_waterLevel) ), ///< get this from the server in case we die when slightly out of the water due to error correction
END_RECV_TABLE()
//-----------------------------------------------------------------------------
void C_Fish::Spawn( void )
{
BaseClass::Spawn();
m_angles = QAngle( 0, 0, 0 );
m_actualAngles = m_angles;
m_vel = Vector( 0, 0, 0 );
m_gotUpdate = false;
m_localLifeState = LIFE_ALIVE;
m_buoyancy = RandomFloat( 0.4f, 1.0f );
m_errorHistoryIndex = 0;
m_errorHistoryCount = 0;
m_averageError = 0.0f;
SetNextClientThink( CLIENT_THINK_ALWAYS );
}
//-----------------------------------------------------------------------------
void C_Fish::ClientThink()
{
if (FishDebug.GetBool())
{
debugoverlay->AddLineOverlay( m_pos, m_actualPos, 255, 0, 0, true, 0.1f );
switch( m_localLifeState )
{
case LIFE_DYING:
debugoverlay->AddTextOverlay( m_pos, 0.1f, "DYING" );
break;
case LIFE_DEAD:
debugoverlay->AddTextOverlay( m_pos, 0.1f, "DEAD" );
break;
}
}
float deltaT = gpGlobals->frametime;
// check if we just died
if (m_localLifeState == LIFE_ALIVE && m_lifeState != LIFE_ALIVE)
{
// we have died
m_localLifeState = LIFE_DYING;
m_deathDepth = m_pos.z;
// determine surface float angle
m_deathAngle = RandomFloat( 87.0f, 93.0f ) * ((RandomInt( 0, 100 ) < 50) ? 1.0f : -1.0f);
}
switch( m_localLifeState )
{
case LIFE_DYING:
{
// depth parameter
float t = (m_pos.z - m_deathDepth) / (m_waterLevel - m_deathDepth);
t *= t;
// roll onto side
m_angles.z = m_deathAngle * t;
// float to surface
const float fudge = 2.0f;
if (m_pos.z < m_waterLevel - fudge)
{
m_vel.z += (1.0f - t) * m_buoyancy * deltaT;
}
else
{
m_localLifeState = LIFE_DEAD;
}
break;
}
case LIFE_DEAD:
{
// depth parameter
float t = (m_pos.z - m_deathDepth) / (m_waterLevel - m_deathDepth);
t *= t;
// roll onto side
m_angles.z = m_deathAngle * t;
// keep near water surface
const float sub = 0.5f;
m_vel.z += 10.0f * (m_waterLevel - m_pos.z - sub) * deltaT;
// bob on surface
const float rollAmp = 5.0f;
const float rollFreq = 2.33f;
m_angles.z += rollAmp * sin( rollFreq * (gpGlobals->curtime + 10.0f * entindex()) ) * deltaT;
const float rollAmp2 = 7.0f;
const float rollFreq2 = 4.0f;
m_angles.x += rollAmp2 * sin( rollFreq2 * (gpGlobals->curtime + 10.0f * entindex()) ) * deltaT;
const float bobAmp = 0.75f;
const float bobFreq = 4.0f;
m_vel.z += bobAmp * sin( bobFreq * (gpGlobals->curtime + 10.0f * entindex()) ) * deltaT;
const float bobAmp2 = 0.75f;
const float bobFreq2 = 3.333f;
m_vel.z += bobAmp2 * sin( bobFreq2 * (gpGlobals->curtime + 10.0f * entindex()) ) * deltaT;
// decay movement speed to zero
const float drag = 1.0f;
m_vel.z -= drag * m_vel.z * deltaT;
break;
}
case LIFE_ALIVE:
{
// use server-side Z coordinate directly
m_pos.z = m_actualPos.z;
// use server-side angles
m_angles = m_actualAngles;
// fishy wiggle based on movement
if (!m_wiggleTimer.IsElapsed())
{
float swimPower = 1.0f - (m_wiggleTimer.GetElapsedTime() / m_wiggleTimer.GetCountdownDuration());
const float amp = 6.0f * swimPower;
float wiggle = amp * sin( m_wigglePhase );
m_wigglePhase += m_wiggleRate * deltaT;
// wiggle decay
const float wiggleDecay = 5.0f;
m_wiggleRate -= wiggleDecay * deltaT;
m_angles.y += wiggle;
}
break;
}
}
// compute error between our local position and actual server position
Vector error = m_actualPos - m_pos;
error.z = 0.0f;
float errorLen = error.Length();
if (m_localLifeState == LIFE_ALIVE)
{
// if error is far above average, start swimming
const float wiggleThreshold = 2.0f;
if (errorLen - m_averageError > wiggleThreshold)
{
// if error is large, we must have started swimming
const float swimTime = 5.0f;
m_wiggleTimer.Start( swimTime );
m_wiggleRate = 2.0f * errorLen;
const float maxWiggleRate = 30.0f;
if (m_wiggleRate > maxWiggleRate)
{
m_wiggleRate = maxWiggleRate;
}
}
// update average error
m_errorHistory[ m_errorHistoryIndex++ ] = errorLen;
if (m_errorHistoryIndex >= MAX_ERROR_HISTORY)
{
m_errorHistoryIndex = 0;
m_errorHistoryCount = MAX_ERROR_HISTORY;
}
else if (m_errorHistoryCount < MAX_ERROR_HISTORY)
{
++m_errorHistoryCount;
}
m_averageError = 0.0f;
if (m_errorHistoryCount)
{
for( int r=0; r<m_errorHistoryCount; ++r )
{
m_averageError += m_errorHistory[r];
}
m_averageError /= (float)m_errorHistoryCount;
}
}
// keep fish motion smooth by correcting towards actual server position
// NOTE: This only tracks XY motion
const float maxError = 20.0f;
float errorT = errorLen / maxError;
if (errorT > 1.0f)
{
errorT = 1.0f;
}
// we want a nonlinear spring force for tracking
errorT *= errorT;
// as fish move faster, their error increases - use a stiffer spring when fast, and a weak one when slow
const float trackRate = 0.0f + errorT * 115.0f;
m_vel.x += trackRate * error.x * deltaT;
m_vel.y += trackRate * error.y * deltaT;
const float trackDrag = 2.0f + errorT * 6.0f;
m_vel.x -= trackDrag * m_vel.x * deltaT;
m_vel.y -= trackDrag * m_vel.y * deltaT;
// euler integration
m_pos += m_vel * deltaT;
SetNetworkOrigin( m_pos );
SetAbsOrigin( m_pos );
SetNetworkAngles( m_angles );
SetAbsAngles( m_angles );
}
//-----------------------------------------------------------------------------
void C_Fish::OnDataChanged( DataUpdateType_t type )
{
//if (!m_gotUpdate)
if (type == DATA_UPDATE_CREATED)
{
// initial update
m_gotUpdate = true;
m_pos = m_actualPos;
m_vel = Vector( 0, 0, 0 );
return;
}
}