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