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