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