aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/shared/env_wind_shared.cpp
blob: 422e9e99c99ee06733b39033747543f94b4b13b3 (plain) (blame)
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
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
// Information about algorithmic stuff that can occur on both client + server
//
// In order to reduce network traffic, it's possible to create a algorithms 
// that will work on both the client and the server and be totally repeatable. 
// All we need do is to send down initial conditions and let the algorithm 
// compute the values at various times. Note that this algorithm will be called 
// at different times with different frequencies on the client and server.
//
// The trick here is that in order for it to be repeatable, the algorithm either
// cannot depend on random numbers, or, if it does, we need to make sure that 
// the random numbers generated are effectively done at the beginning of time, 
// so that differences in frame rate on client and server won't matter. It also 
// is important that the initial state sent across the network is identical 
// bitwise so that we produce the exact same results. Therefore no compression 
// should be used in the datatables. 
//
// Note also that each algorithm must have its own random number stream so that
// it cannot possibly interact with other code using random numbers that will
// be called at various different intervals on the client + server. Use the
// CUniformRandomStream class for this.
//
// There are two types of client-server neutral code: Code that doesn't interact
// with player prediction, and code that does. The code that doesn't interact 
// with player prediction simply has to be able to produce the result f(time) 
// where time is monotonically increasing. For prediction, we have to produce
// the result f(time) where time does *not* monotonically increase (time can be 
// anywhere between the "current" time and the prior 10 seconds).
//
// Code that is not used by player prediction can maintain state because later 
// calls will always compute the value at some future time. This computation can
// use random number generation, but with the following restriction: Your code 
// must generate exactly the same number of random numbers regardless of how 
// frequently the code is called.
//
// In specific, this means that all random numbers used must either be computed
// at init time, or must be used in an 'event-based form'. Namely, use random 
// numbers to compute the time at which events occur and the random inputs for
// those events.  When simulating forward, you must simulate all intervening
// time and generate the same number of random numbers.
//
// For functions planned to be used by player prediction, one method is to use
// some sort of stateless computation (where the only states are the initial
// state and time). Note that random number generators have state implicit in
// the number of calls made to that random number generator, and therefore you
// cannot call a random number generator unless you are able to 
//
// 1) Use a random number generator that can return the ith random number, namely:
//
//	float r = random( i );	// i == the ith number in the random sequence
//
// 2) Be able to accurately know at any given time t how many random numbers 
//		have already been generated (namely, compute the i in part 1 above).
//
// There is another alternative for code meant to be used by player prediction: 
// you could just store a history of 'events' from which you could completely 
// determine the value of f(time). That history would need to be at least 10
// seconds long, which is guaranteed to be longer than the amount of time that
// prediction would need. I've written a class which I haven't tested yet (but
// will be using soon) called CTimedEventQueue (currently located in 
// env_wind_shared.h) which I plan to use to solve my problem (getting wind to
// blow players).
//
//=============================================================================//
#include "cbase.h"
#include "env_wind_shared.h"
#include "soundenvelope.h"
#include "IEffects.h"
#include "engine/IEngineSound.h"
#include "sharedInterface.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

//-----------------------------------------------------------------------------
// globals
//-----------------------------------------------------------------------------
static Vector s_vecWindVelocity( 0, 0, 0 );


CEnvWindShared::CEnvWindShared() : m_WindAveQueue(10), m_WindVariationQueue(10)
{
	m_pWindSound = NULL;
}

CEnvWindShared::~CEnvWindShared()
{
	if (m_pWindSound)
	{
		CSoundEnvelopeController::GetController().Shutdown( m_pWindSound );
	}
}

void CEnvWindShared::Init( int nEntIndex, int iRandomSeed, float flTime, 
						  int iInitialWindYaw, float flInitialWindSpeed )
{
	m_iEntIndex = nEntIndex;
	m_flWindAngleVariation = m_flWindSpeedVariation = 1.0f;
	m_flStartTime = m_flSimTime = m_flSwitchTime = m_flVariationTime = flTime;
	m_iWindSeed = iRandomSeed;
	m_Stream.SetSeed( iRandomSeed );
	m_WindVariationStream.SetSeed( iRandomSeed );
	m_iWindDir = m_iInitialWindDir = iInitialWindYaw;

	m_flAveWindSpeed = m_flWindSpeed = m_flInitialWindSpeed = flInitialWindSpeed;

	/*
	// Cache in the wind sound...
	if (!g_pEffects->IsServer())
	{
		CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
		m_pWindSound = controller.SoundCreate( -1, CHAN_STATIC, 
			"EnvWind.Loop", ATTN_NONE );
		controller.Play( m_pWindSound, 0.0f, 100 );
	}
	*/

	// Next time a change happens (which will happen immediately), it'll stop gusting
	m_bGusting = true;
}


//-----------------------------------------------------------------------------
// Computes wind variation
//-----------------------------------------------------------------------------

#define WIND_VARIATION_UPDATE_TIME 0.1f

void CEnvWindShared::ComputeWindVariation( float flTime )
{
	// The wind variation is updated every 10th of a second..
	while( flTime >= m_flVariationTime )
	{
		m_flWindAngleVariation = m_WindVariationStream.RandomFloat( -10, 10 );
		m_flWindSpeedVariation = 1.0 + m_WindVariationStream.RandomFloat( -0.2, 0.2 );
		m_flVariationTime += WIND_VARIATION_UPDATE_TIME;
	}
}



//-----------------------------------------------------------------------------
// Updates the wind sound
//-----------------------------------------------------------------------------
void CEnvWindShared::UpdateWindSound( float flTotalWindSpeed )
{
	if (!g_pEffects->IsServer())
	{
		float flDuration = random->RandomFloat( 1.0f, 2.0f );
		CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();

		// FIXME: Tweak with these numbers
		float flNormalizedWindSpeed = flTotalWindSpeed / 150.0f;
		if (flNormalizedWindSpeed > 1.0f)
			flNormalizedWindSpeed = 1.0f;
		float flPitch = 120 * Bias( flNormalizedWindSpeed, 0.3f ) + 100;
		float flVolume = 0.3f * Bias( flNormalizedWindSpeed, 0.3f ) + 0.7f;
		controller.SoundChangePitch( m_pWindSound, flPitch, flDuration );
		controller.SoundChangeVolume( m_pWindSound, flVolume, flDuration );
	}
}


//-----------------------------------------------------------------------------
// Updates the wind speed
//-----------------------------------------------------------------------------

#define WIND_ACCELERATION	150.0f	// wind speed can accelerate this many units per second
#define WIND_DECELERATION	15.0f	// wind speed can decelerate this many units per second

float CEnvWindShared::WindThink( float flTime )
{
	// NOTE: This algorithm can be client-server neutal because we're using 
	// the random number generator to generate *time* at which the wind changes.
	// We therefore need to structure the algorithm so that no matter the
	// frequency of calls to this function we produce the same wind speeds...

	ComputeWindVariation( flTime );

	while (true)
	{
		// First, simulate up to the next switch time...
		float flTimeToSwitch = m_flSwitchTime - m_flSimTime;
		float flMaxDeltaTime = flTime - m_flSimTime;

		bool bGotToSwitchTime = (flMaxDeltaTime > flTimeToSwitch);

		float flSimDeltaTime = bGotToSwitchTime ? flTimeToSwitch : flMaxDeltaTime;

		// Now that we've chosen 
		// either ramp up, or sleep till change
		bool bReachedSteadyState = true;
		if ( m_flAveWindSpeed > m_flWindSpeed )
		{
			m_flWindSpeed += WIND_ACCELERATION * flSimDeltaTime;
			if (m_flWindSpeed > m_flAveWindSpeed)
				m_flWindSpeed = m_flAveWindSpeed;
			else
				bReachedSteadyState = false;
		}
		else if ( m_flAveWindSpeed < m_flWindSpeed )
		{
			m_flWindSpeed -= WIND_DECELERATION * flSimDeltaTime;
			if (m_flWindSpeed < m_flAveWindSpeed)
				m_flWindSpeed = m_flAveWindSpeed;
			else
				bReachedSteadyState = false;
		}

		// Update the sim time

		// If we didn't get to a switch point, then we're done simulating for now 
		if (!bGotToSwitchTime)
		{
			m_flSimTime = flTime;

			// We're about to exit, let's set the wind velocity...
			QAngle vecWindAngle( 0, m_iWindDir + m_flWindAngleVariation, 0 );
			AngleVectors( vecWindAngle, &s_vecWindVelocity );
			float flTotalWindSpeed = m_flWindSpeed * m_flWindSpeedVariation;
			s_vecWindVelocity *= flTotalWindSpeed;

			// If we reached a steady state, we don't need to be called until the switch time
			// Otherwise, we should be called immediately

			// FIXME: If we ever call this from prediction, we'll need
			// to only update the sound if it's a new time
			// Or, we'll need to update the sound elsewhere.
			// Update the sound....
//			UpdateWindSound( flTotalWindSpeed );

			// Always immediately call, the wind is forever varying
			return ( flTime + 0.01f );
		}

		m_flSimTime = m_flSwitchTime;

		// Switch gusting state..
		if( m_bGusting )
		{
			// wind is gusting, so return to normal wind
			m_flAveWindSpeed = m_Stream.RandomInt( m_iMinWind, m_iMaxWind );

			// set up for another gust later
			m_bGusting = false;
			m_flSwitchTime += m_flMinGustDelay + m_Stream.RandomFloat( 0, m_flMaxGustDelay );

#ifndef CLIENT_DLL
			m_OnGustEnd.FireOutput( NULL, NULL );
#endif
		}
		else
		{
			// time for a gust.
			m_flAveWindSpeed = m_Stream.RandomInt( m_iMinGust, m_iMaxGust );

			// change wind direction, maybe a lot
			m_iWindDir = anglemod( m_iWindDir + m_Stream.RandomInt(-m_iGustDirChange, m_iGustDirChange) );

			// set up to stop the gust in a short while
			m_bGusting = true;

#ifndef CLIENT_DLL
			m_OnGustStart.FireOutput( NULL, NULL );
#endif

			// !!!HACKHACK - gust duration tied to the length of a particular wave file
			m_flSwitchTime += m_flGustDuration;
		}
	}
}


//-----------------------------------------------------------------------------
// Method to reset windspeed..
//-----------------------------------------------------------------------------
void ResetWindspeed()
{
	s_vecWindVelocity.Init( 0, 0, 0 );
}


//-----------------------------------------------------------------------------
// Method to sample the windspeed at a particular time
//-----------------------------------------------------------------------------
void GetWindspeedAtTime( float flTime, Vector &vecVelocity )
{
	// For now, ignore history and time.. fix later when we use wind to affect
	// client-side prediction
	VectorCopy( s_vecWindVelocity, vecVelocity );
}