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