Revision control

1
/***
2
*
3
* Copyright (c) 1996-2002, Valve LLC. All rights reserved.
4
*
5
* This product contains software technology licensed from Id
6
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
7
* All Rights Reserved.
8
*
9
* Use, distribution, and modification of this source code and/or resulting
10
* object code is restricted to non-commercial enhancements to products from
11
* Valve LLC. All other use, distribution, or modification is prohibited
12
* without written permission from Valve LLC.
13
*
14
****/
15
/*
16
17
===== subs.cpp ========================================================
18
19
frequently used global functions
20
21
*/
22
23
#include "extdll.h"
24
#include "util.h"
25
#include "cbase.h"
26
#include "saverestore.h"
27
#include "nodes.h"
28
#include "doors.h"
29
30
extern CGraph WorldGraph;
31
32
extern BOOL FEntIsVisible(entvars_t* pev, entvars_t* pevTarget);
33
34
extern DLL_GLOBAL int g_iSkillLevel;
35
36
37
// Landmark class
38
void CPointEntity :: Spawn( void )
39
{
40
pev->solid = SOLID_NOT;
41
// UTIL_SetSize(pev, g_vecZero, g_vecZero);
42
}
43
44
45
class CNullEntity : public CBaseEntity
46
{
47
public:
48
void Spawn( void );
49
};
50
51
52
// Null Entity, remove on startup
53
void CNullEntity :: Spawn( void )
54
{
55
REMOVE_ENTITY(ENT(pev));
56
}
57
LINK_ENTITY_TO_CLASS(info_null,CNullEntity);
58
59
class CBaseDMStart : public CPointEntity
60
{
61
public:
62
void KeyValue( KeyValueData *pkvd );
63
BOOL IsTriggered( CBaseEntity *pEntity );
64
65
private:
66
};
67
68
// These are the new entry points to entities.
69
LINK_ENTITY_TO_CLASS(info_player_deathmatch,CBaseDMStart);
70
LINK_ENTITY_TO_CLASS(info_player_start,CPointEntity);
71
LINK_ENTITY_TO_CLASS(info_landmark,CPointEntity);
72
73
void CBaseDMStart::KeyValue( KeyValueData *pkvd )
74
{
75
if (FStrEq(pkvd->szKeyName, "master"))
76
{
77
pev->netname = ALLOC_STRING(pkvd->szValue);
78
pkvd->fHandled = TRUE;
79
}
80
else
81
CPointEntity::KeyValue( pkvd );
82
}
83
84
BOOL CBaseDMStart::IsTriggered( CBaseEntity *pEntity )
85
{
86
BOOL master = UTIL_IsMasterTriggered( pev->netname, pEntity );
87
88
return master;
89
}
90
91
// This updates global tables that need to know about entities being removed
92
void CBaseEntity::UpdateOnRemove( void )
93
{
94
int i;
95
96
if ( FBitSet( pev->flags, FL_GRAPHED ) )
97
{
98
// this entity was a LinkEnt in the world node graph, so we must remove it from
99
// the graph since we are removing it from the world.
100
for ( i = 0 ; i < WorldGraph.m_cLinks ; i++ )
101
{
102
if ( WorldGraph.m_pLinkPool [ i ].m_pLinkEnt == pev )
103
{
104
// if this link has a link ent which is the same ent that is removing itself, remove it!
105
WorldGraph.m_pLinkPool [ i ].m_pLinkEnt = NULL;
106
}
107
}
108
}
109
if ( pev->globalname )
110
gGlobalState.EntitySetState( pev->globalname, GLOBAL_DEAD );
111
}
112
113
// Convenient way to delay removing oneself
114
void CBaseEntity :: SUB_Remove( void )
115
{
116
UpdateOnRemove();
117
if (pev->health > 0)
118
{
119
// this situation can screw up monsters who can't tell their entity pointers are invalid.
120
pev->health = 0;
121
ALERT( at_aiconsole, "SUB_Remove called on entity with health > 0\n");
122
}
123
124
REMOVE_ENTITY(ENT(pev));
125
}
126
127
128
// Convenient way to explicitly do nothing (passed to functions that require a method)
129
void CBaseEntity :: SUB_DoNothing( void )
130
{
131
}
132
133
134
// Global Savedata for Delay
135
TYPEDESCRIPTION CBaseDelay::m_SaveData[] =
136
{
137
DEFINE_FIELD( CBaseDelay, m_flDelay, FIELD_FLOAT ),
138
DEFINE_FIELD( CBaseDelay, m_iszKillTarget, FIELD_STRING ),
139
};
140
141
IMPLEMENT_SAVERESTORE( CBaseDelay, CBaseEntity );
142
143
void CBaseDelay :: KeyValue( KeyValueData *pkvd )
144
{
145
if (FStrEq(pkvd->szKeyName, "delay"))
146
{
147
m_flDelay = atof( pkvd->szValue );
148
pkvd->fHandled = TRUE;
149
}
150
else if (FStrEq(pkvd->szKeyName, "killtarget"))
151
{
152
m_iszKillTarget = ALLOC_STRING(pkvd->szValue);
153
pkvd->fHandled = TRUE;
154
}
155
else
156
{
157
CBaseEntity::KeyValue( pkvd );
158
}
159
}
160
161
162
/*
163
==============================
164
SUB_UseTargets
165
166
If self.delay is set, a DelayedUse entity will be created that will actually
167
do the SUB_UseTargets after that many seconds have passed.
168
169
Removes all entities with a targetname that match self.killtarget,
170
and removes them, so some events can remove other triggers.
171
172
Search for (string)targetname in all entities that
173
match (string)self.target and call their .use function (if they have one)
174
175
==============================
176
*/
177
void CBaseEntity :: SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value )
178
{
179
//
180
// fire targets
181
//
182
if (!FStringNull(pev->target))
183
{
184
FireTargets( STRING(pev->target), pActivator, this, useType, value );
185
}
186
}
187
188
189
void FireTargets( const char *targetName, CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
190
{
191
edict_t *pentTarget = NULL;
192
if ( !targetName )
193
return;
194
195
ALERT( at_aiconsole, "Firing: (%s)\n", targetName );
196
197
for (;;)
198
{
199
pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, targetName);
200
if (FNullEnt(pentTarget))
201
break;
202
203
CBaseEntity *pTarget = CBaseEntity::Instance( pentTarget );
204
if ( pTarget && !(pTarget->pev->flags & FL_KILLME) ) // Don't use dying ents
205
{
206
ALERT( at_aiconsole, "Found: %s, firing (%s)\n", STRING(pTarget->pev->classname), targetName );
207
pTarget->Use( pActivator, pCaller, useType, value );
208
}
209
}
210
}
211
212
LINK_ENTITY_TO_CLASS( DelayedUse, CBaseDelay );
213
214
215
void CBaseDelay :: SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value )
216
{
217
//
218
// exit immediatly if we don't have a target or kill target
219
//
220
if (FStringNull(pev->target) && !m_iszKillTarget)
221
return;
222
223
//
224
// check for a delay
225
//
226
if (m_flDelay != 0)
227
{
228
// create a temp object to fire at a later time
229
CBaseDelay *pTemp = GetClassPtr( (CBaseDelay *)NULL);
230
pTemp->pev->classname = MAKE_STRING("DelayedUse");
231
232
pTemp->pev->nextthink = gpGlobals->time + m_flDelay;
233
234
pTemp->SetThink( &CBaseDelay::DelayThink );
235
236
// Save the useType
237
pTemp->pev->button = (int)useType;
238
pTemp->m_iszKillTarget = m_iszKillTarget;
239
pTemp->m_flDelay = 0; // prevent "recursion"
240
pTemp->pev->target = pev->target;
241
242
// HACKHACK
243
// This wasn't in the release build of Half-Life. We should have moved m_hActivator into this class
244
// but changing member variable hierarchy would break save/restore without some ugly code.
245
// This code is not as ugly as that code
246
if ( pActivator && pActivator->IsPlayer() ) // If a player activates, then save it
247
{
248
pTemp->pev->owner = pActivator->edict();
249
}
250
else
251
{
252
pTemp->pev->owner = NULL;
253
}
254
255
return;
256
}
257
258
//
259
// kill the killtargets
260
//
261
262
if ( m_iszKillTarget )
263
{
264
edict_t *pentKillTarget = NULL;
265
266
ALERT( at_aiconsole, "KillTarget: %s\n", STRING(m_iszKillTarget) );
267
pentKillTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_iszKillTarget) );
268
while ( !FNullEnt(pentKillTarget) )
269
{
270
UTIL_Remove( CBaseEntity::Instance(pentKillTarget) );
271
272
ALERT( at_aiconsole, "killing %s\n", STRING( pentKillTarget->v.classname ) );
273
pentKillTarget = FIND_ENTITY_BY_TARGETNAME( pentKillTarget, STRING(m_iszKillTarget) );
274
}
275
}
276
277
//
278
// fire targets
279
//
280
if (!FStringNull(pev->target))
281
{
282
FireTargets( STRING(pev->target), pActivator, this, useType, value );
283
}
284
}
285
286
287
/*
288
void CBaseDelay :: SUB_UseTargetsEntMethod( void )
289
{
290
SUB_UseTargets(pev);
291
}
292
*/
293
294
/*
295
QuakeEd only writes a single float for angles (bad idea), so up and down are
296
just constant angles.
297
*/
298
void SetMovedir( entvars_t *pev )
299
{
300
if (pev->angles == Vector(0, -1, 0))
301
{
302
pev->movedir = Vector(0, 0, 1);
303
}
304
else if (pev->angles == Vector(0, -2, 0))
305
{
306
pev->movedir = Vector(0, 0, -1);
307
}
308
else
309
{
310
UTIL_MakeVectors(pev->angles);
311
pev->movedir = gpGlobals->v_forward;
312
}
313
314
pev->angles = g_vecZero;
315
}
316
317
318
319
320
void CBaseDelay::DelayThink( void )
321
{
322
CBaseEntity *pActivator = NULL;
323
324
if ( pev->owner != NULL ) // A player activated this on delay
325
{
326
pActivator = CBaseEntity::Instance( pev->owner );
327
}
328
// The use type is cached (and stashed) in pev->button
329
SUB_UseTargets( pActivator, (USE_TYPE)pev->button, 0 );
330
REMOVE_ENTITY(ENT(pev));
331
}
332
333
334
// Global Savedata for Toggle
335
TYPEDESCRIPTION CBaseToggle::m_SaveData[] =
336
{
337
DEFINE_FIELD( CBaseToggle, m_toggle_state, FIELD_INTEGER ),
338
DEFINE_FIELD( CBaseToggle, m_flActivateFinished, FIELD_TIME ),
339
DEFINE_FIELD( CBaseToggle, m_flMoveDistance, FIELD_FLOAT ),
340
DEFINE_FIELD( CBaseToggle, m_flWait, FIELD_FLOAT ),
341
DEFINE_FIELD( CBaseToggle, m_flLip, FIELD_FLOAT ),
342
DEFINE_FIELD( CBaseToggle, m_flTWidth, FIELD_FLOAT ),
343
DEFINE_FIELD( CBaseToggle, m_flTLength, FIELD_FLOAT ),
344
DEFINE_FIELD( CBaseToggle, m_vecPosition1, FIELD_POSITION_VECTOR ),
345
DEFINE_FIELD( CBaseToggle, m_vecPosition2, FIELD_POSITION_VECTOR ),
346
DEFINE_FIELD( CBaseToggle, m_vecAngle1, FIELD_VECTOR ), // UNDONE: Position could go through transition, but also angle?
347
DEFINE_FIELD( CBaseToggle, m_vecAngle2, FIELD_VECTOR ), // UNDONE: Position could go through transition, but also angle?
348
DEFINE_FIELD( CBaseToggle, m_cTriggersLeft, FIELD_INTEGER ),
349
DEFINE_FIELD( CBaseToggle, m_flHeight, FIELD_FLOAT ),
350
DEFINE_FIELD( CBaseToggle, m_hActivator, FIELD_EHANDLE ),
351
DEFINE_FIELD( CBaseToggle, m_pfnCallWhenMoveDone, FIELD_FUNCTION ),
352
DEFINE_FIELD( CBaseToggle, m_vecFinalDest, FIELD_POSITION_VECTOR ),
353
DEFINE_FIELD( CBaseToggle, m_vecFinalAngle, FIELD_VECTOR ),
354
DEFINE_FIELD( CBaseToggle, m_sMaster, FIELD_STRING),
355
DEFINE_FIELD( CBaseToggle, m_bitsDamageInflict, FIELD_INTEGER ), // damage type inflicted
356
};
357
IMPLEMENT_SAVERESTORE( CBaseToggle, CBaseAnimating );
358
359
360
void CBaseToggle::KeyValue( KeyValueData *pkvd )
361
{
362
if (FStrEq(pkvd->szKeyName, "lip"))
363
{
364
m_flLip = atof(pkvd->szValue);
365
pkvd->fHandled = TRUE;
366
}
367
else if (FStrEq(pkvd->szKeyName, "wait"))
368
{
369
m_flWait = atof(pkvd->szValue);
370
pkvd->fHandled = TRUE;
371
}
372
else if (FStrEq(pkvd->szKeyName, "master"))
373
{
374
m_sMaster = ALLOC_STRING(pkvd->szValue);
375
pkvd->fHandled = TRUE;
376
}
377
else if (FStrEq(pkvd->szKeyName, "distance"))
378
{
379
m_flMoveDistance = atof(pkvd->szValue);
380
pkvd->fHandled = TRUE;
381
}
382
else
383
CBaseDelay::KeyValue( pkvd );
384
}
385
386
/*
387
=============
388
LinearMove
389
390
calculate pev->velocity and pev->nextthink to reach vecDest from
391
pev->origin traveling at flSpeed
392
===============
393
*/
394
void CBaseToggle :: LinearMove( Vector vecDest, float flSpeed )
395
{
396
ASSERTSZ(flSpeed != 0, "LinearMove: no speed is defined!");
397
// ASSERTSZ(m_pfnCallWhenMoveDone != NULL, "LinearMove: no post-move function defined");
398
399
m_vecFinalDest = vecDest;
400
401
// Already there?
402
if (vecDest == pev->origin)
403
{
404
LinearMoveDone();
405
return;
406
}
407
408
// set destdelta to the vector needed to move
409
Vector vecDestDelta = vecDest - pev->origin;
410
411
// divide vector length by speed to get time to reach dest
412
float flTravelTime = vecDestDelta.Length() / flSpeed;
413
414
// set nextthink to trigger a call to LinearMoveDone when dest is reached
415
pev->nextthink = pev->ltime + flTravelTime;
416
SetThink( &CBaseToggle::LinearMoveDone );
417
418
// scale the destdelta vector by the time spent traveling to get velocity
419
pev->velocity = vecDestDelta / flTravelTime;
420
}
421
422
423
/*
424
============
425
After moving, set origin to exact final destination, call "move done" function
426
============
427
*/
428
void CBaseToggle :: LinearMoveDone( void )
429
{
430
Vector delta = m_vecFinalDest - pev->origin;
431
float error = delta.Length();
432
if ( error > 0.03125 )
433
{
434
LinearMove( m_vecFinalDest, 100 );
435
return;
436
}
437
438
UTIL_SetOrigin(pev, m_vecFinalDest);
439
pev->velocity = g_vecZero;
440
pev->nextthink = -1;
441
if ( m_pfnCallWhenMoveDone )
442
(this->*m_pfnCallWhenMoveDone)();
443
}
444
445
BOOL CBaseToggle :: IsLockedByMaster( void )
446
{
447
if (m_sMaster && !UTIL_IsMasterTriggered(m_sMaster, m_hActivator))
448
return TRUE;
449
else
450
return FALSE;
451
}
452
453
/*
454
=============
455
AngularMove
456
457
calculate pev->velocity and pev->nextthink to reach vecDest from
458
pev->origin traveling at flSpeed
459
Just like LinearMove, but rotational.
460
===============
461
*/
462
void CBaseToggle :: AngularMove( Vector vecDestAngle, float flSpeed )
463
{
464
ASSERTSZ(flSpeed != 0, "AngularMove: no speed is defined!");
465
// ASSERTSZ(m_pfnCallWhenMoveDone != NULL, "AngularMove: no post-move function defined");
466
467
m_vecFinalAngle = vecDestAngle;
468
469
// Already there?
470
if (vecDestAngle == pev->angles)
471
{
472
AngularMoveDone();
473
return;
474
}
475
476
// set destdelta to the vector needed to move
477
Vector vecDestDelta = vecDestAngle - pev->angles;
478
479
// divide by speed to get time to reach dest
480
float flTravelTime = vecDestDelta.Length() / flSpeed;
481
482
// set nextthink to trigger a call to AngularMoveDone when dest is reached
483
pev->nextthink = pev->ltime + flTravelTime;
484
SetThink( &CBaseToggle::AngularMoveDone );
485
486
// scale the destdelta vector by the time spent traveling to get velocity
487
pev->avelocity = vecDestDelta / flTravelTime;
488
}
489
490
491
/*
492
============
493
After rotating, set angle to exact final angle, call "move done" function
494
============
495
*/
496
void CBaseToggle :: AngularMoveDone( void )
497
{
498
pev->angles = m_vecFinalAngle;
499
pev->avelocity = g_vecZero;
500
pev->nextthink = -1;
501
if ( m_pfnCallWhenMoveDone )
502
(this->*m_pfnCallWhenMoveDone)();
503
}
504
505
506
float CBaseToggle :: AxisValue( int flags, const Vector &angles )
507
{
508
if ( FBitSet(flags, SF_DOOR_ROTATE_Z) )
509
return angles.z;
510
if ( FBitSet(flags, SF_DOOR_ROTATE_X) )
511
return angles.x;
512
513
return angles.y;
514
}
515
516
517
void CBaseToggle :: AxisDir( entvars_t *pev )
518
{
519
if ( FBitSet(pev->spawnflags, SF_DOOR_ROTATE_Z) )
520
pev->movedir = Vector ( 0, 0, 1 ); // around z-axis
521
else if ( FBitSet(pev->spawnflags, SF_DOOR_ROTATE_X) )
522
pev->movedir = Vector ( 1, 0, 0 ); // around x-axis
523
else
524
pev->movedir = Vector ( 0, 1, 0 ); // around y-axis
525
}
526
527
528
float CBaseToggle :: AxisDelta( int flags, const Vector &angle1, const Vector &angle2 )
529
{
530
if ( FBitSet (flags, SF_DOOR_ROTATE_Z) )
531
return angle1.z - angle2.z;
532
533
if ( FBitSet (flags, SF_DOOR_ROTATE_X) )
534
return angle1.x - angle2.x;
535
536
return angle1.y - angle2.y;
537
}
538
539
540
/*
541
=============
542
FEntIsVisible
543
544
returns TRUE if the passed entity is visible to caller, even if not infront ()
545
=============
546
*/
547
BOOL
548
FEntIsVisible(
549
entvars_t* pev,
550
entvars_t* pevTarget)
551
{
552
Vector vecSpot1 = pev->origin + pev->view_ofs;
553
Vector vecSpot2 = pevTarget->origin + pevTarget->view_ofs;
554
TraceResult tr;
555
556
UTIL_TraceLine(vecSpot1, vecSpot2, ignore_monsters, ENT(pev), &tr);
557
558
if (tr.fInOpen && tr.fInWater)
559
return FALSE; // sight line crossed contents
560
561
if (tr.flFraction == 1)
562
return TRUE;
563
564
return FALSE;
565
}
566
567