1 (*
2  * Hedgewars, a free turn based strategy game
3  * Copyright (c) 2004-2015 Andrey Korotaev <unC0Rr@gmail.com>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; version 2 of the License
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  *)
18 
19 {$INCLUDE "options.inc"}
20 
21 unit uStats;
22 interface
23 uses uConsts, uTypes;
24 
25 var TotalRoundsPre: LongInt; // Helper variable for calculating start of Sudden Death and more. Starts at -1 and is incremented on the turn BEFORE the turn which marks the start of the next round. Always -1 while in hog placing phase
26     TotalRoundsReal: LongInt; // Total number of rounds played (-1 if not started or in hog placing phase). Exported to Lua as 'TotalRounds'
27     FinishedTurnsTotal: LongInt;
28     // Variables to disable certain portions of game stats (set by Lua)
29     SendGameResultOn : boolean = true;
30     SendRankingStatsOn : boolean = true;
31     SendAchievementsStatsOn : boolean = true;
32     SendHealthStatsOn : boolean = true;
33     // Clan death log, used for game stats
34     ClanDeathLog : PClanDeathLogEntry;
35 
36 procedure initModule;
37 procedure freeModule;
38 
39 procedure AmmoUsed(am: TAmmoType);
40 procedure HedgehogPoisoned(Gear: PGear; Attacker: PHedgehog);
41 procedure HedgehogSacrificed(Hedgehog: PHedgehog);
42 procedure HedgehogDamaged(Gear: PGear; Attacker: PHedgehog; Damage: Longword; killed: boolean);
43 procedure TargetHit;
44 procedure Skipped;
getIsTurnSkippednull45 function  getIsTurnSkipped: boolean;
46 procedure TurnStats;
47 procedure TurnReaction;
48 procedure TurnStatsReset;
49 procedure SendStats;
50 procedure hedgehogFlight(Gear: PGear; time: Longword);
51 procedure declareAchievement(id, teamname, location: shortstring; value: LongInt);
52 procedure startGhostPoints(n: LongInt);
53 procedure dumpPoint(x, y: LongInt);
54 
55 implementation
56 uses uSound, uLocale, uVariables, uUtils, uIO, uCaptions, uMisc, uConsole, uScript;
57 
58 var DamageClan  : Longword = 0;         // Damage of own clan in turn
59     DamageTeam  : Longword = 0;         // Damage of own team in turn
60     DamageTotal : Longword = 0;         // Total damage dealt in game
61     DamageTurn  : Longword = 0;         // Damage in turn
62     PoisonTurn  : Longword = 0;         // Poisoned enemies in turn
63     PoisonClan  : Longword = 0;         // Poisoned own clan members in turn
64     PoisonTeam  : Longword = 0;         // Poisoned own team members in turn
65     PoisonTotal : Longword = 0;         // Poisoned hogs in whole round
66     KillsClan   : LongWord = 0;         // Own clan members killed in turn
67     KillsTeam   : LongWord = 0;         // Own team members killed in turn
68     KillsSD     : LongWord = 0;         // Killed hedgehogs in turn that died by Sudden Death water rise
69     Kills       : LongWord = 0;         // Killed hedgehogs in turn (including those that died by Sudden Death water rise)
70     KillsTotal  : LongWord = 0;         // Total killed hedgehogs in game
71     HitTargets  : LongWord = 0;         // Target (gtTarget) hits in turn
72     AmmoUsedCount : Longword = 0;       // Number of times an ammo has been used this turn
73     AmmoDamagingUsed : boolean = false; // true if damaging ammo was used in turn
74     FirstBlood  : boolean = false;      // true if the “First blood” taunt has been used in this game
75     StepFirstBlood : boolean = false;   // true if the “First blood” taunt is to be used this turn
76     LeaveMeAlone : boolean = false;     // true if the “Leave me alone” taunt is to be used this turn
77     SkippedTurns: LongWord = 0;         // number of skipped turns in game
78     isTurnSkipped: boolean = false;     // true if this turn was skipped
79     vpHurtSameClan: PVoicepack = nil;   // voicepack of current clan (used for taunts)
80     vpHurtEnemy: PVoicepack = nil;      // voicepack of enemy (used for taunts)
81 
82 procedure HedgehogPoisoned(Gear: PGear; Attacker: PHedgehog);
83 begin
84     if Attacker^.Team^.Clan = Gear^.Hedgehog^.Team^.Clan then
85         begin
86         vpHurtSameClan:= Gear^.Hedgehog^.Team^.voicepack;
87         inc(PoisonClan);
88         if Attacker^.Team = Gear^.Hedgehog^.Team then
89             inc(PoisonTeam);
90         end
91     else
92         begin
93         if not FirstBlood then
94             StepFirstBlood:= true;
95         vpHurtEnemy:= Gear^.Hedgehog^.Team^.voicepack;
96         inc(PoisonTurn)
97         end;
98     Gear^.Hedgehog^.stats.StepPoisoned:= true;
99     inc(PoisonTotal)
100 end;
101 
102 procedure HedgehogSacrificed(Hedgehog: PHedgehog);
103 begin
104     Hedgehog^.stats.Sacrificed:= true
105 end;
106 
107 procedure HedgehogDamaged(Gear: PGear; Attacker: PHedgehog; Damage: Longword; killed: boolean);
108 begin
109 if Attacker^.Team^.Clan = Gear^.Hedgehog^.Team^.Clan then
110     vpHurtSameClan:= Gear^.Hedgehog^.Team^.voicepack
111 else
112     begin
113     if not FirstBlood then
114         StepFirstBlood:= true;
115     vpHurtEnemy:= Gear^.Hedgehog^.Team^.voicepack;
116     if (not killed) and (not bDuringWaterRise) then
117         begin
118         // Check if victim got attacked by RevengeHog again
119         if (Gear^.Hedgehog^.RevengeHog <> nil) and (Gear^.Hedgehog^.RevengeHog = Attacker) and (Gear^.Hedgehog^.stats.StepRevenge = false) then
120             LeaveMeAlone:= true;
121         // Check if attacker got revenge
122         if (Attacker^.RevengeHog <> nil) and (Attacker^.RevengeHog = Gear^.Hedgehog) then
123             begin
124             Attacker^.stats.GotRevenge:= true;
125             // Also reset the "in-row" counter to restore LeaveMeAlone/CutItOut taunts
126             Attacker^.stats.StepDamageRecvInRow:= 0;
127             Attacker^.RevengeHog:= nil;
128             end
129         // If not, victim remembers their attacker to plan *their* revenge
130         else
131             begin
132             Gear^.Hedgehog^.RevengeHog:= Attacker;
133             // To prevent "LeaveMeAlone" being activated if same hog is hit by attacker
134             // multiple times in the same turn.
135             Gear^.Hedgehog^.stats.StepRevenge:= true;
136             end;
137         end
138     end;
139 
140 //////////////////////////
141 
142 if (not bDuringWaterRise) then
143     begin
144     inc(Attacker^.stats.StepDamageGiven, Damage);
145     inc(Gear^.Hedgehog^.stats.StepDamageRecv, Damage);
146     end;
147 
148 if CurrentHedgehog^.Team^.Clan = Gear^.Hedgehog^.Team^.Clan then inc(DamageClan, Damage);
149 if CurrentHedgehog^.Team = Gear^.Hedgehog^.Team then inc(DamageTeam, Damage);
150 
151 if killed then
152     begin
153     Gear^.Hedgehog^.stats.StepDied:= true;
154     inc(Kills);
155 
156     inc(KillsTotal);
157 
158     if bDuringWaterRise then
159         inc(KillsSD)
160     else
161         begin
162         inc(Attacker^.stats.StepKills);
163         inc(Attacker^.Team^.stats.Kills);
164         if (Attacker^.Team^.TeamName = Gear^.Hedgehog^.Team^.TeamName) then
165             begin
166             inc(Attacker^.Team^.stats.TeamKills);
167             inc(Attacker^.Team^.stats.TeamDamage, Gear^.Damage);
168         end;
169         if Gear = Attacker^.Gear then
170             inc(Attacker^.Team^.stats.Suicides);
171         if Attacker^.Team^.Clan = Gear^.Hedgehog^.Team^.Clan then
172             begin
173             inc(KillsClan);
174             if Attacker^.Team = Gear^.Hedgehog^.Team then
175                 inc(KillsTeam);
176             end;
177         end;
178     end;
179 
180 inc(DamageTotal, Damage);
181 inc(DamageTurn, Damage)
182 end;
183 
184 procedure TargetHit();
185 begin
186    inc(HitTargets)
187 end;
188 
189 procedure Skipped;
190 begin
191 inc(SkippedTurns);
192 isTurnSkipped:= true
193 end;
194 
getIsTurnSkippednull195 function getIsTurnSkipped: boolean;
196 begin
197 getIsTurnSkipped:= isTurnSkipped;
198 end;
199 
200 procedure TurnStats;
201 var i, t: LongInt;
202     c: Longword;
203     newEntry: PClanDeathLogEntry;
204 begin
205 inc(FinishedTurnsTotal);
206 
207 for t:= 0 to Pred(TeamsCount) do // send even on zero turn
208     with TeamsArray[t]^ do
209         for i:= 0 to cMaxHHIndex do
210             begin
211             with Hedgehogs[i].stats do
212                 begin
213                 inc(DamageRecv, StepDamageRecv);
214                 inc(DamageGiven, StepDamageGiven);
215                 if StepDamageRecv > MaxStepDamageRecv then
216                     MaxStepDamageRecv:= StepDamageRecv;
217                 if StepDamageGiven > MaxStepDamageGiven then
218                     MaxStepDamageGiven:= StepDamageGiven;
219                 if StepKills > MaxStepKills then
220                     MaxStepKills:= StepKills;
221                 if (Hedgehogs[i].Team <> nil) and (Hedgehogs[i].Team^.Clan^.ClanIndex <> CurrentHedgehog^.Team^.Clan^.ClanIndex) then
222                     begin
223                     if StepDamageRecv > 0 then
224                         inc(StepDamageRecvInRow)
225                     else
226                         StepDamageRecvInRow:= 0;
227                     if StepDamageRecvInRow >= 3 then
228                         LeaveMeAlone:= true;
229                     end;
230                 end;
231             end;
232 
233 
234 // Write into the death log which clans died in this turn,
235 // important for final rankings.
236 c:= 0;
237 newEntry:= nil;
238 for t:= 0 to Pred(ClansCount) do
239     with ClansArray[t]^ do
240         begin
241         if (ClanHealth = 0) and (ClansArray[t]^.DeathLogged = false) then
242             begin
243             if c = 0 then
244                 begin
245                 new(newEntry);
246                 newEntry^.Turn := FinishedTurnsTotal;
247                 newEntry^.NextEntry := nil;
248                 end;
249 
250             newEntry^.KilledClans[c]:= ClansArray[t];
251             inc(c);
252             newEntry^.KilledClansCount := c;
253             ClansArray[t]^.DeathLogged:= true;
254             end;
255 
256         if SendHealthStatsOn then
257             SendStat(siClanHealth, IntToStr(Color) + ' ' + IntToStr(ClanHealth));
258         end;
259 if newEntry <> nil then
260     begin
261     if ClanDeathLog <> nil then
262         begin
263         newEntry^.NextEntry:= ClanDeathLog;
264         end;
265     ClanDeathLog:= newEntry;
266     end;
267 
268 end;
269 
270 procedure TurnReaction;
271 var killsCheck: LongInt;
272     s: ansistring;
273 begin
274 //TryDo(not bBetweenTurns, 'Engine bug: TurnReaction between turns', true);
275 
276 if FinishedTurnsTotal <> 0 then
277     begin
278     s:= ansistring(CurrentHedgehog^.Name);
279     inc(CurrentHedgehog^.stats.FinishedTurns);
280 
281     // killsCheck is used to take deaths into account that were not a traditional "kill"
282     // Hogs that died during SD water rise do not count as "kills" for taunts
283     killsCheck:= KillsSD;
284     // If the hog sacrificed (=kamikaze/piano) itself, this needs to be taken into account for the reactions later
285     if (CurrentHedgehog^.stats.Sacrificed) then
286         inc(killsCheck);
287 
288     // First blood (first damage, poison or kill of enemy)
289     if (StepFirstBlood) and (not FirstBlood) and (ClansCount > 1) and ((DamageTotal > 0) or (KillsTotal > 0) or (PoisonTotal > 0)) then
290         begin
291         FirstBlood:= true;
292         AddVoice(sndFirstBlood, CurrentTeam^.voicepack);
293         end
294 
295     // Hog hurts, poisons or kills itself (except sacrifice)
296     else if (CurrentHedgehog^.stats.Sacrificed = false) and ((CurrentHedgehog^.stats.StepDamageRecv > 0) or (CurrentHedgehog^.stats.StepPoisoned) or (CurrentHedgehog^.stats.StepDied)) then
297         // Hurt itself only (without dying)
298         if (CurrentHedgehog^.stats.StepDamageGiven = CurrentHedgehog^.stats.StepDamageRecv) and (CurrentHedgehog^.stats.StepDamageRecv >= 1) and (not CurrentHedgehog^.stats.StepDied) then
299             begin
300             // Announcer message + random taunt
301             AddCaption(FormatA(GetEventString(eidHurtSelf), s), capcolDefault, capgrpMessage);
302             if (CurrentHedgehog^.stats.StepDamageGiven <= CurrentHedgehog^.stats.StepDamageRecv) and (CurrentHedgehog^.stats.StepDamageRecv >= 1) then
303                 case random(3) of
304                 0: AddVoice(sndStupid, PreviousTeam^.voicepack);
305                 1: AddVoice(sndBugger, CurrentTeam^.voicepack);
306                 2: AddVoice(sndDrat, CurrentTeam^.voicepack);
307                 end;
308             end
309         // Hurt itself and others, or died
310         else
311             AddVoice(sndStupid, PreviousTeam^.voicepack)
312 
313     // Hog hurts, poisons or kills own team/clan member. Sacrifice is taken into account
314     else if (DamageClan <> 0) or (KillsClan > killsCheck) or (PoisonClan <> 0) then
315         if (DamageTurn > DamageClan) or ((Kills-KillsSD) > KillsClan) then
316             if random(2) = 0 then
317                 AddVoice(sndNutter, CurrentTeam^.voicepack)
318             else
319                 AddVoice(sndWatchIt, vpHurtSameClan)
320         else
321             // Attacked same team
322             if (random(2) = 0) and ((DamageTeam <> 0) or (KillsTeam > killsCheck) or (PoisonTeam <> 0)) then
323                 AddVoice(sndSameTeam, vpHurtSameClan)
324             // Attacked same team or a clan member
325             else
326                 AddVoice(sndTraitor, vpHurtSameClan)
327 
328     // Hog hurts, kills or poisons enemy
329     else if (CurrentHedgehog^.stats.StepDamageGiven <> 0) or (CurrentHedgehog^.stats.StepKills > killsCheck) or (PoisonTurn <> 0) then
330         // 3 kills or more
331         if Kills > killsCheck + 2 then
332             AddVoice(sndAmazing, CurrentTeam^.voicepack)
333         // 2 kills
334         else if Kills = (killsCheck + 2) then
335             if random(2) = 0 then
336                 AddVoice(sndBrilliant, CurrentTeam^.voicepack)
337             else
338                 AddVoice(sndExcellent, CurrentTeam^.voicepack)
339         // 1 kill
340         else if Kills = (killsCheck + 1) then
341             AddVoice(sndEnemyDown, CurrentTeam^.voicepack)
342         // 0 kills, only damage or poison
343         else
344             // possible reactions of victim, in the order of preference:
345             // 1. claiming revenge
346             // 2. complaining about getting attacked too often
347             // 3. threatening enemy with retaliation
348             if CurrentHedgehog^.stats.GotRevenge then
349                 begin
350                 AddVoice(sndRevenge, CurrentHedgehog^.Team^.voicepack);
351                 // If revenge taunt was added, one of the following voices is
352                 // added as fallback (4th param), in case of a missing Revenge sound file.
353                 case random(4) of
354                     0: AddVoice(sndRegret, vpHurtEnemy, false, true);
355                     1: AddVoice(sndGonnaGetYou, vpHurtEnemy, false, true);
356                     2: AddVoice(sndIllGetYou, vpHurtEnemy, false, true);
357                     3: AddVoice(sndJustYouWait, vpHurtEnemy, false, true);
358                     end;
359                 end
360             else
361                 if LeaveMeAlone then
362                     if random(2) = 0 then
363                         AddVoice(sndCutItOut, vpHurtEnemy)
364                     else
365                         AddVoice(sndLeaveMeAlone, vpHurtEnemy)
366                 else
367                     case random(4) of
368                         0: AddVoice(sndRegret, vpHurtEnemy);
369                         1: AddVoice(sndGonnaGetYou, vpHurtEnemy);
370                         2: AddVoice(sndIllGetYou, vpHurtEnemy);
371                         3: AddVoice(sndJustYouWait, vpHurtEnemy);
372                     end
373 
374     // Missed shot
375     // A miss is defined as a shot with a damaging weapon with 0 kills, 0 damage, 0 hogs poisoned and 0 targets hit
376     else if AmmoDamagingUsed and (Kills <= killsCheck) and (PoisonTurn = 0) and (PoisonClan = 0) and (DamageTurn = 0) and (HitTargets = 0) then
377         // Chance to call hedgehog stupid or nutter if sacrificed for nothing
378         if CurrentHedgehog^.stats.Sacrificed then
379             case random(3) of
380             0: AddVoice(sndMissed, PreviousTeam^.voicepack);
381             1: AddVoice(sndStupid, PreviousTeam^.voicepack);
382             2: AddVoice(sndNutter, PreviousTeam^.voicepack);
383             end
384         else
385             AddVoice(sndMissed, PreviousTeam^.voicepack)
386 
387     // Timeout
388     else if (AmmoUsedCount > 0) and (not isTurnSkipped) then
389         begin end// nothing ?
390 
391     // Turn skipped
392     else if isTurnSkipped and (not PlacingHogs) and (not PlacingKings) then
393         begin
394         AddVoice(sndCoward, PreviousTeam^.voicepack);
395         AddCaption(FormatA(GetEventString(eidTurnSkipped), s), capcolDefault, capgrpMessage);
396         end
397     end;
398 end;
399 
400 procedure TurnStatsReset;
401 var t, i: LongInt;
402 begin
403 for t:= 0 to Pred(TeamsCount) do // send even on zero turn
404     with TeamsArray[t]^ do
405         for i:= 0 to cMaxHHIndex do
406             with Hedgehogs[i].stats do
407                 begin
408                 StepKills:= 0;
409                 StepDamageRecv:= 0;
410                 StepDamageGiven:= 0;
411                 StepPoisoned:= false;
412                 StepDied:= false;
413                 GotRevenge:= false;
414                 StepRevenge:= false;
415                 end;
416 
417 Kills:= 0;
418 KillsSD:= 0;
419 KillsClan:= 0;
420 KillsTeam:= 0;
421 DamageClan:= 0;
422 DamageTeam:= 0;
423 DamageTurn:= 0;
424 HitTargets:= 0;
425 PoisonClan:= 0;
426 PoisonTeam:= 0;
427 PoisonTurn:= 0;
428 AmmoUsedCount:= 0;
429 LeaveMeAlone:= false;
430 AmmoDamagingUsed:= false;
431 isTurnSkipped:= false;
432 StepFirstBlood:= false;
433 end;
434 
435 procedure AmmoUsed(am: TAmmoType);
436 begin
437 inc(AmmoUsedCount);
438 AmmoDamagingUsed:= AmmoDamagingUsed or Ammoz[am].isDamaging
439 end;
440 
441 procedure hedgehogFlight(Gear: PGear; time: Longword);
442 begin
443 if time > 4000 then
444     begin
445     WriteLnToConsole('FLIGHT');
446     WriteLnToConsole(Gear^.Hedgehog^.Team^.TeamName);
447     WriteLnToConsole(inttostr(time));
448     WriteLnToConsole( '');
449     end
450 end;
451 
452 procedure SendStats;
453 var i, t, c: LongInt;
454     msd, msk: Longword; msdhh, mskhh: PHedgehog;
455     mskcnt: Longword;
456     maxTeamKills : Longword;
457     maxTeamKillsName : shortstring;
458     maxTurnSkips : Longword;
459     maxTurnSkipsName : shortstring;
460     maxTeamDamage : Longword;
461     maxTeamDamageName : shortstring;
462     winnersClan : PClan;
463     deathEntry : PClanDeathLogEntry;
464     currentRank: Longword;
465 begin
466 if SendHealthStatsOn then
467     msd:= 0; msdhh:= nil;
468     msk:= 0; mskhh:= nil;
469     mskcnt:= 0;
470     maxTeamKills := 0;
471     maxTurnSkips := 0;
472     maxTeamDamage := 0;
473     winnersClan:= nil;
474     currentRank:= 0;
475 
476     for t:= 0 to Pred(TeamsCount) do
477         with TeamsArray[t]^ do
478         begin
479             if (not ExtDriven) and SendRankingStatsOn then
480                 SendStat(siTeamStats, GetTeamStatString(TeamsArray[t]));
481             for i:= 0 to cMaxHHIndex do
482                 begin
483                 if Hedgehogs[i].stats.MaxStepDamageGiven > msd then
484                     begin
485                     msdhh:= @Hedgehogs[i];
486                     msd:= Hedgehogs[i].stats.MaxStepDamageGiven
487                     end;
488                 if Hedgehogs[i].stats.MaxStepKills >= msk then
489                     if Hedgehogs[i].stats.MaxStepKills = msk then
490                         inc(mskcnt)
491                     else
492                         begin
493                         mskcnt:= 1;
494                         mskhh:= @Hedgehogs[i];
495                         msk:= Hedgehogs[i].stats.MaxStepKills
496                         end;
497             end;
498 
499             { Send player stats for winner clans/teams.
500             The clan that survived is ranked 1st. }
501             if (Clan^.ClanHealth > 0) then
502                 begin
503                 winnersClan:= Clan;
504                 if SendRankingStatsOn then
505                     begin
506                     currentRank:= 1;
507                     SendStat(siTeamRank, _S'1');
508                     SendStat(siPlayerKills, IntToStr(Clan^.Color) + ' ' +
509                         IntToStr(stats.Kills) + ' ' + TeamName);
510                     end;
511             end;
512 
513             { determine maximum values of TeamKills, TurnSkips, TeamDamage }
514             if stats.TeamKills > maxTeamKills then
515                 begin
516                 maxTeamKills := stats.TeamKills;
517                 maxTeamKillsName := TeamName;
518             end;
519             if stats.TurnSkips > maxTurnSkips then
520                 begin
521                 maxTurnSkips := stats.TurnSkips;
522                 maxTurnSkipsName := TeamName;
523             end;
524             if stats.TeamDamage > maxTeamDamage then
525                 begin
526                 maxTeamDamage := stats.TeamDamage;
527                 maxTeamDamageName := TeamName;
528             end;
529 
530         end;
531 
532     inc(currentRank);
533 
534     { Now send player stats for loser teams/clans.
535     The losing clans are ranked in the reverse order they died.
536     The clan that died last is ranked 2nd,
537     the clan that died second to last is ranked 3rd,
538     and so on.
539     Clans that died in the same turn share their rank.
540     If a clan died multiple times in the match
541     (e.g. due to resurrection), only the *latest* death of
542     that clan counts (handled in gtResurrector).
543     }
544     deathEntry := ClanDeathLog;
545     i:= 0;
546     if SendRankingStatsOn then
547         while (deathEntry <> nil) do
548             begin
549             for c:= 0 to Pred(deathEntry^.KilledClansCount) do
550                 if ((deathEntry^.KilledClans[c]^.ClanHealth) = 0) and (not deathEntry^.KilledClans[c]^.StatsHandled) then
551                     begin
552                     for t:= 0 to Pred(TeamsCount) do
553                         if (TeamsArray[t]^.Clan^.ClanIndex = deathEntry^.KilledClans[c]^.ClanIndex) then
554                             begin
555                             SendStat(siTeamRank, IntToStr(currentRank));
556                             SendStat(siPlayerKills, IntToStr(deathEntry^.killedClans[c]^.Color) + ' ' +
557                                 IntToStr(TeamsArray[t]^.stats.Kills) + ' ' + TeamsArray[t]^.TeamName);
558                             end;
559                     deathEntry^.KilledClans[c]^.StatsHandled:= true;
560                     inc(i);
561                     end;
562             if i > 0 then
563                 inc(currentRank, i);
564             i:= 0;
565             deathEntry:= deathEntry^.NextEntry;
566             end;
567 
568     // "Achievements" / Details part of stats screen
569     if SendAchievementsStatsOn then
570         begin
571         if msdhh <> nil then
572             SendStat(siMaxStepDamage, IntToStr(msd) + ' ' + msdhh^.Name + ' (' + msdhh^.Team^.TeamName + ')');
573         if mskcnt = 1 then
574             SendStat(siMaxStepKills, IntToStr(msk) + ' ' + mskhh^.Name + ' (' + mskhh^.Team^.TeamName + ')');
575 
576         if maxTeamKills > 1 then
577             SendStat(siMaxTeamKills, IntToStr(maxTeamKills) + ' ' + maxTeamKillsName);
578         if maxTurnSkips > 2 then
579             SendStat(siMaxTurnSkips, IntToStr(maxTurnSkips) + ' ' + maxTurnSkipsName);
580         if maxTeamDamage > 30 then
581             SendStat(siMaxTeamDamage, IntToStr(maxTeamDamage) + ' ' + maxTeamDamageName);
582 
583         if KilledHHs > 0 then
584             SendStat(siKilledHHs, IntToStr(KilledHHs));
585         end;
586 
587     // now to console
588     if winnersClan <> nil then
589         begin
590         ScriptCall('onGameResult', winnersClan^.ClanIndex);
591         WriteLnToConsole('WINNERS');
592         WriteLnToConsole(inttostr(winnersClan^.TeamsNumber));
593         for t:= 0 to winnersClan^.TeamsNumber - 1 do
594             WriteLnToConsole(winnersClan^.Teams[t]^.TeamName);
595         end
596     else
597         begin
598         ScriptCall('onGameResult', -1);
599         WriteLnToConsole('DRAW');
600         end;
601 
602     ScriptCall('onAchievementsDeclaration');
603 end;
604 
605 procedure declareAchievement(id, teamname, location: shortstring; value: LongInt);
606 begin
607 if (length(id) = 0) or (length(teamname) = 0) or (length(location) = 0) then exit;
608     WriteLnToConsole('ACHIEVEMENT');
609     WriteLnToConsole(id);
610     WriteLnToConsole(teamname);
611     WriteLnToConsole(location);
612     WriteLnToConsole(inttostr(value));
613 end;
614 
615 procedure startGhostPoints(n: LongInt);
616 begin
617     WriteLnToConsole('GHOST_POINTS');
618     WriteLnToConsole(inttostr(n));
619 end;
620 
621 procedure dumpPoint(x, y: LongInt);
622 begin
623     WriteLnToConsole(inttostr(x));
624     WriteLnToConsole(inttostr(y));
625 end;
626 
627 procedure initModule;
628 begin
629     DamageClan  := 0;
630     DamageTeam  := 0;
631     DamageTotal := 0;
632     DamageTurn  := 0;
633     PoisonClan  := 0;
634     PoisonTeam  := 0;
635     PoisonTurn  := 0;
636     KillsClan   := 0;
637     KillsTeam   := 0;
638     KillsSD     := 0;
639     Kills       := 0;
640     KillsTotal  := 0;
641     HitTargets  := 0;
642     AmmoUsedCount := 0;
643     AmmoDamagingUsed := false;
644     FirstBlood:= false;
645     StepFirstblood:= false;
646     LeaveMeAlone := false;
647     SkippedTurns:= 0;
648     isTurnSkipped:= false;
649     vpHurtSameClan:= nil;
650     vpHurtEnemy:= nil;
651     TotalRoundsPre:= -1;
652     TotalRoundsReal:= -1;
653     FinishedTurnsTotal:= -1;
654     ClanDeathLog:= nil;
655 end;
656 
657 procedure freeModule;
658 begin
659 end;
660 
661 end.
662