1 /*
2  * XPilot NG, a multiplayer space war game.
3  *
4  * Copyright (C) 2000-2004 by
5  *
6  *      Uoti Urpala          <uau@users.sourceforge.net>
7  *      Kristian S�derblom   <kps@users.sourceforge.net>
8  *
9  * Copyright (C) 1991-2001 by
10  *
11  *      Bj�rn Stabell        <bjoern@xpilot.org>
12  *      Ken Ronny Schouten   <ken@xpilot.org>
13  *      Bert Gijsbers        <bert@xpilot.org>
14  *      Dick Balaska         <dick@xpilot.org>
15  *
16  * This program is free software; you can redistribute it and/or modify
17  * it under the terms of the GNU General Public License as published by
18  * the Free Software Foundation; either version 2 of the License, or
19  * (at your option) any later version.
20  *
21  * This program is distributed in the hope that it will be useful,
22  * but WITHOUT ANY WARRANTY; without even the implied warranty of
23  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24  * GNU General Public License for more details.
25  *
26  * You should have received a copy of the GNU General Public License
27  * along with this program; if not, write to the Free Software
28  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
29  */
30 
31 #include "xpserver.h"
32 
Score(player_t * pl,double points,clpos_t pos,const char * msg)33 void Score(player_t * pl, double points, clpos_t pos, const char *msg)
34 {
35     Rank_add_score(pl, points);
36     if (pl->conn != NULL)
37 	Send_score_object(pl->conn, points, pos, msg);
38     updateScores = true;
39 }
40 
Rate(double winner,double loser)41 double Rate(double winner, double loser)
42 {
43     double t;
44 
45     if (options.constantScoring)
46 	return RATE_SIZE / 2;
47     t = ((RATE_SIZE / 2) * RATE_RANGE) / (ABS(loser - winner) +
48 					  RATE_RANGE);
49     if (loser > winner)
50 	t = RATE_SIZE - t;
51     return t;
52 }
53 
54 /*
55  * Cause 'winner' to get 'winner_score' points added with message
56  * 'winner_msg', and similarly with the 'loser' and equivalent
57  * variables.
58  *
59  * In general the winner_score should be positive, and the loser_score
60  * negative, but this need not be true.
61  *
62  * If the winner and loser players are on the same team, the scores are
63  * made negative, since you shouldn't gain points by killing team members,
64  * or being killed by a team member (it is both players faults).
65  *
66  * KK 28-4-98: Same for killing your own tank.
67  * KK 7-11-1: And for killing a member of your alliance
68  */
Score_players(player_t * winner_pl,double winner_score,char * winner_msg,player_t * loser_pl,double loser_score,char * loser_msg,bool transfer_tag)69 void Score_players(player_t * winner_pl, double winner_score,
70 		   char *winner_msg, player_t * loser_pl,
71 		   double loser_score, char *loser_msg, bool transfer_tag)
72 {
73     if (Players_are_teammates(winner_pl, loser_pl)
74 	|| Players_are_allies(winner_pl, loser_pl)
75 	|| (Player_is_tank(loser_pl)
76 	    && loser_pl->lock.pl_id == winner_pl->id)) {
77 	if (winner_score > 0)
78 	    winner_score = -winner_score;
79 	if (loser_score > 0)
80 	    loser_score = -loser_score;
81     }
82 
83     if (options.tagGame
84 	&& winner_score > 0.0 && loser_score < 0.0 && transfer_tag) {
85 	if (tagItPlayerId == winner_pl->id) {
86 	    winner_score *= options.tagItKillScoreMult;
87 	    loser_score *= options.tagItKillScoreMult;
88 	} else if (tagItPlayerId == loser_pl->id) {
89 	    winner_score *= options.tagKillItScoreMult;
90 	    loser_score *= options.tagKillItScoreMult;
91 	    Transfer_tag(loser_pl, winner_pl);
92 	}
93     }
94 
95     Score(winner_pl, winner_score, loser_pl->pos, winner_msg);
96     Score(loser_pl, loser_score, loser_pl->pos, loser_msg);
97 }
98 
Get_Score(player_t * pl)99 double Get_Score(player_t * pl)
100 {
101     return pl->score;
102 }
103 
Set_Score(player_t * pl,double score)104 void Set_Score(player_t * pl, double score)
105 {
106     pl->score = score;
107 }
108 
Add_Score(player_t * pl,double score)109 void Add_Score(player_t * pl, double score)
110 {
111     pl->score += score;
112 }
113 
Handle_Scoring(scoretype_t st,player_t * killer,player_t * victim,void * extra,const char * somemsg)114 void Handle_Scoring(scoretype_t st, player_t * killer, player_t * victim,
115 		    void *extra, const char *somemsg)
116 {
117     double sc = 0.0, sc2 = 0.0, factor = 0.0;
118     int i_tank_owner = 0, j = 0;
119     player_t *true_killer;
120 
121     if (!(killer || victim)) {
122 	warn("Attempted to score with neither victim nor killer");
123 	return;
124     }
125 
126     switch (st) {
127     case SCORE_CANNON_KILL:
128 	sc = Rate(Get_Score(killer), ((cannon_t *) extra)->score)
129 	    * options.cannonKillScoreMult;
130 	if (BIT(world->rules->mode, TEAM_PLAY)
131 	    && killer->team == ((cannon_t *) extra)->team)
132 	    sc = -sc;
133 	if (!options.zeroSumScoring)
134 	    Score(killer, sc, ((cannon_t *) extra)->pos, "");
135 	break;
136     case SCORE_WALL_DEATH:
137 	sc = Rate(WALL_SCORE, Get_Score(victim));
138 	if (somemsg) {
139 	    if (!options.zeroSumScoring)
140 		Score(victim, -sc, victim->pos, somemsg);
141 	} else {
142 	    if (!options.zeroSumScoring)
143 		Score(victim, -sc, victim->pos, victim->name);
144 	}
145 	break;
146     case SCORE_COLLISION:
147 	if (!Player_is_tank(killer) && !Player_is_tank(victim)) {
148 	    sc = Rate(Get_Score(victim),
149 		      Get_Score(killer)) * options.crashScoreMult;
150 	    sc2 =
151 		Rate(Get_Score(killer),
152 		     Get_Score(victim)) * options.crashScoreMult;
153 	    if (!options.zeroSumScoring)
154 		Score_players(killer, -sc, victim->name, victim, -sc2,
155 			      killer->name, false);
156 	    else
157 		Score_players(killer, sc - sc2, victim->name, victim,
158 			      sc2 - sc, killer->name, false);
159 	} else if (Player_is_tank(killer)) {
160 	    player_t *i_tank_owner_pl = Player_by_id(killer->lock.pl_id);
161 	    sc = Rate(Get_Score(i_tank_owner_pl), Get_Score(victim))
162 		* options.tankKillScoreMult;
163 	    Score_players(i_tank_owner_pl, sc, victim->name, victim, -sc,
164 			  killer->name, true);
165 	} else if (Player_is_tank(victim)) {
166 	    player_t *j_tank_owner_pl = Player_by_id(victim->lock.pl_id);
167 	    sc = Rate(Get_Score(j_tank_owner_pl), Get_Score(killer))
168 		* options.tankKillScoreMult;
169 	    Score_players(j_tank_owner_pl, sc, killer->name, killer, -sc,
170 			  victim->name, true);
171 	}
172 	/* don't bother scoring two tanks */
173 	break;
174     case SCORE_ROADKILL:
175 	true_killer = killer;
176 	if (Player_is_tank(killer)) {
177 	    i_tank_owner = GetInd(killer->lock.pl_id);
178 	    if (i_tank_owner == GetInd(victim->id))
179 		i_tank_owner = GetInd(killer->id);
180 	    true_killer = Player_by_index(i_tank_owner);
181 	    Rank_add_tank_kill(true_killer);
182 	    sc = Rate(Get_Score(true_killer), Get_Score(victim))
183 		* options.tankKillScoreMult;
184 	} else {
185 	    Rank_add_runover_kill(killer);
186 	    sc = Rate(Get_Score(killer),
187 		      Get_Score(victim)) * options.runoverKillScoreMult;
188 	}
189 	Score_players(true_killer, sc, victim->name, victim, -sc,
190 		      killer->name, true);
191 	break;
192     case SCORE_BALL_KILL:
193 	if (!killer) {
194 	    sc = Rate(0.0, Get_Score(victim)) * options.ballKillScoreMult
195 		* options.unownedKillScoreMult;
196 	    if (!options.zeroSumScoring)
197 		Score(victim, -sc, victim->pos, "Ball");
198 	} else {
199 	    if (killer == victim) {
200 		sc = Rate(0.0,
201 			  Get_Score(victim)) * options.ballKillScoreMult *
202 		    options.selfKillScoreMult;
203 		if (!options.zeroSumScoring)
204 		    Score(victim, -sc, victim->pos, killer->name);
205 	    } else {
206 		Rank_add_ball_kill(killer);
207 		sc = Rate(Get_Score(killer),
208 			  Get_Score(victim)) * options.ballKillScoreMult;
209 		Score_players(killer, sc, victim->name, victim, -sc,
210 			      killer->name, true);
211 	    }
212 	}
213 	break;
214     case SCORE_HIT_MINE:
215 	sc = Rate(Get_Score(killer),
216 		  Get_Score(victim)) * options.mineScoreMult;
217 	Score_players(killer, sc, victim->name, victim, -sc, killer->name,
218 		      false);
219 	break;
220     case SCORE_EXPLOSION:
221 	if (!killer || killer->id == victim->id) {
222 	    sc = Rate(0.0,
223 		      Get_Score(victim)) * options.explosionKillScoreMult *
224 		options.selfKillScoreMult;
225 	    if (!options.zeroSumScoring)
226 		Score(victim, -sc, victim->pos,
227 		      (killer == NULL) ? "[Explosion]" : victim->name);
228 	} else {
229 	    Rank_add_explosion_kill(killer);
230 	    sc = Rate(Get_Score(killer), Get_Score(victim))
231 		* options.explosionKillScoreMult;
232 	    Score_players(killer, sc, victim->name, victim, -sc,
233 			  killer->name, true);
234 	}
235 	break;
236     case SCORE_ASTEROID_KILL:
237 	sc = Rate(Get_Score(killer),
238 		  ASTEROID_SCORE) * options.unownedKillScoreMult;
239 	if (!options.zeroSumScoring)
240 	    Score(killer, sc, ((wireobject_t *) extra)->pos, "");
241 	break;
242     case SCORE_ASTEROID_DEATH:
243 	sc = Rate(0.0, Get_Score(victim)) * options.unownedKillScoreMult;
244 	if (!options.zeroSumScoring)
245 	    Score(victim, -sc, victim->pos, "[Asteroid]");
246 	break;
247     case SCORE_SHOT_DEATH:
248 	if (BIT(((object_t *) extra)->obj_status, FROMCANNON)) {
249 	    cannon_t *cannon = Cannon_by_id(((object_t *) extra)->id);
250 
251 	    /*KHS: for cannon dodgers; cannon hit substracts */
252 	    /* fixed percentage of score */
253 
254 	    if (options.survivalScore != 0.0)
255 		sc = Get_Score(victim) * 0.02;
256 	    else if (cannon != NULL)
257 		sc = Rate(cannon->score, Get_Score(victim))
258 		    * options.cannonKillScoreMult;
259 	    else {
260 		assert(((object_t *) extra)->id == NO_ID);
261 		sc = Rate(UNOWNED_SCORE, Get_Score(victim))
262 		    * options.cannonKillScoreMult;
263 	    }
264 	} else if (((object_t *) extra)->id == NO_ID) {
265 	    sc = Rate(0.0,
266 		      Get_Score(victim)) * options.unownedKillScoreMult;
267 	} else {
268 	    if (killer->id == victim->id) {
269 		sc = Rate(0.0,
270 			  Get_Score(victim)) * options.selfKillScoreMult;
271 	    } else {
272 		Rank_add_shot_kill(killer);
273 		sc = Rate(Get_Score(killer), Get_Score(victim));
274 	    }
275 	}
276 
277 	switch (((object_t *) extra)->type) {
278 	case OBJ_SHOT:
279 	    if (Mods_get(((object_t *) extra)->mods, ModsCluster))
280 		factor = options.clusterKillScoreMult;
281 	    else
282 		factor = options.shotKillScoreMult;
283 	    break;
284 	case OBJ_TORPEDO:
285 	    factor = options.torpedoKillScoreMult;
286 	    break;
287 	case OBJ_SMART_SHOT:
288 	    factor = options.smartKillScoreMult;
289 	    break;
290 	case OBJ_HEAT_SHOT:
291 	    factor = options.heatKillScoreMult;
292 	    break;
293 	default:
294 	    factor = options.shotKillScoreMult;
295 	    break;
296 	}
297 
298 	sc *= factor;
299 	if (BIT(((object_t *) extra)->obj_status, FROMCANNON)) {
300 	    if (!options.zeroSumScoring)
301 		Score(victim, -sc, victim->pos, "Cannon");
302 	} else if ((((object_t *) extra)->id == NO_ID)
303 		   || (killer && (killer->id == victim->id))) {
304 	    if (!options.zeroSumScoring)
305 		Score(victim, -sc, victim->pos,
306 		      ((object_t *) extra)->id ==
307 		      NO_ID ? "" : victim->name);
308 	} else {
309 	    Score_players(killer, sc, victim->name, victim, -sc,
310 			  killer->name, true);
311 	}
312 
313 	break;
314     case SCORE_LASER:
315 	if (killer) {
316 	    if (victim->id == killer->id) {
317 		sc = Rate(0.0,
318 			  Get_Score(killer)) * options.laserKillScoreMult *
319 		    options.selfKillScoreMult;
320 		if (!options.zeroSumScoring)
321 		    Score(killer, -sc, killer->pos, killer->name);
322 	    } else {
323 		sc = Rate(Get_Score(killer),
324 			  Get_Score(victim)) * options.laserKillScoreMult;
325 		Score_players(killer, sc, victim->name, victim, -sc,
326 			      killer->name, true);
327 		Rank_add_laser_kill(killer);
328 	    }
329 	} else if (((cannon_t *) extra) != NULL) {
330 	    sc = Rate(((cannon_t *) extra)->score, Get_Score(victim))
331 		* options.cannonKillScoreMult;
332 	    if (!options.zeroSumScoring)
333 		Score(victim, -sc, victim->pos, "Cannon");
334 	} else {
335 	    sc = Rate(UNOWNED_SCORE,
336 		      Get_Score(victim)) * options.unownedKillScoreMult;
337 	    if (!options.zeroSumScoring)
338 		Score(victim, -sc, victim->pos, "");
339 	    Set_message_f("%s got roasted alive.", victim->name);
340 	}
341 	break;
342     case SCORE_TARGET:
343 	{
344 	    double por, win_score = 0.0, lose_score = 0.0;
345 	    int win_team_members = 0, lose_team_members =
346 		0, targets_remaining = 0, targets_total = 0;
347 	    target_t *targ = (target_t *) extra;
348 	    bool somebody = false;
349 
350 	    if (BIT(world->rules->mode, TEAM_PLAY)) {
351 		for (j = 0; j < NumPlayers; j++) {
352 		    player_t *pl = Player_by_index(j);
353 
354 		    if (Player_is_tank(pl)
355 			|| (Player_is_paused(pl) && pl->pause_count <= 0)
356 			|| Player_is_waiting(pl))
357 			continue;
358 
359 		    if (pl->team == targ->team) {
360 			lose_score += Get_Score(pl);
361 			lose_team_members++;
362 			if (!Player_is_dead(pl))
363 			    somebody = true;
364 		    } else if (pl->team == killer->team) {
365 			win_score += Get_Score(pl);
366 			win_team_members++;
367 		    }
368 		}
369 	    }
370 	    if (somebody) {
371 		for (j = 0; j < Num_targets(); j++) {
372 		    target_t *t = Target_by_index(j);
373 
374 		    if (t->team == targ->team) {
375 			targets_total++;
376 			if (t->dead_ticks <= 0)
377 			    targets_remaining++;
378 		    }
379 		}
380 	    }
381 	    if (!somebody)
382 		break;
383 
384 	    sound_play_sensors(targ->pos, DESTROY_TARGET_SOUND);
385 
386 	    if (targets_remaining > 0) {
387 		sc = Rate(Get_Score(killer), TARGET_SCORE) / 4;
388 		sc = sc * (targets_total -
389 			   targets_remaining) / (targets_total + 1);
390 		if (sc >= 0.01)
391 		    if (!options.zeroSumScoring)
392 			Score(killer, sc, targ->pos, "Target: ");
393 		break;
394 	    }
395 
396 	    if (options.targetKillTeam)
397 		Rank_add_target_kill(killer);
398 
399 	    sc = Rate(win_score, lose_score);
400 	    por = (sc * lose_team_members) / win_team_members;
401 
402 	    for (j = 0; j < NumPlayers; j++) {
403 		player_t *pl = Player_by_index(j);
404 
405 		if (Player_is_tank(pl)
406 		    || (Player_is_paused(pl) && pl->pause_count <= 0)
407 		    || Player_is_waiting(pl))
408 		    continue;
409 
410 		if (pl->team == targ->team) {
411 		    if (options.targetKillTeam
412 			&& targets_remaining == 0 && Player_is_alive(pl))
413 			Player_set_state(pl, PL_STATE_KILLED);
414 		    if (!options.zeroSumScoring)
415 			Score(pl, -sc, targ->pos, "Target: ");
416 		} else if (pl->team == killer->team &&
417 			   (pl->team != TEAM_NOT_SET
418 			    || pl->id == killer->id))
419 		    if (!options.zeroSumScoring)
420 			Score(pl, por, targ->pos, "Target: ");
421 	    }
422 	    break;
423 	}
424     case SCORE_TREASURE:
425 	{
426 	    double win_score = 0.0, lose_score = 0.0, por;
427 	    int i, win_team_members = 0, lose_team_members = 0;
428 	    treasure_t *treasure = (treasure_t *) extra;
429 	    bool somebody = false;
430 
431 	    if (BIT(world->rules->mode, TEAM_PLAY)) {
432 		for (i = 0; i < NumPlayers; i++) {
433 		    player_t *pl_i = Player_by_index(i);
434 
435 		    if (Player_is_tank(pl_i)
436 			|| (Player_is_paused(pl_i)
437 			    && pl_i->pause_count <= 0)
438 			|| Player_is_waiting(pl_i))
439 			continue;
440 		    if (pl_i->team == treasure->team) {
441 			lose_score += Get_Score(pl_i);
442 			lose_team_members++;
443 			if (!Player_is_dead(pl_i))
444 			    somebody = true;
445 		    } else if (pl_i->team == killer->team) {
446 			win_score += Get_Score(pl_i);
447 			win_team_members++;
448 		    }
449 		}
450 	    }
451 
452 	    if (!somebody) {
453 		if (!options.zeroSumScoring)
454 		    Score(killer, Rate(Get_Score(killer),
455 				       TREASURE_SCORE) / 2, treasure->pos,
456 			  "Treasure:");
457 		break;
458 	    }
459 
460 	    treasure->destroyed++;
461 	    world->teams[treasure->team].TreasuresLeft--;
462 	    world->teams[killer->team].TreasuresDestroyed++;
463 
464 	    sc = 3 * Rate(win_score, lose_score);
465 	    por = (sc * lose_team_members) / (2 * win_team_members + 1);
466 
467 	    for (i = 0; i < NumPlayers; i++) {
468 		player_t *pl_i = Player_by_index(i);
469 
470 		if (Player_is_tank(pl_i)
471 		    || (Player_is_paused(pl_i) && pl_i->pause_count <= 0)
472 		    || Player_is_waiting(pl_i))
473 		    continue;
474 
475 		if (pl_i->team == treasure->team) {
476 		    Score(pl_i, -sc, treasure->pos, "Treasure: ");
477 		    Rank_lost_ball(pl_i);
478 		    if (options.treasureKillTeam)
479 			Player_set_state(pl_i, PL_STATE_KILLED);
480 		} else if (pl_i->team == killer->team &&
481 			   (pl_i->team != TEAM_NOT_SET
482 			    || pl_i->id == killer->id)) {
483 		    if (lose_team_members > 0) {
484 			if (pl_i->id == killer->id)
485 			    Rank_cashed_ball(pl_i);
486 			Rank_won_ball(pl_i);
487 		    }
488 		    Score(pl_i,
489 			  (pl_i->id == killer->id ? 3 * por : 2 * por),
490 			  treasure->pos, "Treasure: ");
491 		}
492 	    }
493 
494 	    if (options.treasureKillTeam)
495 		Rank_add_treasure_kill(killer);
496 
497 	    break;
498 	}
499     case SCORE_SELF_DESTRUCT:
500 	if (options.selfDestructScoreMult != 0) {
501 	    sc = Rate(0.0,
502 		      Get_Score(killer)) * options.selfDestructScoreMult;
503 	    if (!options.zeroSumScoring)
504 		Score(killer, -sc, killer->pos, "Self-Destruct");
505 	}
506 	break;
507     case SCORE_SHOVE_KILL:
508 	sc = (*((double *) extra)) * Rate(Get_Score(killer),
509 					  Get_Score(victim)) *
510 	    options.shoveKillScoreMult;
511 	if (!options.zeroSumScoring)
512 	    Score(killer, sc, victim->pos, victim->name);
513 	break;
514     case SCORE_SHOVE_DEATH:
515 	sc = (*((double *) extra)) * Rate(killer->score,
516 					  Get_Score(victim)) *
517 	    options.shoveKillScoreMult;
518 	if (!options.zeroSumScoring)
519 	    Score(victim, -sc, victim->pos, "[Shove]");
520 	break;
521     default:
522 	error("unknown scoring type!");
523 	break;
524     }
525 }
526