1 /**********************************************************************************
2 * Copyright (c) 2008, 2009 Derek Yu and Mossmouth, LLC
3 * Copyright (c) 2010, Moloch
4 * Copyright (c) 2018, Ketmar Dark
6 * This file is part of Spelunky.
8 * You can redistribute and/or modify Spelunky, including its source code, under
9 * the terms of the Spelunky User License.
11 * Spelunky is distributed in the hope that it will be entertaining and useful,
12 * but WITHOUT WARRANTY. Please see the Spelunky User License for more details.
14 * The Spelunky User License should be available in "Game Information", which
15 * can be found in the Resource Explorer, or as an external file called COPYING.
16 * If not, please obtain a new copy of Spelunky from <http://spelunkyworld.com/>
18 **********************************************************************************/
19 // stats: kills, highscores, etc.
20 class GameStats : Object;
22 transient GameGlobal global;
25 transient bool newMoneyRecord = false;
26 transient bool newKillsRecord = false;
27 transient bool newSavesRecord = false;
28 transient bool newTimeRecord = false;
30 int frameAccum; // yes, save this
31 int playingTime; // in seconds
34 const int tunnel1Price = 100000;
35 const int tunnel2Price = 200000;
36 const int tunnel3Price = 300000;
39 int tunnel1Left = tunnel1Price+1;
40 int tunnel2Left = tunnel2Price+1;
42 bool tunnel1Active = false;
43 bool tunnel2Active = false;
44 bool tunnel3Active = false;
46 int[3] tunnelPaymentLeft;
47 int[3] walksLeftForTunnelMan;
50 bool isTunnelActive (int idx) {
51 return (idx >= 1 && idx <= 3 ? tunnelPaymentLeft[idx-1] <= 0 : false);
55 bool isAnyTunnelActive () {
56 return (tunnelPaymentLeft[0] <= 0 || tunnelPaymentLeft[1] <= 0 || tunnelPaymentLeft[2] <= 0);
60 bool needTunnelMan (int transidx) {
61 if (transidx < 1 || transidx > 3) return false;
63 if (walksLeftForTunnelMan[transidx] > 0) {
64 --walksLeftForTunnelMan[transidx];
67 return (tunnelPaymentLeft[transidx] > 0);
71 int leftForNextTunnel () {
72 foreach (int val; tunnelPaymentLeft) if (val > 0) return val;
77 // returns `true` if new tunnel is built
78 bool payForTunnel (int donation) {
79 if (donation < 1) return false;
81 foreach (ref int val; tunnelPaymentLeft) {
83 int spend = min(val, donation);
86 res = res || (val == 0);
93 void activateTunnel (int idx) {
94 if (idx >= 1 && idx <= 3) {
96 tunnelPaymentLeft[idx] = 0;
101 // ////////////////////////////////////////////////////////////////////////// //
102 // indexed by object name
109 static final void addTotalCollapsed (ref array!TotalItem totals, name aname, optional int amount) {
111 if (!specified_amount) amount = 1;
112 if (amount < 1) return;
113 foreach (int idx, ref auto tt; totals) {
114 if (tt.objName == aname) {
121 totals[$-1].objName = aname;
122 totals[$-1].count = 1;
126 // ////////////////////////////////////////////////////////////////////////// //
127 // all-time totals, collapsed
128 array!TotalItem totalDeaths; // death from xxx
129 array!TotalItem totalKills; // player kills
130 array!TotalItem totalCollected; // collected items
131 array!int levelDeaths; // deaths on the corresponding level (1-based)
132 int totalDamselsSaved;
133 int totalIdolsStolen;
134 int totalIdolsConverted; // successfully carried out of level
135 int totalCrystalIdolsStolen;
136 int totalCrystalIdolsConverted; // successfully carried out of level
137 int totalGhostSummoned;
140 int totalSelfSacrifices;
141 int totalDestroyedKaliAltars;
142 int totalTelefragKills;
143 int totalDiceGamesWon;
144 int totalDiceGamesLost;
145 int totalDiceGamesWonPrize;
149 int maxLevelComplete = 0;
151 int starsKills = 0; // shopkeepers killed
162 // current game, uncollapsed
163 struct LevelStatInfo {
165 // for transition screen
170 transient array!LevelStatInfo kills;
171 transient array!LevelStatInfo collected;
172 transient int damselsSaved;
173 transient int idolsStolen;
174 transient int idolsConverted; // successfully carried out of level
175 transient int crystalIdolsStolen;
176 transient int crystalIdolsConverted; // successfully carried out of level
177 transient int ghostSummoned;
179 transient int scarabs;
181 private transient bool moneyCheat;
184 // ////////////////////////////////////////////////////////////////////////// //
185 final int countKills () {
190 final int getDeathCountOnLevel (int level) {
191 if (level < 1 || level >= levelDeaths.length) return 0;
192 return levelDeaths[level];
195 final void addDeath (name aname) {
197 addTotalCollapsed(totalDeaths, aname, 1);
198 if (global.currLevel > 0 && global.currLevel < 666) {
199 if (global.currLevel >= levelDeaths.length) levelDeaths.length = global.currLevel+1;
200 ++levelDeaths[global.currLevel];
204 final void addKill (name aname, optional bool telefrag) {
206 if (telefrag) ++totalTelefragKills;
208 addTotalCollapsed(totalKills, name(string(aname)~" Telefrag"), 1);
210 addTotalCollapsed(totalKills, aname, 1);
213 kills[$-1].aname = aname;
214 kills[$-1].render = false;
217 final void addCollect (name aname, optional int amount) {
219 if (!specified_amount) amount = 1;
220 if (amount < 1) return;
221 addTotalCollapsed(totalCollected, aname, amount);
222 collected.length += 1;
223 collected[$-1].aname = aname;
224 collected[$-1].render = false;
227 final void addDamselSaved () { ++totalDamselsSaved; ++damselsSaved; }
228 final void addIdolStolen () { ++totalIdolsStolen; ++idolsStolen; }
229 final void addIdolConverted () { ++totalIdolsConverted; ++idolsConverted; }
230 final void addCrystalIdolStolen () { ++totalCrystalIdolsStolen; ++crystalIdolsStolen; }
231 final void addCrystalIdolConverted () { ++totalCrystalIdolsConverted; ++crystalIdolsConverted; }
232 final void addGhostSummoned () { ++totalGhostSummoned; ++ghostSummoned; }
234 final void gameOver () {
235 if (!moneyCheat && money > 0 && money > maxMoney) {
236 newMoneyRecord = true;
241 final void setMoneyCheat () { moneyCheat = true; }
243 final void setMoney (int newmoney) {
244 newmoney = max(0, newmoney);
248 final void addMoney (int amount) {
249 setMoney(money+amount);
252 final void takeMoney (int amount) {
253 setMoney(money-amount);
256 final void oneMoreFramePlayed () {
257 if (++frameAccum >= 30) {
258 playingTime += frameAccum/30;
264 // ////////////////////////////////////////////////////////////////////////// //
265 final void clearLevelTotals () {
271 // ////////////////////////////////////////////////////////////////////////// //
272 final void clearGameTotals () {
277 crystalIdolsStolen = 0;
278 crystalIdolsConverted = 0;
282 newMoneyRecord = false;
287 // ////////////////////////////////////////////////////////////////////////// //
288 void resetTunnelPrices () {
289 tunnelPaymentLeft[0] = tunnel1Price;
290 tunnelPaymentLeft[1] = tunnel2Price;
291 tunnelPaymentLeft[2] = tunnel3Price;
292 walksLeftForTunnelMan[0] = 0;
293 walksLeftForTunnelMan[1] = 0;
294 walksLeftForTunnelMan[2] = 0;
299 // ////////////////////////////////////////////////////////////////////////// //
301 tunnelPaymentLeft[0] = tunnel1Price;
302 tunnelPaymentLeft[1] = tunnel2Price;
303 tunnelPaymentLeft[2] = tunnel3Price;
304 walksLeftForTunnelMan[0] = 3;
305 walksLeftForTunnelMan[1] = 2;
306 walksLeftForTunnelMan[2] = 1;