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