]> icculus.org git repositories - taylor/freespace2.git/blob - src/stats/scoring.cpp
clean up and simplify effects and updating
[taylor/freespace2.git] / src / stats / scoring.cpp
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
6  * the source.
7  */
8
9 /*
10  * $Logfile: /Freespace2/code/Stats/Scoring.cpp $
11  * $Revision$
12  * $Date$
13  * $Author$
14  *
15  * Scoring system code, medals, rank, etc.
16  *
17  * $Log$
18  * Revision 1.5  2003/06/11 18:30:33  taylor
19  * plug memory leaks
20  *
21  * Revision 1.4  2003/05/25 02:30:44  taylor
22  * Freespace 1 support
23  *
24  * Revision 1.3  2002/06/09 04:41:27  relnev
25  * added copyright header
26  *
27  * Revision 1.2  2002/05/07 03:16:52  theoddone33
28  * The Great Newline Fix
29  *
30  * Revision 1.1.1.1  2002/05/03 03:28:10  root
31  * Initial import.
32  *
33  * 
34  * 23    10/25/99 5:51p Jefff
35  * commented some scoring debug code
36  * 
37  * 22    10/15/99 3:01p Jefff
38  * scoring change in coop and tvt.  for big ships, killer gets 100% of
39  * score, but teammates also get 50%.
40  * 
41  * 21    9/12/99 9:56p Jefff
42  * fixed another cause of the
43  * medals-in-training-missions-not-getting-recorded bug
44  * 
45  * 20    9/10/99 9:44p Dave
46  * Bumped version # up. Make server reliable connects not have such a huge
47  * timeout. 
48  * 
49  * 19    9/05/99 11:19p Dave
50  * Made d3d texture cache much more safe. Fixed training scoring bug where
51  * it would backout scores without ever having applied them in the first
52  * place.
53  * 
54  * 18    8/26/99 8:49p Jefff
55  * Updated medals screen and about everything that ever touches medals in
56  * one way or another.  Sheesh.
57  * 
58  * 17    8/22/99 5:53p Dave
59  * Scoring fixes. Added self destruct key. Put callsigns in the logfile
60  * instead of ship designations for multiplayer players.
61  * 
62  * 16    8/22/99 1:19p Dave
63  * Fixed up http proxy code. Cleaned up scoring code. Reverse the order in
64  * which d3d cards are detected.
65  * 
66  * 15    8/17/99 1:12p Dave
67  * Send TvT update when a client has finished joining so he stays nice and
68  * synched.
69  * 
70  * 14    8/06/99 9:46p Dave
71  * Hopefully final changes for the demo.
72  * 
73  * 13    7/30/99 7:01p Dave
74  * Dogfight escort gauge. Fixed up laser rendering in Glide.
75  * 
76  * 12    4/30/99 12:18p Dave
77  * Several minor bug fixes.
78  * 
79  * 11    4/28/99 11:13p Dave
80  * Temporary checkin of artillery code.
81  * 
82  * 10    3/01/99 10:00a Dave
83  * Fxied several dogfight related stats bugs.
84  * 
85  * 9     2/26/99 4:14p Dave
86  * Put in the ability to have multiple shockwaves for ships.
87  * 
88  * 8     2/23/99 2:29p Dave
89  * First run of oldschool dogfight mode. 
90  * 
91  * 7     2/17/99 2:11p Dave
92  * First full run of squad war. All freespace and tracker side stuff
93  * works.
94  * 
95  * 6     1/29/99 2:08a Dave
96  * Fixed beam weapon collisions with players. Reduced size of scoring
97  * struct for multiplayer. Disabled PXO.
98  * 
99  * 5     1/12/99 5:45p Dave
100  * Moved weapon pipeline in multiplayer to almost exclusively client side.
101  * Very good results. Bandwidth goes down, playability goes up for crappy
102  * connections. Fixed object update problem for ship subsystems.
103  * 
104  * 4     10/23/98 3:51p Dave
105  * Full support for tstrings.tbl and foreign languages. All that remains
106  * is to make it active in Fred.
107  * 
108  * 3     10/13/98 9:29a Dave
109  * Started neatening up freespace.h. Many variables renamed and
110  * reorganized. Added AlphaColors.[h,cpp]
111  * 
112  * 2     10/07/98 10:54a Dave
113  * Initial checkin.
114  * 
115  * 1     10/07/98 10:51a Dave
116  * 
117  * 84    9/15/98 11:44a Dave
118  * Renamed builtin ships and wepaons appropriately in FRED. Put in scoring
119  * scale factors. Fixed standalone filtering of MD missions to non-MD
120  * hosts.
121  * 
122  * 83    9/10/98 6:14p Dave
123  * Removed bogus assert.
124  * 
125  * 82    7/14/98 2:37p Allender
126  * fixed bug where two scoring variables were not getting properly reset
127  * between levels in multiplayer game
128  * 
129  * 81    6/01/98 11:43a John
130  * JAS & MK:  Classified all strings for localization.
131  * 
132  * 80    5/23/98 3:16p Allender
133  * work on object update packet optimizations (a new updating system).
134  * Don't allow medals/promotions/badges when playing singple player
135  * missions through the simulator
136  * 
137  * 79    5/18/98 9:14p Dave
138  * Put in network config files support.
139  * 
140  * 78    5/16/98 9:14p Allender
141  * fix scoring ckise fir training missions to actually count medals, but
142  * nothing else.  Code used to SDL_assert when wings were granted then taken
143  * away because they were actually never granted in scoring structure
144  * 
145  * 77    5/15/98 9:52p Dave
146  * Added new stats for freespace. Put in artwork for viewing stats on PXO.
147  * 
148  * 76    5/15/98 4:12p Allender
149  * removed redbook code.  Put back in ingame join timer.  Major fixups for
150  * stats in multiplayer.  Pass correct score, medals, etc when leaving
151  * game.  Be sure clients display medals, badges, etc.
152  * 
153  * 75    5/13/98 5:13p Allender
154  * red alert support to go back to previous mission
155  * 
156  * 74    5/07/98 12:56a Dave
157  * Fixed incorrect calls to free() from stats code. Put in new artwork for
158  * debrief and host options screens. Another modification to scoring
159  * system for secondary weapons.
160  * 
161  * 73    5/06/98 3:15p Allender
162  * fixed some ranking problems.  Made vasudan support ship available in
163  * multiplayer mode.
164  * 
165  * 72    5/04/98 1:43p Dave
166  * Fixed up a standalone resetting problem. Fixed multiplayer stats
167  * collection for clients. Make sure all multiplayer ui screens have the
168  * correct palette at all times.
169  * 
170  * 71    5/03/98 2:52p Dave
171  * Removed multiplayer furball mode.
172  * 
173  * 70    5/01/98 12:34p John
174  * Added code to force FreeSpace to run in the same dir as exe and made
175  * all the parse error messages a little nicer.
176  * 
177  * 69    4/28/98 5:27p Hoffoss
178  * Added kills by type stats to barracks, and fixed some bugs this turned
179  * up but no one knew about yet apparently.
180  * 
181  * 68    4/27/98 6:01p Dave
182  * Modify how missile scoring works. Fixed a team select ui bug. Speed up
183  * multi_lag system. Put in new main hall.
184  * 
185  * 67    4/25/98 3:54p Dave
186  * Fixed a scoring bug where hidden ships had improper return values from
187  * scoring_eval_kill(...)
188  * 
189  * 66    4/21/98 11:55p Dave
190  * Put in player deaths statskeeping. Use arrow keys in the ingame join
191  * ship select screen. Don't quit the game if in the debriefing and server
192  * leaves.
193  * 
194  * 65    4/21/98 4:44p Dave
195  * Implement Vasudan ships in multiplayer. Added a debug function to bash
196  * player rank. Fixed a few rtvoice buffer overrun problems. Fixed ui
197  * problem in options screen.  
198  *
199  * $NoKeywords: $
200  */
201
202 #include "freespace.h"
203 #include "pstypes.h"
204 #include "object.h"
205 #include "ship.h"
206 #include "multi.h"
207 #include "multiutil.h"
208 #include "scoring.h"
209 #include "player.h"
210 #include "parselo.h"
211 #include "multimsgs.h"
212 #include "medals.h"
213 #include "localize.h"
214 #include "multi_team.h"
215 #include "multi_dogfight.h"
216 #include "multi_pmsg.h"
217
218 // 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
219 #define ASSIST_PERCENTAGE                               (0.15f)
220 #define KILL_PERCENTAGE                                 (0.30f)
221
222 // these tables are overwritten with the values from rank.tbl
223 rank_stuff Ranks[NUM_RANKS];
224
225 // scoring scale factors by skill level
226 float Scoring_scale_factors[NUM_SKILL_LEVELS] = {
227         0.2f,                                   // very easy
228         0.4f,                                   // easy
229         0.7f,                                   // medium
230         1.0f,                                   // hard
231         1.25f                                   // insane
232 };
233
234 void parse_rank_tbl()
235 {
236         char buf[MULTITEXT_LENGTH];
237         int idx;
238
239         // open localization
240         lcl_ext_open();
241
242         try {
243                 read_file_text("rank.tbl");
244                 reset_parse();
245
246                 // parse in all the rank names
247                 idx = 0;
248                 skip_to_string("[RANK NAMES]");
249                 ignore_white_space();
250                 while ( required_string_either("#End", "$Name:") ) {
251                         SDL_assert ( idx < NUM_RANKS );
252                         required_string("$Name:");
253                         stuff_string( Ranks[idx].name, F_NAME, NULL );
254                         required_string("$Points:");
255                         stuff_int( &Ranks[idx].points );
256                         required_string("$Bitmap:");
257                         stuff_string( Ranks[idx].bitmap, F_NAME, NULL );
258                         required_string("$Promotion Voice Base:");
259                         stuff_string( Ranks[idx].promotion_voice_base, F_NAME, NULL, MAX_FILENAME_LEN - 2 );
260                         required_string("$Promotion Text:");
261                         stuff_string(buf, F_MULTITEXT, NULL);
262                         drop_white_space(buf);
263                         compact_multitext_string(buf);
264                         Ranks[idx].promotion_text = strdup(buf);
265                         idx++;
266                 }
267
268                 required_string("#End");
269         } catch (parse_error_t rval) {
270                 Error(LOCATION, "Error parsing 'rank.tbl'\r\nError code = %i.\r\n", (int)rval);
271         }
272
273         // be sure that all rank points are in order
274 #ifndef NDEBUG
275         for ( idx = 0; idx < NUM_RANKS-1; idx++ ) {
276                 if ( Ranks[idx].points >= Ranks[idx+1].points )
277                         Int3();
278         }
279 #endif
280
281         // close localization
282         lcl_ext_close();
283 }
284
285 // free memory from table parsing
286 void scoring_tbl_close()
287 {
288         int i;
289         
290         for (i=0; i<NUM_RANKS; i++) {
291                 if (Ranks[i].promotion_text) {
292                         free(Ranks[i].promotion_text);
293                         Ranks[i].promotion_text = NULL;
294                 }
295         }
296 }
297
298 // initialize a nice blank scoring element
299 void init_scoring_element(scoring_struct *s)
300 {
301         int i;
302
303         if (s == NULL) {
304                 Int3(); //      DaveB -- Fix this!
305                 // read_pilot_file(char* callsign);
306                 return;
307         }
308
309         memset(s, 0, sizeof(scoring_struct));
310         s->score = 0;
311         s->rank = RANK_ENSIGN;
312         s->assists = 0;
313         s->kill_count = 0;
314         s->kill_count_ok = 0;
315
316         for (i=0; i<NUM_MEDALS; i++){
317                 s->medals[i] = 0;
318         }
319
320         for (i=0; i<MAX_SHIP_TYPES; i++){
321                 s->kills[i] = 0;
322                 s->m_kills[i] = 0;
323         }
324
325         s->m_kill_count         = 0;
326         s->m_kill_count_ok      = 0;
327
328         s->m_score = 0;
329         s->m_assists = 0;
330    s->p_bonehead_hits=0; s->mp_bonehead_hits=0;
331         s->s_bonehead_hits=0; s->ms_bonehead_hits=0;
332         s->m_bonehead_kills=0;
333         
334         s->bonehead_kills=0;   
335    
336         s->p_shots_fired=0; s->p_shots_hit=0;
337    s->s_shots_fired=0; s->s_shots_hit=0;
338
339    s->mp_shots_fired=0; s->mp_shots_hit=0;
340    s->ms_shots_fired=0; s->ms_shots_hit=0;
341
342         s->m_player_deaths = 0;
343
344    s->flags = 0;        
345
346         s->missions_flown = 0;
347         s->flight_time = 0;
348         s->last_flown = 0;
349         s->last_backup = 0;
350 #ifndef MAKE_FS1
351         for(i=0; i<MAX_PLAYERS; i++){
352                 s->m_dogfight_kills[i] = 0;
353         }
354 #endif
355 }
356
357 #ifndef NDEBUG
358 //XSTR:OFF
359 void scoring_eval_harbison( ship *shipp )
360 {
361         FILE *fp;
362
363         if ( !SDL_strcasecmp(shipp->ship_name, "alpha 2") && (!SDL_strcasecmp(Game_current_mission_filename, "demo01") || !SDL_strcasecmp(Game_current_mission_filename, "sm1-01")) ) {
364                 int death_count;
365
366                 fp = fopen("i:\\volition\\cww\\harbison.txt", "r+t");
367                 if ( !fp )
368                         return;
369                 fscanf(fp, "%d", &death_count );
370                 death_count++;
371                 fseek(fp, 0, SEEK_SET);
372                 fprintf(fp, "%d\n", death_count);
373                 fclose(fp);
374         }
375 }
376 //XSTR:ON
377 #endif
378
379 // initialize the Player's mission-based stats before he goes into a mission
380 void scoring_level_init( scoring_struct *scp )
381 {
382         int i;
383
384         scp->m_medal_earned = -1;               // hasn't earned a medal yet
385         scp->m_promotion_earned = -1;
386         scp->m_badge_earned = -1;
387    scp->m_score = 0;
388         scp->m_assists = 0;
389         scp->mp_shots_fired=0;
390         scp->mp_shots_hit = 0;
391         scp->ms_shots_fired = 0;
392         scp->ms_shots_hit = 0;
393
394         scp->mp_bonehead_hits=0;
395         scp->ms_bonehead_hits=0;
396         scp->m_bonehead_kills=0;
397
398    for (i=0; i<MAX_SHIP_TYPES; i++){
399                 scp->m_kills[i] = 0;
400                 scp->m_okKills[i]=0;
401         }
402
403         scp->m_kill_count = 0;
404         scp->m_kill_count_ok = 0;
405         
406         scp->m_player_deaths =0;
407 #ifndef MAKE_FS1
408         for(i=0; i<MAX_PLAYERS; i++){
409                 scp->m_dogfight_kills[i] = 0;
410         }
411 #endif
412 }
413
414 void scoring_eval_rank( scoring_struct *sc )
415 {
416         int i, score, new_rank, old_rank;
417
418         old_rank = sc->rank;
419         new_rank = old_rank;
420
421         // first check to see if the promotion flag is set -- if so, return the new rank
422         if ( Player->flags & PLAYER_FLAGS_PROMOTED ) {
423         
424                 new_rank = sc->rank;
425
426                 // if the player does indeed get promoted, we should change his mission score
427                 // to reflect the differce between all time and new rank score
428 #ifdef MAKE_FS1
429                 if ( sc->rank < MAX_FREESPACE1_RANK ) {
430 #else
431                 if ( sc->rank < MAX_FREESPACE2_RANK ) {
432 #endif
433                         new_rank = sc->rank + 1;
434                         if ( (sc->m_score + sc->score) < Ranks[new_rank].points )
435                                 sc->m_score = (Ranks[new_rank].points - sc->score);
436                 }
437         } else {
438                 // we get here only if player wasn't promoted automatically.
439                 score = sc->m_score + sc->score;
440                 for (i=0; i<NUM_RANKS; i++) {
441                         if ( score >= Ranks[i].points )
442                                 new_rank = i;
443                 }
444         }
445
446         // if the ranks do not match, then "grant" the new rank
447         if ( old_rank != new_rank ) {
448                 SDL_assert( new_rank >= 0 );
449                 sc->m_promotion_earned = new_rank;
450                 sc->rank = new_rank;
451         }
452 }
453
454 // function to evaluate whether or not a new badge is going to be awarded.  This function returns
455 // which medal is awarded.
456 void scoring_eval_badges(scoring_struct *sc)
457 {
458         int i, total_kills, badge;
459
460         // to determine badges, we count kills based on fighter/bomber types.  We must count kills in
461         // all time stats + current mission stats.  And, only for enemy fighters/bombers
462         total_kills = 0;
463         for (i = 0; i < MAX_SHIP_TYPES; i++ ) {
464                 if ( (Ship_info[i].flags & SIF_FIGHTER) || (Ship_info[i].flags & SIF_BOMBER) ) {
465                         total_kills += sc->m_okKills[i];
466                         total_kills += sc->kills[i];
467                 }
468         }
469
470         // total_kills should now reflect the number of kills on hostile fighters/bombers.  Check this number
471         // against badge kill numbers, and return the badge index if we would get a new one.
472         badge = -1;
473         for (i = 0; i < MAX_BADGES; i++ ) {
474                 if ( total_kills >= Medals[Badge_index[i]].kills_needed ){
475                         badge = i;
476                 }
477         }
478
479         // if player could have a badge based on kills, and doesn't currently have this badge, then
480         // return the badge id.
481         if ( (badge != -1 ) && (sc->medals[Badge_index[badge]] < 1) ) {
482                 sc->medals[Badge_index[badge]] = 1;
483                 sc->m_badge_earned = badge;
484         }
485 }
486
487 // central point for dealing with accepting the score for a misison.
488 void scoring_do_accept(scoring_struct *score)
489 {
490         int idx;
491
492         // do rank, badges, and medals first since they require the alltime stuff
493         // to not be updated yet.       
494
495         // do medal stuff
496         if ( score->m_medal_earned != -1 ){
497                 score->medals[score->m_medal_earned]++;
498         }
499
500         // return when in training mission.  We can grant a medal in training, but don't
501         // want to calculate any other statistics.
502         if (The_mission.game_type == MISSION_TYPE_TRAINING){
503                 return;
504         }       
505
506         scoring_eval_rank(score);
507         scoring_eval_badges(score);
508
509         score->kill_count += score->m_kill_count;
510         score->kill_count_ok += score->m_kill_count_ok;
511
512         score->score += score->m_score;
513         score->assists += score->m_assists;
514         score->p_shots_fired += score->mp_shots_fired;
515         score->s_shots_fired += score->ms_shots_fired;
516
517         score->p_shots_hit += score->mp_shots_hit;
518         score->s_shots_hit += score->ms_shots_hit;
519
520         score->p_bonehead_hits += score->mp_bonehead_hits;
521         score->s_bonehead_hits += score->ms_bonehead_hits;
522         score->bonehead_kills += score->m_bonehead_kills;
523
524         for(idx=0;idx<MAX_SHIP_TYPES;idx++){
525                 score->kills[idx] = (unsigned short)(score->kills[idx] + score->m_okKills[idx]);
526         }
527
528         // add in mission time
529         score->flight_time += (unsigned int)f2fl(Missiontime);
530         score->last_backup = score->last_flown;
531         score->last_flown = (fs_time_t)time(NULL);
532         score->missions_flown++;
533 }
534
535 // backout the score for a mission.  This function gets called when the player chooses to refly a misison
536 // after debriefing
537 void scoring_backout_accept( scoring_struct *score )
538 {
539         int idx;
540
541         // if a badge was earned, take it back
542         if ( score->m_badge_earned != -1){
543                 score->medals[Badge_index[score->m_badge_earned]] = 0;
544         }
545
546         // return when in training mission.  We can grant a medal in training, but don't
547         // want to calculate any other statistics.
548         if (The_mission.game_type == MISSION_TYPE_TRAINING){
549                 return;
550         }
551
552         score->kill_count -= score->m_kill_count;
553         score->kill_count_ok -= score->m_kill_count_ok;
554
555         score->score -= score->m_score;
556         score->assists -= score->m_assists;
557         score->p_shots_fired -= score->mp_shots_fired;
558         score->s_shots_fired -= score->ms_shots_fired;
559
560         score->p_shots_hit -= score->mp_shots_hit;
561         score->s_shots_hit -= score->ms_shots_hit;
562
563         score->p_bonehead_hits -= score->mp_bonehead_hits;
564         score->s_bonehead_hits -= score->ms_bonehead_hits;
565         score->bonehead_kills -= score->m_bonehead_kills;
566
567         for(idx=0;idx<MAX_SHIP_TYPES;idx++){
568                 score->kills[idx] = (unsigned short)(score->kills[idx] - score->m_okKills[idx]);
569         }
570
571         // if the player was given a medal, take it back
572         if ( score->m_medal_earned != -1 ) {
573                 score->medals[score->m_medal_earned]--;
574                 SDL_assert( score->medals[score->m_medal_earned] >= 0 );
575         }
576
577         // if the player was promoted, take it back
578         if ( score->m_promotion_earned != -1) {
579                 score->rank--;
580                 SDL_assert( score->rank >= 0 );
581         }       
582
583         score->flight_time -= (unsigned int)f2fl(Missiontime);
584         score->last_flown = score->last_backup; 
585         score->missions_flown--;
586 }
587
588 // merge any mission stats accumulated into the alltime stats (as well as updating per campaign stats)
589 void scoring_level_close(int accepted)
590 {
591         int idx;
592         scoring_struct *sc;
593
594         // want to calculate any other statistics.
595         if (The_mission.game_type == MISSION_TYPE_TRAINING){
596                 // call scoring_do_accept
597                 // this will grant any potential medals and then early bail, and
598                 // then we will early bail
599                 scoring_do_accept(&Player->stats);
600                 return;
601         }
602
603         if(accepted){
604                 // apply mission stats for all players in the game
605                 if(Game_mode & GM_MULTIPLAYER){
606                         nprintf(("Network","Storing stats for all players now\n"));
607                         for(idx=0;idx<MAX_PLAYERS;idx++){
608                                 if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx])){
609                                         // get the scoring struct
610                                         sc = &Net_players[idx].player->stats;
611                                         scoring_do_accept( sc );
612                                 }
613                         }
614                 } else {
615                         nprintf(("General","Storing stats now\n"));
616                         scoring_do_accept( &Player->stats );
617                 }
618
619                 // If this mission doesn't allow promotion or badges
620                 // then be sure that these don't get done.  Don't allow promotions or badges when
621                 // playing normally and not in a campaign.
622                 if ( (The_mission.flags & MISSION_FLAG_NO_PROMOTION) || ((Game_mode & GM_NORMAL) && !(Game_mode & GM_CAMPAIGN_MODE)) ) {
623                         if ( Player->stats.m_promotion_earned != -1) {
624                                 Player->stats.rank--;
625                                 Player->stats.m_promotion_earned = -1;
626                         }
627
628                         // if a badge was earned, take it back
629                         if ( Player->stats.m_badge_earned != -1){
630                                 Player->stats.medals[Badge_index[Player->stats.m_badge_earned]] = -1;
631                                 Player->stats.m_badge_earned = -1;
632                         }
633                 }
634
635         }       
636 }
637
638 // STATS damage, assists recording stuff
639 void scoring_add_damage(object *ship_obj,object *other_obj,float damage)
640 {
641         int found_slot, signature;
642         int lowest_index,idx;
643         object *use_obj;
644         ship *sp;
645
646         // multiplayer clients bail here
647         if(MULTIPLAYER_CLIENT){
648                 return;
649         }
650
651         // if we have no other object, bail
652         if(other_obj == NULL){
653                 return;
654         }       
655
656         // for player kill/assist evaluation, we have to know exactly how much damage really mattered. For example, if
657         // 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 
658         // points of damage, only that 1 point would count
659         float actual_damage = 0.0f;
660         
661         // other_obj might not always be the parent of other_obj (in the case of debug code for sure).  See
662         // if the other_obj has a parent, and if so, use the parent.  If no parent, see if other_obj is a ship
663         // and if so, use that ship.
664         if ( other_obj->parent != -1 ){         
665                 use_obj = &Objects[other_obj->parent];
666                 signature = use_obj->signature;
667         } else {
668                 signature = other_obj->signature;
669                 use_obj = other_obj;
670         }
671         
672         // don't count damage done to a ship by himself
673         if(use_obj == ship_obj){
674                 return;
675         }
676
677         // get a pointer to the ship and add the actual amount of damage done to it
678         // get the ship object, and determine the _actual_ amount of damage done
679         sp = &Ships[ship_obj->instance];
680         // see comments at beginning of function
681         if(ship_obj->hull_strength < 0.0f){
682                 actual_damage = damage + ship_obj->hull_strength;
683         } else {
684                 actual_damage = damage;
685         }
686         if(actual_damage < 0.0f){
687                 actual_damage = 0.0f;
688         }
689         sp->total_damage_received += actual_damage;
690
691         // go through and clear out all old damagers
692         for(idx=0; idx<MAX_DAMAGE_SLOTS; idx++){
693                 if((sp->damage_ship_id[idx] >= 0) && (ship_get_by_signature(sp->damage_ship_id[idx]) < 0)){
694                         sp->damage_ship_id[idx] = -1;
695                         sp->damage_ship[idx] = 0;
696                 }
697         }
698
699         // only evaluate possible kill/assist numbers if the hitting object (use_obj) is a piloted ship (ie, ignore asteroids, etc)
700         // don't store damage a ship may do to himself
701         if((ship_obj->type == OBJ_SHIP) && (use_obj->type == OBJ_SHIP)){
702                 found_slot = 0;
703                 // try and find an open slot
704                 for(idx=0;idx<MAX_DAMAGE_SLOTS;idx++){
705                         // if this ship object doesn't exist anymore, use the slot
706                         if((sp->damage_ship_id[idx] == -1) || (ship_get_by_signature(sp->damage_ship_id[idx]) < 0) || (sp->damage_ship_id[idx] == signature) ){
707                                 found_slot = 1;
708                                 break;
709                         }
710                 }
711
712                 // if not found (implying all slots are taken), then find the slot with the lowest damage % and use that
713                 if(!found_slot){
714                         lowest_index = 0;
715                         for(idx=0;idx<MAX_DAMAGE_SLOTS;idx++){
716                                 if(sp->damage_ship[idx] < sp->damage_ship[lowest_index]){
717                                    lowest_index = idx;
718                                 }
719                         }
720                 } else {
721                         lowest_index = idx;
722                 }
723
724                 // fill in the slot damage and damager-index
725                 if(found_slot){
726                         sp->damage_ship[lowest_index] += actual_damage;                                                         
727                 } else {
728                         sp->damage_ship[lowest_index] = actual_damage;
729                 }
730                 sp->damage_ship_id[lowest_index] = signature;
731         }       
732 }
733
734 char Scoring_debug_text[4096];
735
736 // evaluate a kill on a ship
737 void scoring_eval_kill(object *ship_obj)
738 {               
739         float max_damage_pct;           // the pct% of total damage the max damage object did
740         int max_damage_index;           // the index into the dying ship's damage_ship[] array corresponding the greatest amount of damage
741         int killer_sig;                         // signature of the guy getting credit for the kill (or -1 if none)
742         int idx,net_player_num;
743         player *plr;                                    // pointer to a player struct if it was a player who got the kill
744         net_player *net_plr = NULL;
745         ship *dead_ship;                                // the ship which was killed
746         net_player *dead_plr = NULL;
747         int i;
748
749         // multiplayer clients bail here
750         if(MULTIPLAYER_CLIENT){
751                 return;
752         }
753
754         // we don't evaluate kills on anything except ships
755         if(ship_obj->type != OBJ_SHIP){
756                 return; 
757         }
758         if((ship_obj->instance < 0) || (ship_obj->instance >= MAX_SHIPS)){
759                 return;
760         }
761
762         // assign the dead ship
763         dead_ship = &Ships[ship_obj->instance];
764
765         // evaluate player deaths
766         if(Game_mode & GM_MULTIPLAYER){
767                 net_player_num = multi_find_player_by_object(ship_obj);
768                 if(net_player_num != -1){
769                         Net_players[net_player_num].player->stats.m_player_deaths++;
770                         nprintf(("Network","Setting player %s deaths to %d\n",Net_players[net_player_num].player->callsign,Net_players[net_player_num].player->stats.m_player_deaths));
771                         dead_plr = &Net_players[net_player_num];
772                 }
773         } else {
774                 if(ship_obj == Player_obj){
775                         Player->stats.m_player_deaths++;
776                 }
777         }
778
779         // if this ship doesn't show up on player sensors, then don't eval a kill
780         if ( dead_ship->flags & SF_HIDDEN_FROM_SENSORS ){
781                 // make sure to set invalid killer id numbers
782                 dead_ship->damage_ship_id[0] = -1;
783                 dead_ship->damage_ship[0] = -1.0f;
784                 return;
785         }
786
787 #ifndef NDEBUG
788         scoring_eval_harbison( dead_ship );
789 #endif
790
791         // clear out invalid damager ships
792         for(idx=0; idx<MAX_DAMAGE_SLOTS; idx++){
793                 if((dead_ship->damage_ship_id[idx] >= 0) && (ship_get_by_signature(dead_ship->damage_ship_id[idx]) < 0)){
794                         dead_ship->damage_ship[idx] = 0.0f;
795                         dead_ship->damage_ship_id[idx] = -1;
796                 }
797         }
798                         
799         // determine which object did the most damage to the dying object, and how much damage that was
800         max_damage_index = -1;
801         for(idx=0;idx<MAX_DAMAGE_SLOTS;idx++){
802                 // bogus ship
803                 if(dead_ship->damage_ship_id[idx] < 0){
804                         continue;
805                 }
806
807                 // if this slot did more damage then the next highest slot
808                 if((max_damage_index == -1) || (dead_ship->damage_ship[idx] > dead_ship->damage_ship[max_damage_index])){
809                         max_damage_index = idx;
810                 }                       
811         }
812         
813         // doh
814         if((max_damage_index < 0) || (max_damage_index >= MAX_DAMAGE_SLOTS)){
815                 return;
816         }
817
818         // the pct of total damage applied to this ship
819         max_damage_pct = dead_ship->damage_ship[max_damage_index] / dead_ship->total_damage_received;
820         if(max_damage_pct < 0.0f){
821                 max_damage_pct = 0.0f;
822         } 
823         if(max_damage_pct > 1.0f){
824                 max_damage_pct = 1.0f;
825         }
826
827         // only evaluate if the max damage % is high enough to record a kill and it was done by a valid object
828         if((max_damage_pct >= KILL_PERCENTAGE) && (dead_ship->damage_ship_id[max_damage_index] >= 0)){
829                 // set killer_sig for this ship to the signature of the guy who gets credit for the kill
830                 killer_sig = dead_ship->damage_ship_id[max_damage_index];
831
832                 // null this out for now
833                 plr = NULL;
834                 net_plr = NULL;
835
836                 // get the player (whether single or multiplayer)
837                 net_player_num = -1;
838                 if(Game_mode & GM_MULTIPLAYER){
839                         net_player_num = multi_find_player_by_signature(killer_sig);
840                         if(net_player_num != -1){
841                                 plr = Net_players[net_player_num].player;
842                                 net_plr = &Net_players[net_player_num];
843                         }
844                 } else {
845                         if(Objects[Player->objnum].signature == killer_sig){
846                                 plr = Player;
847                         }
848                 }               
849
850                 // if we found a valid player, evaluate some kill details
851                 if(plr != NULL){
852                         int si_index;
853
854                         // bogus
855                         if((plr->objnum < 0) || (plr->objnum >= MAX_OBJECTS)){
856                                 return;
857                         }                       
858
859                         // get the ship info index of the ship type of this kill.  we need to take ship
860                         // copies into account here.
861                         si_index = dead_ship->ship_info_index;
862                         if ( Ship_info[si_index].flags & SIF_SHIP_COPY ){
863                                 si_index = ship_info_base_lookup( si_index );
864                         }
865
866                         // if you hit this next SDL_assert, find allender.  If not here, don't worry about it, you may safely
867                         // continue
868                         SDL_assert( !(Ship_info[si_index].flags & SIF_SHIP_COPY) );
869
870                         // if he killed a guy on his own team increment his bonehead kills
871                         if((Ships[Objects[plr->objnum].instance].team == dead_ship->team) && !((Game_mode & GM_MULTIPLAYER) && (Netgame.type_flags & NG_TYPE_DOGFIGHT))){
872                                 plr->stats.m_bonehead_kills++;
873                                 plr->stats.m_score -= (int)(dead_ship->score * scoring_get_scale_factor());
874
875                                 // squad war
876                                 if(net_plr != NULL){
877                                         multi_team_maybe_add_score(-(int)(dead_ship->score * scoring_get_scale_factor()), net_plr->p_info.team);
878                                 }
879                         } 
880                         // otherwise increment his valid kill count and score
881                         else {
882                                 // dogfight mode
883                                 if((Game_mode & GM_MULTIPLAYER) && (Netgame.type_flags & NG_TYPE_DOGFIGHT) && (multi_find_player_by_object(ship_obj) < 0)){
884                                         // don't add a kill for dogfight kills on non-players
885                                 } else {
886                                         plr->stats.m_okKills[si_index]++;               
887                                         plr->stats.m_kill_count_ok++;
888                                         plr->stats.m_score += (int)(dead_ship->score * scoring_get_scale_factor());
889                                         hud_gauge_popup_start(HUD_KILLS_GAUGE);
890
891                                         // multiplayer
892                                         if(net_plr != NULL){
893                                                 multi_team_maybe_add_score((int)(dead_ship->score * scoring_get_scale_factor()), net_plr->p_info.team);
894
895                                                 // award teammates 50% of score value for big ship kills
896                                                 // not in dogfight tho
897                                                 if (!(Netgame.type_flags & NG_TYPE_DOGFIGHT) && (Ship_info[dead_ship->ship_info_index].flags & (SIF_BIG_SHIP | SIF_HUGE_SHIP))) {
898                                                         for (i=0; i<MAX_PLAYERS; i++) {
899                                                                 if (MULTI_CONNECTED(Net_players[i]) && (Net_players[i].p_info.team == net_plr->p_info.team) && (&Net_players[i] != net_plr)) {
900                                                                         Net_players[i].player->stats.m_score += (int)(dead_ship->score * scoring_get_scale_factor() * 0.5f);
901 /*
902 #if !defined(RELEASE_REAL)
903                                                                         // DEBUG CODE TO TEST NEW SCORING
904                                                                         char score_text[1024] = "";
905                                                                         sprintf(score_text, "You get %d pts for the helping kill the big ship", (int)(dead_ship->score * scoring_get_scale_factor() * 0.5f));                                                   
906                                                                         if (Net_players[i].player != Net_player->player) {                      // check if its me
907                                                                                 send_game_chat_packet(Net_player, score_text, MULTI_MSG_TARGET, &Net_players[i], NULL, 2);                                                              
908                                                                         } else {
909                                                                                 HUD_printf(score_text);
910                                                                         }
911 #endif
912 */
913                                                                 }
914                                                         }
915                                                 }
916
917                                                 // death message
918                                                 if((Net_player != NULL) && (Net_player->flags & NETINFO_FLAG_AM_MASTER) && (net_plr != NULL) && (dead_plr != NULL) && (net_plr->player != NULL) && (dead_plr->player != NULL)){
919                                                         char dead_text[1024] = "";
920
921                                                         SDL_snprintf(dead_text, SDL_arraysize(dead_text), "%s gets the kill for %s", net_plr->player->callsign, dead_plr->player->callsign);
922                                                         send_game_chat_packet(Net_player, dead_text, MULTI_MSG_ALL, NULL, NULL, 2);
923                                                         HUD_printf(dead_text);
924                                                 }
925                                         }
926                                 }
927                         }
928                                 
929                         // increment his all-encompassing kills
930                         plr->stats.m_kills[si_index]++;
931                         plr->stats.m_kill_count++;                      
932                         
933                         // update everyone on this guy's kills if this is multiplayer
934                         if(MULTIPLAYER_MASTER && (net_player_num != -1)){
935                                 // send appropriate stats
936                                 if(Netgame.type_flags & NG_TYPE_DOGFIGHT){
937                                         // evaluate dogfight kills
938                                         multi_df_eval_kill(&Net_players[net_player_num], ship_obj);
939
940                                         // update stats
941                                         send_player_stats_block_packet(&Net_players[net_player_num], STATS_DOGFIGHT_KILLS);
942                                 } else {
943                                         send_player_stats_block_packet(&Net_players[net_player_num], STATS_MISSION_KILLS);
944                                 }                               
945                         }
946                 }
947         } else {
948                 // set killer_sig for this ship to -1, indicating no one got the kill for it
949                 killer_sig = -1;
950         }               
951                 
952         // pass in the guy who got the credit for the kill (if any), so that he doesn't also
953         // get credit for an assist
954         scoring_eval_assists(dead_ship,killer_sig);     
955
956         // bash damage_ship_id[0] with the signature of the guy who is getting credit for the kill
957         dead_ship->damage_ship_id[0] = killer_sig;
958         dead_ship->damage_ship[0] = max_damage_pct;
959
960 /*
961         // debug code
962 #if !defined(RELEASE_REAL)
963         if (Game_mode & GM_MULTIPLAYER) {
964                 char buf[256];
965                 sprintf(Scoring_debug_text, "%s killed.\nDamage by ship:\n\n", Ship_info[dead_ship->ship_info_index].name);
966
967                 // show damage done by player
968                 for (int i=0; i<MAX_DAMAGE_SLOTS; i++) {
969                         int net_player_num = multi_find_player_by_signature(dead_ship->damage_ship_id[i]);
970                         if (net_player_num != -1) {
971                                 plr = Net_players[net_player_num].player;
972                                 sprintf(buf, "%s: %f", plr->callsign, dead_ship->damage_ship[i]);
973
974                                 if (dead_ship->damage_ship_id[i] == killer_sig ) {
975                                         strcat(buf, "  KILLER\n");
976                                 } else {
977                                         strcat(buf, "\n");
978                                 }
979
980                                 strcat(Scoring_debug_text, buf);        
981                         }
982
983                 }
984         }
985 #endif
986 */
987 }
988
989 // kill_id is the object signature of the guy who got the credit for the kill (may be -1, if no one got it)
990 // this is to insure that you don't also get an assist if you get the kill.
991 void scoring_eval_assists(ship *sp,int killer_sig)
992 {
993         int idx;
994         player *plr;
995
996         // multiplayer clients bail here
997         if(MULTIPLAYER_CLIENT){
998                 return;
999         }
1000                 
1001         // evaluate each damage slot to see if it did enough to give the assis
1002         for(idx=0;idx<MAX_DAMAGE_SLOTS;idx++){
1003                 // if this slot did enough damage to get an assist
1004                 if((sp->damage_ship[idx]/sp->total_damage_received) >= ASSIST_PERCENTAGE){
1005                         // get the player which did this damage (if any)
1006                         plr = NULL;
1007                         
1008                         // multiplayer
1009                         if(Game_mode & GM_MULTIPLAYER){
1010                                 int net_player_num = multi_find_player_by_signature(sp->damage_ship_id[idx]);
1011                                 if(net_player_num != -1){
1012                                         plr = Net_players[net_player_num].player;
1013                                 }
1014                         }
1015                         // single player
1016                         else {
1017                                 if(Objects[Player->objnum].signature == sp->damage_ship_id[idx]){
1018                                         plr = Player;
1019                                 }
1020                         }
1021
1022                         // if we found a player, give him the assist if it wasn't on his own team
1023                         if((plr != NULL) && (sp->team != Ships[Objects[plr->objnum].instance].team) && (killer_sig != Objects[plr->objnum].signature)){
1024                                 plr->stats.m_assists++;
1025
1026                                 nprintf(("Network","-==============GAVE PLAYER %s AN ASSIST=====================-\n",plr->callsign));
1027                                 break;
1028                         }
1029                 }
1030         }
1031 }
1032
1033 // eval a hit on an object (for primary and secondary hit purposes)
1034 void scoring_eval_hit(object *hit_obj, object *other_obj,int from_blast)
1035 {       
1036         // multiplayer clients bail here
1037         if(MULTIPLAYER_CLIENT){
1038                 return;
1039         }
1040
1041         // only evaluate hits on ships and asteroids
1042         if((hit_obj->type != OBJ_SHIP) && (hit_obj->type != OBJ_ASTEROID)){
1043                 return;
1044         }
1045
1046         // if the other_obj == NULL, we can't evaluate where it came from, so bail here
1047         if(other_obj == NULL){
1048                 return;
1049         }
1050
1051         // other bogus situtations
1052         if(other_obj->instance < 0){
1053                 return;
1054         }
1055         
1056         if((other_obj->type == OBJ_WEAPON) && !(Weapons[other_obj->instance].weapon_flags & WF_ALREADY_APPLIED_STATS)){         
1057                 // bogus weapon
1058                 if(other_obj->instance >= MAX_WEAPONS){
1059                         return;
1060                 }
1061
1062                 // bogus parent
1063                 if(other_obj->parent < 0){
1064                         return;
1065                 }
1066                 if(other_obj->parent >= MAX_SHIPS){
1067                         return;
1068                 }
1069                 if(Objects[other_obj->parent].type != OBJ_SHIP){
1070                         return;
1071                 }
1072                 if((Objects[other_obj->parent].instance < 0) || (Objects[other_obj->parent].instance >= MAX_SHIPS)){
1073                         return;
1074                 }               
1075
1076                 int is_bonehead = 0;
1077                 int sub_type = Weapon_info[Weapons[other_obj->instance].weapon_info_index].subtype;
1078
1079                 // determine if this was a bonehead hit or not
1080                 if(hit_obj->type == OBJ_SHIP){
1081                    is_bonehead = Ships[hit_obj->instance].team==Ships[Objects[other_obj->parent].instance].team ? 1 : 0;
1082                 }
1083                 // can't have a bonehead hit on an asteroid
1084                 else {
1085                         is_bonehead = 0;
1086                 }
1087
1088                 // set the flag indicating that we've already applied a "stats" hit for this weapon
1089                 // Weapons[other_obj->instance].weapon_flags |= WF_ALREADY_APPLIED_STATS;
1090
1091                 // in multiplayer -- only the server records the stats
1092                 if( Game_mode & GM_MULTIPLAYER ) {
1093                         if ( Net_player->flags & NETINFO_FLAG_AM_MASTER ) {
1094                                 int player_num;
1095
1096                                 // get the player num of the parent object.  A player_num of -1 means that the
1097                                 // parent of this object was not a player
1098                                 player_num = multi_find_player_by_object( &Objects[other_obj->parent] );
1099                                 if ( player_num != -1 ) {
1100                                         switch(sub_type) {
1101                                         case WP_LASER : 
1102                                                 if(is_bonehead){
1103                                                         Net_players[player_num].player->stats.mp_bonehead_hits++;
1104                                                 } else {
1105                                                         Net_players[player_num].player->stats.mp_shots_hit++; 
1106                                                 }
1107
1108                                                 // SDL_assert( Net_players[player_num].player->stats.mp_shots_hit <= Net_players[player_num].player->stats.mp_shots_fired );
1109                                                 break;
1110                                         case WP_MISSILE :
1111                                                 // friendly hit, once it hits a friendly, its done
1112                                                 if(is_bonehead){                                        
1113                                                         if(!from_blast){
1114                                                                 Net_players[player_num].player->stats.ms_bonehead_hits++;
1115                                                         }                                       
1116                                                 }
1117                                                 // hostile hit
1118                                                 else {
1119                                                         // if its a bomb, count every bit of damage it does
1120                                                         if(Weapons[other_obj->instance].weapon_flags & WIF_BOMB){
1121                                                                 // once we get impact damage, stop keeping track of it
1122                                                                 Net_players[player_num].player->stats.ms_shots_hit++;
1123                                                         }
1124                                                         // if its not a bomb, only count impact damage
1125                                                         else {
1126                                                                 if(!from_blast){
1127                                                                         Net_players[player_num].player->stats.ms_shots_hit++;
1128                                                                 }       
1129                                                         }                               
1130                                                 }
1131                                         default : 
1132                                                 break;
1133                                         }
1134                                 }
1135                         }
1136                 } else if(Player_obj == &(Objects[other_obj->parent])){
1137                         switch(sub_type){
1138                         case WP_LASER : 
1139                                 if(is_bonehead){
1140                                         Player->stats.mp_bonehead_hits++;
1141                                 } else {
1142                                         Player->stats.mp_shots_hit++; 
1143                                 }
1144                                 break;
1145                         case WP_MISSILE :
1146                                 // friendly hit, once it hits a friendly, its done
1147                                 if(is_bonehead){                                        
1148                                         if(!from_blast){
1149                                                 Player->stats.ms_bonehead_hits++;
1150                                         }                                       
1151                                 }
1152                                 // hostile hit
1153                                 else {
1154                                         // if its a bomb, count every bit of damage it does
1155                                         if(Weapons[other_obj->instance].weapon_flags & WIF_BOMB){
1156                                                 // once we get impact damage, stop keeping track of it
1157                                                 Player->stats.ms_shots_hit++;
1158                                         }
1159                                         // if its not a bomb, only count impact damage
1160                                         else {
1161                                                 if(!from_blast){
1162                                                         Player->stats.ms_shots_hit++;
1163                                                 }
1164                                         }
1165                                 }                               
1166                                 break;
1167                         default : 
1168                                 break;
1169                         }
1170                 }
1171         }
1172 }
1173
1174 // get a scaling factor for adding/subtracting from mission score
1175 float scoring_get_scale_factor()
1176 {
1177         // multiplayer dogfight. don't scale anything
1178         if((Game_mode & GM_MULTIPLAYER) && (Netgame.type_flags & NG_TYPE_DOGFIGHT)){
1179                 return 1.0f;
1180         }
1181
1182         // check for bogus Skill_level values
1183         SDL_assert((Game_skill_level >= 0) && (Game_skill_level < NUM_SKILL_LEVELS));
1184         if((Game_skill_level < 0) || (Game_skill_level > NUM_SKILL_LEVELS-1)){
1185                 return Scoring_scale_factors[0];
1186         }
1187
1188         // return the correct scale value
1189         return Scoring_scale_factors[Game_skill_level];
1190 }
1191
1192
1193 // ----------------------------------------------------------------------------------------
1194 // DCF functions
1195 //
1196
1197 // bash the passed player to the specified rank
1198 void scoring_bash_rank(player *pl,int rank)
1199 {       
1200         // if this is an invalid rank, do nothing
1201         if((rank < RANK_ENSIGN) || (rank > RANK_ADMIRAL)){
1202                 nprintf(("General","Could not bash player rank - invalid value!!!\n"));
1203                 return;
1204         }
1205
1206         // set the player's score and rank
1207         pl->stats.score = Ranks[rank].points + 1;
1208         pl->stats.rank = rank;
1209 }
1210
1211 DCF(rank, "changes scoring vars")
1212 {
1213         if(Dc_command){         
1214                 dc_get_arg(ARG_INT);            
1215                 
1216                 // parse the argument and change things around accordingly              
1217                 if((Dc_arg_type & ARG_INT) && (Player != NULL)){                                                        
1218                         scoring_bash_rank(Player,Dc_arg_int);
1219                 }               
1220         }
1221         dc_printf("Usage\n0 : Ensign\n1 : Lieutenant Junior Grade\n");
1222         dc_printf("2 : Lietenant\n3 : Lieutenant Commander\n");
1223         dc_printf("4 : Commander\n5 : Captain\n6 : Commodore\n");
1224         dc_printf("7 : Rear Admiral\n8 : Vice Admiral\n9 : Admiral");
1225 }