1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell
5  * or otherwise commercially exploit the source or things you created based on the
6  * source.
7  *
8 */
9 
10 
11 
12 
13 #include "stats/scoring.h"
14 #include "freespace2/freespace.h"
15 #include "object/object.h"
16 #include "ship/ship.h"
17 #include "playerman/player.h"
18 #include "parse/parselo.h"
19 #include "stats/medals.h"
20 #include "localization/localize.h"
21 #include "mission/missionparse.h"
22 #include "hud/hud.h"
23 #include "hud/hudmessage.h"
24 #include "weapon/weapon.h"
25 #include "iff_defs/iff_defs.h"
26 #include "network/multi.h"
27 #include "network/multiutil.h"
28 #include "network/multimsgs.h"
29 #include "network/multi_team.h"
30 #include "network/multi_dogfight.h"
31 #include "network/multi_pmsg.h"
32 #include "ai/ai_profiles.h"
33 #include "pilotfile/pilotfile.h"
34 
35 /*
36 // uncomment to get extra debug messages when a player scores
37 #define SCORING_DEBUG
38 */
39 // what percent of points of total damage to a ship a player has to have done to get an assist (or a kill) when it is killed
40 float Kill_percentage = 0.30f;
41 float Assist_percentage = 0.15f;
42 
43 // these tables are overwritten with the values from rank.tbl
44 rank_stuff Ranks[NUM_RANKS];
45 
46 // scoring scale factors by skill level
47 float Scoring_scale_factors[NUM_SKILL_LEVELS] = {
48 	0.2f,					// very easy
49 	0.4f,					// easy
50 	0.7f,					// medium
51 	1.0f,					// hard
52 	1.25f					// insane
53 };
54 
55 void scoreing_close();
56 
parse_rank_tbl()57 void parse_rank_tbl()
58 {
59 	atexit(scoreing_close);
60 	char buf[MULTITEXT_LENGTH];
61 	int rval, idx, persona;
62 
63 	// open localization
64 	lcl_ext_open();
65 
66 	if ((rval = setjmp(parse_abort)) != 0) {
67 		mprintf(("TABLES: Unable to parse '%s'!  Error code = %i.\n", "rank.tbl", rval));
68 		lcl_ext_close();
69 		return;
70 	}
71 
72 	read_file_text("rank.tbl", CF_TYPE_TABLES);
73 	reset_parse();
74 
75 	// parse in all the rank names
76 	idx = 0;
77 	skip_to_string("[RANK NAMES]");
78 	ignore_white_space();
79 	while ( required_string_either("#End", "$Name:") ) {
80 		Assert ( idx < NUM_RANKS );
81 		required_string("$Name:");
82 		stuff_string( Ranks[idx].name, F_NAME, NAME_LENGTH );
83 		required_string("$Points:");
84 		stuff_int( &Ranks[idx].points );
85 		required_string("$Bitmap:");
86 		stuff_string( Ranks[idx].bitmap, F_NAME, MAX_FILENAME_LEN );
87 		required_string("$Promotion Voice Base:");
88 		stuff_string( Ranks[idx].promotion_voice_base, F_NAME, MAX_FILENAME_LEN );
89 		while (check_for_string("$Promotion Text:")) {
90 			required_string("$Promotion Text:");
91 			stuff_string(buf, F_MULTITEXT, sizeof(buf));
92 			drop_white_space(buf);
93 			compact_multitext_string(buf);
94 			persona = -1;
95 			if (optional_string("+Persona:")) {
96 				stuff_int(&persona);
97 				if (persona < 0) {
98 					Warning(LOCATION, "Debriefing text for %s rank is assigned to an invalid persona: %i (must be 0 or greater).\n", Ranks[idx].name, persona);
99 					continue;
100 				}
101 			}
102 			Ranks[idx].promotion_text[persona] = vm_strdup(buf);
103 		}
104 		if (Ranks[idx].promotion_text.find(-1) == Ranks[idx].promotion_text.end()) {
105 			Warning(LOCATION, "%s rank is missing default debriefing text.\n", Ranks[idx].name);
106 			Ranks[idx].promotion_text[-1] = "";
107 		}
108 		idx++;
109 	}
110 
111 	required_string("#End");
112 
113 	// be sure that all rank points are in order
114 #ifndef NDEBUG
115 	for ( idx = 0; idx < NUM_RANKS-1; idx++ ) {
116 		if ( Ranks[idx].points >= Ranks[idx+1].points )
117 			Int3();
118 	}
119 #endif
120 
121 	// close localization
122 	lcl_ext_close();
123 }
124 
125 // initialize a nice blank scoring element
init()126 void scoring_struct::init()
127 {
128 	flags = 0;
129 	score = 0;
130 	rank = RANK_ENSIGN;
131 
132 	medal_counts.assign(Num_medals, 0);
133 
134 	memset(kills, 0, MAX_SHIP_CLASSES * sizeof(int));
135 	assists = 0;
136 	kill_count = 0;
137 	kill_count_ok = 0;
138 	p_shots_fired = 0;
139 	s_shots_fired = 0;
140 
141 	p_shots_hit = 0;
142 	s_shots_hit = 0;
143 
144 	p_bonehead_hits = 0;
145 	s_bonehead_hits = 0;
146 	bonehead_kills = 0;
147 
148 	missions_flown = 0;
149 	flight_time = 0;
150 	last_flown = 0;
151 	last_backup = 0;
152 
153 	m_medal_earned = -1;		// hasn't earned a medal yet
154 	m_promotion_earned = -1;
155 	m_badge_earned = -1;
156 
157 	m_score = 0;
158 	memset(m_kills, 0, MAX_SHIP_CLASSES * sizeof(int));
159 	memset(m_okKills, 0, MAX_SHIP_CLASSES * sizeof(int));
160 	m_kill_count = 0;
161 	m_kill_count_ok = 0;
162 	m_assists = 0;
163 	mp_shots_fired = 0;
164 	ms_shots_fired = 0;
165 	mp_shots_hit = 0;
166 	ms_shots_hit = 0;
167 	mp_bonehead_hits = 0;
168 	ms_bonehead_hits = 0;
169 	m_bonehead_kills = 0;
170 	m_player_deaths = 0;
171 
172 	memset(m_dogfight_kills, 0, MAX_PLAYERS * sizeof(int));
173 }
174 
175 // clone someone else's scoring element
assign(const scoring_struct & s)176 void scoring_struct::assign(const scoring_struct &s)
177 {
178 	flags = s.flags;
179 	score = s.score;
180 	rank = s.rank;
181 
182 	medal_counts.assign(s.medal_counts.begin(), s.medal_counts.end());
183 
184 	memcpy(kills, s.kills, MAX_SHIP_CLASSES * sizeof(int));
185 	assists = s.assists;
186 	kill_count = s.kill_count;
187 	kill_count_ok = s.kill_count_ok;
188 	p_shots_fired = s.p_shots_fired;
189 	s_shots_fired = s.s_shots_fired;
190 
191 	p_shots_hit = s.p_shots_hit;
192 	s_shots_hit = s.s_shots_hit;
193 
194 	p_bonehead_hits = s.p_bonehead_hits;
195 	s_bonehead_hits = s.s_bonehead_hits;
196 	bonehead_kills = s.bonehead_kills;
197 
198 	missions_flown = s.missions_flown;
199 	flight_time = s.flight_time;
200 	last_flown = s.last_flown;
201 	last_backup = s.last_backup;
202 
203 	m_medal_earned = s.m_medal_earned;
204 	m_promotion_earned = s.m_promotion_earned;
205 	m_badge_earned = s.m_badge_earned;
206 
207 	m_score = s.m_score;
208 	memcpy(m_kills, s.m_kills, MAX_SHIP_CLASSES * sizeof(int));
209 	memcpy(m_okKills, s.m_okKills, MAX_SHIP_CLASSES * sizeof(int));
210 	m_kill_count = s.m_kill_count;
211 	m_kill_count_ok = s.m_kill_count_ok;
212 	m_assists = s.m_assists;
213 	mp_shots_fired = s.mp_shots_fired;
214 	ms_shots_fired = s.ms_shots_fired;
215 	mp_shots_hit = s.mp_shots_hit;
216 	ms_shots_hit = s.ms_shots_hit;
217 	mp_bonehead_hits = s.mp_bonehead_hits;
218 	ms_bonehead_hits = s.ms_bonehead_hits;
219 	m_bonehead_kills = s.m_bonehead_kills;
220 	m_player_deaths = s.m_player_deaths;
221 
222 	memcpy(m_dogfight_kills, s.m_dogfight_kills, MAX_PLAYERS * sizeof(int));
223 }
224 
225 #ifndef NDEBUG
226 //XSTR:OFF
scoring_eval_harbison(ship * shipp)227 void scoring_eval_harbison( ship *shipp )
228 {
229 	FILE *fp;
230 
231 	if ( !stricmp(shipp->ship_name, "alpha 2") && (!stricmp(Game_current_mission_filename, "demo01") || !stricmp(Game_current_mission_filename, "sm1-01")) ) {
232 		int death_count;
233 
234 		fp = fopen("i:\\volition\\cww\\harbison.txt", "r+t");
235 		if ( !fp )
236 			return;
237 		fscanf(fp, "%d", &death_count );
238 		death_count++;
239 		fseek(fp, 0, SEEK_SET);
240 		fprintf(fp, "%d\n", death_count);
241 		fclose(fp);
242 	}
243 }
244 //XSTR:ON
245 #endif
246 
247 // initialize the Player's mission-based stats before he goes into a mission
scoring_level_init(scoring_struct * scp)248 void scoring_level_init( scoring_struct *scp )
249 {
250 	scp->m_medal_earned = -1;		// hasn't earned a medal yet
251 	scp->m_promotion_earned = -1;
252 	scp->m_badge_earned = -1;
253 	scp->m_score = 0;
254 	scp->m_assists = 0;
255 	scp->mp_shots_fired = 0;
256 	scp->mp_shots_hit = 0;
257 	scp->ms_shots_fired = 0;
258 	scp->ms_shots_hit = 0;
259 
260 	scp->mp_bonehead_hits=0;
261 	scp->ms_bonehead_hits=0;
262 	scp->m_bonehead_kills=0;
263 
264 	memset(scp->m_kills, 0, MAX_SHIP_CLASSES * sizeof(int));
265 	memset(scp->m_okKills, 0, MAX_SHIP_CLASSES * sizeof(int));
266 
267 	scp->m_kill_count = 0;
268 	scp->m_kill_count_ok = 0;
269 
270 	scp->m_player_deaths = 0;
271 
272 	memset(scp->m_dogfight_kills, 0, MAX_PLAYERS * sizeof(int));
273 
274 	if (The_mission.ai_profile != NULL) {
275 		Kill_percentage = The_mission.ai_profile->kill_percentage_scale[Game_skill_level];
276 		Assist_percentage = The_mission.ai_profile->assist_percentage_scale[Game_skill_level];
277 	} else {
278 		Kill_percentage = 0.30f;
279 		Assist_percentage = 0.15f;
280 	}
281 }
282 
scoring_eval_rank(scoring_struct * sc)283 void scoring_eval_rank( scoring_struct *sc )
284 {
285 	int i, score, new_rank, old_rank;
286 
287 	old_rank = sc->rank;
288 	new_rank = old_rank;
289 
290 	// first check to see if the promotion flag is set -- if so, return the new rank
291 	if ( Player->flags & PLAYER_FLAGS_PROMOTED ) {
292 
293 		// if the player does indeed get promoted, we should change his mission score
294 		// to reflect the difference between all time and new rank score
295 		if ( old_rank < MAX_FREESPACE2_RANK ) {
296 			new_rank++;
297 			if ( (sc->m_score + sc->score) < Ranks[new_rank].points )
298 				sc->m_score = (Ranks[new_rank].points - sc->score);
299 		}
300 	} else {
301 		// we get here only if player wasn't promoted automatically.
302 		// it is possible to get a negative mission score but that will
303 		// never result in a degradation
304 		score = sc->m_score + sc->score;
305 		for (i=old_rank + 1; i<NUM_RANKS; i++) {
306 			if ( score >= Ranks[i].points )
307 				new_rank = i;
308 		}
309 	}
310 
311 	// if the ranks do not match, then "grant" the new rank
312 	if ( old_rank != new_rank ) {
313 		Assert( new_rank >= 0 );
314 		sc->m_promotion_earned = new_rank;
315 		sc->rank = new_rank;
316 	}
317 }
318 
319 // function to evaluate whether or not a new badge is going to be awarded.  This function returns
320 // which medal is awarded.
scoring_eval_badges(scoring_struct * sc)321 void scoring_eval_badges(scoring_struct *sc)
322 {
323 	int i, total_kills;
324 
325 	// to determine badges, we count kills based on fighter/bomber types.  We must count kills in
326 	// all time stats + current mission stats.  And, only for enemy fighters/bombers
327 	total_kills = 0;
328 	for (i = 0; i < MAX_SHIP_CLASSES; i++ ) {
329 		if ( (Ship_info[i].flags & SIF_FIGHTER) || (Ship_info[i].flags & SIF_BOMBER) ) {
330 			total_kills += sc->m_okKills[i];
331 			total_kills += sc->kills[i];
332 		}
333 	}
334 
335 	// total_kills should now reflect the number of kills on hostile fighters/bombers.  Check this number
336 	// against badge kill numbers, and return the badge index if we would get a new one.
337 	int badge = -1;
338 	int last_badge_kills = 0;
339 	for (i = 0; i < Num_medals; i++ ) {
340 		if ( total_kills >= Medals[i].kills_needed
341 			&& Medals[i].kills_needed > last_badge_kills
342 			&& Medals[i].kills_needed > 0 )
343 		{
344 			last_badge_kills = Medals[i].kills_needed;
345 			badge = i;
346 		}
347 	}
348 
349 	// if player could have a badge based on kills, and doesn't currently have this badge, then
350 	// return the badge id.
351 	if ( (badge != -1 ) && (sc->medal_counts[badge] < 1) ) {
352 		sc->medal_counts[badge] = 1;
353 		sc->m_badge_earned = badge;
354 	}
355 }
356 
357 // central point for dealing with accepting the score for a misison.
scoring_do_accept(scoring_struct * score)358 void scoring_do_accept(scoring_struct *score)
359 {
360 	int idx;
361 
362 	// do rank, badges, and medals first since they require the alltime stuff
363 	// to not be updated yet.
364 
365 	// do medal stuff
366 	if ( score->m_medal_earned != -1 ){
367 		score->medal_counts[score->m_medal_earned]++;
368 	}
369 
370 	// return when in training mission.  We can grant a medal in training, but don't
371 	// want to calculate any other statistics.
372 	if (The_mission.game_type == MISSION_TYPE_TRAINING){
373 		return;
374 	}
375 
376 	scoring_eval_rank(score);
377 	scoring_eval_badges(score);
378 
379 	score->kill_count += score->m_kill_count;
380 	score->kill_count_ok += score->m_kill_count_ok;
381 
382 	score->score += score->m_score;
383 	score->assists += score->m_assists;
384 	score->p_shots_fired += score->mp_shots_fired;
385 	score->s_shots_fired += score->ms_shots_fired;
386 
387 	score->p_shots_hit += score->mp_shots_hit;
388 	score->s_shots_hit += score->ms_shots_hit;
389 
390 	score->p_bonehead_hits += score->mp_bonehead_hits;
391 	score->s_bonehead_hits += score->ms_bonehead_hits;
392 	score->bonehead_kills += score->m_bonehead_kills;
393 
394 	for(idx=0;idx<MAX_SHIP_CLASSES;idx++){
395 		score->kills[idx] = (int)(score->kills[idx] + score->m_okKills[idx]);
396 	}
397 
398 	// add in mission time
399 	score->flight_time += (unsigned int)f2fl(Missiontime);
400 	score->last_backup = score->last_flown;
401 	score->last_flown = (_fs_time_t)time(NULL);
402 	score->missions_flown++;
403 }
404 
405 // backout the score for a mission.  This function gets called when the player chooses to refly a misison
406 // after debriefing
scoring_backout_accept(scoring_struct * score)407 void scoring_backout_accept( scoring_struct *score )
408 {
409 	int idx;
410 
411 	// if a badge was earned, take it back
412 	if ( score->m_badge_earned != -1){
413 		score->medal_counts[score->m_badge_earned] = 0;
414 	}
415 
416 	// return when in training mission.  We can grant a medal in training, but don't
417 	// want to calculate any other statistics.
418 	if (The_mission.game_type == MISSION_TYPE_TRAINING){
419 		return;
420 	}
421 
422 	score->kill_count -= score->m_kill_count;
423 	score->kill_count_ok -= score->m_kill_count_ok;
424 
425 	score->score -= score->m_score;
426 	score->assists -= score->m_assists;
427 	score->p_shots_fired -= score->mp_shots_fired;
428 	score->s_shots_fired -= score->ms_shots_fired;
429 
430 	score->p_shots_hit -= score->mp_shots_hit;
431 	score->s_shots_hit -= score->ms_shots_hit;
432 
433 	score->p_bonehead_hits -= score->mp_bonehead_hits;
434 	score->s_bonehead_hits -= score->ms_bonehead_hits;
435 	score->bonehead_kills -= score->m_bonehead_kills;
436 
437 	for(idx=0;idx<MAX_SHIP_CLASSES;idx++){
438 		score->kills[idx] = (unsigned short)(score->kills[idx] - score->m_okKills[idx]);
439 	}
440 
441 	// if the player was given a medal, take it back
442 	if ( score->m_medal_earned != -1 ) {
443 		score->medal_counts[score->m_medal_earned]--;
444 		Assert( score->medal_counts[score->m_medal_earned] >= 0 );
445 	}
446 
447 	// if the player was promoted, take it back
448 	if ( score->m_promotion_earned != -1) {
449 		score->rank--;
450 		Assert( score->rank >= 0 );
451 	}
452 
453 	score->flight_time -= (unsigned int)f2fl(Missiontime);
454 	score->last_flown = score->last_backup;
455 	score->missions_flown--;
456 }
457 
458 // merge any mission stats accumulated into the alltime stats (as well as updating per campaign stats)
scoring_level_close(int accepted)459 void scoring_level_close(int accepted)
460 {
461 	// want to calculate any other statistics.
462 	if (The_mission.game_type == MISSION_TYPE_TRAINING){
463 		// call scoring_do_accept
464 		// this will grant any potential medals and then early bail, and
465 		// then we will early bail
466 		scoring_do_accept(&Player->stats);
467 		Pilot.update_stats(&Player->stats, true);
468 		return;
469 	}
470 
471 	if(accepted){
472 		// apply mission stats for all players in the game
473 		int idx;
474 		scoring_struct *sc;
475 
476 		if(Game_mode & GM_MULTIPLAYER){
477 			nprintf(("Network","Storing stats for all players now\n"));
478 			for(idx=0;idx<MAX_PLAYERS;idx++){
479 				if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx])){
480 					// get the scoring struct
481 					sc = &Net_players[idx].m_player->stats;
482 					scoring_do_accept( sc );
483 
484 					if (Net_player == &Net_players[idx]) {
485 						Pilot.update_stats(sc);
486 					}
487 				}
488 			}
489 		} else {
490 			nprintf(("General","Storing stats now\n"));
491 			scoring_do_accept( &Player->stats );
492 		}
493 
494 		// If this mission doesn't allow promotion or badges
495 		// then be sure that these don't get done.  Don't allow promotions or badges when
496 		// playing normally and not in a campaign.
497 		if ( (The_mission.flags & MISSION_FLAG_NO_PROMOTION) || ((Game_mode & GM_NORMAL) && !(Game_mode & GM_CAMPAIGN_MODE)) ) {
498 			if ( Player->stats.m_promotion_earned != -1) {
499 				Player->stats.rank--;
500 				Player->stats.m_promotion_earned = -1;
501 			}
502 
503 			// if a badge was earned, take it back
504 			if ( Player->stats.m_badge_earned != -1){
505 				Player->stats.medal_counts[Player->stats.m_badge_earned] = 0;
506 				Player->stats.m_badge_earned = -1;
507 			}
508 		}
509 
510 		if ( !(Game_mode & GM_MULTIPLAYER) ) {
511 			Pilot.update_stats(&Player->stats);
512 		}
513 	}
514 }
515 
516 // STATS damage, assists recording stuff
scoring_add_damage(object * ship_objp,object * other_obj,float damage)517 void scoring_add_damage(object *ship_objp,object *other_obj,float damage)
518 {
519 	int found_slot, signature;
520 	int lowest_index,idx;
521 	object *use_obj;
522 	ship *sp;
523 
524 	// multiplayer clients bail here
525 	if(MULTIPLAYER_CLIENT){
526 		return;
527 	}
528 
529 	// if we have no other object, bail
530 	if(other_obj == NULL){
531 		return;
532 	}
533 
534 	// for player kill/assist evaluation, we have to know exactly how much damage really mattered. For example, if
535 	// a ship had 1 hit point left, and the player hit it with a nuke, it doesn't matter that it did 10,000,000
536 	// points of damage, only that 1 point would count
537 	float actual_damage = 0.0f;
538 
539 	// other_obj might not always be the parent of other_obj (in the case of debug code for sure).  See
540 	// if the other_obj has a parent, and if so, use the parent.  If no parent, see if other_obj is a ship
541 	// and if so, use that ship.
542 	if ( other_obj->parent != -1 ){
543 		use_obj = &Objects[other_obj->parent];
544 		signature = use_obj->signature;
545 	} else {
546 		signature = other_obj->signature;
547 		use_obj = other_obj;
548 	}
549 
550 	// don't count damage done to a ship by himself
551 	if(use_obj == ship_objp){
552 		return;
553 	}
554 
555 	// get a pointer to the ship and add the actual amount of damage done to it
556 	// get the ship object, and determine the _actual_ amount of damage done
557 	sp = &Ships[ship_objp->instance];
558 	// see comments at beginning of function
559 	if(ship_objp->hull_strength < 0.0f){
560 		actual_damage = damage + ship_objp->hull_strength;
561 	} else {
562 		actual_damage = damage;
563 	}
564 	if(actual_damage < 0.0f){
565 		actual_damage = 0.0f;
566 	}
567 	sp->total_damage_received += actual_damage;
568 
569 	// go through and clear out all old damagers
570 	for(idx=0; idx<MAX_DAMAGE_SLOTS; idx++){
571 		if((sp->damage_ship_id[idx] >= 0) && (ship_get_by_signature(sp->damage_ship_id[idx]) < 0)){
572 			sp->damage_ship_id[idx] = -1;
573 			sp->damage_ship[idx] = 0;
574 		}
575 	}
576 
577 	// only evaluate possible kill/assist numbers if the hitting object (use_obj) is a piloted ship (ie, ignore asteroids, etc)
578 	// don't store damage a ship may do to himself
579 	if((ship_objp->type == OBJ_SHIP) && (use_obj->type == OBJ_SHIP)){
580 		found_slot = 0;
581 		// try and find an open slot
582 		for(idx=0;idx<MAX_DAMAGE_SLOTS;idx++){
583 			// if this ship object doesn't exist anymore, use the slot
584 			if((sp->damage_ship_id[idx] == -1) || (ship_get_by_signature(sp->damage_ship_id[idx]) < 0) || (sp->damage_ship_id[idx] == signature) ){
585 				found_slot = 1;
586 				break;
587 			}
588 		}
589 
590 		// if not found (implying all slots are taken), then find the slot with the lowest damage % and use that
591 		if(!found_slot){
592 			lowest_index = 0;
593 			for(idx=0;idx<MAX_DAMAGE_SLOTS;idx++){
594 				if(sp->damage_ship[idx] < sp->damage_ship[lowest_index]){
595 				   lowest_index = idx;
596 				}
597 			}
598 		} else {
599 			lowest_index = idx;
600 		}
601 
602 		// fill in the slot damage and damager-index
603 		if(found_slot){
604 			sp->damage_ship[lowest_index] += actual_damage;
605 		} else {
606 			sp->damage_ship[lowest_index] = actual_damage;
607 		}
608 		sp->damage_ship_id[lowest_index] = signature;
609 	}
610 }
611 
612 char Scoring_debug_text[4096];
613 
614 // evaluate a kill on a ship
scoring_eval_kill(object * ship_objp)615 int scoring_eval_kill(object *ship_objp)
616 {
617 	float max_damage_pct;		// the pct% of total damage the max damage object did
618 	int max_damage_index;		// the index into the dying ship's damage_ship[] array corresponding the greatest amount of damage
619 	int killer_sig;				// signature of the guy getting credit for the kill (or -1 if none)
620 	int idx,net_player_num;
621 	player *plr;					// pointer to a player struct if it was a player who got the kill
622 	net_player *net_plr = NULL;
623 	ship *dead_ship;				// the ship which was killed
624 	net_player *dead_plr = NULL;
625 	float scoring_scale_by_damage = 1;	// percentage to scale the killer's score by if we score based on the amount of damage caused
626 	int kill_score, assist_score;
627 	bool is_enemy_player = false;		// true if the player just killed an enemy player ship
628 
629 
630 	// multiplayer clients bail here
631 	if(MULTIPLAYER_CLIENT){
632 		return -1;
633 	}
634 
635 	// we don't evaluate kills on anything except ships
636 	if(ship_objp->type != OBJ_SHIP){
637 		return -1;
638 	}
639 	if((ship_objp->instance < 0) || (ship_objp->instance >= MAX_SHIPS)){
640 		return -1;
641 	}
642 
643 	// assign the dead ship
644 	dead_ship = &Ships[ship_objp->instance];
645 
646 	// evaluate player deaths
647 	if(Game_mode & GM_MULTIPLAYER){
648 		net_player_num = multi_find_player_by_object(ship_objp);
649 		if(net_player_num != -1){
650 			Net_players[net_player_num].m_player->stats.m_player_deaths++;
651 			nprintf(("Network","Setting player %s deaths to %d\n",Net_players[net_player_num].m_player->callsign,Net_players[net_player_num].m_player->stats.m_player_deaths));
652 			dead_plr = &Net_players[net_player_num];
653 			is_enemy_player = true;
654 		}
655 	} else {
656 		if(ship_objp == Player_obj){
657 			Player->stats.m_player_deaths++;
658 		}
659 	}
660 
661 
662 #ifndef NDEBUG
663 	scoring_eval_harbison( dead_ship );
664 #endif
665 
666 	net_player_num = -1;
667 
668 	// clear out invalid damager ships
669 	for(idx=0; idx<MAX_DAMAGE_SLOTS; idx++){
670 		if((dead_ship->damage_ship_id[idx] >= 0) && (ship_get_by_signature(dead_ship->damage_ship_id[idx]) < 0)){
671 			dead_ship->damage_ship[idx] = 0.0f;
672 			dead_ship->damage_ship_id[idx] = -1;
673 		}
674 	}
675 
676 	// determine which object did the most damage to the dying object, and how much damage that was
677 	max_damage_index = -1;
678 	for(idx=0;idx<MAX_DAMAGE_SLOTS;idx++){
679 		// bogus ship
680 		if(dead_ship->damage_ship_id[idx] < 0){
681 			continue;
682 		}
683 
684 		// if this slot did more damage then the next highest slot
685 		if((max_damage_index == -1) || (dead_ship->damage_ship[idx] > dead_ship->damage_ship[max_damage_index])){
686 			max_damage_index = idx;
687 		}
688 	}
689 
690 	// doh
691 	if((max_damage_index < 0) || (max_damage_index >= MAX_DAMAGE_SLOTS)){
692 		return -1;
693 	}
694 
695 	// the pct of total damage applied to this ship
696 	max_damage_pct = dead_ship->damage_ship[max_damage_index] / dead_ship->total_damage_received;
697 
698     CLAMP(max_damage_pct, 0.0f, 1.0f);
699 
700 	// only evaluate if the max damage % is high enough to record a kill and it was done by a valid object
701 	if((max_damage_pct >= Kill_percentage) && (dead_ship->damage_ship_id[max_damage_index] >= 0)){
702 		// set killer_sig for this ship to the signature of the guy who gets credit for the kill
703 		killer_sig = dead_ship->damage_ship_id[max_damage_index];
704 
705 		// set the scale value if we only award 100% score for 100% damage
706 		if (The_mission.ai_profile->flags & AIPF_KILL_SCORING_SCALES_WITH_DAMAGE) {
707 			scoring_scale_by_damage = max_damage_pct;
708 		}
709 
710 		// null this out for now
711 		plr = NULL;
712 		net_plr = NULL;
713 
714 		// get the player (whether single or multiplayer)
715 		net_player_num = -1;
716 
717 		if(Game_mode & GM_MULTIPLAYER){
718 			net_player_num = multi_find_player_by_signature(killer_sig);
719 			if(net_player_num != -1){
720 				plr = Net_players[net_player_num].m_player;
721 				net_plr = &Net_players[net_player_num];
722 			}
723 		} else {
724 			if(Objects[Player->objnum].signature == killer_sig){
725 				plr = Player;
726 			}
727 		}
728 
729 		// if we found a valid player, evaluate some kill details
730 		if(plr != NULL){
731 			int si_index;
732 
733 			// bogus
734 			if((plr->objnum < 0) || (plr->objnum >= MAX_OBJECTS)){
735 				return -1;
736 			}
737 
738 			// get the ship info index of the ship type of this kill.  we need to take ship
739 			// copies into account here.
740 			si_index = dead_ship->ship_info_index;
741 			if (Ship_info[si_index].flags & SIF_SHIP_COPY)
742 			{
743 				char temp[NAME_LENGTH];
744 				strcpy_s(temp, Ship_info[si_index].name);
745 				end_string_at_first_hash_symbol(temp);
746 
747 				// Goober5000 - previous error checking guarantees that this will be >= 0
748 				si_index = ship_info_lookup(temp);
749 			}
750 
751 			// if he killed a guy on his own team increment his bonehead kills
752 			if((Ships[Objects[plr->objnum].instance].team == dead_ship->team) && !MULTI_DOGFIGHT ){
753 				if (!(The_mission.flags & MISSION_FLAG_NO_TRAITOR)) {
754 					plr->stats.m_bonehead_kills++;
755 					kill_score = -(int)(dead_ship->score * scoring_get_scale_factor());
756 					plr->stats.m_score += kill_score;
757 
758 					if(net_plr != NULL ) {
759 						multi_team_maybe_add_score(-(dead_ship->score), net_plr->p_info.team);
760 					}
761 				}
762 			}
763 			// otherwise increment his valid kill count and score
764 			else {
765 				// dogfight mode
766 				if(MULTI_DOGFIGHT && (multi_find_player_by_object(ship_objp) < 0)){
767 					// don't add a kill for dogfight kills on non-players
768 				} else {
769 					plr->stats.m_okKills[si_index]++;
770 					plr->stats.m_kill_count_ok++;
771 
772 					// only computer controlled enemies should scale with difficulty
773 					if (is_enemy_player) {
774 						kill_score = (int)(dead_ship->score * scoring_scale_by_damage);
775 					}
776 					else {
777 						kill_score = (int)(dead_ship->score * scoring_get_scale_factor() * scoring_scale_by_damage);
778 					}
779 
780 
781 					plr->stats.m_score += kill_score;
782 					hud_gauge_popup_start(HUD_KILLS_GAUGE);
783 
784 #ifdef SCORING_DEBUG
785 					char kill_score_text[1024] = "";
786 					sprintf(kill_score_text, "SCORING : %s killed a ship worth %d points and gets %d pts for the kill\n", plr->callsign, dead_ship->score, kill_score);
787 					if (MULTIPLAYER_MASTER) {
788 						send_game_chat_packet(Net_player, kill_score_text, MULTI_MSG_ALL);
789 					}
790 					HUD_printf(kill_score_text);
791 					mprintf((kill_score_text));
792 #endif
793 
794 					// multiplayer
795 					if(net_plr != NULL){
796 						multi_team_maybe_add_score(dead_ship->score , net_plr->p_info.team);
797 
798 						// award teammates % of score value for big ship kills
799 						// not in dogfight tho
800 						// and not if there is no assist threshold (as otherwise assists could get higher scores than kills)
801 						if (!(Netgame.type_flags & NG_TYPE_DOGFIGHT) && (Ship_info[dead_ship->ship_info_index].flags & (SIF_BIG_SHIP | SIF_HUGE_SHIP))) {
802 							for (idx=0; idx<MAX_PLAYERS; idx++) {
803 								if (MULTI_CONNECTED(Net_players[idx]) && (Net_players[idx].p_info.team == net_plr->p_info.team) && (&Net_players[idx] != net_plr)) {
804 									assist_score = (int)(dead_ship->score * The_mission.ai_profile->assist_award_percentage_scale[Game_skill_level]);
805 									Net_players[idx].m_player->stats.m_score += assist_score;
806 
807 #ifdef SCORING_DEBUG
808 									// DEBUG CODE TO TEST NEW SCORING
809 									char score_text[1024] = "";
810 									sprintf(score_text, "SCORING : All team mates get %d pts for helping kill the capship\n", assist_score);
811 									send_game_chat_packet(Net_player, score_text, MULTI_MSG_ALL);
812 									HUD_printf(score_text);
813 									mprintf((score_text));
814 #endif
815 								}
816 							}
817 						}
818 
819 						// death message
820 						if((Net_player != NULL) && (Net_player->flags & NETINFO_FLAG_AM_MASTER) && (net_plr != NULL) && (dead_plr != NULL) && (net_plr->m_player != NULL) && (dead_plr->m_player != NULL)){
821 							char dead_text[1024] = "";
822 
823 							sprintf(dead_text, "%s gets the kill for %s", net_plr->m_player->callsign, dead_plr->m_player->callsign);
824 							send_game_chat_packet(Net_player, dead_text, MULTI_MSG_ALL, NULL, NULL, 2);
825 							HUD_printf(dead_text);
826 						}
827 					}
828 				}
829 			}
830 
831 			// increment his all-encompassing kills
832 			plr->stats.m_kills[si_index]++;
833 			plr->stats.m_kill_count++;
834 
835 			// update everyone on this guy's kills if this is multiplayer
836 			if(MULTIPLAYER_MASTER && (net_player_num != -1)){
837 				// send appropriate stats
838 				if(Netgame.type_flags & NG_TYPE_DOGFIGHT){
839 					// evaluate dogfight kills
840 					multi_df_eval_kill(&Net_players[net_player_num], ship_objp);
841 
842 					// update stats
843 					send_player_stats_block_packet(&Net_players[net_player_num], STATS_DOGFIGHT_KILLS);
844 				} else {
845 					send_player_stats_block_packet(&Net_players[net_player_num], STATS_MISSION_KILLS);
846 				}
847 			}
848 		}
849 	} else {
850 		// set killer_sig for this ship to -1, indicating no one got the kill for it
851 		killer_sig = -1;
852 	}
853 
854 	// pass in the guy who got the credit for the kill (if any), so that he doesn't also
855 	// get credit for an assist
856 	scoring_eval_assists(dead_ship,killer_sig, is_enemy_player);
857 
858 #ifdef SCORING_DEBUG
859 
860 	if (Game_mode & GM_MULTIPLAYER) {
861 		char buf[256];
862 		sprintf(Scoring_debug_text, "SCORING : %s killed.\nDamage by ship:\n\n", Ship_info[dead_ship->ship_info_index].name);
863 
864 		// show damage done by player
865 		for (int i=0; i<MAX_DAMAGE_SLOTS; i++) {
866 			int net_player_num = multi_find_player_by_signature(dead_ship->damage_ship_id[i]);
867 			if (net_player_num != -1) {
868 				plr = Net_players[net_player_num].m_player;
869 				sprintf(buf, "%s: %f", plr->callsign, dead_ship->damage_ship[i]);
870 
871 				if (dead_ship->damage_ship_id[i] == killer_sig ) {
872 					strcat_s(buf, "  KILLER\n");
873 				} else {
874 					strcat_s(buf, "\n");
875 				}
876 
877 				strcat_s(Scoring_debug_text, buf);
878 			}
879 		}
880 		mprintf ((Scoring_debug_text));
881 	}
882 #endif
883 
884 	return max_damage_index;
885 }
886 
887 //evaluate a kill on a weapon, right now this is only called on bombs. -Halleck
scoring_eval_kill_on_weapon(object * weapon_obj,object * other_obj)888 int scoring_eval_kill_on_weapon(object *weapon_obj, object *other_obj) {
889 	float max_damage_pct;		// the pct% of total damage the max damage object did
890 	int max_damage_index;		// the index into the dying ship's damage_ship[] array corresponding the greatest amount of damage
891 	int killer_sig;				// signature of the guy getting credit for the kill (or -1 if none)
892 	int idx,net_player_num;
893 	player *plr;					// pointer to a player struct if it was a player who got the kill
894 	net_player *net_plr = NULL;
895 	net_player *dead_plr = NULL;
896 	float scoring_scale_by_damage = 1;	// percentage to scale the killer's score by if we score based on the amount of damage caused
897 	int kill_score;
898 
899 	weapon *dead_wp;						// the weapon that was killed
900 	weapon_info *dead_wip;				// info on the weapon that was killed
901 	dead_wp = &Weapons[weapon_obj->instance]; //assign the dead weapon
902 	dead_wip = &Weapon_info[dead_wp->weapon_info_index];
903 
904 	// multiplayer clients bail here
905 	if(MULTIPLAYER_CLIENT){
906 		return -1;
907 	}
908 
909 	// we don't evaluate kills on anything except weapons
910 	if(weapon_obj->type != OBJ_WEAPON){
911 		return -1;
912 	}
913 
914 	// we don't evaluate kills on anything except bombs, currently. -Halleck
915 	if(!(dead_wip->wi_flags & WIF_BOMB))  {
916 		return -1;
917 	}
918 
919 	if((weapon_obj->instance < 0) || (weapon_obj->instance >= MAX_WEAPONS)){
920 		return -1;
921 	}
922 
923 	// clear out invalid damager ships
924 	for(idx=0; idx<MAX_DAMAGE_SLOTS; idx++){
925 		if((dead_wp->damage_ship_id[idx] >= 0) && (ship_get_by_signature(dead_wp->damage_ship_id[idx]) < 0)){
926 			dead_wp->damage_ship[idx] = 0.0f;
927 			dead_wp->damage_ship_id[idx] = -1;
928 		}
929 	}
930 
931 	// determine which object did the most damage to the dying object, and how much damage that was
932 	max_damage_index = -1;
933 	for(idx=0;idx<MAX_DAMAGE_SLOTS;idx++){
934 		// bogus ship
935 		if(dead_wp->damage_ship_id[idx] < 0){
936 			continue;
937 		}
938 
939 		// if this slot did more damage then the next highest slot
940 		if((max_damage_index == -1) || (dead_wp->damage_ship[idx] > dead_wp->damage_ship[max_damage_index])){
941 			max_damage_index = idx;
942 		}
943 	}
944 
945 	// doh
946 	if((max_damage_index < 0) || (max_damage_index >= MAX_DAMAGE_SLOTS)){
947 		return -1;
948 	}
949 
950 	// the pct of total damage applied to this ship
951 	max_damage_pct = dead_wp->damage_ship[max_damage_index] / dead_wp->total_damage_received;
952 
953 	CLAMP(max_damage_pct, 0.0f, 1.0f);
954 
955 	// only evaluate if the max damage % is high enough to record a kill and it was done by a valid object
956 	if((max_damage_pct >= Kill_percentage) && (dead_wp->damage_ship_id[max_damage_index] >= 0)){
957 		// set killer_sig for this ship to the signature of the guy who gets credit for the kill
958 		killer_sig = dead_wp->damage_ship_id[max_damage_index];
959 
960 		// set the scale value if we only award 100% score for 100% damage
961 		if (The_mission.ai_profile->flags & AIPF_KILL_SCORING_SCALES_WITH_DAMAGE) {
962 			scoring_scale_by_damage = max_damage_pct;
963 		}
964 
965 		// null this out for now
966 		plr = NULL;
967 		net_plr = NULL;
968 
969 		// get the player (whether single or multiplayer)
970 		net_player_num = -1;
971 
972 		if(Game_mode & GM_MULTIPLAYER){
973 			net_player_num = multi_find_player_by_signature(killer_sig);
974 			if(net_player_num != -1){
975 				plr = Net_players[net_player_num].m_player;
976 				net_plr = &Net_players[net_player_num];
977 			}
978 		} else {
979 			if(Objects[Player->objnum].signature == killer_sig){
980 				plr = Player;
981 			}
982 		}
983 
984 		// if we found a valid player, evaluate some kill details
985 		if(plr != NULL){
986 			//int si_index;
987 
988 			// bogus
989 			if((plr->objnum < 0) || (plr->objnum >= MAX_OBJECTS)){
990 				return -1;
991 			}
992 
993 			// get the ship info index of the ship type of this kill.  we need to take ship
994 			// copies into account here.
995 			//si_index = dead_wp->weapon_info_index;
996 
997 			// if he killed a guy on his own team increment his bonehead kills
998 			if((Ships[Objects[plr->objnum].instance].team == dead_wp->team) && !MULTI_DOGFIGHT ){
999 				if (!(The_mission.flags & MISSION_FLAG_NO_TRAITOR)) {
1000 					plr->stats.m_bonehead_kills++;
1001 					kill_score = -(int)(dead_wip->score * scoring_get_scale_factor());
1002 					plr->stats.m_score += kill_score;
1003 
1004 					if(net_plr != NULL ) {
1005 						multi_team_maybe_add_score(-(dead_wip->score), net_plr->p_info.team);
1006 					}
1007 				}
1008 			}
1009 			// otherwise increment his valid kill count and score
1010 			else {
1011 				// dogfight mode
1012 				if(MULTI_DOGFIGHT && (multi_find_player_by_object(weapon_obj) < 0)){
1013 					// don't add a kill for dogfight kills on non-players
1014 				} else {
1015 
1016 					// bombs don't scale with difficulty at the moment. If we change this we want to *scoring_get_scale_factor() too
1017 					kill_score = (int)(dead_wip->score * scoring_scale_by_damage);
1018 
1019 					plr->stats.m_score += kill_score;
1020 					hud_gauge_popup_start(HUD_KILLS_GAUGE);
1021 
1022 #ifdef SCORING_DEBUG
1023 					char kill_score_text[1024] = "";
1024 					sprintf(kill_score_text, "SCORING : %s killed a ship worth %i points and gets %i pts for the kill", plr->callsign, dead_wip->score, kill_score);
1025 					if (MULTIPLAYER_MASTER) {
1026 						send_game_chat_packet(Net_player, kill_score_text, MULTI_MSG_ALL);
1027 					}
1028 					HUD_printf(kill_score_text);
1029 					mprintf((kill_score_text));
1030 #endif
1031 
1032 					// multiplayer
1033 					if(net_plr != NULL){
1034 						multi_team_maybe_add_score(dead_wip->score , net_plr->p_info.team);
1035 
1036 						// award teammates % of score value for big ship kills
1037 						// not in dogfight tho
1038 						// and not if there is no assist threshold (as otherwise assists could get higher scores than kills)
1039 						/* Below is N/A. -Halleck
1040 						if (!(Netgame.type_flags & NG_TYPE_DOGFIGHT) && (Ship_info[dead_wp->ship_info_index].flags & (SIF_BIG_SHIP | SIF_HUGE_SHIP))) {
1041 							for (idx=0; idx<MAX_PLAYERS; idx++) {
1042 								if (MULTI_CONNECTED(Net_players[idx]) && (Net_players[idx].p_info.team == net_plr->p_info.team) && (&Net_players[idx] != net_plr)) {
1043 									assist_score = (int)(dead_wip->score * The_mission.ai_profile->assist_award_percentage_scale[Game_skill_level]);
1044 									Net_players[idx].m_player->stats.m_score += assist_score;
1045 
1046 #ifdef SCORING_DEBUG
1047 									// DEBUG CODE TO TEST NEW SCORING
1048 									char score_text[1024] = "";
1049 									sprintf(score_text, "SCORING : All team mates get %d pts for helping kill the capship", assist_score);
1050 									send_game_chat_packet(Net_player, score_text, MULTI_MSG_ALL);
1051 									HUD_printf(score_text);
1052 									mprintf((score_text));
1053 #endif
1054 								}
1055 							}
1056 						}
1057 						*/
1058 
1059 						// death message
1060 						if((Net_player != NULL) && (Net_player->flags & NETINFO_FLAG_AM_MASTER) && (net_plr != NULL) && (dead_plr != NULL) && (net_plr->m_player != NULL) && (dead_plr->m_player != NULL)){
1061 							char dead_text[1024] = "";
1062 
1063 							sprintf(dead_text, "%s gets the kill for %s", net_plr->m_player->callsign, dead_plr->m_player->callsign);
1064 							send_game_chat_packet(Net_player, dead_text, MULTI_MSG_ALL, NULL, NULL, 2);
1065 							HUD_printf(dead_text);
1066 						}
1067 					}
1068 				}
1069 			}
1070 
1071 			// increment his all-encompassing kills
1072 			/*Not really a kill. -Halleck
1073 			plr->stats.m_kills[si_index]++;
1074 			plr->stats.m_kill_count++;
1075 
1076 			// update everyone on this guy's kills if this is multiplayer
1077 			if(MULTIPLAYER_MASTER && (net_player_num != -1)){
1078 				// send appropriate stats
1079 				if(Netgame.type_flags & NG_TYPE_DOGFIGHT){
1080 					// evaluate dogfight kills
1081 					multi_df_eval_kill(&Net_players[net_player_num], weapon_obj);
1082 
1083 					// update stats
1084 					send_player_stats_block_packet(&Net_players[net_player_num], STATS_DOGFIGHT_KILLS);
1085 				} else {
1086 					send_player_stats_block_packet(&Net_players[net_player_num], STATS_MISSION_KILLS);
1087 				}
1088 			}
1089 			*/
1090 		}
1091 	} else {
1092 		// set killer_sig for this ship to -1, indicating no one got the kill for it
1093 		killer_sig = -1;
1094 	}
1095 
1096 	// pass in the guy who got the credit for the kill (if any), so that he doesn't also
1097 	// get credit for an assist
1098 	/* TODO: ADD ASSIST CALC BACK IN. -Halleck
1099 	scoring_eval_assists(dead_wp,killer_sig, is_enemy_player);
1100 	*/
1101 
1102 #ifdef SCORING_DEBUG
1103 
1104 	if (Game_mode & GM_MULTIPLAYER) {
1105 		char buf[256];
1106 		sprintf(Scoring_debug_text, "SCORING : %s killed.\nDamage by ship:\n\n", dead_wip->name);
1107 
1108 		// show damage done by player
1109 		for (int i=0; i<MAX_DAMAGE_SLOTS; i++) {
1110 			int net_player_num = multi_find_player_by_signature(dead_wp->damage_ship_id[i]);
1111 			if (net_player_num != -1) {
1112 				plr = Net_players[net_player_num].m_player;
1113 				sprintf(buf, "%s: %f", plr->callsign, dead_wp->damage_ship[i]);
1114 
1115 				if (dead_wp->damage_ship_id[i] == killer_sig ) {
1116 					strcat_s(buf, "  KILLER\n");
1117 				} else {
1118 					strcat_s(buf, "\n");
1119 				}
1120 
1121 				strcat_s(Scoring_debug_text, buf);
1122 			}
1123 		}
1124 		mprintf ((Scoring_debug_text));
1125 	}
1126 #endif
1127 
1128 	return max_damage_index;
1129 }
1130 
1131 // kill_id is the object signature of the guy who got the credit for the kill (may be -1, if no one got it)
1132 // this is to ensure that you don't also get an assist if you get the kill.
scoring_eval_assists(ship * sp,int killer_sig,bool is_enemy_player)1133 void scoring_eval_assists(ship *sp,int killer_sig, bool is_enemy_player)
1134 {
1135 	int idx;
1136 	player *plr;
1137 	float scoring_scale_by_damage = 1;	// percentage to scale the score by if we score based on the amount of damage caused
1138 	int assist_score;
1139 	int net_player_num;
1140 	float scoring_scale_factor;
1141 
1142 
1143 	// multiplayer clients bail here
1144 	if(MULTIPLAYER_CLIENT){
1145 		return;
1146 	}
1147 
1148 	// evaluate each damage slot to see if it did enough to give the assis
1149 	for(idx=0;idx<MAX_DAMAGE_SLOTS;idx++){
1150 		// if this slot did enough damage to get an assist
1151 		if(((sp->damage_ship[idx]/sp->total_damage_received) >= Assist_percentage) || (The_mission.ai_profile->flags & AIPF_ASSIST_SCORING_SCALES_WITH_DAMAGE)){
1152 			// get the player which did this damage (if any)
1153 			plr = NULL;
1154 
1155 			// multiplayer
1156 			if(Game_mode & GM_MULTIPLAYER){
1157 				net_player_num = multi_find_player_by_signature(sp->damage_ship_id[idx]);
1158 				if(net_player_num != -1){
1159 					plr = Net_players[net_player_num].m_player;
1160 				}
1161 			}
1162 			// single player
1163 			else {
1164 				if(Objects[Player->objnum].signature == sp->damage_ship_id[idx]){
1165 					plr = Player;
1166 				}
1167 			}
1168 
1169 			// if we found a player, give him the assist if he attacks it
1170 			if ((plr != NULL) && (iff_x_attacks_y(Ships[Objects[plr->objnum].instance].team, sp->team)) && (killer_sig != Objects[plr->objnum].signature))
1171 			{
1172 				// player has to equal the threshold to get an assist
1173 				if ((sp->damage_ship[idx]/sp->total_damage_received) >= Assist_percentage) {
1174 					plr->stats.m_assists++;
1175 					nprintf(("Network","-==============GAVE PLAYER %s AN ASSIST=====================-\n",plr->callsign));
1176 				}
1177 
1178 				// Don't scale in TvT and dogfight
1179 				if (is_enemy_player) {
1180 					Assert(Game_mode & GM_MULTIPLAYER);
1181 					scoring_scale_factor = 1.0f;
1182 				}
1183 				else {
1184 					scoring_scale_factor = scoring_get_scale_factor();
1185 				}
1186 
1187 
1188 				// maybe award assist points based on damage
1189 				if (The_mission.ai_profile->flags & AIPF_ASSIST_SCORING_SCALES_WITH_DAMAGE) {
1190 					scoring_scale_by_damage = (sp->damage_ship[idx]/sp->total_damage_received);
1191 					assist_score = (int)(sp->score * scoring_scale_factor * scoring_scale_by_damage);
1192 					plr->stats.m_score += assist_score;
1193 				}
1194 				// otherwise give the points based on the percentage in the mission file
1195 				else {
1196 					assist_score = (int)(sp->score * sp->assist_score_pct * scoring_scale_factor );
1197 					plr->stats.m_score += assist_score;
1198 				}
1199 
1200 #ifdef SCORING_DEBUG
1201 
1202 				// DEBUG CODE TO TEST NEW SCORING
1203 				char score_text[1024] = "";
1204 				sprintf(score_text, "SCORING : %s gets %d pts for getting an assist\n", plr->callsign, assist_score);
1205 				if (MULTIPLAYER_MASTER) {
1206 					send_game_chat_packet(Net_player, score_text, MULTI_MSG_ALL);
1207 				}
1208 				HUD_printf(score_text);
1209 				mprintf ((score_text));
1210 #endif
1211 			}
1212 		}
1213 	}
1214 }
1215 
1216 // eval a hit on an object (for primary and secondary hit purposes)
scoring_eval_hit(object * hit_obj,object * other_obj,int from_blast)1217 void scoring_eval_hit(object *hit_obj, object *other_obj,int from_blast)
1218 {
1219 	// multiplayer clients bail here
1220 	if(MULTIPLAYER_CLIENT){
1221 		return;
1222 	}
1223 
1224 	// only evaluate hits on ships, asteroids, and weapons! -Halleck
1225 	if((hit_obj->type != OBJ_SHIP) && (hit_obj->type != OBJ_ASTEROID) && (hit_obj->type != OBJ_WEAPON)){
1226 		return;
1227 	}
1228 
1229 	// if the other_obj == NULL, we can't evaluate where it came from, so bail here
1230 	if(other_obj == NULL){
1231 		return;
1232 	}
1233 
1234 	// other bogus situtations
1235 	if(other_obj->instance < 0){
1236 		return;
1237 	}
1238 
1239 	if((other_obj->type == OBJ_WEAPON) && !(Weapons[other_obj->instance].weapon_flags & WF_ALREADY_APPLIED_STATS)){
1240 		// bogus weapon
1241 		if(other_obj->instance >= MAX_WEAPONS){
1242 			return;
1243 		}
1244 
1245 		// bogus parent
1246 		if(other_obj->parent < 0){
1247 			return;
1248 		}
1249 		if(other_obj->parent >= MAX_OBJECTS){
1250 			return;
1251 		}
1252 		if(Objects[other_obj->parent].type != OBJ_SHIP){
1253 			return;
1254 		}
1255 		if((Objects[other_obj->parent].instance < 0) || (Objects[other_obj->parent].instance >= MAX_SHIPS)){
1256 			return;
1257 		}
1258 
1259 		//Only evaluate hits on bomb weapons. -Halleck
1260 		bool hit_obj_is_bomb = false;
1261 
1262 		if(hit_obj->type == OBJ_WEAPON){
1263 
1264 			//Hit weapon is bogus
1265 			if (hit_obj->instance >= MAX_WEAPONS) {
1266 				return;
1267 			}
1268 
1269 			hit_obj_is_bomb = (Weapon_info[Weapons[hit_obj->instance].weapon_info_index].wi_flags & WIF_BOMB) ? true : false;
1270 
1271 			//If it's not a bomb but just a regular weapon, we don't care about it (for now, at least.) -Halleck
1272 			if (!hit_obj_is_bomb) {
1273 				return;
1274 			}
1275 		}
1276 
1277 		int is_bonehead = 0;
1278 		int sub_type = Weapon_info[Weapons[other_obj->instance].weapon_info_index].subtype;
1279 
1280 		// determine if this was a bonehead hit or not
1281 		if(hit_obj->type == OBJ_SHIP){
1282 		   is_bonehead = Ships[hit_obj->instance].team==Ships[Objects[other_obj->parent].instance].team ? 1 : 0;
1283 		} else if (hit_obj_is_bomb) {
1284 			is_bonehead = Weapons[hit_obj->instance].team==Ships[Objects[other_obj->parent].instance].team ? 1 : 0;
1285 		}
1286 		// can't have a bonehead hit on an asteroid
1287 		else {
1288 			is_bonehead = 0;
1289 		}
1290 
1291 		// set the flag indicating that we've already applied a "stats" hit for this weapon
1292 		// Weapons[other_obj->instance].weapon_flags |= WF_ALREADY_APPLIED_STATS;
1293 
1294 		// in multiplayer -- only the server records the stats
1295 		if( Game_mode & GM_MULTIPLAYER ) {
1296 			if ( Net_player->flags & NETINFO_FLAG_AM_MASTER ) {
1297 				int player_num;
1298 
1299 				// get the player num of the parent object.  A player_num of -1 means that the
1300 				// parent of this object was not a player
1301 				player_num = multi_find_player_by_object( &Objects[other_obj->parent] );
1302 				if ( player_num != -1 ) {
1303 					switch(sub_type) {
1304 					case WP_LASER :
1305 						if(is_bonehead){
1306 							Net_players[player_num].m_player->stats.mp_bonehead_hits++;
1307 						} else {
1308 							Net_players[player_num].m_player->stats.mp_shots_hit++;
1309 						}
1310 
1311 						// Assert( Net_players[player_num].player->stats.mp_shots_hit <= Net_players[player_num].player->stats.mp_shots_fired );
1312 						break;
1313 					case WP_MISSILE :
1314 						// friendly hit, once it hits a friendly, its done
1315 						if(is_bonehead){
1316 							if(!from_blast){
1317 								Net_players[player_num].m_player->stats.ms_bonehead_hits++;
1318 							}
1319 						}
1320 						// hostile hit
1321 						else {
1322 							// if its a bomb, count every bit of damage it does
1323 							if(Weapon_info[Weapons[other_obj->instance].weapon_info_index].wi_flags & WIF_BOMB){
1324 								// once we get impact damage, stop keeping track of it
1325 								Net_players[player_num].m_player->stats.ms_shots_hit++;
1326 							}
1327 							// if its not a bomb, only count impact damage
1328 							else {
1329 								if(!from_blast){
1330 									Net_players[player_num].m_player->stats.ms_shots_hit++;
1331 								}
1332 							}
1333 						}
1334 					default :
1335 						break;
1336 					}
1337 				}
1338 			}
1339 		} else {
1340 			if(Player_obj == &(Objects[other_obj->parent])){
1341 			switch(sub_type){
1342 			case WP_LASER :
1343 				if(is_bonehead){
1344 					Player->stats.mp_bonehead_hits++;
1345 				} else {
1346 					Player->stats.mp_shots_hit++;
1347 				}
1348 				break;
1349 			case WP_MISSILE :
1350 				// friendly hit, once it hits a friendly, its done
1351 				if(is_bonehead){
1352 					if(!from_blast){
1353 						Player->stats.ms_bonehead_hits++;
1354 					}
1355 				}
1356 				// hostile hit
1357 				else {
1358 					// if its a bomb, count every bit of damage it does
1359 					if(Weapon_info[Weapons[other_obj->instance].weapon_info_index].wi_flags & WIF_BOMB){
1360 						// once we get impact damage, stop keeping track of it
1361 						Player->stats.ms_shots_hit++;
1362 					}
1363 					// if its not a bomb, only count impact damage
1364 					else {
1365 						if(!from_blast){
1366 							Player->stats.ms_shots_hit++;
1367 						}
1368 					}
1369 				}
1370 				break;
1371 			default :
1372 				break;
1373 			}
1374 			}
1375 		}
1376 	}
1377 }
1378 
1379 // STATS damage, assists recording stuff
scoring_add_damage_to_weapon(object * weapon_obj,object * other_obj,float damage)1380 void scoring_add_damage_to_weapon(object *weapon_obj,object *other_obj,float damage)
1381 {
1382 	int found_slot, signature;
1383 	int lowest_index,idx;
1384 	object *use_obj;
1385 	weapon *wp;
1386 
1387 	// multiplayer clients bail here
1388 	if(MULTIPLAYER_CLIENT){
1389 		return;
1390 	}
1391 
1392 	// if we have no other object, bail
1393 	if(other_obj == NULL){
1394 		return;
1395 	}
1396 
1397 	// for player kill/assist evaluation, we have to know exactly how much damage really mattered. For example, if
1398 	// a ship had 1 hit point left, and the player hit it with a nuke, it doesn't matter that it did 10,000,000
1399 	// points of damage, only that 1 point would count
1400 	float actual_damage = 0.0f;
1401 
1402 	// other_obj might not always be the parent of other_obj (in the case of debug code for sure).  See
1403 	// if the other_obj has a parent, and if so, use the parent.  If no parent, see if other_obj is a ship
1404 	// and if so, use that ship.
1405 	if ( other_obj->parent != -1 ){
1406 		use_obj = &Objects[other_obj->parent];
1407 		signature = use_obj->signature;
1408 	} else {
1409 		signature = other_obj->signature;
1410 		use_obj = other_obj;
1411 	}
1412 
1413 	// don't count damage done to a ship by himself
1414 	if(use_obj == weapon_obj){
1415 		return;
1416 	}
1417 
1418 	// get a pointer to the ship and add the actual amount of damage done to it
1419 	// get the ship object, and determine the _actual_ amount of damage done
1420 	wp = &Weapons[weapon_obj->instance];
1421 	// see comments at beginning of function
1422 	if(weapon_obj->hull_strength < 0.0f){
1423 		actual_damage = damage + weapon_obj->hull_strength;
1424 	} else {
1425 		actual_damage = damage;
1426 	}
1427 	if(actual_damage < 0.0f){
1428 		actual_damage = 0.0f;
1429 	}
1430 	wp->total_damage_received += actual_damage;
1431 
1432 	// go through and clear out all old damagers
1433 	for(idx=0; idx<MAX_DAMAGE_SLOTS; idx++){
1434 		if((wp->damage_ship_id[idx] >= 0) && (ship_get_by_signature(wp->damage_ship_id[idx]) < 0)){
1435 			wp->damage_ship_id[idx] = -1;
1436 			wp->damage_ship[idx] = 0;
1437 		}
1438 	}
1439 
1440 	// only evaluate possible kill/assist numbers if the hitting object (use_obj) is a piloted ship (ie, ignore asteroids, etc)
1441 	// don't store damage a ship may do to himself
1442 	if((weapon_obj->type == OBJ_WEAPON) && (use_obj->type == OBJ_SHIP)){
1443 		found_slot = 0;
1444 		// try and find an open slot
1445 		for(idx=0;idx<MAX_DAMAGE_SLOTS;idx++){
1446 			// if this ship object doesn't exist anymore, use the slot
1447 			if((wp->damage_ship_id[idx] == -1) || (ship_get_by_signature(wp->damage_ship_id[idx]) < 0) || (wp->damage_ship_id[idx] == signature) ){
1448 				found_slot = 1;
1449 				break;
1450 			}
1451 		}
1452 
1453 		// if not found (implying all slots are taken), then find the slot with the lowest damage % and use that
1454 		if(!found_slot){
1455 			lowest_index = 0;
1456 			for(idx=0;idx<MAX_DAMAGE_SLOTS;idx++){
1457 				if(wp->damage_ship[idx] < wp->damage_ship[lowest_index]){
1458 				   lowest_index = idx;
1459 				}
1460 			}
1461 		} else {
1462 			lowest_index = idx;
1463 		}
1464 
1465 		// fill in the slot damage and damager-index
1466 		if(found_slot){
1467 			wp->damage_ship[lowest_index] += actual_damage;
1468 		} else {
1469 			wp->damage_ship[lowest_index] = actual_damage;
1470 		}
1471 		wp->damage_ship_id[lowest_index] = signature;
1472 	}
1473 }
1474 
1475 // get a scaling factor for adding/subtracting from mission score
scoring_get_scale_factor()1476 float scoring_get_scale_factor()
1477 {
1478 	// multiplayer dogfight. don't scale anything
1479 	if(MULTI_DOGFIGHT){
1480 		return 1.0f;
1481 	}
1482 
1483 	// check for bogus Skill_level values
1484 	Assert((Game_skill_level >= 0) && (Game_skill_level < NUM_SKILL_LEVELS));
1485 	if((Game_skill_level < 0) || (Game_skill_level > NUM_SKILL_LEVELS-1)){
1486 		return Scoring_scale_factors[0];
1487 	}
1488 
1489 	// return the correct scale value
1490 	return Scoring_scale_factors[Game_skill_level];
1491 }
1492 
1493 
1494 // ----------------------------------------------------------------------------------------
1495 // DCF functions
1496 //
1497 
1498 // bash the passed player to the specified rank
scoring_bash_rank(player * pl,int rank)1499 void scoring_bash_rank(player *pl,int rank)
1500 {
1501 	// if this is an invalid rank, do nothing
1502 	if((rank < RANK_ENSIGN) || (rank > RANK_ADMIRAL)){
1503 		nprintf(("General","Could not bash player rank - invalid value!!!\n"));
1504 		return;
1505 	}
1506 
1507 	// set the player's score and rank
1508 	pl->stats.score = Ranks[rank].points + 1;
1509 	pl->stats.rank = rank;
1510 }
1511 
1512 DCF(rank, "changes scoring vars")
1513 {
1514 	if(Dc_command){
1515 		dc_get_arg(ARG_INT);
1516 
1517 		// parse the argument and change things around accordingly
1518 		if((Dc_arg_type & ARG_INT) && (Player != NULL)){
1519 			scoring_bash_rank(Player,Dc_arg_int);
1520 		}
1521 	}
1522 	dc_printf("Usage\n0 : Ensign\n1 : Lieutenant Junior Grade\n");
1523 	dc_printf("2 : Lietenant\n3 : Lieutenant Commander\n");
1524 	dc_printf("4 : Commander\n5 : Captain\n6 : Commodore\n");
1525 	dc_printf("7 : Rear Admiral\n8 : Vice Admiral\n9 : Admiral");
1526 }
1527 
scoreing_close()1528 void scoreing_close()
1529 {
1530 	SCP_map<int, char*>::iterator it;
1531 	for(int i = 0; i<NUM_RANKS; i++) {
1532 		for (it = Ranks[i].promotion_text.begin(); it != Ranks[i].promotion_text.end(); it++) {
1533 			if (it->second) {
1534 				vm_free(it->second);
1535 			}
1536 		}
1537 		Ranks[i].promotion_text.clear();
1538 	}
1539 }
1540