2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
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
10 * $Logfile: /Freespace2/code/Stats/Scoring.cpp $
15 * Scoring system code, medals, rank, etc.
18 * Revision 1.5 2003/06/11 18:30:33 taylor
21 * Revision 1.4 2003/05/25 02:30:44 taylor
24 * Revision 1.3 2002/06/09 04:41:27 relnev
25 * added copyright header
27 * Revision 1.2 2002/05/07 03:16:52 theoddone33
28 * The Great Newline Fix
30 * Revision 1.1.1.1 2002/05/03 03:28:10 root
34 * 23 10/25/99 5:51p Jefff
35 * commented some scoring debug code
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%.
41 * 21 9/12/99 9:56p Jefff
42 * fixed another cause of the
43 * medals-in-training-missions-not-getting-recorded bug
45 * 20 9/10/99 9:44p Dave
46 * Bumped version # up. Make server reliable connects not have such a huge
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
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.
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.
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.
66 * 15 8/17/99 1:12p Dave
67 * Send TvT update when a client has finished joining so he stays nice and
70 * 14 8/06/99 9:46p Dave
71 * Hopefully final changes for the demo.
73 * 13 7/30/99 7:01p Dave
74 * Dogfight escort gauge. Fixed up laser rendering in Glide.
76 * 12 4/30/99 12:18p Dave
77 * Several minor bug fixes.
79 * 11 4/28/99 11:13p Dave
80 * Temporary checkin of artillery code.
82 * 10 3/01/99 10:00a Dave
83 * Fxied several dogfight related stats bugs.
85 * 9 2/26/99 4:14p Dave
86 * Put in the ability to have multiple shockwaves for ships.
88 * 8 2/23/99 2:29p Dave
89 * First run of oldschool dogfight mode.
91 * 7 2/17/99 2:11p Dave
92 * First full run of squad war. All freespace and tracker side stuff
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.
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.
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.
108 * 3 10/13/98 9:29a Dave
109 * Started neatening up freespace.h. Many variables renamed and
110 * reorganized. Added AlphaColors.[h,cpp]
112 * 2 10/07/98 10:54a Dave
115 * 1 10/07/98 10:51a Dave
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
122 * 83 9/10/98 6:14p Dave
123 * Removed bogus assert.
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
129 * 81 6/01/98 11:43a John
130 * JAS & MK: Classified all strings for localization.
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
137 * 79 5/18/98 9:14p Dave
138 * Put in network config files support.
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
145 * 77 5/15/98 9:52p Dave
146 * Added new stats for freespace. Put in artwork for viewing stats on PXO.
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.
153 * 75 5/13/98 5:13p Allender
154 * red alert support to go back to previous mission
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.
161 * 73 5/06/98 3:15p Allender
162 * fixed some ranking problems. Made vasudan support ship available in
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.
170 * 71 5/03/98 2:52p Dave
171 * Removed multiplayer furball mode.
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.
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.
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.
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(...)
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
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.
202 #include "freespace.h"
207 #include "multiutil.h"
211 #include "multimsgs.h"
213 #include "localize.h"
214 #include "multi_team.h"
215 #include "multi_dogfight.h"
216 #include "multi_pmsg.h"
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)
222 // these tables are overwritten with the values from rank.tbl
223 rank_stuff Ranks[NUM_RANKS];
225 // scoring scale factors by skill level
226 float Scoring_scale_factors[NUM_SKILL_LEVELS] = {
234 void parse_rank_tbl()
236 char buf[MULTITEXT_LENGTH];
243 read_file_text("rank.tbl");
246 // parse in all the rank names
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);
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);
273 // be sure that all rank points are in order
275 for ( idx = 0; idx < NUM_RANKS-1; idx++ ) {
276 if ( Ranks[idx].points >= Ranks[idx+1].points )
281 // close localization
285 // free memory from table parsing
286 void scoring_tbl_close()
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;
298 // initialize a nice blank scoring element
299 void init_scoring_element(scoring_struct *s)
304 Int3(); // DaveB -- Fix this!
305 // read_pilot_file(char* callsign);
309 memset(s, 0, sizeof(scoring_struct));
311 s->rank = RANK_ENSIGN;
314 s->kill_count_ok = 0;
316 for (i=0; i<NUM_MEDALS; i++){
320 for (i=0; i<MAX_SHIP_TYPES; i++){
326 s->m_kill_count_ok = 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;
336 s->p_shots_fired=0; s->p_shots_hit=0;
337 s->s_shots_fired=0; s->s_shots_hit=0;
339 s->mp_shots_fired=0; s->mp_shots_hit=0;
340 s->ms_shots_fired=0; s->ms_shots_hit=0;
342 s->m_player_deaths = 0;
346 s->missions_flown = 0;
351 for(i=0; i<MAX_PLAYERS; i++){
352 s->m_dogfight_kills[i] = 0;
359 void scoring_eval_harbison( ship *shipp )
363 if ( !SDL_strcasecmp(shipp->ship_name, "alpha 2") && (!SDL_strcasecmp(Game_current_mission_filename, "demo01") || !SDL_strcasecmp(Game_current_mission_filename, "sm1-01")) ) {
366 fp = fopen("i:\\volition\\cww\\harbison.txt", "r+t");
369 fscanf(fp, "%d", &death_count );
371 fseek(fp, 0, SEEK_SET);
372 fprintf(fp, "%d\n", death_count);
379 // initialize the Player's mission-based stats before he goes into a mission
380 void scoring_level_init( scoring_struct *scp )
384 scp->m_medal_earned = -1; // hasn't earned a medal yet
385 scp->m_promotion_earned = -1;
386 scp->m_badge_earned = -1;
389 scp->mp_shots_fired=0;
390 scp->mp_shots_hit = 0;
391 scp->ms_shots_fired = 0;
392 scp->ms_shots_hit = 0;
394 scp->mp_bonehead_hits=0;
395 scp->ms_bonehead_hits=0;
396 scp->m_bonehead_kills=0;
398 for (i=0; i<MAX_SHIP_TYPES; i++){
403 scp->m_kill_count = 0;
404 scp->m_kill_count_ok = 0;
406 scp->m_player_deaths =0;
408 for(i=0; i<MAX_PLAYERS; i++){
409 scp->m_dogfight_kills[i] = 0;
414 void scoring_eval_rank( scoring_struct *sc )
416 int i, score, new_rank, old_rank;
421 // first check to see if the promotion flag is set -- if so, return the new rank
422 if ( Player->flags & PLAYER_FLAGS_PROMOTED ) {
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
429 if ( sc->rank < MAX_FREESPACE1_RANK ) {
431 if ( sc->rank < MAX_FREESPACE2_RANK ) {
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);
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 )
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;
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)
458 int i, total_kills, badge;
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
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];
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.
473 for (i = 0; i < MAX_BADGES; i++ ) {
474 if ( total_kills >= Medals[Badge_index[i]].kills_needed ){
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;
487 // central point for dealing with accepting the score for a misison.
488 void scoring_do_accept(scoring_struct *score)
492 // do rank, badges, and medals first since they require the alltime stuff
493 // to not be updated yet.
496 if ( score->m_medal_earned != -1 ){
497 score->medals[score->m_medal_earned]++;
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){
506 scoring_eval_rank(score);
507 scoring_eval_badges(score);
509 score->kill_count += score->m_kill_count;
510 score->kill_count_ok += score->m_kill_count_ok;
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;
517 score->p_shots_hit += score->mp_shots_hit;
518 score->s_shots_hit += score->ms_shots_hit;
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;
524 for(idx=0;idx<MAX_SHIP_TYPES;idx++){
525 score->kills[idx] = (unsigned short)(score->kills[idx] + score->m_okKills[idx]);
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++;
535 // backout the score for a mission. This function gets called when the player chooses to refly a misison
537 void scoring_backout_accept( scoring_struct *score )
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;
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){
552 score->kill_count -= score->m_kill_count;
553 score->kill_count_ok -= score->m_kill_count_ok;
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;
560 score->p_shots_hit -= score->mp_shots_hit;
561 score->s_shots_hit -= score->ms_shots_hit;
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;
567 for(idx=0;idx<MAX_SHIP_TYPES;idx++){
568 score->kills[idx] = (unsigned short)(score->kills[idx] - score->m_okKills[idx]);
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 );
577 // if the player was promoted, take it back
578 if ( score->m_promotion_earned != -1) {
580 SDL_assert( score->rank >= 0 );
583 score->flight_time -= (unsigned int)f2fl(Missiontime);
584 score->last_flown = score->last_backup;
585 score->missions_flown--;
588 // merge any mission stats accumulated into the alltime stats (as well as updating per campaign stats)
589 void scoring_level_close(int accepted)
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);
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 );
615 nprintf(("General","Storing stats now\n"));
616 scoring_do_accept( &Player->stats );
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;
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;
638 // STATS damage, assists recording stuff
639 void scoring_add_damage(object *ship_obj,object *other_obj,float damage)
641 int found_slot, signature;
642 int lowest_index,idx;
646 // multiplayer clients bail here
647 if(MULTIPLAYER_CLIENT){
651 // if we have no other object, bail
652 if(other_obj == NULL){
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;
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;
668 signature = other_obj->signature;
672 // don't count damage done to a ship by himself
673 if(use_obj == ship_obj){
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;
684 actual_damage = damage;
686 if(actual_damage < 0.0f){
687 actual_damage = 0.0f;
689 sp->total_damage_received += actual_damage;
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;
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)){
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) ){
712 // if not found (implying all slots are taken), then find the slot with the lowest damage % and use that
715 for(idx=0;idx<MAX_DAMAGE_SLOTS;idx++){
716 if(sp->damage_ship[idx] < sp->damage_ship[lowest_index]){
724 // fill in the slot damage and damager-index
726 sp->damage_ship[lowest_index] += actual_damage;
728 sp->damage_ship[lowest_index] = actual_damage;
730 sp->damage_ship_id[lowest_index] = signature;
734 char Scoring_debug_text[4096];
736 // evaluate a kill on a ship
737 void scoring_eval_kill(object *ship_obj)
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;
749 // multiplayer clients bail here
750 if(MULTIPLAYER_CLIENT){
754 // we don't evaluate kills on anything except ships
755 if(ship_obj->type != OBJ_SHIP){
758 if((ship_obj->instance < 0) || (ship_obj->instance >= MAX_SHIPS)){
762 // assign the dead ship
763 dead_ship = &Ships[ship_obj->instance];
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];
774 if(ship_obj == Player_obj){
775 Player->stats.m_player_deaths++;
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;
788 scoring_eval_harbison( dead_ship );
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;
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++){
803 if(dead_ship->damage_ship_id[idx] < 0){
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;
814 if((max_damage_index < 0) || (max_damage_index >= MAX_DAMAGE_SLOTS)){
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;
823 if(max_damage_pct > 1.0f){
824 max_damage_pct = 1.0f;
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];
832 // null this out for now
836 // get the player (whether single or multiplayer)
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];
845 if(Objects[Player->objnum].signature == killer_sig){
850 // if we found a valid player, evaluate some kill details
855 if((plr->objnum < 0) || (plr->objnum >= MAX_OBJECTS)){
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 );
866 // if you hit this next SDL_assert, find allender. If not here, don't worry about it, you may safely
868 SDL_assert( !(Ship_info[si_index].flags & SIF_SHIP_COPY) );
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());
877 multi_team_maybe_add_score(-(int)(dead_ship->score * scoring_get_scale_factor()), net_plr->p_info.team);
880 // otherwise increment his valid kill count and score
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
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);
893 multi_team_maybe_add_score((int)(dead_ship->score * scoring_get_scale_factor()), net_plr->p_info.team);
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);
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);
909 HUD_printf(score_text);
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] = "";
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);
929 // increment his all-encompassing kills
930 plr->stats.m_kills[si_index]++;
931 plr->stats.m_kill_count++;
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);
941 send_player_stats_block_packet(&Net_players[net_player_num], STATS_DOGFIGHT_KILLS);
943 send_player_stats_block_packet(&Net_players[net_player_num], STATS_MISSION_KILLS);
948 // set killer_sig for this ship to -1, indicating no one got the kill for it
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);
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;
962 #if !defined(RELEASE_REAL)
963 if (Game_mode & GM_MULTIPLAYER) {
965 sprintf(Scoring_debug_text, "%s killed.\nDamage by ship:\n\n", Ship_info[dead_ship->ship_info_index].name);
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]);
974 if (dead_ship->damage_ship_id[i] == killer_sig ) {
975 strcat(buf, " KILLER\n");
980 strcat(Scoring_debug_text, buf);
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)
996 // multiplayer clients bail here
997 if(MULTIPLAYER_CLIENT){
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)
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;
1017 if(Objects[Player->objnum].signature == sp->damage_ship_id[idx]){
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++;
1026 nprintf(("Network","-==============GAVE PLAYER %s AN ASSIST=====================-\n",plr->callsign));
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)
1036 // multiplayer clients bail here
1037 if(MULTIPLAYER_CLIENT){
1041 // only evaluate hits on ships and asteroids
1042 if((hit_obj->type != OBJ_SHIP) && (hit_obj->type != OBJ_ASTEROID)){
1046 // if the other_obj == NULL, we can't evaluate where it came from, so bail here
1047 if(other_obj == NULL){
1051 // other bogus situtations
1052 if(other_obj->instance < 0){
1056 if((other_obj->type == OBJ_WEAPON) && !(Weapons[other_obj->instance].weapon_flags & WF_ALREADY_APPLIED_STATS)){
1058 if(other_obj->instance >= MAX_WEAPONS){
1063 if(other_obj->parent < 0){
1066 if(other_obj->parent >= MAX_SHIPS){
1069 if(Objects[other_obj->parent].type != OBJ_SHIP){
1072 if((Objects[other_obj->parent].instance < 0) || (Objects[other_obj->parent].instance >= MAX_SHIPS)){
1076 int is_bonehead = 0;
1077 int sub_type = Weapon_info[Weapons[other_obj->instance].weapon_info_index].subtype;
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;
1083 // can't have a bonehead hit on an asteroid
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;
1091 // in multiplayer -- only the server records the stats
1092 if( Game_mode & GM_MULTIPLAYER ) {
1093 if ( Net_player->flags & NETINFO_FLAG_AM_MASTER ) {
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 ) {
1103 Net_players[player_num].player->stats.mp_bonehead_hits++;
1105 Net_players[player_num].player->stats.mp_shots_hit++;
1108 // SDL_assert( Net_players[player_num].player->stats.mp_shots_hit <= Net_players[player_num].player->stats.mp_shots_fired );
1111 // friendly hit, once it hits a friendly, its done
1114 Net_players[player_num].player->stats.ms_bonehead_hits++;
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++;
1124 // if its not a bomb, only count impact damage
1127 Net_players[player_num].player->stats.ms_shots_hit++;
1136 } else if(Player_obj == &(Objects[other_obj->parent])){
1140 Player->stats.mp_bonehead_hits++;
1142 Player->stats.mp_shots_hit++;
1146 // friendly hit, once it hits a friendly, its done
1149 Player->stats.ms_bonehead_hits++;
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++;
1159 // if its not a bomb, only count impact damage
1162 Player->stats.ms_shots_hit++;
1174 // get a scaling factor for adding/subtracting from mission score
1175 float scoring_get_scale_factor()
1177 // multiplayer dogfight. don't scale anything
1178 if((Game_mode & GM_MULTIPLAYER) && (Netgame.type_flags & NG_TYPE_DOGFIGHT)){
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];
1188 // return the correct scale value
1189 return Scoring_scale_factors[Game_skill_level];
1193 // ----------------------------------------------------------------------------------------
1197 // bash the passed player to the specified rank
1198 void scoring_bash_rank(player *pl,int rank)
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"));
1206 // set the player's score and rank
1207 pl->stats.score = Ranks[rank].points + 1;
1208 pl->stats.rank = rank;
1211 DCF(rank, "changes scoring vars")
1214 dc_get_arg(ARG_INT);
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);
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");