1 /*
2 ===========================================================================
3 Copyright (C) 1999 - 2005, Id Software, Inc.
4 Copyright (C) 2000 - 2013, Raven Software, Inc.
5 Copyright (C) 2001 - 2013, Activision, Inc.
6 Copyright (C) 2013 - 2015, OpenJK contributors
7
8 This file is part of the OpenJK source code.
9
10 OpenJK is free software; you can redistribute it and/or modify it
11 under the terms of the GNU General Public License version 2 as
12 published by the Free Software Foundation.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, see <http://www.gnu.org/licenses/>.
21 ===========================================================================
22 */
23
24 #include "../icarus/IcarusInterface.h"
25 #include "../cgame/cg_local.h"
26 #include "Q3_Interface.h"
27 #include "g_local.h"
28 #include "g_functions.h"
29 #include "anims.h"
30 #include "wp_saber.h"
31 #include "g_vehicles.h"
32 #include "objectives.h"
33 #include "b_local.h"
34
35 extern int WP_SaberInitBladeData( gentity_t *ent );
36 extern void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *weaponModel, int boltNum, int weaponNum );
37 extern qboolean CheatsOk( gentity_t *ent );
38 extern void Boba_Precache( void );
39
40 extern cvar_t *g_char_model;
41 extern cvar_t *g_char_skin_head;
42 extern cvar_t *g_char_skin_torso;
43 extern cvar_t *g_char_skin_legs;
44 extern cvar_t *g_char_color_red;
45 extern cvar_t *g_char_color_green;
46 extern cvar_t *g_char_color_blue;
47 extern cvar_t *g_saber;
48 extern cvar_t *g_saber2;
49 extern cvar_t *g_saber_color;
50 extern cvar_t *g_saber2_color;
51 extern cvar_t *g_saberDarkSideSaberColor;
52
53 // g_client.c -- client functions that don't happen every frame
54
55 float DEFAULT_MINS_0 = -16;
56 float DEFAULT_MINS_1 = -16;
57 float DEFAULT_MAXS_0 = 16;
58 float DEFAULT_MAXS_1 = 16;
59 float DEFAULT_PLAYER_RADIUS = sqrt((DEFAULT_MAXS_0*DEFAULT_MAXS_0) + (DEFAULT_MAXS_1*DEFAULT_MAXS_1));
60 vec3_t playerMins = {DEFAULT_MINS_0, DEFAULT_MINS_1, DEFAULT_MINS_2};
61 vec3_t playerMinsStep = {DEFAULT_MINS_0, DEFAULT_MINS_1, DEFAULT_MINS_2+STEPSIZE};
62 vec3_t playerMaxs = {DEFAULT_MAXS_0, DEFAULT_MAXS_1, DEFAULT_MAXS_2};
63
64 void SP_misc_teleporter_dest (gentity_t *ent);
65
66 /*QUAK-ED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) KEEP_PREV DROPTOFLOOR x x x STUN_BATON NOWEAPON x
67 potential spawning position for deathmatch games.
68 Targets will be fired when someone spawns in on them.
69 */
SP_info_player_deathmatch(gentity_t * ent)70 void SP_info_player_deathmatch(gentity_t *ent) {
71 SP_misc_teleporter_dest (ent);
72
73 if ( ent->spawnflags & 32 ) // STUN_BATON
74 {
75 RegisterItem( FindItemForWeapon( WP_STUN_BATON ));
76 }
77 else
78 {
79 RegisterItem( FindItemForWeapon( WP_SABER ) ); //these are given in ClientSpawn(), but we register them now before cgame starts
80 saberInfo_t saber;
81 WP_SaberParseParms( g_saber->string, &saber );//get saber sounds and models cached before client begins
82 if (saber.model) G_ModelIndex( saber.model );
83 if (saber.brokenSaber1) G_ModelIndex( saber.brokenSaber1 );
84 if (saber.brokenSaber2) G_ModelIndex( saber.brokenSaber2 );
85 if (saber.skin) G_SkinIndex( saber.skin );
86 WP_SaberFreeStrings(saber);
87 }
88 }
89
90 /*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) KEEP_PREV DROPTOFLOOR x x x STUN_BATON NOWEAPON x
91 KEEP_PREV - keep previous health + armor
92 DROPTOFLOOR - Player will start on the first solid structure under it
93 STUN_BATON - Gives player the stun baton and bryar pistol, but not the saber, plus any weapons they may have carried over from previous levels.
94
95 Targets will be fired when someone spawns in on them.
96 equivalant to info_player_deathmatch
97 */
SP_info_player_start(gentity_t * ent)98 void SP_info_player_start(gentity_t *ent) {
99 ent->classname = "info_player_deathmatch";
100
101 SP_info_player_deathmatch( ent );
102 }
103
104
105
106 /*
107 =======================================================================
108
109 SelectSpawnPoint
110
111 =======================================================================
112 */
113
114 /*
115 ================
116 SpotWouldTelefrag
117
118 ================
119 */
SpotWouldTelefrag(gentity_t * spot,team_t checkteam)120 qboolean SpotWouldTelefrag( gentity_t *spot, team_t checkteam )
121 {
122 int i, num;
123 gentity_t *touch[MAX_GENTITIES], *hit;
124 vec3_t mins, maxs;
125
126 // If we have a mins, use that instead of the hardcoded bounding box
127 if ( !VectorCompare(spot->mins, vec3_origin) && VectorLength( spot->mins ) )
128 VectorAdd( spot->s.origin, spot->mins, mins );
129 else
130 VectorAdd( spot->s.origin, playerMins, mins );
131
132 // If we have a maxs, use that instead of the hardcoded bounding box
133 if ( !VectorCompare(spot->maxs, vec3_origin) && VectorLength( spot->maxs ) )
134 VectorAdd( spot->s.origin, spot->maxs, maxs );
135 else
136 VectorAdd( spot->s.origin, playerMaxs, maxs );
137
138 num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
139
140 for (i=0 ; i<num ; i++)
141 {
142 hit = touch[i];
143 if ( hit != spot && hit->client && hit->client->ps.stats[STAT_HEALTH] > 0 )
144 {
145 if ( hit->contents & CONTENTS_BODY )
146 {
147 if( checkteam == TEAM_FREE || hit->client->playerTeam == checkteam )
148 {//checking against teammates only...?
149 return qtrue;
150 }
151 }
152 }
153 }
154
155 return qfalse;
156 }
157
SpotWouldTelefrag2(gentity_t * mover,vec3_t dest)158 qboolean SpotWouldTelefrag2( gentity_t *mover, vec3_t dest )
159 {
160 int i, num;
161 gentity_t *touch[MAX_GENTITIES], *hit;
162 vec3_t mins, maxs;
163
164 VectorAdd( dest, mover->mins, mins );
165 VectorAdd( dest, mover->maxs, maxs );
166 num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
167
168 for (i=0 ; i<num ; i++)
169 {
170 hit = touch[i];
171 if ( hit == mover )
172 {
173 continue;
174 }
175
176 if ( hit->contents & mover->contents )
177 {
178 return qtrue;
179 }
180 }
181
182 return qfalse;
183 }
184 /*
185 ================
186 SelectNearestDeathmatchSpawnPoint
187
188 Find the spot that we DON'T want to use
189 ================
190 */
191 #define MAX_SPAWN_POINTS 128
SelectNearestDeathmatchSpawnPoint(vec3_t from,team_t team)192 gentity_t *SelectNearestDeathmatchSpawnPoint( vec3_t from, team_t team ) {
193 gentity_t *spot;
194 float dist, nearestDist;
195 gentity_t *nearestSpot;
196
197 nearestDist = (float)WORLD_SIZE*(float)WORLD_SIZE;
198 nearestSpot = NULL;
199 spot = NULL;
200
201 while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
202 /*if ( team == TEAM_RED && ( spot->spawnflags & 2 ) ) {
203 continue;
204 }
205 if ( team == TEAM_BLUE && ( spot->spawnflags & 1 ) ) {
206 continue;
207 }*/
208
209 if ( spot->targetname != NULL ) {
210 //this search routine should never find a spot that is targetted
211 continue;
212 }
213 dist = DistanceSquared( spot->s.origin, from );
214 if ( dist < nearestDist ) {
215 nearestDist = dist;
216 nearestSpot = spot;
217 }
218 }
219
220 return nearestSpot;
221 }
222
223
224 /*
225 ================
226 SelectRandomDeathmatchSpawnPoint
227
228 go to a random point that doesn't telefrag
229 ================
230 */
231 #define MAX_SPAWN_POINTS 128
SelectRandomDeathmatchSpawnPoint(team_t team)232 gentity_t *SelectRandomDeathmatchSpawnPoint( team_t team ) {
233 gentity_t *spot;
234 int count;
235 int selection;
236 gentity_t *spots[MAX_SPAWN_POINTS];
237
238 count = 0;
239 spot = NULL;
240
241 while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
242 /*if ( team == TEAM_RED && ( spot->spawnflags & 2 ) ) {
243 continue;
244 }
245 if ( team == TEAM_BLUE && ( spot->spawnflags & 1 ) ) {
246 continue;
247 }*/
248
249 if ( spot->targetname != NULL ) {
250 //this search routine should never find a spot that is targetted
251 continue;
252 }
253 if ( SpotWouldTelefrag( spot, TEAM_FREE ) ) {
254 continue;
255 }
256 spots[ count ] = spot;
257 count++;
258 }
259
260 if ( !count ) { // no spots that won't telefrag
261 spot = G_Find( NULL, FOFS(classname), "info_player_deathmatch");
262 if ( !spot )
263 {
264 return NULL;
265 }
266 if ( spot->targetname != NULL )
267 {
268 //this search routine should never find a spot that is targetted
269 return NULL;
270 }
271 else
272 {
273 return spot;
274 }
275 }
276
277 selection = rand() % count;
278 return spots[ selection ];
279 }
280
281
282 /*
283 ===========
284 SelectSpawnPoint
285
286 Chooses a player start, deathmatch start, etc
287 ============
288 */
SelectSpawnPoint(vec3_t avoidPoint,team_t team,vec3_t origin,vec3_t angles)289 gentity_t *SelectSpawnPoint ( vec3_t avoidPoint, team_t team, vec3_t origin, vec3_t angles ) {
290 gentity_t *spot;
291 gentity_t *nearestSpot;
292
293 if ( level.spawntarget[0] )
294 {//we have a spawnpoint specified, try to find it
295 if ( (nearestSpot = spot = G_Find( NULL, FOFS(targetname), level.spawntarget )) == NULL )
296 {//you HAVE to be able to find the desired spot
297 G_Error( "Couldn't find spawntarget %s\n", level.spawntarget );
298 return NULL;
299 }
300 }
301 else
302 {//not looking for a special startspot
303 nearestSpot = SelectNearestDeathmatchSpawnPoint( avoidPoint, team );
304
305 spot = SelectRandomDeathmatchSpawnPoint ( team );
306 if ( spot == nearestSpot ) {
307 // roll again if it would be real close to point of death
308 spot = SelectRandomDeathmatchSpawnPoint ( team );
309 }
310 }
311
312 // find a single player start spot
313 if (!spot) {
314 G_Error( "Couldn't find a spawn point\n" );
315 }
316
317
318 VectorCopy( spot->s.origin, origin );
319 if ( spot->spawnflags & 2 )
320 {
321 trace_t tr;
322
323 origin[2] = MIN_WORLD_COORD;
324 gi.trace(&tr, spot->s.origin, playerMins, playerMaxs, origin, ENTITYNUM_NONE, MASK_PLAYERSOLID, (EG2_Collision)0, 0 );
325 if ( tr.fraction < 1.0 && !tr.allsolid && !tr.startsolid )
326 {//found a floor
327 VectorCopy(tr.endpos, origin );
328 }
329 else
330 {//In solid or too far
331 VectorCopy( spot->s.origin, origin );
332 }
333 }
334
335 origin[2] += 9;
336 VectorCopy (spot->s.angles, angles);
337
338 return spot;
339 }
340
341
342 //======================================================================
343
344
345 /*
346 ==================
347 SetClientViewAngle
348
349 ==================
350 */
SetClientViewAngle(gentity_t * ent,vec3_t angle)351 void SetClientViewAngle( gentity_t *ent, vec3_t angle ) {
352 int i;
353
354 // set the delta angle
355 for (i=0 ; i<3 ; i++)
356 {
357 ent->client->ps.delta_angles[i] = (ANGLE2SHORT(angle[i]) - ent->client->pers.cmd_angles[i])&0xffff;
358 }
359 VectorCopy( angle, ent->s.angles );
360 VectorCopy (ent->s.angles, ent->client->ps.viewangles);
361 }
362
363 /*
364 ================
365 respawn
366 ================
367 */
respawn(gentity_t * ent)368 void respawn( gentity_t *ent ) {
369
370 gi.SendConsoleCommand("load *respawn\n"); // special case
371 }
372
373 /*
374 ===========
375 ClientCheckName
376 ============
377 */
ClientCleanName(const char * in,char * out,int outSize)378 static void ClientCleanName( const char *in, char *out, int outSize )
379 {
380 int outpos = 0, colorlessLen = 0, spaces = 0, ats = 0;
381
382 // discard leading spaces
383 for ( ; *in == ' '; in++);
384
385 // discard leading asterisk's (fail raven for using * as a skipnotify)
386 // apparently .* causes the issue too so... derp
387 //for(; *in == '*'; in++);
388
389 for(; *in && outpos < outSize - 1; in++)
390 {
391 out[outpos] = *in;
392
393 if ( *in == ' ' )
394 {// don't allow too many consecutive spaces
395 if ( spaces > 2 )
396 continue;
397
398 spaces++;
399 }
400 else if ( *in == '@' )
401 {// don't allow too many consecutive at signs
402 if ( ats > 2 )
403 continue;
404
405 ats++;
406 }
407 else if ( outpos > 0 && out[outpos-1] == Q_COLOR_ESCAPE )
408 {
409 if ( Q_IsColorStringExt( &out[outpos-1] ) )
410 {
411 colorlessLen--;
412
413 #if 0
414 if ( ColorIndex( *in ) == 0 )
415 {// Disallow color black in names to prevent players from getting advantage playing in front of black backgrounds
416 outpos--;
417 continue;
418 }
419 #endif
420 }
421 else
422 {
423 spaces = ats = 0;
424 colorlessLen++;
425 }
426 }
427 else
428 {
429 spaces = ats = 0;
430 colorlessLen++;
431 }
432
433 outpos++;
434 }
435
436 out[outpos] = '\0';
437
438 // don't allow empty names
439 if ( *out == '\0' || colorlessLen == 0 )
440 Q_strncpyz( out, "Padawan", outSize );
441 }
442
443 /*
444 ===========
445 ClientUserInfoChanged
446
447 Called from ClientConnect when the player first connects and
448 directly by the server system when the player updates a userinfo variable.
449
450 The game can override any of the settings and call gi.SetUserinfo
451 if desired.
452 ============
453 */
ClientUserinfoChanged(int clientNum)454 void ClientUserinfoChanged( int clientNum ) {
455 gentity_t *ent = g_entities + clientNum;
456 gclient_t *client = ent->client;
457 int health=100, maxHealth=100;
458 const char *s=NULL;
459 char userinfo[MAX_INFO_STRING]={0}, buf[MAX_INFO_STRING]={0},
460 sound[MAX_STRING_CHARS]={0}, oldname[34]={0};
461
462 gi.GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );
463
464 // check for malformed or illegal info strings
465 /*if ( !Info_Validate(userinfo) ) {
466 strcpy (userinfo, "\\name\\badinfo");
467 }*/
468
469 // set name
470 Q_strncpyz ( oldname, client->pers.netname, sizeof( oldname ) );
471 s = Info_ValueForKey (userinfo, "name");
472 ClientCleanName( s, client->pers.netname, sizeof( client->pers.netname ) );
473
474 // set max health
475 maxHealth = 100;
476 health = Com_Clampi( 1, 100, atoi( Info_ValueForKey( userinfo, "handicap" ) ) );
477 client->pers.maxHealth = health;
478 if ( client->pers.maxHealth < 1 || client->pers.maxHealth > maxHealth )
479 client->pers.maxHealth = 100;
480 client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth;
481
482 // sounds
483 Q_strncpyz( sound, Info_ValueForKey (userinfo, "snd"), sizeof( sound ) );
484
485 // send over a subset of the userinfo keys so other clients can
486 // print scoreboards, display models, and play custom sounds
487 buf[0] = '\0';
488 Q_strcat( buf, sizeof( buf ), va( "n\\%s\\", client->pers.netname ) );
489 Q_strcat( buf, sizeof( buf ), va( "t\\%i\\", client->sess.sessionTeam ) );
490 Q_strcat( buf, sizeof( buf ), "headModel\\\\" );
491 Q_strcat( buf, sizeof( buf ), "torsoModel\\\\" );
492 Q_strcat( buf, sizeof( buf ), "legsModel\\\\" );
493 Q_strcat( buf, sizeof( buf ), va( "hc\\%i\\", client->pers.maxHealth ) );
494 Q_strcat( buf, sizeof( buf ), va( "snd\\%s\\", sound ) );
495
496 gi.SetConfigstring( CS_PLAYERS+clientNum, buf );
497 }
498
499
500 /*
501 ===========
502 ClientConnect
503
504 Called when a player begins connecting to the server.
505 Called again for every map change or tournement restart.
506
507 The session information will be valid after exit.
508
509 Return NULL if the client should be allowed, otherwise return
510 a string with the reason for denial.
511
512 Otherwise, the client will be sent the current gamestate
513 and will eventually get to ClientBegin.
514
515 firstTime will be qtrue the very first time a client connects
516 to the server machine, but qfalse on map changes and tournement
517 restarts.
518 ============
519 */
ClientConnect(int clientNum,qboolean firstTime,SavedGameJustLoaded_e eSavedGameJustLoaded)520 char *ClientConnect( int clientNum, qboolean firstTime, SavedGameJustLoaded_e eSavedGameJustLoaded )
521 {
522 gentity_t *ent = &g_entities[ clientNum ];
523 char userinfo[MAX_INFO_STRING] = {0};
524
525 gi.GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );
526
527 // they can connect
528 ent->client = level.clients + clientNum;
529 gclient_t *client = ent->client;
530
531 // if (!qbFromSavedGame)
532 if (eSavedGameJustLoaded != eFULL)
533 {
534 clientSession_t savedSess = client->sess; //
535 memset( client, 0, sizeof(*client) );
536 client->sess = savedSess;
537 if ( firstTime ) { //not loading full, and directconnect
538 client->playerTeam = TEAM_PLAYER; //set these now because after an auto_load kyle can see your team for a bit before you really join.
539 client->enemyTeam = TEAM_ENEMY;
540 }
541 }
542
543 client->pers.connected = CON_CONNECTING;
544
545 if (eSavedGameJustLoaded == eFULL)//qbFromSavedGame)
546 {
547 // G_WriteClientSessionData( client ); // forget it, this is DM stuff anyway
548 // get and distribute relevent paramters
549 ClientUserinfoChanged( clientNum );
550 }
551 else
552 {
553 // read or initialize the session data
554 if ( firstTime ) {
555 G_InitSessionData( client, userinfo );
556 }
557 G_ReadSessionData( client );
558
559 // get and distribute relevent paramters
560 ClientUserinfoChanged( clientNum );
561
562 // don't do the "xxx connected" messages if they were caried over from previous level
563 if ( firstTime ) {
564 gi.SendServerCommand( -1, "print \"%s connected\n\"", client->pers.netname);
565 }
566 }
567
568 return NULL;
569 }
570
571 /*
572 ===========
573 ClientBegin
574
575 called when a client has finished connecting, and is ready
576 to be placed into the level. This will happen every level load,
577 and on transition between teams, but doesn't happen on respawns
578 ============
579 */
ClientBegin(int clientNum,usercmd_t * cmd,SavedGameJustLoaded_e eSavedGameJustLoaded)580 void ClientBegin( int clientNum, usercmd_t *cmd, SavedGameJustLoaded_e eSavedGameJustLoaded)
581 // qboolean qbFromSavedGame
582 {
583 gentity_t *ent;
584 gclient_t *client;
585
586 ent = g_entities + clientNum;
587 client = level.clients + clientNum;
588
589 if (eSavedGameJustLoaded == eFULL)//qbFromSavedGame)
590 {
591 client->pers.connected = CON_CONNECTED;
592 ent->client = client;
593 ClientSpawn( ent, eSavedGameJustLoaded );
594 }
595 else
596 {
597 if ( ent->linked ) {
598 gi.unlinkentity( ent );
599 }
600 G_InitGentity( ent, qfalse );
601 ent->e_TouchFunc = touchF_NULL;
602 ent->e_PainFunc = painF_PlayerPain;//painF_NULL;
603 ent->client = client;
604
605 client->pers.connected = CON_CONNECTED;
606 client->pers.teamState.state = TEAM_BEGIN;
607 VectorCopyM( cmd->angles, client->pers.cmd_angles );
608
609 memset( &client->ps, 0, sizeof( client->ps ) );
610 if( gi.Cvar_VariableIntegerValue( "g_clearstats" ) )
611 {
612 memset( &client->sess.missionStats, 0, sizeof( client->sess.missionStats ) );
613 client->sess.missionStats.totalSecrets = gi.Cvar_VariableIntegerValue("newTotalSecrets");
614 }
615
616 // locate ent at a spawn point
617 if ( ClientSpawn( ent, eSavedGameJustLoaded) ) // SavedGameJustLoaded_e
618 {
619 // send teleport event
620 }
621 client->ps.inventory[INV_GOODIE_KEY] = 0;
622 client->ps.inventory[INV_SECURITY_KEY] = 0;
623 }
624 }
625
626
627
628 /*
629 ============
630 Player_CacheFromPrevLevel
631 Description : just need to grab the weapon items we're going to have when we spawn so they'll be cached
632 Return type : void
633 Argument : void
634 ============
635 */
Player_CacheFromPrevLevel(void)636 void Player_CacheFromPrevLevel(void)
637 {
638 char s[MAX_STRING_CHARS];
639 int i;
640
641 gi.Cvar_VariableStringBuffer( sCVARNAME_PLAYERSAVE, s, sizeof(s) );
642
643 if (s[0]) // actually this would be safe anyway because of the way sscanf() works, but this is clearer
644 {
645 int iDummy, bits, ibits;
646
647 sscanf( s, "%i %i %i %i",
648 &iDummy,//client->ps.stats[STAT_HEALTH],
649 &iDummy,//client->ps.stats[STAT_ARMOR],
650 &bits, //client->ps.stats[STAT_WEAPONS]
651 &ibits //client->ps.stats[STAT_ITEMS]
652 );
653
654 for ( i = 1 ; i < 16 ; i++ )
655 {
656 if ( bits & ( 1 << i ) )
657 {
658 RegisterItem( FindItemForWeapon( (weapon_t)i ) );
659 }
660 }
661
662 extern gitem_t *FindItemForInventory( int inv );
663
664 for ( i = 0 ; i < 16 ; i++ )
665 {
666 if ( ibits & ( 1 << i ) )
667 {
668 RegisterItem( FindItemForInventory( i ));
669 }
670 }
671 }
672 }
673
674 /*
675 ============
676 Player_RestoreFromPrevLevel
677 Description : retrieve maptransition data recorded by server when exiting previous level (to carry over weapons/ammo/health/etc)
678 Return type : void
679 Argument : gentity_t *ent
680 ============
681 */
Player_RestoreFromPrevLevel(gentity_t * ent,SavedGameJustLoaded_e eSavedGameJustLoaded)682 static void Player_RestoreFromPrevLevel(gentity_t *ent, SavedGameJustLoaded_e eSavedGameJustLoaded)
683 {
684 gclient_t *client = ent->client;
685 int i;
686
687 assert(client);
688 if (client) // though I can't see it not being true...
689 {
690 char s[MAX_STRING_CHARS];
691 char saber0Name[MAX_QPATH];
692 char saber1Name[MAX_QPATH];
693 const char *var;
694
695 gi.Cvar_VariableStringBuffer( sCVARNAME_PLAYERSAVE, s, sizeof(s) );
696
697 if (strlen(s)) // actually this would be safe anyway because of the way sscanf() works, but this is clearer
698 {// |general info |-force powers |-saber 1 |-saber 2 |-general saber
699 int saber1BladeActive[8];
700 int saber2BladeActive[8];
701 unsigned int saber1BladeColor[8];
702 unsigned int saber2BladeColor[8];
703
704 sscanf( s, "%i %i %i %i %i %i %i %f %f %f %i %i %i %i %i %s %i %i %i %i %i %i %i %i %u %u %u %u %u %u %u %u %s %i %i %i %i %i %i %i %i %u %u %u %u %u %u %u %u %i %i %i %i",
705 &client->ps.stats[STAT_HEALTH],
706 &client->ps.stats[STAT_ARMOR],
707 &client->ps.stats[STAT_WEAPONS],
708 &client->ps.stats[STAT_ITEMS],
709 &client->ps.weapon,
710 &client->ps.weaponstate,
711 &client->ps.batteryCharge,
712 &client->ps.viewangles[0],
713 &client->ps.viewangles[1],
714 &client->ps.viewangles[2],
715 //force power data
716 &client->ps.forcePowersKnown,
717 &client->ps.forcePower,
718 &client->ps.forcePowerMax,
719 &client->ps.forcePowerRegenRate,
720 &client->ps.forcePowerRegenAmount,
721 //saber 1 data
722 saber0Name,
723 &saber1BladeActive[0],
724 &saber1BladeActive[1],
725 &saber1BladeActive[2],
726 &saber1BladeActive[3],
727 &saber1BladeActive[4],
728 &saber1BladeActive[5],
729 &saber1BladeActive[6],
730 &saber1BladeActive[7],
731 &saber1BladeColor[0],
732 &saber1BladeColor[1],
733 &saber1BladeColor[2],
734 &saber1BladeColor[3],
735 &saber1BladeColor[4],
736 &saber1BladeColor[5],
737 &saber1BladeColor[6],
738 &saber1BladeColor[7],
739 //saber 2 data
740 saber1Name,
741 &saber2BladeActive[0],
742 &saber2BladeActive[1],
743 &saber2BladeActive[2],
744 &saber2BladeActive[3],
745 &saber2BladeActive[4],
746 &saber2BladeActive[5],
747 &saber2BladeActive[6],
748 &saber2BladeActive[7],
749 &saber2BladeColor[0],
750 &saber2BladeColor[1],
751 &saber2BladeColor[2],
752 &saber2BladeColor[3],
753 &saber2BladeColor[4],
754 &saber2BladeColor[5],
755 &saber2BladeColor[6],
756 &saber2BladeColor[7],
757 //general saber data
758 &client->ps.saberStylesKnown,
759 &client->ps.saberAnimLevel,
760 &client->ps.saberLockEnemy,
761 &client->ps.saberLockTime
762 );
763 for (int j = 0; j < 8; j++)
764 {
765 client->ps.saber[0].blade[j].active = saber1BladeActive[j] ? qtrue : qfalse;
766 client->ps.saber[0].blade[j].color = (saber_colors_t)saber1BladeColor[j];
767 client->ps.saber[1].blade[j].active = saber2BladeActive[j] ? qtrue : qfalse;
768 client->ps.saber[1].blade[j].color = (saber_colors_t)saber2BladeColor[j];
769 }
770
771 ent->health = client->ps.stats[STAT_HEALTH];
772
773 if(ent->client->ps.saber[0].name && gi.bIsFromZone(ent->client->ps.saber[0].name, TAG_G_ALLOC)) {
774 gi.Free(ent->client->ps.saber[0].name);
775 }
776 ent->client->ps.saber[0].name=0;
777
778 if(ent->client->ps.saber[1].name && gi.bIsFromZone(ent->client->ps.saber[1].name, TAG_G_ALLOC) ) {
779 gi.Free(ent->client->ps.saber[1].name);
780 }
781 ent->client->ps.saber[1].name=0;
782 //NOTE: if sscanf can get a "(null)" out of strings that had NULL string pointers plugged into the original string
783 if ( saber0Name[0] && Q_stricmp( "(null)", saber0Name ) != 0 )
784 {
785 ent->client->ps.saber[0].name = G_NewString( saber0Name );
786 }
787 if ( saber1Name[0] && Q_stricmp( "(null)", saber1Name ) != 0 )
788 {//have a second saber
789 ent->client->ps.saber[1].name = G_NewString( saber1Name );
790 ent->client->ps.dualSabers = qtrue;
791 }
792 else
793 {//have only 1 saber
794 ent->client->ps.dualSabers = qfalse;
795 }
796
797 // slight issue with ths for the moment in that although it'll correctly restore angles it doesn't take into account
798 // the overall map orientation, so (eg) exiting east to enter south will be out by 90 degrees, best keep spawn angles for now
799 //
800 // VectorClear (ent->client->pers.cmd_angles);
801 //
802 // SetClientViewAngle( ent, ent->client->ps.viewangles);
803
804 //ammo
805 gi.Cvar_VariableStringBuffer( "playerammo", s, sizeof(s) );
806 i=0;
807 var = strtok( s, " " );
808 while( var != NULL )
809 {
810 /* While there are tokens in "s" */
811 client->ps.ammo[i++] = atoi(var);
812 /* Get next token: */
813 var = strtok( NULL, " " );
814 }
815 assert (i==AMMO_MAX);
816
817 //inventory
818 gi.Cvar_VariableStringBuffer( "playerinv", s, sizeof(s) );
819 i=0;
820 var = strtok( s, " " );
821 while( var != NULL )
822 {
823 /* While there are tokens in "s" */
824 client->ps.inventory[i++] = atoi(var);
825 /* Get next token: */
826 var = strtok( NULL, " " );
827 }
828 assert (i==INV_MAX);
829
830
831 // the new JK2 stuff - force powers, etc...
832 //
833 gi.Cvar_VariableStringBuffer( "playerfplvl", s, sizeof(s) );
834 i=0;
835 var = strtok( s, " " );
836 while( var != NULL )
837 {
838 /* While there are tokens in "s" */
839 client->ps.forcePowerLevel[i++] = atoi(var);
840 /* Get next token: */
841 var = strtok( NULL, " " );
842 }
843 assert (i==NUM_FORCE_POWERS);
844
845 client->ps.forceGripEntityNum = client->ps.forceDrainEntityNum = ENTITYNUM_NONE;
846 }
847 }
848 }
849
850 /*
851 Ghoul2 Insert Start
852 */
853
G_SetSkin(gentity_t * ent)854 static void G_SetSkin( gentity_t *ent )
855 {
856 char skinName[MAX_QPATH];
857 //ok, lets register the skin name, and then pass that name to the config strings so the client can get it too.
858 if (Q_stricmp( "hoth2", level.mapname ) == 0 //hack, is this the only map?
859 ||
860 Q_stricmp( "hoth3", level.mapname ) == 0 // no! ;-)
861 )
862 {
863 Com_sprintf( skinName, sizeof( skinName ), "models/players/%s/|%s|%s|%s", g_char_model->string, g_char_skin_head->string, "torso_g1", "lower_e1" );
864 }
865 else if(Q_stricmp(g_char_skin_head->string, "model_default") == 0 && Q_stricmp(g_char_skin_torso->string, "model_default") == 0 && Q_stricmp(g_char_skin_legs->string, "model_default") == 0)
866 {
867 Com_sprintf( skinName, sizeof( skinName ), "models/players/%s/model_default.skin", g_char_model->string );
868 }
869 else
870 {
871 Com_sprintf( skinName, sizeof( skinName ), "models/players/%s/|%s|%s|%s", g_char_model->string, g_char_skin_head->string, g_char_skin_torso->string, g_char_skin_legs->string );
872 }
873
874 // lets see if it's out there
875 int skin = gi.RE_RegisterSkin( skinName );
876 if ( skin )
877 {//what if this returns 0 because *one* part of a multi-skin didn't load?
878 // put it in the config strings
879 // and set the ghoul2 model to use it
880 gi.G2API_SetSkin( &ent->ghoul2[ent->playerModel], G_SkinIndex( skinName ), skin );
881 }
882
883 //color tinting
884 if ( g_char_color_red->integer
885 || g_char_color_green->integer
886 || g_char_color_blue->integer )
887 {
888 ent->client->renderInfo.customRGBA[0] = g_char_color_red->integer;
889 ent->client->renderInfo.customRGBA[1] = g_char_color_green->integer;
890 ent->client->renderInfo.customRGBA[2] = g_char_color_blue->integer;
891 ent->client->renderInfo.customRGBA[3] = 255;
892 }
893 }
894
G_StandardHumanoid(gentity_t * self)895 qboolean G_StandardHumanoid( gentity_t *self )
896 {
897 if ( !self || !self->ghoul2.size() )
898 {
899 return qfalse;
900 }
901 if ( self->playerModel < 0 || self->playerModel >= self->ghoul2.size() )
902 {
903 return qfalse;
904 }
905 const char *GLAName = gi.G2API_GetGLAName( &self->ghoul2[self->playerModel] );
906 assert(GLAName);
907 if (GLAName)
908 {
909 if ( !Q_stricmpn( "models/players/_humanoid", GLAName, 24 ) )///_humanoid", GLAName, 36) )
910 {//only _humanoid skeleton is expected to have these
911 return qtrue;
912 }
913 if ( !Q_stricmp( "models/players/protocol/protocol", GLAName ) )
914 {//protocol droid duplicates many of these
915 return qtrue;
916 }
917 if ( !Q_stricmp( "models/players/assassin_droid/model", GLAName ) )
918 {//assassin_droid duplicates many of these
919 return qtrue;
920 }
921 if ( !Q_stricmp( "models/players/saber_droid/model", GLAName ) )
922 {//saber_droid duplicates many of these
923 return qtrue;
924 }
925 if ( !Q_stricmp( "models/players/hazardtrooper/hazardtrooper", GLAName ) )
926 {//hazardtrooper duplicates many of these
927 return qtrue;
928 }
929 if ( !Q_stricmp( "models/players/rockettrooper/rockettrooper", GLAName ) )
930 {//rockettrooper duplicates many of these
931 return qtrue;
932 }
933 if ( !Q_stricmp( "models/players/wampa/wampa", GLAName ) )
934 {//rockettrooper duplicates many of these
935 return qtrue;
936 }
937 }
938 return qfalse;
939 }
940
G_ClassHasBadBones(int NPC_class)941 qboolean G_ClassHasBadBones( int NPC_class )
942 {
943 switch ( NPC_class )
944 {
945 case CLASS_WAMPA:
946 case CLASS_ROCKETTROOPER:
947 case CLASS_SABER_DROID:
948 case CLASS_HAZARD_TROOPER:
949 case CLASS_ASSASSIN_DROID:
950 case CLASS_RANCOR:
951 return qtrue;
952 }
953 return qfalse;
954 }
955
956 const char *AxesNames[] =
957 {
958 "ORIGIN",//ORIGIN,
959 "POSITIVE_X",//POSITIVE_X,
960 "POSITIVE_Z",//POSITIVE_Z,
961 "POSITIVE_Y",//POSITIVE_Y,
962 "NEGATIVE_X",//NEGATIVE_X,
963 "NEGATIVE_Z",//NEGATIVE_Z,
964 "NEGATIVE_Y"//NEGATIVE_Y
965 };
966
967 Eorientations testAxes[3]={POSITIVE_X,POSITIVE_Z,POSITIVE_Y};
968 int axes_0 = POSITIVE_X;
969 int axes_1 = POSITIVE_Z;
970 int axes_2 = POSITIVE_Y;
G_NextTestAxes(void)971 void G_NextTestAxes( void )
972 {
973 static int whichAxes = 0;
974 int axesCount = 0;
975 do
976 {
977 whichAxes++;
978 if ( whichAxes > 216 )
979 {
980 whichAxes = 0;
981 Com_Printf( S_COLOR_RED"WRAPPED\n" );
982 break;
983 }
984 axesCount = 0;
985 axes_0 = 0;
986 axes_1 = 0;
987 axes_2 = 0;
988 for ( axes_0 = 0; axes_0 < 6 && (axesCount<whichAxes); axes_0++ )
989 {
990 axesCount++;
991 for ( axes_1 = 0; axes_1 < 6 && (axesCount<whichAxes); axes_1++ )
992 {
993 axesCount++;
994 for ( axes_2 = 0; axes_2 < 6 && (axesCount<whichAxes); axes_2++ )
995 {
996 axesCount++;
997 }
998 }
999 }
1000 testAxes[0] = (Eorientations)((axes_0%6)+1);
1001 testAxes[1] = (Eorientations)((axes_1%6)+1);
1002 testAxes[2] = (Eorientations)((axes_2%6)+1);
1003 } while ( testAxes[1] == testAxes[0] || (testAxes[1]-testAxes[0]) == 3 || (testAxes[0]-testAxes[1]) == 3
1004 || testAxes[2] == testAxes[0] || (testAxes[2]-testAxes[0]) == 3 || (testAxes[0]-testAxes[2]) == 3
1005 || testAxes[2] == testAxes[1] || (testAxes[2]-testAxes[1]) == 3 || (testAxes[1]-testAxes[2]) == 3 );
1006
1007 Com_Printf( "Up: %s\nRight: %s\nForward: %s\n", AxesNames[testAxes[0]], AxesNames[testAxes[1]], AxesNames[testAxes[2]] );
1008 if ( testAxes[0] == POSITIVE_X
1009 && testAxes[1] == POSITIVE_Z
1010 && testAxes[2] == POSITIVE_Y )
1011 {
1012 Com_Printf( S_COLOR_RED"WRAPPED\n" );
1013 }
1014 }
1015
G_BoneOrientationsForClass(int NPC_class,const char * boneName,Eorientations * oUp,Eorientations * oRt,Eorientations * oFwd)1016 void G_BoneOrientationsForClass( int NPC_class, const char *boneName, Eorientations *oUp, Eorientations *oRt, Eorientations *oFwd )
1017 {
1018 //defaults
1019 *oUp = POSITIVE_X;
1020 *oRt = NEGATIVE_Y;
1021 *oFwd = NEGATIVE_Z;
1022 //switch off class
1023 switch ( NPC_class )
1024 {
1025 case CLASS_RANCOR:
1026 *oUp = NEGATIVE_X;
1027 *oRt = POSITIVE_Y;
1028 *oFwd = POSITIVE_Z;
1029 //*oUp = testAxes[0];
1030 //*oRt = testAxes[1];
1031 //*oFwd = testAxes[2];
1032 break;
1033 case CLASS_ROCKETTROOPER:
1034 case CLASS_HAZARD_TROOPER:
1035 //Root is:
1036 //*oUp = POSITIVE_Z;
1037 //*oRt = NEGATIVE_X;
1038 //*oFwd = NEGATIVE_Y;
1039 if ( Q_stricmp( "pelvis", boneName ) == 0 )
1040 {//child of root
1041 //in ModView:
1042 //*oUp = NEGATIVE_X;
1043 //*oRt = NEGATIVE_Z;
1044 //*oFwd = NEGATIVE_Y;
1045 //actual, when differences with root are accounted for:
1046 *oUp = POSITIVE_Z;
1047 *oRt = NEGATIVE_X;
1048 *oFwd = NEGATIVE_Y;
1049 }
1050 else
1051 {//all the rest are the same, children of root (not pelvis)
1052 //in ModView:
1053 //*oUp = POSITIVE_X;
1054 //*oRt = POSITIVE_Y;
1055 //*oFwd = POSITIVE_Z;
1056 //actual, when differences with root are accounted for:
1057 //*oUp = POSITIVE_Z;
1058 //*oRt = NEGATIVE_Y;
1059 //*oFwd = NEGATIVE_X;
1060 *oUp = NEGATIVE_X;
1061 *oRt = POSITIVE_Y;
1062 *oFwd = POSITIVE_Z;
1063 }
1064 break;
1065 case CLASS_SABER_DROID:
1066 if ( Q_stricmp( "pelvis", boneName ) == 0
1067 || Q_stricmp( "thoracic", boneName ) == 0 )
1068 {
1069 *oUp = NEGATIVE_X;
1070 *oRt = NEGATIVE_Z;
1071 *oFwd = NEGATIVE_Y;
1072 }
1073 else
1074 {
1075 *oUp = NEGATIVE_X;//POSITIVE_X;
1076 *oRt = POSITIVE_Y;
1077 *oFwd = POSITIVE_Z;
1078 }
1079 break;
1080 case CLASS_WAMPA:
1081 if ( Q_stricmp( "pelvis", boneName ) == 0 )
1082 {
1083 *oUp = NEGATIVE_X;
1084 *oRt = POSITIVE_Y;
1085 *oFwd = NEGATIVE_Z;
1086 }
1087 else
1088 {
1089 //*oUp = POSITIVE_X;
1090 //*oRt = POSITIVE_Y;
1091 //*oFwd = POSITIVE_Z;
1092 //kinda worked
1093 *oUp = NEGATIVE_X;
1094 *oRt = POSITIVE_Y;
1095 *oFwd = POSITIVE_Z;
1096 }
1097 break;
1098 case CLASS_ASSASSIN_DROID:
1099 if ( Q_stricmp( "pelvis", boneName ) == 0
1100 || Q_stricmp( "lower_lumbar", boneName ) == 0
1101 || Q_stricmp( "upper_lumbar", boneName ) == 0 )
1102 {//only these 3 bones on them are wrong
1103 //*oUp = POSITIVE_X;
1104 //*oRt = POSITIVE_Y;
1105 //*oFwd = POSITIVE_Z;
1106 *oUp = NEGATIVE_X;
1107 *oRt = POSITIVE_Y;
1108 *oFwd = POSITIVE_Z;
1109 }
1110 break;
1111 }
1112 }
1113
1114 extern void G_LoadAnimFileSet( gentity_t *ent, const char *modelName );
G_SetG2PlayerModelInfo(gentity_t * ent,const char * modelName,const char * customSkin,const char * surfOff,const char * surfOn)1115 qboolean G_SetG2PlayerModelInfo( gentity_t *ent, const char *modelName, const char *customSkin, const char *surfOff, const char *surfOn )
1116 {
1117 if ( ent->playerModel != -1 )
1118 {// we found the model ok
1119 vec3_t angles = {0,0,0};
1120 const char *token;
1121 const char *p;
1122
1123 //Now turn on/off any surfaces
1124 if ( surfOff && surfOff[0] )
1125 {
1126 p = surfOff;
1127 COM_BeginParseSession();
1128 while ( 1 )
1129 {
1130 token = COM_ParseExt( &p, qtrue );
1131 if ( !token[0] )
1132 {//reached end of list
1133 break;
1134 }
1135 //turn off this surf
1136 gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], token, 0x00000002/*G2SURFACEFLAG_OFF*/ );
1137
1138 }
1139 COM_EndParseSession();
1140 }
1141 if ( surfOn && surfOn[0] )
1142 {
1143 p = surfOn;
1144 COM_BeginParseSession();
1145 while ( 1 )
1146 {
1147 token = COM_ParseExt( &p, qtrue );
1148 if ( !token[0] )
1149 {//reached end of list
1150 break;
1151 }
1152 //turn on this surf
1153 gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], token, 0 );
1154 }
1155 COM_EndParseSession();
1156 }
1157 if ( ent->client->NPC_class == CLASS_IMPERIAL && ent->message )
1158 {//carrying a key, turn on the key sleeve surface (assuming we have one)
1159 gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], "l_arm_key", 0 );
1160 }
1161
1162 G_LoadAnimFileSet( ent, modelName );
1163
1164 ent->headBolt = ent->cervicalBolt = ent->torsoBolt = ent->gutBolt = ent->chestBolt =
1165 ent->crotchBolt = ent->elbowLBolt = ent->elbowRBolt = ent->handLBolt =
1166 ent->handRBolt = ent->kneeLBolt = ent->kneeRBolt = ent->footLBolt =
1167 ent->footRBolt = -1;
1168 // now turn on the bolt in the hand - this one would be best always turned on.
1169 if ( G_StandardHumanoid( ent ) )
1170 {//only _humanoid skeleton is expected to have these
1171 ent->headBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*head_eyes");
1172 ent->cervicalBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "cervical" );
1173 if ( !Q_stricmp("protocol", modelName ) )
1174 {//*sigh*, no thoracic bone
1175 ent->gutBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "upper_lumbar");
1176 ent->chestBolt = ent->gutBolt;
1177 }
1178 else
1179 {
1180 ent->chestBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "thoracic");
1181 ent->gutBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "upper_lumbar");
1182 }
1183 ent->torsoBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "lower_lumbar");
1184 ent->crotchBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "pelvis");
1185 ent->elbowLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*l_arm_elbow");
1186 ent->elbowRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*r_arm_elbow");
1187 ent->handLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*l_hand");
1188 ent->handRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*r_hand");
1189 ent->kneeLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*hips_l_knee");
1190 ent->kneeRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*hips_r_knee");
1191 ent->footLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*l_leg_foot");
1192 ent->footRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*r_leg_foot");
1193 if ( ent->client->NPC_class == CLASS_BOBAFETT
1194 || ent->client->NPC_class == CLASS_ROCKETTROOPER )
1195 {//get jet bolts
1196 ent->genericBolt1 = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], "*jet1" );
1197 ent->genericBolt2 = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], "*jet2" );
1198 }
1199 if ( ent->client->NPC_class == CLASS_BOBAFETT )
1200 {//get the flamethrower bolt
1201 ent->genericBolt3 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flamethrower");
1202 }
1203 }
1204 else
1205 {
1206 if ( ent->client->NPC_class == CLASS_VEHICLE )
1207 {//do vehicles tags
1208
1209 // Setup the driver tag (where the driver is mounted to).
1210 ent->crotchBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*driver");
1211
1212 // Setup the droid unit (or other misc tag we're using this for).
1213 ent->m_pVehicle->m_iDroidUnitTag = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*droidunit");
1214
1215 char strTemp[128];
1216
1217 // Setup the Exhausts.
1218 for ( int i = 0; i < MAX_VEHICLE_EXHAUSTS; i++ )
1219 {
1220 Com_sprintf( strTemp, 128, "*exhaust%d", i + 1 );
1221 ent->m_pVehicle->m_iExhaustTag[i] = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], strTemp );
1222 }
1223
1224 // Setup the Muzzles.
1225 for ( int i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
1226 {
1227 Com_sprintf( strTemp, 128, "*muzzle%d", i + 1 );
1228 ent->m_pVehicle->m_iMuzzleTag[i] = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], strTemp );
1229 if ( ent->m_pVehicle->m_iMuzzleTag[i] == -1 )
1230 {//ergh, try *flash?
1231 Com_sprintf( strTemp, 128, "*flash%d", i + 1 );
1232 ent->m_pVehicle->m_iMuzzleTag[i] = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], strTemp );
1233 }
1234 }
1235 }
1236 else if ( ent->client->NPC_class == CLASS_HOWLER )
1237 {
1238 ent->genericBolt1 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "Tongue01" );// tongue base
1239 ent->genericBolt2 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "Tongue08" );// tongue tip
1240 }
1241 else if ( !Q_stricmp( "gonk", modelName ) || !Q_stricmp( "seeker", modelName ) || !Q_stricmp( "remote", modelName )
1242 || !Q_stricmpn( "r2d2", modelName, 4 ) || !Q_stricmpn( "r5d2", modelName, 4 ) )
1243 {//TEMP HACK: not a non-humanoid droid
1244 ent->headBolt = -1;
1245 }
1246 else if (!Q_stricmp( "interrogator",modelName))
1247 {
1248 ent->headBolt = -1;
1249 }
1250 else if (!Q_stricmpn( "probe",modelName, 5))
1251 {
1252 ent->headBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "cranium"); // head pivot point
1253 ent->genericBolt1 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash"); // Gun 1
1254 }
1255 else if (!Q_stricmp( "sentry",modelName))
1256 {
1257 ent->headBolt = -1;
1258 ent->genericBolt1 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash1"); // Gun 1
1259 ent->genericBolt2 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash2"); // Gun 2
1260 ent->genericBolt3 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash03"); // Gun 3
1261 }
1262 else if (!Q_stricmp( "mark1",modelName))
1263 {
1264 ent->headBolt = -1;
1265 ent->handRBolt = ent->genericBolt1 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash1"); // Blaster Gun 1
1266 ent->genericBolt2 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash2"); // Blaster Gun 2
1267 ent->genericBolt3 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash3"); // Blaster Gun 3
1268 ent->genericBolt4 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash4"); // Blaster Gun 4
1269 ent->genericBolt5 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash5"); // Missile Gun 1
1270 }
1271 else if (!Q_stricmp( "mark2",modelName))
1272 {
1273 ent->headBolt = -1;
1274 ent->handRBolt = ent->genericBolt1 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash"); // Blaster Gun 1
1275 }
1276 else if (!Q_stricmp( "atst",modelName) )//&& (ent->client->playerTeam != TEAM_PLAYER))
1277 {
1278 ent->headBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*head");
1279
1280 ent->handLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash1"); // Front guns
1281 ent->handRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash2");
1282
1283 ent->genericBolt1 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash3"); // Left side gun
1284 ent->genericBolt2 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash4"); // Right side missle launcher
1285
1286 ent->footLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*l_foot");
1287 ent->footRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*r_foot");
1288 }
1289 else if ( !Q_stricmp( "minemonster", modelName ))
1290 {
1291 ent->handRBolt = ent->headBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*head_f1");
1292 }
1293 else if ( !Q_stricmp( "rancor", modelName )
1294 || !Q_stricmp( "mutant_rancor", modelName ))
1295 {
1296 ent->handLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*l_hand");
1297 ent->handRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*r_hand");
1298 ent->headBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*head_eyes");
1299 ent->gutBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*head_mouth");
1300 }
1301 else if ( !Q_stricmp( "sand_creature", modelName ))
1302 {
1303 ent->gutBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*mouth");
1304 ent->crotchBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*ground");
1305 }
1306 else if ( !Q_stricmp( "wampa", modelName ))
1307 {
1308 ent->headBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*head_eyes");
1309 ent->cervicalBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "neck_bone" );
1310 ent->chestBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "upper_spine");
1311 ent->gutBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "mid_spine");
1312 ent->torsoBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "lower_spine");
1313 ent->crotchBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "rear_bone");
1314 ent->elbowLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*l_arm_elbow");
1315 ent->elbowRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*r_arm_elbow");
1316 ent->handLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*l_hand");
1317 ent->handRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*r_hand");
1318 ent->kneeLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*hips_l_knee");
1319 ent->kneeRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*hips_r_knee");
1320 ent->footLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*l_leg_foot");
1321 ent->footRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*r_leg_foot");
1322 }
1323 else
1324 {//TEMP HACK: not a non-humanoid droid
1325 ent->handRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*weapon");//should be r_hand
1326 if ( Q_stricmp( "atst", modelName ) )
1327 {//not an ATST
1328 ent->headBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*headg");
1329 ent->cervicalBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "cervical" );
1330 ent->torsoBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "lower_lumbar");
1331 ent->gutBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "upper_lumbar");
1332 ent->chestBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "thoracic");
1333 ent->crotchBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "pelvis");
1334 ent->elbowLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*bicep_lg");
1335 ent->elbowRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*bicep_rg");
1336 ent->handLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*hand_l");
1337 ent->kneeLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*thigh_lg");
1338 ent->kneeRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*thigh_rg");
1339 ent->footLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*foot_lg");
1340 ent->footRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*foot_rg");
1341 }
1342 }
1343 }
1344
1345 ent->faceBone = BONE_INDEX_INVALID;
1346 ent->craniumBone = BONE_INDEX_INVALID;
1347 ent->cervicalBone = BONE_INDEX_INVALID;
1348 ent->thoracicBone = BONE_INDEX_INVALID;
1349 ent->upperLumbarBone = BONE_INDEX_INVALID;
1350 ent->lowerLumbarBone = BONE_INDEX_INVALID;
1351 ent->motionBone = BONE_INDEX_INVALID;
1352 ent->hipsBone = BONE_INDEX_INVALID;
1353 ent->rootBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "model_root", qtrue );
1354 #ifndef FINAL_BUILD
1355 if ( g_developer->integer && ent->rootBone == -1 )
1356 {
1357 Com_Error(ERR_DROP,"ERROR: model %s has no model_root bone (and hence cannot animate)!!!\n", modelName );
1358 }
1359 #endif
1360 ent->footLBone = BONE_INDEX_INVALID;
1361 ent->footRBone = BONE_INDEX_INVALID;
1362 ent->humerusRBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "rhumerus", qtrue );
1363
1364 // now add overrides on specific joints so the client can set angle overrides on the legs, torso and head
1365 if ( ent->client->NPC_class == CLASS_VEHICLE )
1366 {//do vehicles tags
1367 //vehicleInfo_t *vehicle = ent->m_pVehicle->m_pVehicleInfo;
1368 }
1369 else if ( ent->client->NPC_class == CLASS_HOWLER )
1370 {
1371 }
1372 else if ( !Q_stricmp( "gonk", modelName ) || !Q_stricmp( "seeker", modelName ) || !Q_stricmp( "remote", modelName ) )
1373 {//
1374 }
1375 else if (!Q_stricmp( "sentry",modelName))
1376 {
1377 }
1378 else if (!Q_stricmpn( "probe", modelName, 5 ))
1379 {
1380 ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue );
1381 if (ent->craniumBone>=0)
1382 {
1383 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1384 }
1385 ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "pelvis", qtrue );
1386 if (ent->thoracicBone>=0)
1387 {
1388 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1389 }
1390 }
1391 else if (!Q_stricmp( "interrogator", modelName ))
1392 {
1393 ent->genericBone1 = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "left_arm", qtrue );
1394 if (ent->genericBone1>=0)
1395 {
1396 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->genericBone1, angles, BONE_ANGLES_POSTMULT, NEGATIVE_Y, NEGATIVE_X, NEGATIVE_Z, NULL, 0, 0 );
1397 }
1398 ent->genericBone2 = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "right_arm", qtrue );
1399 if (ent->genericBone2>=0)
1400 {
1401 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->genericBone2, angles, BONE_ANGLES_POSTMULT, NEGATIVE_Y, NEGATIVE_X, NEGATIVE_Z, NULL, 0, 0 );
1402 }
1403 ent->genericBone3 = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "claw", qtrue );
1404 if (ent->genericBone3>=0)
1405 {
1406 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->genericBone3, angles, BONE_ANGLES_POSTMULT, NEGATIVE_Y, NEGATIVE_X, NEGATIVE_Z, NULL, 0, 0 );
1407 }
1408 }
1409 else if (!Q_stricmpn( "r2d2", modelName, 4 ))
1410 {
1411 ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue );
1412 if (ent->craniumBone>=0)
1413 {
1414 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1415 }
1416 ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "body", qtrue );
1417 if (ent->thoracicBone>=0)
1418 {
1419 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1420 }
1421 ent->genericBone1 = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "f_eye", qtrue );
1422 if (ent->genericBone1>=0)
1423 {
1424 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->genericBone1, angles, BONE_ANGLES_POSTMULT, NEGATIVE_Y, NEGATIVE_X, NEGATIVE_Z, NULL, 0, 0 );
1425 }
1426 }
1427 else if (!Q_stricmpn( "r5d2", modelName, 4 ))
1428 {
1429 ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue );
1430 if (ent->craniumBone>=0)
1431 {
1432 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1433 }
1434 ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "body", qtrue );
1435 if (ent->thoracicBone>=0)
1436 {
1437 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1438 }
1439 }
1440 else if ( !Q_stricmp( "atst", modelName ))
1441 {
1442 ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue );
1443 if (ent->craniumBone>=0)
1444 {
1445 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1446 }
1447 ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "thoracic", qtrue );
1448 if (ent->thoracicBone>=0)
1449 {
1450 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1451 }
1452 ent->footLBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "l_tarsal", qtrue );
1453 if (ent->footLBone>=0)
1454 {
1455 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->footLBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, NEGATIVE_X, NULL, 0, 0 );
1456 }
1457 ent->footRBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "r_tarsal", qtrue );
1458 if (ent->footRBone>=0)
1459 {
1460 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->footRBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, NEGATIVE_X, NULL, 0, 0 );
1461 }
1462 }
1463 else if ( !Q_stricmp( "mark1", modelName ))
1464 {
1465 ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue );
1466 if (ent->craniumBone>=0)
1467 {
1468 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1469 }
1470 ent->upperLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue );
1471 if (ent->upperLumbarBone>=0)
1472 {
1473 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->upperLumbarBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1474 }
1475 }
1476 else if ( !Q_stricmp( "mark2", modelName ))
1477 {
1478 ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue );
1479 if (ent->craniumBone>=0)
1480 {
1481 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1482 }
1483 ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "thoracic", qtrue );
1484 if (ent->thoracicBone>=0)
1485 {
1486 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1487 }
1488 }
1489 else if ( !Q_stricmp( "minemonster", modelName ))
1490 {
1491 ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "thoracic1", qtrue );
1492 if (ent->thoracicBone>=0)
1493 {
1494 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1495 }
1496 ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue );
1497 if (ent->craniumBone>=0)
1498 {
1499 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1500 }
1501 }
1502 else if ( ent->client->NPC_class == CLASS_RANCOR )
1503 /*!Q_stricmp( "rancor", modelName ) || !Q_stricmp( "mutant_rancor", modelName ) )*/
1504 {
1505 Eorientations oUp, oRt, oFwd;
1506 //regular bones we need
1507 ent->lowerLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "lower_spine", qtrue );
1508 if (ent->lowerLumbarBone>=0)
1509 {
1510 G_BoneOrientationsForClass( ent->client->NPC_class, "lower_lumbar", &oUp, &oRt, &oFwd );
1511 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->lowerLumbarBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL, 0, 0 );
1512 }
1513 ent->upperLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "mid_spine", qtrue );
1514 if (ent->upperLumbarBone>=0)
1515 {
1516 G_BoneOrientationsForClass( ent->client->NPC_class, "upper_lumbar", &oUp, &oRt, &oFwd );
1517 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->upperLumbarBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL, 0, 0 );
1518 }
1519 ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "upper_spine", qtrue );
1520 if (ent->thoracicBone>=0)
1521 {
1522 G_BoneOrientationsForClass( ent->client->NPC_class, "thoracic", &oUp, &oRt, &oFwd );
1523 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL, 0, 0 );
1524 }
1525 }
1526 else if ( !Q_stricmp( "sand_creature", modelName ))
1527 {
1528 }
1529 else if ( !Q_stricmp( "wampa", modelName ) )
1530 {
1531 //Eorientations oUp, oRt, oFwd;
1532 //bone needed for turning anims
1533 /*
1534 //SIGH... fucks him up BAD
1535 ent->hipsBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "pelvis", qtrue );
1536 if (ent->hipsBone>=0)
1537 {
1538 G_BoneOrientationsForClass( ent->client->NPC_class, "pelvis", &oUp, &oRt, &oFwd );
1539 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->hipsBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL );
1540 }
1541 */
1542 /*
1543 //SIGH... no anim split
1544 ent->lowerLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "lower_lumbar", qtrue );
1545 if (ent->lowerLumbarBone>=0)
1546 {
1547 G_BoneOrientationsForClass( ent->client->NPC_class, "lower_lumbar", &oUp, &oRt, &oFwd );
1548 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->lowerLumbarBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL );
1549 }
1550 */
1551 /*
1552 //SIGH... spine wiggles fuck all this shit
1553 ent->upperLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "upper_lumbar", qtrue );
1554 if (ent->upperLumbarBone>=0)
1555 {
1556 G_BoneOrientationsForClass( ent->client->NPC_class, "upper_lumbar", &oUp, &oRt, &oFwd );
1557 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->upperLumbarBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL );
1558 }
1559 ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "thoracic", qtrue );
1560 if (ent->thoracicBone>=0)
1561 {
1562 G_BoneOrientationsForClass( ent->client->NPC_class, "thoracic", &oUp, &oRt, &oFwd );
1563 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL );
1564 }
1565 ent->cervicalBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cervical", qtrue );
1566 if (ent->cervicalBone>=0)
1567 {
1568 G_BoneOrientationsForClass( ent->client->NPC_class, "cervical", &oUp, &oRt, &oFwd );
1569 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->cervicalBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL );
1570 }
1571 ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue );
1572 if (ent->craniumBone>=0)
1573 {
1574 G_BoneOrientationsForClass( ent->client->NPC_class, "cranium", &oUp, &oRt, &oFwd );
1575 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL );
1576 }
1577 */
1578 }
1579 else if ( !Q_stricmp( "rockettrooper", modelName )
1580 || !Q_stricmp( "hazardtrooper", modelName )
1581 || !Q_stricmp( "saber_droid", modelName )
1582 || !Q_stricmp( "assassin_droid", modelName ) )
1583 {
1584 Eorientations oUp, oRt, oFwd;
1585 if ( Q_stricmp( "saber_droid", modelName ) )
1586 {//saber droid doesn't use these lower bones
1587 //regular bones we need
1588 ent->upperLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "upper_lumbar", qtrue );
1589 if (ent->upperLumbarBone>=0)
1590 {
1591 G_BoneOrientationsForClass( ent->client->NPC_class, "upper_lumbar", &oUp, &oRt, &oFwd );
1592 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->upperLumbarBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL, 0, 0 );
1593 }
1594 ent->lowerLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "lower_lumbar", qtrue );
1595 if (ent->lowerLumbarBone>=0)
1596 {
1597 G_BoneOrientationsForClass( ent->client->NPC_class, "lower_lumbar", &oUp, &oRt, &oFwd );
1598 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->lowerLumbarBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL, 0, 0 );
1599 }
1600 }
1601 if ( Q_stricmp( "hazardtrooper", modelName ) )
1602 {//hazard trooper doesn't have these upper bones
1603 if ( Q_stricmp( "saber_droid", modelName ) )
1604 {//saber droid doesn't use thoracic bone
1605 ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "thoracic", qtrue );
1606 if (ent->thoracicBone>=0)
1607 {
1608 G_BoneOrientationsForClass( ent->client->NPC_class, "thoracic", &oUp, &oRt, &oFwd );
1609 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL, 0, 0 );
1610 }
1611 }
1612 ent->cervicalBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cervical", qtrue );
1613 if (ent->cervicalBone>=0)
1614 {
1615 G_BoneOrientationsForClass( ent->client->NPC_class, "cervical", &oUp, &oRt, &oFwd );
1616 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->cervicalBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL, 0, 0 );
1617 }
1618 ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue );
1619 if (ent->craniumBone>=0)
1620 {
1621 G_BoneOrientationsForClass( ent->client->NPC_class, "cranium", &oUp, &oRt, &oFwd );
1622 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL, 0, 0 );
1623 }
1624 }
1625 }
1626 else
1627 {
1628 //special case motion bone - to match up split anims
1629 ent->motionBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "Motion", qtrue );
1630 if (ent->motionBone>=0)
1631 {
1632 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->motionBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_X, NEGATIVE_Y, NULL, 0, 0 );
1633 }
1634 ent->motionBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "Motion");
1635 //bone needed for turning anims
1636 ent->hipsBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "pelvis", qtrue );
1637 if (ent->hipsBone>=0)
1638 {
1639 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->hipsBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1640 }
1641 //regular bones we need
1642 ent->upperLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "upper_lumbar", qtrue );
1643 if (ent->upperLumbarBone>=0)
1644 {
1645 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->upperLumbarBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1646 }
1647 ent->lowerLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "lower_lumbar", qtrue );
1648 if (ent->lowerLumbarBone>=0)
1649 {
1650 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->lowerLumbarBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1651 }
1652
1653 ent->faceBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "face", qtrue );
1654 if (ent->faceBone>=0)
1655 {
1656 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->faceBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1657 }
1658 ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue );
1659 if (ent->craniumBone>=0)
1660 {
1661 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1662 }
1663 ent->cervicalBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cervical", qtrue );
1664 if (ent->cervicalBone>=0)
1665 {
1666 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->cervicalBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1667 }
1668 ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "thoracic", qtrue );
1669 if (ent->thoracicBone>=0)
1670 {
1671 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1672 }
1673 }
1674 ent->client->clientInfo.infoValid = qtrue;
1675
1676 }
1677
1678 if ( ent->client->NPC_class == CLASS_SAND_CREATURE )
1679 {
1680 ent->s.radius = 256;
1681 }
1682 else if ( ent->client->NPC_class == CLASS_RANCOR )
1683 {
1684 if ( (ent->spawnflags&1) )
1685 {//mutant
1686 ent->s.radius = 300;
1687 }
1688 else
1689 {
1690 ent->s.radius = 150;
1691 }
1692 }
1693 else if ( ent->s.radius <= 0 )//radius cannot be negative or zero
1694 {//set the radius to be the largest axial distance on the entity
1695 float max;
1696 max = ent->mins[0];//NOTE: mins is always negative
1697 if ( max > ent->mins[1] )
1698 {
1699 max = ent->mins[1];
1700 }
1701
1702 if ( max > ent->mins[2] )
1703 {
1704 max = ent->mins[2];
1705 }
1706
1707 max = fabs(max);//convert to positive to compare with maxs
1708 if ( max < ent->maxs[0] )
1709 {
1710 max = ent->maxs[0];
1711 }
1712
1713 if ( max < ent->maxs[1] )
1714 {
1715 max = ent->maxs[1];
1716 }
1717
1718 if ( max < ent->maxs[2] )
1719 {
1720 max = ent->maxs[2];
1721 }
1722
1723 ent->s.radius = (int)max;
1724
1725 if (!ent->s.radius) // Still no radius?
1726 {
1727 ent->s.radius = 60;
1728 }
1729 }
1730
1731 // set the weaponmodel to -1 so we don't try to remove it in Pmove before we have it built
1732 ent->weaponModel[0] = -1;
1733
1734 if ( ent->playerModel == -1 )
1735 {
1736 return qfalse;
1737 }
1738 return qtrue;
1739 }
1740
G_SetG2PlayerModel(gentity_t * const ent,const char * modelName,const char * customSkin,const char * surfOff,const char * surfOn)1741 void G_SetG2PlayerModel( gentity_t * const ent, const char *modelName, const char *customSkin, const char *surfOff, const char *surfOn )
1742 {
1743 char skinName[MAX_QPATH];
1744
1745 //ok, lets register the skin name, and then pass that name to the config strings so the client can get it too.
1746 if ( !customSkin )
1747 {//use the default
1748 Com_sprintf( skinName, sizeof( skinName ), "models/players/%s/model_default.skin", modelName );
1749 }
1750 else
1751 {
1752 if (strchr(customSkin, '|'))
1753 {//three part skin
1754 Com_sprintf( skinName, sizeof( skinName ), "models/players/%s/|%s", modelName, customSkin );
1755 }
1756 else
1757 {
1758 Com_sprintf( skinName, sizeof( skinName ), "models/players/%s/model_%s.skin", modelName, customSkin );
1759 }
1760 }
1761 int skin = gi.RE_RegisterSkin( skinName );
1762 //now generate the ghoul2 model this client should be.
1763 if ( ent->client->NPC_class == CLASS_VEHICLE )
1764 {//vehicles actually grab their model from the appropriate vehicle data entry
1765
1766 // This will register the model and other assets.
1767 Vehicle_t *pVeh = ent->m_pVehicle;
1768 pVeh->m_pVehicleInfo->RegisterAssets( pVeh );
1769 ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, va("models/players/%s/model.glm", modelName), pVeh->m_pVehicleInfo->modelIndex, G_SkinIndex( skinName ), NULL_HANDLE, 0, 0 );
1770 }
1771 else
1772 {
1773 //NOTE: it still loads the default skin's tga's because they're referenced in the .glm.
1774 ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, va("models/players/%s/model.glm", modelName), G_ModelIndex( va("models/players/%s/model.glm", modelName) ), G_SkinIndex( skinName ), NULL_HANDLE, 0, 0 );
1775 }
1776 if (ent->playerModel == -1)
1777 {//try the stormtrooper as a default
1778 gi.Printf( S_COLOR_RED"G_SetG2PlayerModel: cannot load model %s\n", modelName );
1779 modelName = "stormtrooper";
1780 Com_sprintf( skinName, sizeof( skinName ), "models/players/%s/model_default.skin", modelName );
1781 skin = gi.RE_RegisterSkin( skinName );
1782 ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, va("models/players/%s/model.glm", modelName), G_ModelIndex( va("models/players/%s/model.glm", modelName) ), NULL_HANDLE, NULL_HANDLE, 0, 0 );
1783 }
1784 if (ent->playerModel == -1)
1785 {//very bad thing here!
1786 Com_Error(ERR_DROP, "Cannot fall back to default model %s!", modelName);
1787 }
1788
1789 gi.G2API_SetSkin( &ent->ghoul2[ent->playerModel], G_SkinIndex( skinName ), skin );//this is going to set the surfs on/off matching the skin file
1790
1791 // did we find a ghoul2 model? if so, load the animation.cfg file
1792 if ( !G_SetG2PlayerModelInfo( ent, modelName, customSkin, surfOff, surfOn ) )
1793 {//couldn't set g2 info, fall back to a mouse md3
1794 NPC_ParseParms( "mouse", ent );
1795 Com_Printf( S_COLOR_RED"couldn't load playerModel %s!\n", va("models/players/%s/model.glm", modelName) );
1796 }
1797 }
1798 /*
1799 Ghoul2 Insert End
1800 */
1801
G_RemovePlayerModel(gentity_t * ent)1802 void G_RemovePlayerModel( gentity_t *ent )
1803 {
1804 if ( ent->playerModel >= 0 && ent->ghoul2.size() )
1805 {
1806 gi.G2API_RemoveGhoul2Model( ent->ghoul2, ent->playerModel );
1807 ent->playerModel = -1;
1808 }
1809 }
1810
G_RemoveWeaponModels(gentity_t * ent)1811 void G_RemoveWeaponModels( gentity_t *ent )
1812 {
1813 if ( ent->ghoul2.size() )
1814 {
1815 if ( ent->weaponModel[0] > 0 )
1816 {
1817 gi.G2API_RemoveGhoul2Model( ent->ghoul2, ent->weaponModel[0] );
1818 ent->weaponModel[0] = -1;
1819 }
1820 if ( ent->weaponModel[1] > 0 )
1821 {
1822 gi.G2API_RemoveGhoul2Model( ent->ghoul2, ent->weaponModel[1] );
1823 ent->weaponModel[1] = -1;
1824 }
1825 }
1826 }
1827
G_AddWeaponModels(gentity_t * ent)1828 void G_AddWeaponModels( gentity_t *ent )
1829 {
1830 if ( !ent || !ent->client )
1831 {
1832 return;
1833 }
1834 if ( ent->weaponModel[0] == -1 )
1835 {
1836 if ( ent->client->ps.weapon == WP_SABER )
1837 {
1838 WP_SaberAddG2SaberModels( ent );
1839 }
1840 else if ( ent->client->ps.weapon != WP_NONE )
1841 {
1842 G_CreateG2AttachedWeaponModel( ent, weaponData[ent->client->ps.weapon].weaponMdl, ent->handRBolt, 0 );
1843 }
1844 }
1845 }
1846
1847 extern saber_colors_t TranslateSaberColor( const char *name );
1848 extern void WP_RemoveSaber( gentity_t *ent, int saberNum );
1849 void G_ChangePlayerModel( gentity_t *ent, const char *newModel );
G_SetSabersFromCVars(gentity_t * ent)1850 void G_SetSabersFromCVars( gentity_t *ent )
1851 {
1852 if ( g_saber->string
1853 && g_saber->string[0]
1854 && Q_stricmp( "none", g_saber->string )
1855 && Q_stricmp( "NULL", g_saber->string ) )
1856 {//FIXME: how to specify second saber?
1857 WP_SaberParseParms( g_saber->string, &ent->client->ps.saber[0] );
1858 if ( ent->client->ps.saber[0].stylesLearned )
1859 {
1860 ent->client->ps.saberStylesKnown |= ent->client->ps.saber[0].stylesLearned;
1861 }
1862 if ( ent->client->ps.saber[0].singleBladeStyle )
1863 {
1864 ent->client->ps.saberStylesKnown |= ent->client->ps.saber[0].singleBladeStyle;
1865 }
1866 }
1867
1868 if ( player
1869 && player->client
1870 && player->client->sess.mission_objectives[LIGHTSIDE_OBJ].status == 2
1871 && g_saberDarkSideSaberColor->integer )
1872 {//dark side!
1873 //always use red
1874 for ( int n = 0; n < MAX_BLADES; n++ )
1875 {
1876 ent->client->ps.saber[0].blade[n].color = SABER_RED;
1877 }
1878 }
1879 else if ( g_saber_color->string )
1880 {//FIXME: how to specify color for each blade and/or color for second saber?
1881 saber_colors_t color = TranslateSaberColor( g_saber_color->string );
1882 for ( int n = 0; n < MAX_BLADES; n++ )
1883 {
1884 ent->client->ps.saber[0].blade[n].color = color;
1885 }
1886 }
1887 if ( g_saber2->string
1888 && g_saber2->string[0]
1889 && Q_stricmp( "none", g_saber2->string )
1890 && Q_stricmp( "NULL", g_saber2->string ) )
1891 {
1892 if ( !(ent->client->ps.saber[0].saberFlags&SFL_TWO_HANDED) )
1893 {//can't use a second saber if first one is a two-handed saber...?
1894 WP_SaberParseParms( g_saber2->string, &ent->client->ps.saber[1] );
1895 if ( ent->client->ps.saber[1].stylesLearned )
1896 {
1897 ent->client->ps.saberStylesKnown |= ent->client->ps.saber[1].stylesLearned;
1898 }
1899 if ( ent->client->ps.saber[1].singleBladeStyle )
1900 {
1901 ent->client->ps.saberStylesKnown |= ent->client->ps.saber[1].singleBladeStyle;
1902 }
1903 if ( (ent->client->ps.saber[1].saberFlags&SFL_TWO_HANDED) )
1904 {//tsk tsk, can't use a twoHanded saber as second saber
1905 WP_RemoveSaber( ent, 1 );
1906 }
1907 else
1908 {
1909 ent->client->ps.dualSabers = qtrue;
1910 if ( player
1911 && player->client
1912 && player->client->sess.mission_objectives[LIGHTSIDE_OBJ].status == 2
1913 && g_saberDarkSideSaberColor->integer )
1914 {//dark side!
1915 //always use red
1916 for ( int n = 0; n < MAX_BLADES; n++ )
1917 {
1918 ent->client->ps.saber[1].blade[n].color = SABER_RED;
1919 }
1920 }
1921 else if ( g_saber2_color->string )
1922 {//FIXME: how to specify color for each blade and/or color for second saber?
1923 saber_colors_t color = TranslateSaberColor( g_saber2_color->string );
1924 for ( int n = 0; n < MAX_BLADES; n++ )
1925 {
1926 ent->client->ps.saber[1].blade[n].color = color;
1927 }
1928 }
1929 }
1930 }
1931 }
1932 }
1933
G_InitPlayerFromCvars(gentity_t * ent)1934 void G_InitPlayerFromCvars( gentity_t *ent )
1935 {
1936 //set model based on cvars
1937 if(Q_stricmp(g_char_skin_head->string, "model_default") == 0 && Q_stricmp(g_char_skin_torso->string, "model_default") == 0 && Q_stricmp(g_char_skin_legs->string, "model_default") == 0)
1938 G_ChangePlayerModel( ent, va("%s|model_default", g_char_model->string) );
1939 else
1940 G_ChangePlayerModel( ent, va("%s|%s|%s|%s", g_char_model->string, g_char_skin_head->string, g_char_skin_torso->string, g_char_skin_legs->string) );
1941
1942 //FIXME: parse these 2 from some cvar or require playermodel to be in a *.npc?
1943 if( ent->NPC_type && gi.bIsFromZone(ent->NPC_type, TAG_G_ALLOC) ) {
1944 gi.Free(ent->NPC_type);
1945 }
1946
1947 // Bad casting I know, but NPC_type can also come the memory manager,
1948 // and you can't free a const-pointer later on. This seemed like the
1949 // better options.
1950 ent->NPC_type = (char *)"player";//default for now
1951 if( ent->client->clientInfo.customBasicSoundDir && gi.bIsFromZone(ent->client->clientInfo.customBasicSoundDir, TAG_G_ALLOC) ) {
1952 gi.Free(ent->client->clientInfo.customBasicSoundDir);
1953 }
1954
1955 char snd[512];
1956 gi.Cvar_VariableStringBuffer( "snd", snd, sizeof(snd) );
1957
1958 ent->client->clientInfo.customBasicSoundDir = G_NewString(snd); //copy current cvar
1959
1960 //set the lightsaber
1961 G_RemoveWeaponModels( ent );
1962 G_SetSabersFromCVars( ent );
1963 //set up weapon models, etc.
1964 G_AddWeaponModels( ent );
1965 NPC_SetAnim( ent, SETANIM_LEGS, ent->client->ps.legsAnim, SETANIM_FLAG_NORMAL|SETANIM_FLAG_RESTART );
1966 NPC_SetAnim( ent, SETANIM_TORSO, ent->client->ps.torsoAnim, SETANIM_FLAG_NORMAL|SETANIM_FLAG_RESTART );
1967 if ( !ent->s.number )
1968 {//the actual player, not an NPC pretending to be a player
1969 ClientUserinfoChanged( ent->s.number );
1970 }
1971 //color tinting
1972 //FIXME: the customRGBA shouldn't be set if the shader this guys .skin is using doesn't have the tinting on it
1973 if ( g_char_color_red->integer
1974 || g_char_color_green->integer
1975 || g_char_color_blue->integer )
1976 {
1977 ent->client->renderInfo.customRGBA[0] = g_char_color_red->integer;
1978 ent->client->renderInfo.customRGBA[1] = g_char_color_green->integer;
1979 ent->client->renderInfo.customRGBA[2] = g_char_color_blue->integer;
1980 ent->client->renderInfo.customRGBA[3] = 255;
1981 }
1982 }
1983
G_ChangePlayerModel(gentity_t * ent,const char * newModel)1984 void G_ChangePlayerModel( gentity_t *ent, const char *newModel )
1985 {
1986 if ( !ent || !ent->client || !newModel )
1987 {
1988 return;
1989 }
1990
1991 G_RemovePlayerModel( ent );
1992 if ( Q_stricmp( "player", newModel ) == 0 )
1993 {
1994 G_InitPlayerFromCvars( ent );
1995 return;
1996 }
1997
1998 //attempt to free the string (currently can't since it's always "player" )
1999 if( ent->NPC_type && gi.bIsFromZone(ent->NPC_type, TAG_G_ALLOC) ) {
2000 gi.Free(ent->NPC_type);
2001 }
2002 ent->NPC_type = G_NewString( newModel );
2003 G_RemoveWeaponModels( ent );
2004
2005 if ( strchr(newModel,'|') )
2006 {
2007 char name[MAX_QPATH];
2008 strcpy(name, newModel);
2009 char *p = strchr(name, '|');
2010 *p=0;
2011 p++;
2012
2013 if ( strstr(p, "model_default" ) )
2014 G_SetG2PlayerModel( ent, name, NULL, NULL, NULL );
2015 else
2016 G_SetG2PlayerModel( ent, name, p, NULL, NULL );
2017 }
2018 else
2019 {
2020 //FIXME: everything but force powers gets reset, those should, too...
2021 // currently leaves them as is except where otherwise noted in the NPCs.cfg?
2022 //FIXME: remove all weapons?
2023 if ( NPC_ParseParms( ent->NPC_type, ent ) )
2024 {
2025 G_AddWeaponModels( ent );
2026 NPC_SetAnim( ent, SETANIM_LEGS, ent->client->ps.legsAnim, SETANIM_FLAG_NORMAL|SETANIM_FLAG_RESTART );
2027 NPC_SetAnim( ent, SETANIM_TORSO, ent->client->ps.torsoAnim, SETANIM_FLAG_NORMAL|SETANIM_FLAG_RESTART );
2028 ClientUserinfoChanged( ent->s.number );
2029 //Ugh, kind of a hack for now:
2030 if ( ent->client->NPC_class == CLASS_BOBAFETT
2031 || ent->client->NPC_class == CLASS_ROCKETTROOPER )
2032 {
2033 //FIXME: remove saber, too?
2034 Boba_Precache(); // player as boba?
2035 }
2036 }
2037 else
2038 {
2039 gi.Printf( S_COLOR_RED"G_ChangePlayerModel: cannot find NPC %s\n", newModel );
2040 G_ChangePlayerModel( ent, "stormtrooper" ); //need a better fallback?
2041 }
2042 }
2043 }
2044
G_ReloadSaberData(gentity_t * ent)2045 void G_ReloadSaberData( gentity_t *ent )
2046 {
2047 //dualSabers should already be set
2048 if ( ent->client->ps.saber[0].name != NULL )
2049 {
2050 WP_SaberParseParms( ent->client->ps.saber[0].name, &ent->client->ps.saber[0], qfalse );
2051 if ( ent->client->ps.saber[0].stylesLearned )
2052 {
2053 ent->client->ps.saberStylesKnown |= ent->client->ps.saber[0].stylesLearned;
2054 }
2055 if ( ent->client->ps.saber[0].singleBladeStyle )
2056 {
2057 ent->client->ps.saberStylesKnown |= ent->client->ps.saber[0].singleBladeStyle;
2058 }
2059 }
2060 if ( ent->client->ps.saber[1].name != NULL )
2061 {
2062 WP_SaberParseParms( ent->client->ps.saber[1].name, &ent->client->ps.saber[1], qfalse );
2063 if ( ent->client->ps.saber[1].stylesLearned )
2064 {
2065 ent->client->ps.saberStylesKnown |= ent->client->ps.saber[1].stylesLearned;
2066 }
2067 if ( ent->client->ps.saber[1].singleBladeStyle )
2068 {
2069 ent->client->ps.saberStylesKnown |= ent->client->ps.saber[1].singleBladeStyle;
2070 }
2071 }
2072 }
2073
G_PlayerSpawned(void)2074 qboolean G_PlayerSpawned( void )
2075 {
2076 if ( !player
2077 || !player->client
2078 || player->client->pers.teamState.state != TEAM_ACTIVE
2079 || level.time - player->client->pers.enterTime < 100 )
2080 {//player hasn't spawned yet
2081 return qfalse;
2082 }
2083 return qtrue;
2084 }
2085
2086 /*
2087 ===========
2088 ClientSpawn
2089
2090 Called every time a client is placed fresh in the world:
2091 after the first ClientBegin, and after each respawn
2092 Initializes all non-persistant parts of playerState
2093 ============
2094 */
2095
G_CheckPlayerDarkSide(void)2096 qboolean G_CheckPlayerDarkSide( void )
2097 {
2098 if ( player && player->client && player->client->sess.mission_objectives[LIGHTSIDE_OBJ].status == 2 )
2099 {//dark side player!
2100 player->client->playerTeam = TEAM_FREE;
2101 player->client->enemyTeam = TEAM_FREE;
2102 if ( g_saberDarkSideSaberColor->integer )
2103 {//dark side!
2104 //always use red
2105 for ( int n = 0; n < MAX_BLADES; n++ )
2106 {
2107 player->client->ps.saber[0].blade[n].color = player->client->ps.saber[1].blade[n].color = SABER_RED;
2108 }
2109 }
2110 G_SoundIndex( "sound/chars/jedi2/28je2008.wav" );
2111 G_SoundIndex( "sound/chars/jedi2/28je2009.wav" );
2112 G_SoundIndex( "sound/chars/jedi2/28je2012.wav" );
2113 return qtrue;
2114 }
2115 return qfalse;
2116 }
2117
2118 void G_ChangePlayerModel( gentity_t *ent, const char *newModel );
ClientSpawn(gentity_t * ent,SavedGameJustLoaded_e eSavedGameJustLoaded)2119 qboolean ClientSpawn(gentity_t *ent, SavedGameJustLoaded_e eSavedGameJustLoaded )
2120 {
2121 int index;
2122 vec3_t spawn_origin, spawn_angles;
2123 gclient_t *client;
2124 int i;
2125 clientPersistant_t saved;
2126 clientSession_t savedSess;
2127 clientInfo_t savedCi;
2128 int persistant[MAX_PERSISTANT];
2129 usercmd_t ucmd;
2130 gentity_t *spawnPoint;
2131 qboolean beamInEffect = qfalse;
2132 extern qboolean g_qbLoadTransition;
2133
2134 index = ent - g_entities;
2135 client = ent->client;
2136
2137 if ( eSavedGameJustLoaded == eFULL && g_qbLoadTransition == qfalse )//qbFromSavedGame)
2138 {//loading up a full save game
2139 ent->client->pers.teamState.state = TEAM_ACTIVE;
2140
2141 // increment the spawncount so the client will detect the respawn
2142 client->ps.persistant[PERS_SPAWN_COUNT]++;
2143 client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam;
2144
2145 client->airOutTime = level.time + 12000;
2146
2147 for (i=0; i<3; i++)
2148 {
2149 ent->client->pers.cmd_angles[i] = 0.0f;
2150 }
2151
2152 SetClientViewAngle( ent, ent->client->ps.viewangles);//spawn_angles );
2153
2154 gi.linkentity (ent);
2155
2156 // run the presend to set anything else
2157 ClientEndFrame( ent );
2158
2159 // clear entity state values
2160 PlayerStateToEntityState( &client->ps, &ent->s );
2161
2162 // ALL OF MY RAGE... they decided it would be a great idea to treat NPC_type like a player model here,
2163 // which is all kinds of unbelievable. I will be having a stern talk with James later. --eez
2164 if( ent->NPC_type &&
2165 Q_stricmp( ent->NPC_type, "player" ) )
2166 {
2167 // FIXME: game doesn't like it when you pass ent->NPC_type into this func. Insert all kinds of noises here --eez
2168 char bleh[MAX_SPAWN_VARS_CHARS];
2169 Q_strncpyz(bleh, ent->NPC_type, sizeof(bleh));
2170
2171 G_ChangePlayerModel( ent, bleh );
2172 }
2173 else
2174 {
2175 G_LoadAnimFileSet( ent, ent->NPC_type );
2176 G_SetSkin( ent );
2177 }
2178
2179 //setup sabers
2180 G_ReloadSaberData( ent );
2181 //force power levels should already be set
2182 }
2183 else
2184 {
2185 // find a spawn point
2186 // do it before setting health back up, so farthest
2187 // ranging doesn't count this client
2188 // don't spawn near existing origin if possible
2189 spawnPoint = SelectSpawnPoint ( ent->client->ps.origin,
2190 (team_t) ent->client->ps.persistant[PERS_TEAM], spawn_origin, spawn_angles);
2191
2192 ent->client->pers.teamState.state = TEAM_ACTIVE;
2193
2194 // clear everything but the persistant data
2195 saved = client->pers;
2196 savedSess = client->sess;
2197 for ( i = 0 ; i < MAX_PERSISTANT ; i++ )
2198 {
2199 persistant[i] = client->ps.persistant[i];
2200 }
2201 //Preserve clientInfo
2202 memcpy (&savedCi, &client->clientInfo, sizeof(clientInfo_t));
2203
2204 memset (client, 0, sizeof(*client));
2205
2206 memcpy (&client->clientInfo, &savedCi, sizeof(clientInfo_t));
2207
2208 client->pers = saved;
2209 client->sess = savedSess;
2210 for ( i = 0 ; i < MAX_PERSISTANT ; i++ )
2211 {
2212 client->ps.persistant[i] = persistant[i];
2213 }
2214
2215 // increment the spawncount so the client will detect the respawn
2216 client->ps.persistant[PERS_SPAWN_COUNT]++;
2217 client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam;
2218
2219 client->airOutTime = level.time + 12000;
2220
2221 // clear entity values
2222 client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth;
2223 ent->s.groundEntityNum = ENTITYNUM_NONE;
2224 ent->client = &level.clients[index];
2225 ent->mass = 10;
2226 ent->takedamage = qtrue;
2227 ent->inuse = qtrue;
2228 SetInUse(ent);
2229 ent->m_iIcarusID = IIcarusInterface::ICARUS_INVALID;
2230 if ( !ent->NPC_type )
2231 {
2232 ent->NPC_type = (char *)"player";
2233 }
2234 ent->classname = "player";
2235 ent->targetname = ent->script_targetname = "player";
2236 if ( ent->client->NPC_class == CLASS_NONE )
2237 {
2238 ent->client->NPC_class = CLASS_PLAYER;
2239 }
2240 client->playerTeam = TEAM_PLAYER;
2241 client->enemyTeam = TEAM_ENEMY;
2242 ent->contents = CONTENTS_BODY;
2243 ent->clipmask = MASK_PLAYERSOLID;
2244 ent->e_DieFunc = dieF_player_die;
2245 ent->waterlevel = 0;
2246 ent->watertype = 0;
2247 client->ps.friction = 6;
2248 client->ps.gravity = g_gravity->value;
2249 ent->flags &= ~FL_NO_KNOCKBACK;
2250 client->renderInfo.lookTarget = ENTITYNUM_NONE;
2251 client->renderInfo.lookTargetClearTime = 0;
2252 client->renderInfo.lookMode = LM_ENT;
2253
2254 VectorCopy (playerMins, ent->mins);
2255 VectorCopy (playerMaxs, ent->maxs);
2256 client->crouchheight = CROUCH_MAXS_2;
2257 client->standheight = DEFAULT_MAXS_2;
2258
2259 client->ps.clientNum = index;
2260
2261 // give default weapons
2262 //these are precached in g_items, ClearRegisteredItems()
2263 client->ps.stats[STAT_WEAPONS] = ( 1 << WP_NONE );
2264 //client->ps.inventory[INV_ELECTROBINOCULARS] = 1;
2265 //ent->client->ps.inventory[INV_BACTA_CANISTER] = 1;
2266
2267 // give EITHER the saber or the stun baton..never both
2268 if ( spawnPoint->spawnflags & 32 ) // STUN_BATON
2269 {
2270 client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_STUN_BATON );
2271 client->ps.weapon = WP_STUN_BATON;
2272 }
2273 else
2274 { // give the saber because most test maps will not have the STUN BATON flag set
2275 client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_SABER ); //this is precached in SP_info_player_deathmatch
2276 client->ps.weapon = WP_SABER;
2277 }
2278 // force the base weapon up
2279 client->ps.weaponstate = WEAPON_READY;
2280
2281 for ( i = FIRST_WEAPON; i < MAX_PLAYER_WEAPONS; i++ ) // don't give ammo for explosives
2282 {
2283 if ( (client->ps.stats[STAT_WEAPONS]&(1<<i)) )
2284 {//if starting with this weapon, gimme max ammo for it
2285 client->ps.ammo[weaponData[i].ammoIndex] = ammoData[weaponData[i].ammoIndex].max;
2286 }
2287 }
2288
2289 if ( eSavedGameJustLoaded == eNO )
2290 {
2291 //FIXME: get player's info from NPCs.cfg
2292 client->ps.dualSabers = qfalse;
2293 WP_SaberParseParms( g_saber->string, &client->ps.saber[0] );//get saber info
2294
2295 client->ps.saberStylesKnown |= (1<<gi.Cvar_VariableIntegerValue("g_fighting_style"));
2296
2297 // if ( client->ps.saber[0].stylesLearned )
2298 // {
2299 // client->ps.saberStylesKnown |= client->ps.saber[0].stylesLearned;
2300 // }
2301 // if ( ent->client->ps.saber[1].singleSaberStyle )
2302 // {
2303 // ent->client->ps.saberStylesKnown |= ent->client->ps.saber[1].singleSaberStyle;
2304 // }
2305 WP_InitForcePowers( ent );//Initialize force powers
2306 }
2307 else
2308 {//autoload, will be taken care of below
2309 }
2310 //
2311
2312 ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH];
2313 ent->client->dismemberProbHead = 0;
2314 ent->client->dismemberProbArms = 5;
2315 ent->client->dismemberProbHands = 20;
2316 ent->client->dismemberProbWaist = 0;
2317 ent->client->dismemberProbLegs = 0;
2318
2319 ent->client->ps.batteryCharge = 2500;
2320
2321 VectorCopy( spawn_origin, client->ps.origin );
2322 VectorCopy( spawn_origin, ent->currentOrigin );
2323
2324 // the respawned flag will be cleared after the attack and jump keys come up
2325 client->ps.pm_flags |= PMF_RESPAWNED;
2326
2327 SetClientViewAngle( ent, spawn_angles );
2328
2329 G_KillBox( ent );
2330 gi.linkentity (ent);
2331
2332 // don't allow full run speed for a bit
2333 client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
2334 client->ps.pm_time = 100;
2335
2336 client->respawnTime = level.time;
2337 client->latched_buttons = 0;
2338
2339 // set default animations
2340 client->ps.torsoAnim = BOTH_STAND2;
2341 client->ps.legsAnim = BOTH_STAND2;
2342
2343 //clear IK grabbing stuff
2344 client->ps.heldClient = client->ps.heldByClient = ENTITYNUM_NONE;
2345 client->ps.saberLockEnemy = ENTITYNUM_NONE; //duh, don't think i'm locking with myself
2346
2347 // restore some player data
2348 //
2349 Player_RestoreFromPrevLevel(ent, eSavedGameJustLoaded);
2350
2351 //FIXME: put this BEFORE the Player_RestoreFromPrevLevel check above?
2352 if (eSavedGameJustLoaded == eNO)
2353 {//fresh start
2354 if (!(spawnPoint->spawnflags&1)) // not KEEP_PREV
2355 {//then restore health and armor
2356 ent->health = client->ps.stats[STAT_ARMOR] = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH];
2357 ent->client->ps.forcePower = ent->client->ps.forcePowerMax;
2358 }
2359 G_InitPlayerFromCvars( ent );
2360 }
2361 else
2362 {//autoload
2363 if( ent->NPC_type &&
2364 Q_stricmp( ent->NPC_type, "player" ) )
2365 {
2366 // FIXME: game doesn't like it when you pass ent->NPC_type into this func. Insert all kinds of noises here --eez
2367 char bleh[MAX_SPAWN_VARS_CHARS];
2368 Q_strncpyz(bleh, ent->NPC_type, sizeof(bleh));
2369
2370 G_ChangePlayerModel( ent, bleh );
2371 }
2372 else
2373 {
2374 G_LoadAnimFileSet( ent, ent->NPC_type );
2375 G_SetSkin( ent );
2376 }
2377 G_ReloadSaberData( ent );
2378 //force power levels should already be set
2379 }
2380
2381 //NEVER start a map with either of your sabers or blades on...
2382 ent->client->ps.SaberDeactivate();
2383 // run a client frame to drop exactly to the floor,
2384 // initialize animations and other things
2385 client->ps.commandTime = level.time - 100;
2386 ucmd = client->pers.lastCommand;
2387 ucmd.serverTime = level.time;
2388 VectorCopyM( client->pers.cmd_angles, ucmd.angles );
2389 ucmd.weapon = client->ps.weapon; // client think calls Pmove which sets the client->ps.weapon to ucmd.weapon, so ...
2390 ent->client->ps.groundEntityNum = ENTITYNUM_NONE;
2391 ClientThink( ent-g_entities, &ucmd );
2392
2393 // run the presend to set anything else
2394 ClientEndFrame( ent );
2395
2396 // clear entity state values
2397 PlayerStateToEntityState( &client->ps, &ent->s );
2398
2399 //ICARUS include
2400 Quake3Game()->FreeEntity( ent );
2401 Quake3Game()->InitEntity( ent );
2402
2403 // Make sure no Sequencer exists then Get a new one.
2404 IIcarusInterface::GetIcarus()->DeleteIcarusID( ent->m_iIcarusID );
2405 ent->m_iIcarusID = IIcarusInterface::GetIcarus()->GetIcarusID( ent->s.number );
2406
2407 if ( spawnPoint->spawnflags & 64 ) //NOWEAPON
2408 {//player starts with absolutely no weapons
2409 ent->client->ps.stats[STAT_WEAPONS] = ( 1 << WP_NONE );
2410 ent->client->ps.ammo[weaponData[WP_NONE].ammoIndex] = 32000;
2411 ent->client->ps.weapon = WP_NONE;
2412 ent->client->ps.weaponstate = WEAPON_READY;
2413 ent->client->ps.dualSabers = qfalse;
2414 }
2415
2416 if ( ent->client->ps.stats[STAT_WEAPONS] & ( 1 << WP_SABER ) )
2417 {//set up so has lightsaber
2418 WP_SaberInitBladeData( ent );
2419 if ( (ent->weaponModel[0] <= 0 || (ent->weaponModel[1]<=0&&ent->client->ps.dualSabers)) //one or both of the saber models is not initialized
2420 && ent->client->ps.weapon == WP_SABER )//current weapon is saber
2421 {//add the proper models
2422 WP_SaberAddG2SaberModels( ent );
2423 }
2424 }
2425 if ( ent->weaponModel[0] == -1 && ent->client->ps.weapon != WP_NONE )
2426 {
2427 G_CreateG2AttachedWeaponModel( ent, weaponData[ent->client->ps.weapon].weaponMdl, ent->handRBolt, 0 );
2428 }
2429
2430 {
2431 // fire the targets of the spawn point
2432 G_UseTargets( spawnPoint, ent );
2433 //Designers needed them to fire off target2's as well... this is kind of messy
2434 G_UseTargets2( spawnPoint, ent, spawnPoint->target2 );
2435
2436 /*
2437 // select the highest weapon number available, after any
2438 // spawn given items have fired
2439 client->ps.weapon = 1;
2440 for ( i = WP_NUM_WEAPONS - 1 ; i > 0 ; i-- ) {
2441 if ( client->ps.stats[STAT_WEAPONS] & ( 1 << i ) ) {
2442 client->ps.weapon = i;
2443 break;
2444 }
2445 }*/
2446 }
2447 }
2448
2449 client->pers.enterTime = level.time;//needed mainly to stop the weapon switch to WP_NONE that happens on loads
2450 ent->max_health = client->ps.stats[STAT_MAX_HEALTH];
2451
2452 if ( eSavedGameJustLoaded == eNO )
2453 {//on map transitions, Ghoul2 frame gets reset to zero, restart our anim
2454 NPC_SetAnim( ent, SETANIM_LEGS, ent->client->ps.legsAnim, SETANIM_FLAG_NORMAL|SETANIM_FLAG_RESTART );
2455 NPC_SetAnim( ent, SETANIM_TORSO, ent->client->ps.torsoAnim, SETANIM_FLAG_NORMAL|SETANIM_FLAG_RESTART );
2456 }
2457
2458 if ( ent->s.number == 0 )
2459 {//player
2460 G_CheckPlayerDarkSide();
2461 }
2462
2463 if ( (ent->client->ps.stats[STAT_WEAPONS]&(1<<WP_SABER))
2464 && !ent->client->ps.saberStylesKnown )
2465 {//um, if you have a saber, you need at least 1 style to use it with...
2466 ent->client->ps.saberStylesKnown |= (1<<SS_MEDIUM);
2467 }
2468
2469 return beamInEffect;
2470 }
2471
2472
2473 /*
2474 ===========
2475 ClientDisconnect
2476
2477 Called when a player drops from the server.
2478 Will not be called between levels.
2479 ============
2480 */
ClientDisconnect(int clientNum)2481 void ClientDisconnect( int clientNum ) {
2482 gentity_t *ent;
2483
2484 ent = g_entities + clientNum;
2485 if ( !ent->client ) {
2486 return;
2487 }
2488
2489 // send effect if they were completely connected
2490 /* if ( ent->client->pers.connected == CON_CONNECTED ) {
2491 // They don't get to take powerups with them!
2492 // Especially important for stuff like CTF flags
2493 TossClientItems ( ent );
2494 }
2495 */
2496 gi.unlinkentity (ent);
2497 ent->s.modelindex = 0;
2498 ent->inuse = qfalse;
2499 ClearInUse(ent);
2500 ent->classname = "disconnected";
2501 ent->client->pers.connected = CON_DISCONNECTED;
2502 ent->client->ps.persistant[PERS_TEAM] = TEAM_FREE;
2503
2504 gi.SetConfigstring( CS_PLAYERS + clientNum, "");
2505
2506 IIcarusInterface::GetIcarus()->DeleteIcarusID(ent->m_iIcarusID);
2507
2508 }
2509
2510
2511
2512