File: multi_respawn.cpp

package info (click to toggle)
freespace2 24.2.0%2Brepack-1
  • links: PTS, VCS
  • area: non-free
  • in suites: forky, sid
  • size: 43,716 kB
  • sloc: cpp: 595,001; ansic: 21,741; python: 1,174; sh: 457; makefile: 248; xml: 181
file content (980 lines) | stat: -rw-r--r-- 29,899 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
/*
 * Copyright (C) Volition, Inc. 1999.  All rights reserved.
 *
 * All source code herein is the property of Volition, Inc. You may not sell 
 * or otherwise commercially exploit the source or things you created based on the 
 * source.
 *
*/




#include "network/multi_respawn.h"
#include "network/multi.h"
#include "object/object.h"
#include "object/objcollide.h"
#include "globalincs/linklist.h"
#include "network/multimsgs.h"
#include "network/multiutil.h"
#include "missionui/missionweaponchoice.h"
#include "gamesequence/gamesequence.h"
#include "hud/hudets.h"
#include "hud/hudconfig.h"
#include "hud/hudobserver.h"
#include "hud/hudmessage.h"
#include "network/multi_observer.h"
#include "network/multi_team.h"
#include "hud/hudwingmanstatus.h"
#include "mission/missionparse.h"
#include "ship/ship.h"
#include "playerman/player.h"
#include "missionui/missionscreencommon.h"
#include "network/multiteamselect.h"
#include "io/timer.h"
#include "iff_defs/iff_defs.h"


// ---------------------------------------------------------------------------------------
// MULTI RESPAWN DEFINES/VARS
//

// respawn notice codes
#define RESPAWN_BROADCAST			0x1		// server to clients - create this ship, and if you're the respawner, set it to be your object
#define RESPAWN_REQUEST				0x2		// client to server requesting a respawn (observer or normal)
#define AI_RESPAWN_NOTICE			0x3		// respawn an AI ship

// struct used to store AI objects which should get respawned
#define MAX_AI_RESPAWNS				MAX_PLAYERS
#define AI_RESPAWN_TIME				(7000)			// should respawn 2.0 seconds after they die

typedef struct ai_respawn
{
	p_object		*pobjp;				// parse object
	int			timestamp;			// timestamp when this object should get respawned
} ai_respawn;

ai_respawn Ai_respawns[MAX_AI_RESPAWNS];			// this is way too many, but I don't care

// respawn point
typedef struct respawn_point {
	char ship_name[NAME_LENGTH+1];					// for priority respawns
	vec3d pos;												// respawn location (non-priority respawns)
	int team;												// team it belongs to
} respawn_point;

// respawn points
#define MAX_MULTI_RESPAWN_POINTS					MAX_PLAYERS
respawn_point Multi_respawn_points[MAX_MULTI_RESPAWN_POINTS];
int Multi_respawn_point_count = 0;
int Multi_next_respawn_point = 0;

// priority ships for respawning
#define MAX_PRIORITY_POINTS						10
respawn_point Multi_respawn_priority_ships[MAX_PRIORITY_POINTS];
int Multi_respawn_priority_count = 0;

// ---------------------------------------------------------------------------------------
// MULTI RESPAWN FORWARD DECLARATIONS
//

// respawn the passed player with the passed ship object and weapon link settings
void multi_respawn_player(net_player *pl, char cur_primary_bank, char cur_secondary_bank, ubyte cur_link_status, ushort ship_ets, ushort net_sign, char *parse_name, vec3d *pos = NULL);

// respawn an AI ship
void multi_respawn_ai(p_object *pobjp);

// respawn myself as an observer
void multi_respawn_as_observer();

// send a request to the server saying I want to respawn (as an observer or not)
void multi_respawn_send_request(int as_observer);

// send a request to respawn an AI ship
void multi_respawn_send_ai_respawn(ushort net_signature);

// send a broadcast pack indicating a player has respawned
void multi_respawn_broadcast(net_player *player);

// <server> make the given player an observer
void multi_respawn_make_observer(net_player *pl);

// place a newly respawned object intelligently
void multi_respawn_place(object *new_obj, int team);

// respawn the server immediately
void multi_respawn_server();

void prevent_spawning_collision(object *new_obj);

// ---------------------------------------------------------------------------------------
// MULTI RESPAWN FUNCTIONS
//

// check to see if a net player needs to be respawned
void multi_respawn_check(object *objp)
{
	int player_index;
	net_player *pl = NULL;
	p_object *pobjp;

	// get the parse object since we are storing all data for the respawns in the parse object
	pobjp = mission_parse_get_arrival_ship( objp->net_signature );
	
	// the server should check against all players
	if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
		player_index = multi_find_player_by_object(objp);
		if(player_index != -1){
			pl = &Net_players[player_index];
		}
	}
	// clients should just check against themselves
	else if(objp == Player_obj){
		pl = Net_player;		
	}	

	// if this ship isn't a player ship, then maybe it is an AI ship which should get respawed.  Only respawn
	// on the server, then send message to respawn on client.
	if( pl == NULL ) {

		// try and find the parse object with this net signature.  If we found it, and it's a player start
		// position, respawn it.
		if ( Net_player->flags & NETINFO_FLAG_AM_MASTER ) {
			if ( !pobjp ){
				return;
			}

			// if we need to respawn this ai ship, add him to a list of ships to get respawned
			if ( (pobjp->flags[Mission::Parse_Object_Flags::OF_Player_start]) && (pobjp->respawn_count < Netgame.respawn) && !(Netgame.type_flags & NG_TYPE_DOGFIGHT) ){
				int i;

				for (i = 0; i < MAX_AI_RESPAWNS; i++ ) {
					if ( Ai_respawns[i].pobjp == NULL ) {
						Ai_respawns[i].pobjp = pobjp;
						Ai_respawns[i].timestamp = timestamp(AI_RESPAWN_TIME);
						break;
					}
				}
				Assert( i < MAX_AI_RESPAWNS );
			}
		}

		return;
	} else {
		// reset his datarate timestamp
		extern int OO_gran;
		pl->s_info.rate_stamp = timestamp( (int)(1000.0f / (float)OO_gran) );
	}

	Assert( pl != NULL );
	Assert( pobjp );				// we have a player, and we should have a record of it.
	
	// mark the player as in the state of respawning
	if( (pobjp->respawn_count < Netgame.respawn) || (Netgame.type_flags & NG_TYPE_DOGFIGHT) ){
		pl->flags |= NETINFO_FLAG_RESPAWNING;
	}
	// otherwise mark the player as being in limbo
	else {
		pl->flags |= NETINFO_FLAG_LIMBO;
	}
}

// notify of a player leaving
void multi_respawn_player_leave(net_player *pl)
{
	// bogus
	if(pl == NULL){
		return;
	}
	if( MULTI_OBSERVER((*pl)) ){
		return;
	}
	if((Net_player == NULL) || !(Net_player->flags & NETINFO_FLAG_AM_MASTER)){
		return;
	}
	if(pl->p_info.p_objp == NULL){
		return;
	}

	// dogfight mode
	if(MULTI_DOGFIGHT){
		return;
	}	

	// if we need to respawn this ai ship, add him to a list of ships to get respawned
	p_object *pobjp = pl->p_info.p_objp;
	if ( (pobjp->flags[Mission::Parse_Object_Flags::OF_Player_start]) && (pobjp->respawn_count < Netgame.respawn) ){
		int i;

		for (i = 0; i < MAX_AI_RESPAWNS; i++ ) {
			if ( Ai_respawns[i].pobjp == NULL ) {
				Ai_respawns[i].pobjp = pobjp;
				Ai_respawns[i].timestamp = timestamp(AI_RESPAWN_TIME);
				break;
			}
		}
	}
}

// respawn normally
void multi_respawn_normal()
{
	// make sure we should be respawning and _not_ as an observer
	Assert((Net_player->flags & NETINFO_FLAG_RESPAWNING));
	Assert(!(Net_player->flags & NETINFO_FLAG_LIMBO));
	
	// server respawns immediately
	if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
		multi_respawn_server();
	} else {
		// client sends a respawn request (multiple ones are ok if he clicks over and over)
		multi_respawn_send_request(0);
	}
}

// respawn as an observer
void multi_respawn_observer()
{
	// make sure we should be respawning as an observer 
	Assert(!(Net_player->flags & NETINFO_FLAG_RESPAWNING) && (Net_player->flags & NETINFO_FLAG_LIMBO));

	// respawn as an observer
	multi_respawn_as_observer();

	// clients should notify the server that they are doing so
	if(!(Net_player->flags & NETINFO_FLAG_AM_MASTER)){
		multi_respawn_send_request(1);
	}	

	// jump back into the game right away
	gameseq_post_event(GS_EVENT_ENTER_GAME);				
}	

// server should check to see if any respawned players have run out of their invulnerability
void multi_respawn_handle_invul_players()
{
	int idx;
	object *objp;
	for(idx=0;idx<MAX_PLAYERS;idx++){
		if(MULTI_CONNECTED(Net_players[idx]) && (Objects[Net_players[idx].m_player->objnum].flags[Object::Object_Flags::Invulnerable])){	
			// make him normal (_non_ invulnerable) on either of 2 conditions :
			// 1.) More than 5 seconds have passed
			// 2.) He's fired either a primary or a secondary weapon
			if( ((Net_players[idx].s_info.invul_timestamp != -1) && (timestamp_elapsed(Net_players[idx].s_info.invul_timestamp))) ||
				 ((Net_players[idx].m_player->ci.fire_primary_count > 0) || (Net_players[idx].m_player->ci.fire_secondary_count > 0)) ) {
				objp = &Objects[Net_players[idx].m_player->objnum];
				obj_set_flags(objp, objp->flags - Object::Object_Flags::Invulnerable);
			}
		}
	}
}

// build a list of respawn points for the mission
void multi_respawn_build_points()
{
	respawn_point *r;

	// respawn points
	Multi_respawn_point_count = 0;
	Multi_next_respawn_point = 0;

	for (auto moveup: list_range(&Ship_obj_list)){
		if (Objects[moveup->objnum].flags[Object::Object_Flags::Should_be_dead])
			continue;

		// player ships
		if(Objects[moveup->objnum].flags[Object::Object_Flags::Player_ship] || Objects[moveup->objnum].flags[Object::Object_Flags::Could_be_player]){
			r = &Multi_respawn_points[Multi_respawn_point_count++];
			
			r->pos = Objects[moveup->objnum].pos;
			r->team = Ships[Objects[moveup->objnum].instance].team;			
		}
	}	

	// priority respawn points
	Multi_respawn_priority_count = 0;

	for (auto moveup: list_range(&Ship_obj_list)){
		if (Objects[moveup->objnum].flags[Object::Object_Flags::Should_be_dead])
			continue;

		// stuff info
		if((Ships[Objects[moveup->objnum].instance].respawn_priority > 0) && (Multi_respawn_priority_count < MAX_PRIORITY_POINTS)){
			r = &Multi_respawn_priority_ships[Multi_respawn_priority_count++];

			strcpy_s(r->ship_name, Ships[Objects[moveup->objnum].instance].ship_name);
			r->team = Ships[Objects[moveup->objnum].instance].team;
		}
	}	
}


// ---------------------------------------------------------------------------------------
// MULTI RESPAWN FORWARD DECLARATIONS
//

void multi_respawn_wing_stuff(ship *shipp)
{
	wing *wingp;

	// deal with re-adding this ship to it's wing
	Assert( shipp->wingnum != -1 );
	wingp = &Wings[shipp->wingnum];
	wingp->ship_index[wingp->current_count] = SHIP_INDEX(shipp);
	wingp->current_count++;

	hud_set_wingman_status_alive(shipp->wing_status_wing_index, shipp->wing_status_wing_pos);
}

int multi_respawn_common_stuff(p_object *pobjp)
{
	int objnum, team, slot_index;
	object *objp;
	ship *shipp;

	// create the object
	objnum = parse_create_object(pobjp);
	Assert(objnum != -1);
	objp = &Objects[objnum];

	// get the team and slot
	shipp = &Ships[objp->instance];
	multi_ts_get_team_and_slot(shipp->ship_name, &team, &slot_index);
	Assert( team != -1 );
	Assert( slot_index != -1 );


	// change the ship type and the weapons
	if (team != -1 && slot_index != -1) {
		change_ship_type(objp->instance, Wss_slots_teams[team][slot_index].ship_class);
		wl_bash_ship_weapons(&shipp->weapons,&Wss_slots_teams[team][slot_index]);
	}
	multi_respawn_wing_stuff( shipp );

	if(Netgame.type_flags & NG_TYPE_TEAM){
		multi_team_mark_ship(&Ships[Objects[objnum].instance]);
	}

	// need to make sure that we will update this object and that the frame tracker knows this is a valid ship again.
	multi_oo_respawn_reset_info(objp);

	pobjp->respawn_count++;

	return objnum;
}

// respawn the passed player with the passed ship object and weapon link settings
void multi_respawn_player(net_player *pl, char cur_primary_bank, char cur_secondary_bank, ubyte cur_link_status, ushort ship_ets, ushort net_sig, char *parse_name, vec3d *pos)
{
	int objnum;
	object *objp;
	ship *shipp;
	p_object *pobjp;	

	// try and find the parse object
	pobjp = mission_parse_get_arrival_ship(parse_name);		
	Assert(pobjp != NULL);
	if(pobjp == NULL){
		return;
	}
	objnum = multi_respawn_common_stuff(pobjp);

	Assert( objnum != -1 );
	objp = &Objects[objnum];
	shipp = &Ships[objp->instance];	

	// this is a player, so mark him as a player,
    objp->flags.set(Object::Object_Flags::Player_ship);
    objp->flags.remove(Object::Object_Flags::Could_be_player);

	// server should mark this player as invulerable for a short time
	if ( MULTIPLAYER_MASTER ) {
        objp->flags.set(Object::Object_Flags::Invulnerable);
		pl->s_info.invul_timestamp = timestamp(RESPAWN_INVUL_TIMESTAMP); 					
		multi_respawn_place( objp, shipp->team );
	}

	// reset his datarate timestamp
	extern int OO_gran;
	pl->s_info.rate_stamp = timestamp( (int)(1000.0f / (float)OO_gran) );

	// set some player information
	pl->m_player->objnum = objnum;
	if ( pl == Net_player ) {
		object *oldplr = Player_obj;

		// Cyborg17 - Despite the fact that the object and ship are getting deleted below, unless the net_signature is cleared here
		// respawning in a rollback enabled game will crash the server.
		oldplr->net_signature = 0;

		Player_obj = objp;
		Player_ship = shipp;
		Player_ai = &Ai_info[Player_ship->ai_index];

		// this is a hack to ensure that old (dead) player ships are destroyed, since at this point he's actually an OBJ_GHOST
        oldplr->flags.set(Object::Object_Flags::Should_be_dead);
		obj_delete(OBJ_INDEX(oldplr));	

		//	get rid of the annoying HUD dead message text.
		HUD_init_fixed_text();
	}	

	// clients bash net signature
	if(!(Net_player->flags & NETINFO_FLAG_AM_MASTER)){
		objp->net_signature = net_sig;
	}

	// restore the correct weapon bank selections
	shipp->weapons.current_primary_bank = (int)cur_primary_bank;
	shipp->weapons.current_secondary_bank = (int)cur_secondary_bank;
	if(cur_link_status & (1<<0)){
		shipp->flags.set(Ship::Ship_Flags::Primary_linked);
	} else {
        shipp->flags.remove(Ship::Ship_Flags::Primary_linked);
	}			
	if(cur_link_status & (1<<1)){
		shipp->flags.set(Ship::Ship_Flags::Secondary_dual_fire);
    }
    else {
        shipp->flags.remove(Ship::Ship_Flags::Secondary_dual_fire);
    }

	Assert( ship_ets != 0 );		// find dave or allender

	// restore the correct ets settings
	set_recharge_rates(&Objects[shipp->objnum], ((ship_ets & 0x0f00) >> 8), ((ship_ets & 0x00f0) >> 4), (ship_ets & 0x000f));

	// give the current bank a half-second timestamp so that we don't fire immediately unpon respawn
	shipp->weapons.next_secondary_fire_stamp[shipp->weapons.current_secondary_bank] = timestamp(500);

	// if this is a dogfight mission, make him TEAM_TRAITOR
	if(Netgame.type_flags & NG_TYPE_DOGFIGHT){
		shipp->team = Iff_traitor;
	}

	// maybe bash ship position
	if(pos != NULL){
		objp->pos = *pos;
	}

	// unset his respawning flag
	pl->flags &= ~(NETINFO_FLAG_RESPAWNING | NETINFO_FLAG_LIMBO);

	// blast his control and button info clear
	memset(&pl->m_player->bi, 0, sizeof(pl->m_player->bi));
	memset(&pl->m_player->ci, 0, sizeof(pl->m_player->ci));

	// set throttle based on initial velocity specified in mission (the vel gets calculated
	//   like a percentage of our max speed, so we can just use it as-is for the throttle)
	pl->m_player->ci.forward_cruise_percent = (float)pobjp->initial_velocity;
	CLAMP(pl->m_player->ci.forward_cruise_percent, 0.0f, 100.0f);

	// if this is me, clear accum button info
	if(pl == Net_player){
		// clear multiplayer button info			
		extern button_info Multi_ship_status_bi;
		memset(&Multi_ship_status_bi, 0, sizeof(button_info));
	}

	// notify other players of the respawn
	if ( MULTIPLAYER_MASTER ){
		multi_respawn_broadcast(pl);
	}
}

// respawns an AI ship.
void multi_respawn_ai( p_object *pobjp )
{
	int objnum;
	object *objp;

	// create the object and change the ship type
	objnum = multi_respawn_common_stuff( pobjp);
	objp = &Objects[objnum];

	// be sure the the OF_PLAYER_SHIP flag is unset, and the could be player flag is set
	obj_set_flags(objp, objp->flags + Object::Object_Flags::Could_be_player);
    objp->flags.remove(Object::Object_Flags::Player_ship);
}

// <server> make the given player an observer
void multi_respawn_make_observer(net_player *pl)
{	
	pl->flags |= (NETINFO_FLAG_OBSERVER | NETINFO_FLAG_OBS_PLAYER);		
	pl->flags &= ~(NETINFO_FLAG_RESPAWNING | NETINFO_FLAG_LIMBO);

	// MWA 6/3/98 -- don't set to high -- let it default to whatever player chose 
	//pl->p_info.options.obj_update_level = OBJ_UPDATE_HIGH;
	pl->last_heard_time = timer_get_fixed_seconds();	

	// reset the ping time for this player
	multi_ping_reset(&pl->s_info.ping);
	
	// timestamp his last_full_update_time
	pl->s_info.last_full_update_time = UI_TIMESTAMP::immediate();

	// create an observer object for him
	multi_obs_create_observer(pl);		
}

// respawn myself as an observer
void multi_respawn_as_observer()
{
	// configure the hud to be in "observer" mode
	hud_config_as_observer(Player_ship,Player_ai);	

	// blow away my old player object
    Player_obj->flags.set(Object::Object_Flags::Should_be_dead);
	obj_delete(OBJ_INDEX(Player_obj));

	// create a new shiny observer object for me
	multi_obs_create_observer(Net_player);
	
	// set my object to be the observer object
	Player_obj = &Objects[Net_player->m_player->objnum];
	Player_ship = &Hud_obs_ship;	
	Player_ai = &Hud_obs_ai;	
	
	// set some flags for myself
	Net_player->flags |= NETINFO_FLAG_OBSERVER;
	Net_player->flags |= NETINFO_FLAG_OBS_PLAYER;
	Net_player->flags &= ~(NETINFO_FLAG_LIMBO);

	// clear my auto-match speed flag
	Net_player->m_player->flags &= ~(PLAYER_FLAGS_AUTO_MATCH_SPEED | PLAYER_FLAGS_MATCH_TARGET);
	
	// reset the control info structure
	memset(&Player->ci,0,sizeof(control_info));	
}

// send a request to respawn an AI object
void multi_respawn_send_ai_respawn( ushort net_signature )
{
	ubyte data[50],val;
	int packet_size = 0;

	// build the header and add the opcode
	BUILD_HEADER(RESPAWN_NOTICE);
	val = AI_RESPAWN_NOTICE;
	ADD_DATA(val);
	ADD_USHORT( net_signature );

	// broadcast the packet to all players
	Assert(Net_player->flags & NETINFO_FLAG_AM_MASTER);
	multi_io_send_to_all_reliable(data, packet_size);	
}

// send a request to the server saying I want to respawn (as an observer or not)
void multi_respawn_send_request(int as_observer)
{
	ubyte data[10],val;
	int packet_size = 0;

	// build the header and add the opcode
	BUILD_HEADER(RESPAWN_NOTICE);
	val = RESPAWN_REQUEST;
	ADD_DATA(val);

	// add a byte indicating whether or not we want to respawn as an observer
	val = (ubyte)as_observer;
	ADD_DATA(val);

	// send the request to the server	
	multi_io_send_reliable(Net_player, data, packet_size);
}

// send a broadcast pack indicating a player has respawned
void multi_respawn_broadcast(net_player *np)
{
	ubyte data[50],val;
	int packet_size = 0;
	ushort signature;
	vec3d pos;

	// broadcast the packet to all players
	Assert(Net_player->flags & NETINFO_FLAG_AM_MASTER);

	signature = Objects[np->m_player->objnum].net_signature;
	pos = Objects[np->m_player->objnum].pos;

	// build the header and add the opcode
	BUILD_HEADER(RESPAWN_NOTICE);
	val = RESPAWN_BROADCAST;
	ADD_DATA(val);

	// add the data for the respawn
	ADD_USHORT(signature);
	ADD_VECTOR(pos);
	ADD_SHORT(np->player_id);
	ADD_DATA(np->s_info.cur_primary_bank);
	ADD_DATA(np->s_info.cur_secondary_bank);
	ADD_DATA(np->s_info.cur_link_status);
	ADD_USHORT(np->s_info.ship_ets);
	ADD_STRING(np->p_info.p_objp->name);

	Assert( np->s_info.ship_ets != 0 );		// find dave or allender

	multi_io_send_to_all_reliable(data, packet_size);
}

// process an incoming respawn info packet
void multi_respawn_process_packet(ubyte *data, header *hinfo)
{
	ubyte code,cur_link_status;
	char cur_primary_bank,cur_secondary_bank;
	ushort net_sig,ship_ets;
	short player_id;
	int player_index;
	vec3d v;	
	char parse_name[1024] = "";
	int offset = HEADER_LENGTH;

	// determine who send the packet	
	player_index = find_player_index(hinfo->id);
	if(player_index == -1){
		nprintf(("Network","Couldn't find player for processing respawn packet!\n"));
	}

	// get the opcode
	GET_DATA(code);

	// do something based upon the opcode
	switch((int)code){

	case AI_RESPAWN_NOTICE: 
		p_object *pobjp;

		GET_USHORT( net_sig );

		// only attempt to respawn if we are in the mission
		if (!(Net_player->flags & NETINFO_FLAG_WARPING_OUT)) {
			pobjp = mission_parse_get_arrival_ship(net_sig);
			Assert(pobjp != NULL);
			multi_respawn_ai(pobjp);
		}

		break;		

	case RESPAWN_BROADCAST:
		// get the respawn data
		GET_USHORT(net_sig);
		GET_VECTOR(v);
		GET_SHORT(player_id);
		GET_DATA(cur_primary_bank);
		GET_DATA(cur_secondary_bank);
		GET_DATA(cur_link_status);
		GET_USHORT(ship_ets);
		GET_STRING(parse_name);

		if (!(Net_player->flags & NETINFO_FLAG_WARPING_OUT)) {
			player_index = find_player_index(player_id);

			if(player_index == -1){
				nprintf(("Network","Couldn't find player to respawn!\n"));
				break;
			}

			// create the ship and assign its position, net_signature, and class
			// respawn the player
			multi_respawn_player(&Net_players[player_index], cur_primary_bank, cur_secondary_bank, cur_link_status, ship_ets, net_sig, parse_name, &v);

			// if this is for me, I should jump back into gameplay
			if(&Net_players[player_index] == Net_player){
				gameseq_post_event(GS_EVENT_ENTER_GAME);
			}
		}
		break;
	
	case RESPAWN_REQUEST:
		// determine whether he wants to respawn as an observer or not
		GET_DATA(code);

		if(player_index == -1){
			nprintf(("Network","Received respawn request from unknown player!\n"));
			break;
		} 		     		
		nprintf(("Network","Received respawn request for player %s\n", Net_players[player_index].m_player->callsign));

		// make sure he's not making an invalid request
		if((code == 0) && !(Net_players[player_index].flags & NETINFO_FLAG_RESPAWNING)){
			nprintf(("Network","This player shouldn't be respawning!\n"));
			Int3();
			break;
		} else if((code == 1) && !(Net_players[player_index].flags & NETINFO_FLAG_LIMBO)){
			nprintf(("Network","This is a respawn observer request from a player who shouldn't be respawning as an observer!\n"));
			Int3();
			break;
		}

		// otherwise perform the operation
		// respawn the guy as an observer
		if(code){
			multi_respawn_make_observer(&Net_players[player_index]);			
		}
		// respawn him as normal
		else {						
			// create his new ship, and change him from respawning to respawned
			Assert(Net_players[player_index].p_info.p_objp != NULL);
			if(Net_players[player_index].p_info.p_objp != NULL){
				multi_respawn_player(&Net_players[player_index], Net_players[player_index].s_info.cur_primary_bank, Net_players[player_index].s_info.cur_secondary_bank,Net_players[player_index].s_info.cur_link_status, Net_players[player_index].s_info.ship_ets, 0, Net_players[player_index].p_info.p_objp->name);
			}			
		}	
		break;
	}

	PACKET_SET_SIZE();
}

// respawn the server immediately
void multi_respawn_server()
{	
	Assert(Net_player->flags & NETINFO_FLAG_AM_MASTER);

	// respawn me
	multi_respawn_player(Net_player, Net_player->s_info.cur_primary_bank, Net_player->s_info.cur_secondary_bank, Net_player->s_info.cur_link_status, Net_player->s_info.ship_ets, Net_player->p_info.p_objp->net_signature, Net_player->p_info.p_objp->name);

	// jump back into the game
	gameseq_post_event(GS_EVENT_ENTER_GAME);	
}

// level init for respawn stuff
void multi_respawn_init()
{
	int i;

	for (i = 0; i < MAX_AI_RESPAWNS; i++ ) {
		Ai_respawns[i].pobjp = NULL;
		Ai_respawns[i].timestamp = timestamp(-1);
	}
}

// function to detect whether or not we have AI ships to respawn this frame
void multi_respawn_check_ai()
{
	int i;

	for (i = 0; i < MAX_AI_RESPAWNS; i++ ) {
		if ( Ai_respawns[i].pobjp != NULL ) {
			if ( timestamp_elapsed(Ai_respawns[i].timestamp) ) {

				// be sure that ship is actually gone before respawning it.
				if ( ship_name_lookup(Ai_respawns[i].pobjp->name) != -1 ) {
					Ai_respawns[i].timestamp = timestamp(1000);
				} else {
					multi_respawn_ai( Ai_respawns[i].pobjp );
					multi_respawn_send_ai_respawn( Ai_respawns[i].pobjp->net_signature );
					Ai_respawns[i].pobjp = NULL;
					Ai_respawns[i].timestamp = timestamp(-1);
				}
			}
		}
	}
}



// this is a completely off the cuff way of doing things. Feel free to find a better way.
// Currently :
// 1. Take the average vector position of all the ships in the game
// 2. Check to make sure we aren't within the radius of any of the ships in the game
//    a.) If we are, move away along the vector between the two ships (by the radius of the ship it collided with)
//    b.) repeat step 2

void multi_respawn_place(object *new_obj, int team)
{
	ship *pri = NULL;
	object *pri_obj = NULL;
	int idx, lookup;

	// first determine if there are any appropriate priority ships to use
	pri = NULL;
	pri_obj = NULL;
	for(idx=0; idx<Multi_respawn_priority_count; idx++){
		// all relevant ships
		if((Multi_respawn_priority_ships[idx].team == team) || !(Netgame.type_flags & NG_TYPE_TEAM)){

			lookup = ship_name_lookup(Multi_respawn_priority_ships[idx].ship_name);
			if( (lookup >= 0) && ((pri == NULL) || (Ships[lookup].respawn_priority > pri->respawn_priority)) && (Ships[lookup].objnum >= 0) && (Ships[lookup].objnum < MAX_OBJECTS)){
				pri = &Ships[lookup];
				pri_obj = &Objects[Ships[lookup].objnum];
			}
		}
	}
	
	// if we have a relevant respawn ship
	if((pri != NULL) && (pri_obj != NULL)){
		// pick a point just outside his bounding box
		polymodel *pm = model_get(Ship_info[pri->ship_info_index].model_num); 

		// hmm, ugly. Pick a point 2000 meters to the y direction
		if(pm == NULL){			
			vm_vec_scale_add(&new_obj->pos, &pri_obj->pos, &pri_obj->orient.vec.rvec, 2000.0f);
		} else {
			// pick a random direction
			int d = (int)frand_range(0.0f, 5.9f);
			switch(d){
			case 0:
				vm_vec_scale_add(&new_obj->pos, &pri_obj->pos, &pri_obj->orient.vec.rvec, (pm->maxs.xyz.x - pm->mins.xyz.x)); 
				break;

			case 1:
				vm_vec_scale_add(&new_obj->pos, &pri_obj->pos, &pri_obj->orient.vec.rvec, -(pm->maxs.xyz.x - pm->mins.xyz.x)); 
				break;

			case 2:
				vm_vec_scale_add(&new_obj->pos, &pri_obj->pos, &pri_obj->orient.vec.uvec, (pm->maxs.xyz.y - pm->mins.xyz.y)); 
				break;

			case 3:
				vm_vec_scale_add(&new_obj->pos, &pri_obj->pos, &pri_obj->orient.vec.uvec, -(pm->maxs.xyz.y - pm->mins.xyz.y)); 
				break;

			case 4:
				vm_vec_scale_add(&new_obj->pos, &pri_obj->pos, &pri_obj->orient.vec.fvec, (pm->maxs.xyz.z - pm->mins.xyz.z)); 
				break;

			case 5:
				vm_vec_scale_add(&new_obj->pos, &pri_obj->pos, &pri_obj->orient.vec.fvec, -(pm->maxs.xyz.z - pm->mins.xyz.z)); 
				break;

			default:
				vm_vec_scale_add(&new_obj->pos, &pri_obj->pos, &pri_obj->orient.vec.uvec, -(pm->maxs.xyz.y - pm->mins.xyz.y)); 
				break;
			}
		}
	}
	// otherwise, resort to plain respawn points
	else {
		Assert(Multi_respawn_point_count > 0);
		
		// get the next appropriate respawn point by team
		lookup = 0;		
		int count = 0;
		while(!lookup && (count < 13)){
			if((team == Iff_traitor) || (team == Multi_respawn_points[Multi_next_respawn_point].team)){
				lookup = 1;
			}			

			// next item
			if(!lookup){
				if(Multi_next_respawn_point >= (Multi_respawn_point_count-1)){
					Multi_next_respawn_point = 0;
				} else {
					Multi_next_respawn_point++;
				}				
			}

			count++;
		}

		// set respawn info
		new_obj->pos = Multi_respawn_points[Multi_next_respawn_point].pos;		
	}

	// now make sure we're not colliding with anyone
	prevent_spawning_collision(new_obj);
}


/*
#define MOVE_AWAY() { vec3d away; vm_vec_sub(&away,&new_obj->pos,&hit_check->pos); \
	                   vm_vec_normalize_quick(&away); vm_vec_scale(&away,hit_check->radius+hit_check->radius); \
							 vm_vec_add2(&new_obj->pos,&away); }

#define WITHIN_RADIUS() { float dist; dist=vm_vec_dist(&new_obj->pos,&hit_check->pos); \
	                       if(dist <= hit_check->radius) collided = 1; }
*/

#define WITHIN_BBOX()	do { \
	if (pm != NULL) { \
		constexpr float scale = 2.0f; \
		collided = false; \
		vec3d temp = new_obj->pos; \
		vec3d gpos; \
		vm_vec_sub2(&temp, &hit_check->pos); \
		vm_vec_rotate(&gpos, &temp, &hit_check->orient); \
		if((gpos.xyz.x >= pm->mins.xyz.x * scale) && (gpos.xyz.y >= pm->mins.xyz.y * scale) && (gpos.xyz.z >= pm->mins.xyz.z * scale) && (gpos.xyz.x <= pm->maxs.xyz.x * scale) && (gpos.xyz.y <= pm->maxs.xyz.y * scale) && (gpos.xyz.z <= pm->maxs.xyz.z * scale)) { \
			collided = true; \
		} \
	} \
} while(false)

#define MOVE_AWAY_BBOX() do { \
	if (pm != NULL) { \
		switch(Random::next(6)) { \
		case 0: \
			new_obj->pos.xyz.x += 200.0f; \
			break; \
		case 1: \
			new_obj->pos.xyz.x -= 200.0f; \
			break; \
		case 2: \
			new_obj->pos.xyz.y += 200.0f; \
			break; \
		case 3: \
			new_obj->pos.xyz.y -= 200.0f; \
			break; \
		case 4: \
			new_obj->pos.xyz.z += 200.0f; \
			break; \
		case 5: \
			new_obj->pos.xyz.z -= 200.0f; \
			break; \
		default: \
			UNREACHABLE("Invalid random number in MOVE_AWAY_BBOX"); \
			break; \
		} \
	} \
} while(false)

void prevent_spawning_collision(object *new_obj)
{
	bool collided;
	ship_obj *moveup;
	object *hit_check;
	ship *s_check;

	if (!new_obj->flags[Object::Object_Flags::Collides])
		return;

	do {
		collided = false;

		for (moveup = GET_FIRST(&Ship_obj_list); moveup != END_OF_LIST(&Ship_obj_list); moveup = GET_NEXT(moveup))
		{
			// don't check the new object itself!!
			if (moveup->objnum == OBJ_INDEX(new_obj))
				continue;

			hit_check = &Objects[moveup->objnum];

			if (hit_check->flags[Object::Object_Flags::Should_be_dead])
				continue;

			// consider collision configuration
			if (!hit_check->flags[Object::Object_Flags::Collides])
				continue;
			if (reject_due_collision_groups(new_obj, hit_check))
				continue;

			Assert(hit_check->type == OBJ_SHIP);
			Assert(hit_check->instance >= 0);
			if ((hit_check->type != OBJ_SHIP) || (hit_check->instance < 0))
				continue;

			s_check = &Ships[hit_check->instance];
							
			// just to make sure we don't get any strange magnitude errors
			if (vm_vec_same(&hit_check->pos, &new_obj->pos))
				new_obj->pos.xyz.x += 1.0f;
							
			polymodel *pm = model_get(Ship_info[s_check->ship_info_index].model_num);
			WITHIN_BBOX();				
			if (collided)
			{
				MOVE_AWAY_BBOX();
				break;
			}
		}
	} while (collided);
}