-
Notifications
You must be signed in to change notification settings - Fork 10
/
state.js
3021 lines (2618 loc) · 129 KB
/
state.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
Ethereal Farm
Copyright (C) 2020-2024 Lode Vandevenne
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
var CROPINDEX = 16;
var FIELD_TREE_TOP = 1;
var FIELD_TREE_BOTTOM = 2;
var FIELD_REMAINDER = 3; // remainder debris of temporary plant. Counts as empty field (same as index == 0) for all purposes. Purely a visual effect, to remember that this is the spot you're using for watercress (and not accidently put a flower there or so)
var FIELD_ROCK = 4; // for challenges with rocks
var FIELD_MULTIPART = 5; // a field tile used by a multi-cell crop (2x2 pumpkin) but which isn't the main cell of the crop
var FIELD_POND = 6; // center of infinity field
var FIELD_BURROW = 7; // burrow, starting point of pests during the tower defense challenge
// field cell
// fieldtype: 1=basic, 2=ethereal, 3=infinity, 10=pond (fishes)
function Cell(x, y, fieldttype) {
// index of crop, but with different numerical values:
// 0 = empty
// 1..(CROPINDEX-1): special: 1=tree top, 2=tree bottom, ...
// >= CROPINDEX: crop with crop index = this.index - CROPINDEX.
this.index = 0;
this.growth = 0; // 0.0-1.0: percentage completed, or 1 if fullgrown
this.x = x;
this.y = y;
// only used for ethereal field. TODO: make a Cell2 class for ethereal field instead
this.justplanted = false; // planted during this run (this transcension), in the past this meant couldn't be deleted until next run. Currently unused, maybe needed for ethereal ferns.
this.justreplaced = false; // has been replaced, so if it's growing now it doesn't count as a free ethereal delete
this.fieldttype = fieldttype;
this.nexttotree = false; // whether this crop is considered next to tree, with criteria depending on this crop (e.g. whether this is true in case it's diagonal, depends on the crop; and for crops for which this isn't used, this isn't filled in at all; ethereal mistletoe uses it, but basic field mistletoe does not because there that info is stored in prefield instead.)
}
Cell.prototype.isFullGrown = function() {
if(this.index < CROPINDEX) return false; // not relevant for non-crops
var c = this.getCrop();
if(c.type == CROPTYPE_BRASSICA) return this.growth > 0;
if(this.fieldttype == 1 && state.challenge == challenge_wither) return this.growth > 0;
return this.growth >= 1;
};
// like fullgrown, but includes end-of-life semi-active wasabi
Cell.prototype.isSemiFullGrown = function() {
if(this.isFullGrown()) return true;
var c = this.getCrop();
if(c && hasBrassicaInfiniteLifetime(c)) {
return true;
}
return false;
};
// opt_multipart: also returns true if it's the non-main part of a multipart crop
Cell.prototype.hasCrop = function(opt_multipart) {
if(opt_multipart && this.index == FIELD_MULTIPART) return this.getMainMultiPiece().hasCrop(false);
return this.index >= CROPINDEX;
};
// a crop that actually produces or does something, excluding templates or ghosts
Cell.prototype.hasRealCrop = function(opt_multipart) {
if(opt_multipart && this.index == FIELD_MULTIPART) return this.getMainMultiPiece().hasRealCrop(false);
return this.index >= CROPINDEX && this.getCrop().isReal();
};
// only valid if hasCrop(), else returns an out of bounds value
Cell.prototype.cropIndex = function(opt_multipart) {
if(opt_multipart && this.index == FIELD_MULTIPART) return this.getMainMultiPiece().cropIndex(false);
return this.index - CROPINDEX;
};
// Only valid for the basic field, not for the ethereal field.
// returns crops object if the field has a crop, undefined otherwise
// TODO: make a class Cell2 for ethereal field instead
Cell.prototype.getCrop = function(opt_multipart) {
if(opt_multipart && this.index == FIELD_MULTIPART) return this.getMainMultiPiece().getCrop(false);
if(this.index < CROPINDEX) return undefined;
if(this.fieldttype == 2) return crops2[this.index - CROPINDEX];
if(this.fieldttype == 3) return crops3[this.index - CROPINDEX];
if(this.fieldttype == 10) return fishes[this.index - CROPINDEX];
return crops[this.index - CROPINDEX];
};
// non-template
Cell.prototype.getRealCrop = function(opt_multipart) {
if(opt_multipart && this.index == FIELD_MULTIPART) return this.getMainMultiPiece().getRealCrop(false);
var result = this.getCrop();
if(result && (result.istemplate || result.isghost)) return undefined;
return result;
};
// if the crop is a 2x2 crop, returns the quadrant this x, y part is in: 0 for top left, 1 for top right, 2 for bottom left, 3 for bottom right
// this assumes the crop is valid, that is, it's part of a 2x2 region
// does not support multiple of this type touching
function getQuadPos(x, y) {
if(state.field[y][x].index != FIELD_MULTIPART) return 0;
var n = (y > 0) && state.field[y - 1][x].index == FIELD_MULTIPART;
var e = (x + 1 < state.numw) && state.field[y][x + 1].index == FIELD_MULTIPART;
var s = (y + 1 < state.numh) && state.field[y + 1][x].index == FIELD_MULTIPART;
var w = (x > 0) && state.field[y][x - 1].index == FIELD_MULTIPART;
//var es = (x + 1 < state.numw && y + 1 < state.numh) && state.field[y][x + 1].index == FIELD_MULTIPART && state.field[y + 1][x].index == FIELD_MULTIPART;
if(s && !w) return 1;
if(e && !n) return 2;
//if(e && s && !es) return 3;
if(n && w) return 3;
return 0;
}
// returns the main field cell for this 2x2 crop (or 1x2 for tree), given any piece of it
// returns self if this is already the main cell, or if this is not a multi-part crop at all
Cell.prototype.getMainMultiPiece = function() {
if(this.index == FIELD_TREE_TOP) return this;
if(this.index == FIELD_TREE_BOTTOM && this.y > 0) return state.field[this.y - 1][this.x];
if(this.index == FIELD_MULTIPART) {
var q = getQuadPos(this.x, this.y);
if(q == 1) return state.field[this.y][this.x - 1];
if(q == 2) return state.field[this.y - 1][this.x];
if(q == 3) return state.field[this.y - 1][this.x - 1];
}
return this;
};
// function to check if two neighbors are legitimately diagonlly connected, geometrically speaking. In case of 2x2 crops or tree, something isn't diagonally connected if it's already orthogonally connected to it, to avoid double counting.
// does NOT take into account upgrades (such as the diagonal ethreal tree squirrel upgrade or diagonal brassica), only the geometry (this is especially related to pumpkin), so upgrades must be checked at the call site
// this is for the main field, not the ethereal field
// field parameter: state.field for basic field, state.field2 for ethereal field
function diagConnected(x0, y0, x1, y1, field) {
var f0 = field[y0][x0];
var f1 = field[y1][x1];
var c0 = f0.getCrop();
var c1 = f1.getCrop();
var simple0 = f0.index != FIELD_TREE_BOTTOM && f0.index != FIELD_TREE_TOP && f0.index != FIELD_MULTIPART && !(c0 && c0.quad);
var simple1 = f1.index != FIELD_TREE_BOTTOM && f1.index != FIELD_TREE_TOP && f1.index != FIELD_MULTIPART && !(c1 && c1.quad);
if(simple0 && simple1) return true;
// the two orthogonally-connected pieces
var f2 = field[y0][x1];
var f3 = field[y1][x0];
var m0 = f0.getMainMultiPiece();
var m1 = f1.getMainMultiPiece();
var m2 = f2.getMainMultiPiece();
var m3 = f3.getMainMultiPiece();
// if one of the orthogonally in-between cells is already part of the same crop, then don't connect it diagonally since it's already orthogonally connected
return !(m2 == m0 || m2 == m1 || m3 == m0 || m3 == m1);
}
var neighbors_1x1 = [[0, -1, false], [1, 0, false], [0, 1, false], [-1, 0, false]];
var neighbors_1x1_diag = [[-1, -1, true], [0, -1, false], [1, -1, true], [-1, 0, false], [1, 0, false], [-1, 1, true], [0, 1, false], [1, 1, true]];
var neighbors_2x2 = [[0, -1, false], [1, -1, false], [-1, 0, false], [2, 0, false], [-1, 1, false], [2, 1, false], [0, 2, false], [1, 2, false]];
var neighbors_2x2_diag = [[-1, -1, true], [0, -1, false], [1, -1, false], [2, -1, true], [-1, 0, false], [2, 0, false], [-1, 1, false], [2, 1, false], [-1, 2, true], [0, 2, false], [1, 2, false], [2, 2, true]];
// returns list of neighbors from this crop, based on its shape (1x1 or 2x2). Does not filter out out-of-bounds cells, returns fixed possible arrays of relative coordinates.
// must still bound-check, and possibly use diagConnected, when iterating through those relative coordinates
// each returned direction has a third value, a boolean indicating whether it represents a diagonal direction. When include_diag is true, some of the values will have this true
Cell.prototype.getNeighborDirsFrom = function(include_diag) {
var c = this.getCrop(false);
if(c && c.quad) {
return include_diag ? neighbors_2x2_diag : neighbors_2x2;
}
return include_diag ? neighbors_1x1_diag : neighbors_1x1;
};
// get the connected neighbors of the current cell, each max once (in case of e.g. pumpkin which could take up 2 neighbors spots, but it'll count only once)
// like Cell.prototype.getNeighborDirsFrom, but outputs absolute coordinates, does bounds checking, checks diagonals, and resolves multipart target crops (while getNeighborDirsFrom only handles those for the source)
function getNeighbors(field, x, y, include_diag) {
var f = field[y][x];
var numh = field.length;
var numw = field[0].length;
var neighbors = f.getNeighborDirsFrom(include_diag);
var result = [];
for(var i = 0; i < neighbors.length; i++) {
var x2 = x + neighbors[i][0];
var y2 = y + neighbors[i][1];
var diag = y + neighbors[i][2];
if(x2 < 0 || y2 < 0 || x2 >= numw || y2 >= numh) continue;
// diagConnected also is what prevents double counting e.g. brassica copying from pumpkin
if(diag && !diagConnected(x, y, x2, y2, field)) continue;
var f2 = field[y2][x2];
if(f2.index == FIELD_MULTIPART) {
f2 = f2.getMainMultiPiece();
x2 = f2.x;
y2 = f2.y;
}
result.push([x2, y2, diag]);
}
return result;
};
// is empty so that you can plant on it (rocks do not count for this)
Cell.prototype.isEmpty = function() {
return this.index == 0 || this.index == FIELD_REMAINDER;
};
Cell.prototype.isTree = function() {
return this.index == FIELD_TREE_BOTTOM || this.index == FIELD_TREE_TOP;
};
Cell.prototype.isTemplate = function() {
return this.hasCrop() && this.getCrop().istemplate;
};
Cell.prototype.isGhost = function() {
return this.hasCrop() && this.getCrop().isghost;
};
////////////////////////////////////////////////////////////////////////////////
function CropState() {
this.unlocked = false;
this.prestige = 0;
this.known = 0; // ever seen in any run. If it has an unlock upgrade, having seen the upgrade is enough. Value >= 1 means it was ever seen, value >= 2 means ever seen in prestiged form (e.g. the prestige upgrade visible), etc...
// had a fullgrown version of this crop during this run, is an integer to indicate max prestige level had: the value is prestige + 1 (e.g. once a prestige 1 crop is fullgrown, had is set to 2; without any prestige 0 indicates not had, 1 means unprestiged crop had)
// this is better to use than state.fullgrowncropcount directly for unlocks caused by the crop (unless a specific amount must be checked) because it stays even if the crop got deleted again (e.g. if automaton overrides blueprint due to trigger based on fullgrown crop)
// the way to check if having crop of current prestige level is: c.had > c.prestige.
this.had = 0;
}
function Crop2State() {
this.unlocked = false;
this.had = false; // becomes true if you have a fullgrown version of this crop
}
function Crop3State() {
this.unlocked = false;
this.had = false; // becomes true if you have a fullgrown version of this crop
}
function FishState() {
this.unlocked = false;
this.had = false; // becomes true if you ever had this fish
}
function UpgradeState() {
this.seen = false; // seen the upgrade in the upgrades tab
this.seen2 = false; // seen the upgrade ever
this.had = false; // had the upgrade ever
this.unlocked = false;
// how many times this upgrade was done
// if is_choice, then this is the choice instead of a count. 0 means no choice made yet, 1 means first choice, 2 means second choice.
this.count = 0;
}
function Upgrade2State() {
this.unlocked = false;
// how many times this upgrade was done
this.count = 0;
}
// squirrel upgrades
function SquirrelUpgradeState() {
this.count = 0;
}
function SquirrelStageState() {
this.num = [0, 0, 0]; // how far in each branch
this.seen = [0, 0, 0]; // how far ever bought in this branch. seen here means bought the upgrade ever before, so won't show as '???' after respec. However, seeing it due to having been the next one after a bought one before, does not count.
}
function MedalState() {
this.seen = false; // seen the achievement in the achievements tab
this.earned = false;
}
function ChallengeState() {
this.unlocked = false;
this.completed = 0; // whether the challenge was successfully completed, or higher values if higher versions of the challenge with extra rewards were completed. Not useful to determine if cycles of cycling challenges were completed, use num_completed for that
this.num = 0; // amount of times started, whether successful or not, excluding the current one
this.num_completed = 0; // how often, the challenge was successfully completed (excluding currently ongoing challenge, if any)
this.num_completed2 = 0; // how often, the challenge was successfully completed to final stage, or 0 if this challenge only has 1 stage
this.maxlevel = 0; // max level reached with this challenge (excluding the current ongoing challenge if any)
this.besttime = 0; // best time for reaching first targetlevel, even when not resetting. If continuing the challenge for higher maxlevel, still only the time to reach targetlevel is counted, so it's the best time for completing the first main reward part of the challenge.
this.besttime2 = 0; // best time for reaching last targetlevel, or 0 if this challenge only has 1 stage. NOTE: so best time of first and last stage are tracked, if there are more intermediate stages, those are not tracked
this.maxlevels = undefined; // max level if this is a cycling challenge: the max level per cycle. If enabled, this this is an array.
this.besttimes = undefined; // for cycling challenges
this.besttimes2 = undefined; // for cycling challenges
// last run stats. There is always only one of these, not multiple for cycling challenges. This is for the last run, whether it completed or not
this.last_completion_level = 0; // challenge level of last completion
this.last_completion_time = 0; // runtime of last completion
this.last_completion_resin = Num(0); // resin gained during last run
this.last_completion_twigs = Num(0); // twigs gained during last run
this.last_completion_date = 0; // when did you complete this challenge the last time, or 0 if unknown
this.last_completion_total_resin = Num(0); // approximate amount of resin owned during the last time this challenge was ran
this.last_completion_level2 = 0; // level of ethereal tree at time of completing the challenge last time
this.last_completion_g_level = 0; // highest tree level ever (in any non-challenge or challenge run) at the time of last completion
}
function BluePrint() {
this.numw = 0;
this.numh = 0;
/*
2D array. meaning of codes roughly matches that of fraction arrays of automaton, though it stops matching at squirrel.
0: empty
1: N/A, unused
2: w: watercress
3: b: berry
4: m: mushroom
5: f: flower
6: n: nettle
7: z: bee ("buzz")
8: i: mistletoe
9: u: nuts
ethereal:
32: a: automaton
33: s: squirrel
34: l: lotus
35: e: fern
special/event:
60: pumpkin
*/
this.data = [];
// for ethereal blueprints, whether higher level crop used in this spot
this.tier = [];
this.name = '';
}
// converts blueprint type code to a blueprint crop index, or -1 if empty
BluePrint.toCrop = function(i) {
if(i == 0) return -1;
if(i == 2) return watercress_template;
if(i == 3) return berry_template;
if(i == 4) return mush_template;
if(i == 5) return flower_template;
if(i == 6) return nettle_template;
if(i == 7) return bee_template;
if(i == 8) return mistletoe_template;
if(i == 9) return nut_template;
if(i == 60) return pumpkin_template;
if(i == 100) return challengestatue_0_template;
if(i == 101) return challengestatue_1_template;
if(i == 102) return challengestatue_2_template;
if(i == 103) return challengestatue_3_template;
if(i == 104) return challengestatue_4_template;
if(i == 105) return challengestatue_5_template;
return -1;
}
function toCrop2Base(i) {
if(i == 0) return -1;
if(i == 2) return brassica2_template;
if(i == 3) return berry2_template;
if(i == 4) return mush2_template;
if(i == 5) return flower2_template;
if(i == 6) return nettle2_template;
if(i == 7) return bee2_template;
if(i == 8) return mistletoe2_template;
//if(i == 9) return nut2_template;
if(i == 32) return automaton2_template;
if(i == 33) return squirrel2_template;
if(i == 34) return lotus2_template;
if(i == 35) return fern2_template;
return -1;
}
BluePrint.toCrop2 = function(i, tier) {
var index = toCrop2Base(i);
if(index == -1) return index;
var c = crops2[index];
if(!c) return index;
if(tier != undefined) {
var c2 = croptype2_tiers[c.type][tier];
if(c2) return c2.index;
}
return index;
}
BluePrint.fromCrop = function(c) {
if(!c) return 0;
if(c.type == CROPTYPE_BRASSICA) return 2;
if(c.type == CROPTYPE_BERRY) return 3;
if(c.type == CROPTYPE_MUSH) return 4;
if(c.type == CROPTYPE_FLOWER) return 5;
if(c.type == CROPTYPE_STINGING) return 6;
if(c.type == CROPTYPE_BEE) return 7;
if(c.type == CROPTYPE_MISTLETOE) return 8;
if(c.type == CROPTYPE_NUT) return 9;
if(c.type == CROPTYPE_AUTOMATON) return 32;
if(c.type == CROPTYPE_SQUIRREL) return 33;
if(c.type == CROPTYPE_LOTUS) return 34;
if(c.type == CROPTYPE_FERN) return 35;
if(c.type == CROPTYPE_PUMPKIN) return 60;
if(c.index == challengestatue_0) return 100;
if(c.index == challengestatue_1) return 101;
if(c.index == challengestatue_2) return 102;
if(c.index == challengestatue_3) return 103;
if(c.index == challengestatue_4) return 104;
if(c.index == challengestatue_5) return 105;
return 0;
}
BluePrint.toChar = function(i) {
if(i == 0) return '.'; // use a dot, not space, spaces are whitespace that is ignored and can be used for alignment
if(i == 2) return 'W'; // watercress (brassica)
if(i == 3) return 'B'; // berry
if(i == 4) return 'M'; // mushroom
if(i == 5) return 'F'; // flower
if(i == 6) return 'S'; // stinging (nettle, ...)
if(i == 7) return 'Z'; // bee ("buzz")
if(i == 8) return 'I'; // mistletoe
if(i == 9) return 'N'; // nuts
if(i == 32) return 'A'; // automaton
if(i == 33) return 'Q'; // squirrel
if(i == 34) return 'L'; // lotus
if(i == 35) return 'E'; // fern
if(i == 60) return 'U'; // pumpkin
// numbers for TD challenge statue. Note that in ethereal blueprints, numbers are used for crop tiers instead.
if(i == 100) return '0'; // challenge statue
if(i == 101) return '1'; // challenge statue
if(i == 102) return '2'; // challenge statue
if(i == 103) return '3'; // challenge statue
if(i == 104) return '4'; // challenge statue
if(i == 105) return '5'; // challenge statue
return -1;
}
BluePrint.fromChar = function(c) {
if(!c) return 0;
c = c.toUpperCase();
if(c == '.') return 0;
if(c == 'W') return 2;
if(c == 'B') return 3;
if(c == 'M') return 4;
if(c == 'F') return 5;
if(c == 'S') return 6;
if(c == 'Z') return 7;
if(c == 'I') return 8;
if(c == 'N') return 9;
if(c == 'A') return 32;
if(c == 'Q') return 33;
if(c == 'L') return 34;
if(c == 'E') return 35;
if(c == 'U') return 60;
// numbers for TD challenge statue. Note that in ethereal blueprints, numbers are used for crop tiers instead.
if(c == '0') return 100;
if(c == '1') return 101;
if(c == '2') return 102;
if(c == '3') return 103;
if(c == '4') return 104;
if(c == '5') return 105;
return 0;
}
BluePrint.copyTo = function(from, to) {
if(!to) return;
if(!from) {
to.numw = 0;
to.numh = 0;
to.data = [];
to.tier = [];
}
to.numw = from.numw;
to.numh = from.numh;
to.data = util.clone(from.data);
to.tier = util.clone(from.tier);
to.name = from.name;
}
BluePrint.copy = function(b) {
var result = new BluePrint();
BluePrint.copyTo(b, result);
return result;
}
// the trigger condition for an auto action
function AutoActionTriggerState() {
this.type = 0; // what triggers this action: 0 = tree level, 1/2/3 = unlocked/growing/fullgrown crop type, 4 = run time, 5 = upgraded crop type
this.level = 10; // tree level, for type 0
this.crop = 0; // unlocked crop id + 1, or 0 to indicate none, for type 1, 2, 3 and 5
this.prestige = 0; // prestige level required from the crop
this.time = 0; // runtime for the trigger based on runtime, for type 4
this.upgrade_level = 1; // upgrade level for the 'upgraded crop' trigger type
this.crop_count = 1; // for planted and fullgrown trigger types: how many of this crop
}
// a single set of auto action effect settings, for a single season
function AutoActionEffectState() {
this.enable_blueprint = false;
this.blueprint = 0; // index of blueprint to use + 1, or 0 if not yet configured
this.enable_blueprint2 = false; // if true, replaces ethereal blueprint instead
this.blueprint2 = 0; // index of ethereal blueprint to use + 1, or 0 if not yet configured
this.enable_fruit = false;
this.fruit = 0; // fruit slot for enable_fruit
this.enable_weather = false;
this.weather = 0; // 0=sun, 1=mist, 2=rainbow
this.enable_brassica = false; // brassica refresh
this.enable_fern = false; // fern pickup
this.enable_transcend = false;
this.enable_hold_season = false;
}
function AutoActionState() {
this.enabled = false; // if false, this one is individually disabled
// already done this action this round. Can het higher values for having done next stages that have 5-second intervals between them
// the steps are (but depending on the auto action configuration, some are skipped):
// 0: auto action not yet done
// 1: done first part
// 2: done second part (picking up fern)
// 3: done third and final part (transcend) (fully done)
this.done = 0;
this.time2 = 0; // the time at which the next part must be done. Only used while done has not yet reached the final value of 3
this.trigger = new AutoActionTriggerState();
this.effect = new AutoActionEffectState();
this.trigger_season_override = [false, false, false, false];
this.trigger_seasonal = [new AutoActionTriggerState(), new AutoActionTriggerState(), new AutoActionTriggerState(), new AutoActionTriggerState()];
this.effect_season_override = [false, false, false, false];
this.effect_seasonal = [new AutoActionEffectState(), new AutoActionEffectState(), new AutoActionEffectState(), new AutoActionEffectState()];
this.getTrigger = function() {
var season = getSeason();
if(autoActionSeasonOverrideUnlocked() && season >= 0 && season <= 3 && this.trigger_season_override[season]) {
return this.trigger_seasonal[season];
}
return this.trigger;
};
this.getEffect = function() {
var season = getSeason();
if(autoActionSeasonOverrideUnlocked() && season >= 0 && season <= 3 && this.effect_season_override[season]) {
return this.effect_seasonal[season];
}
return this.effect;
};
}
function MistletoeUpgradeState() {
this.time = 0; // current time spent upgrading it
this.num = 0; // current level
}
var state_ctor_count = 0;
// all the state that should be able to get saved
function State() {
state_ctor_count++;
// prevtime is used to know how much time elapsed at next tick, including after loading a savegame
// everything in the game such work such that no matter if there was 1 tick of 100 seconds, or 100 ticks of 1 second, the result is the same (other than numerical precision possibly), and this too even if many days of duration in between
// so that saving, and browsers pausing tabs, both have no effect on game
this.prevtime = 0; // time of previous update frame, in fractional seconds since unix epoch
/*
Compensate for switching between different computers with different clocks: This handles the following scenario:
Sombody plays the game in two locations with different computer clock or timezone: this is common (e.g. home and non-home computer): even though javascript returns UTC time so timezones shouldn't matter, it can happen that computers display the correct local time but have the wrong UTC time.
This mechanism is not there to prevent computer clock related cheating: that is impossible to prevent in a single player game. But the 2-different-computer-clock situation is legitimate and shouldn't affect the gameplay in a bad (like ferns not spawning for hours) nor in a too good (like big production time delta) way.
Say location B is 2 hours farther in the future than location A.
when moving from A to B, this gives 2 hours of not-actually-deserved production bonus.
when moving from B to A, this should in theory punish by taking away 2 hours of production bonus. However, this should not be punished: there are legit situations where this can occur.
So the mechanism will work as follows:
when moving from B to A, then when loading savegame you see a savegame time in the future. This time difference is added to negative_time.
when moving from A to B, then when loading savegame, you see that more than 2 hours have passed. But, before applying the 2 hour production bonus, first any negative_time is subtracted from that (only partially if negative_time is even greater than this 2h)
Side note: other things that must be done when going from B to A (going to the past, negative time delta):
-adjust last fern time, such that it won't take hours before a fern appears
-similar adjustments for last ability time, etc...
*/
this.negative_time = 0;
// total and max negative time ever seen, for debugging in case something goes wrong with this
this.total_negative_time = 0;
this.max_negative_time = 0;
this.last_negative_time = 0;
// amount of times a negative time issue happened
this.num_negative_time = 0;
// current time, this is usually the same as util.getTime(), but if an update() is broken into multiple pieces, then it is
// the end of the current piece.
// not saved. set by update(). recommended to use instead of util.getTime() for game time duration related computations such as special abilities
this.time = 0;
this.seed0 = -1; // if there's ever a new feature added to the game requiring a new random seed, this can be used to initialize that new seed to ensure the new seed can't be cheesed by refreshing with a savegame that didn't have the new seed yet
this.beta = 0; // if higher value, it's an alpha/beta/test version of the game
this.currentTab = 0; // currently selected tab
this.numTabs = 0; // amount of visible tabs
this.lastPlanted = -1; // for shift+plant
this.lastPlanted2 = -1; // for shift+plant on field2
this.lastPlanted3 = -1; // for shift+plant on field3
this.lastPlantedFish = -1; // for shift+plant on pond
// resources
this.res = undefined;
// resin that will be gained at the next soft reset, not added to res until the soft reset
this.resin = Num(0);
// twigs that will be gained at the next soft reset, not added to res until the soft reset
this.twigs = Num(0);
this.treelevel = 0;
this.lasttreeleveluptime = 0; // time of previous time tree leveled, after transcend this is time tree leveled before that transcend (for amber)! if that's undesired, then use the function lastTreeLevelUpTime(state) or timeAtTreeLevel(state)
this.lasttree2leveluptime = 0;
this.lastambertime = 0;
// for the fruit abilities that increase twigs and resin
/*this.resinfruittime = 0;
this.twigsfruittime = 0;
this.prevresinfruitratio = 0;
this.prevtwigsfruitratio = 0;
this.overlevel = false; // when this tree level was reached with spores from the previous level*/
this.resinfruitspores = Num(0); // spores gained while any resin-ability fruit was active
this.twigsfruitspores = Num(0); // spores gained while any twigs-ability fruit was active
this.fruitspores_total = Num(0); // not saved, computed during update() as a more-frequently-updated version of c_res.spores due to intermediate computations involving resinfruitspores and twigsfruitspores before the final c_res update
this.infinitystarttime = 0; // when the infinity field was started
this.prevleveltime = [0, 0, 0]; // previous tree level time durations. E.g. if tree level is now 10, this is the duration 9-10, 8-9 and 7-8 took respectively. Used for the computation of the weighted time at level bonus (weightedTimeAtLevel)
this.recentweighedleveltime = [0, 0]; // the best weighed level time seen at recent tree level ups. For use when taking fern: this allows you to change from seed fruit to spore fruit, which immediately levels up the tree a lot, but still pick up a fern with the production bonus from the previous weighted level time, since you can normally do that anyway by picking up the fern very fast after switching fruit. This is especially helpful for auto-actions that change fruit and take fern
this.recentweighedleveltime_time = 0; // when the last snapshot of recentweighedleveltime was taken
this.fern = 0; // 0 = no fern, 1 = standard fern, 2 = lucky fern
this.fernx = 0;
this.ferny = 0;
this.fernwait = 0; // how much time the currently active fern took to appear
this.fern_seed = -1; // random seed for the fern drops
this.lastFernTime = 0; // if there is a fern: time since it spawned. If there is no fern: time that there was no fern
this.fernresin = new Res(); // amount of resin gotten from ferns during the current run. counted separately from state.resin, to not count towards the max ever itself
this.fernres = new Res(); // resources player had (totally produced this run) at the moment the fern appeared. only stores spores and seeds. used for idle fern computation
// presents were holiday gifts in january 2022, eggs in spring 2022
this.present_effect = 0; // 0 = no present, 1+ = various effects: 1=seeds, 2=spores, 3=prod boost, 4=nuts, 5=growspeed, 6=fruit, 7=amber. Some effects will be replaced with alternatives during some challenges
this.present_image = 0;
this.presentx = 0;
this.presenty = 0;
this.presentwait = 0;
this.present_seed = -1; // random seed for the present drops
this.lastPresentTime = 0;
this.present_grow_speed_time = 0;
this.present_production_boost_time = 0;
this.infspawn = 0; // called infspawn internally, "infinity symbol" in the UI
this.infspawnx = 0;
this.infspawny = 0;
this.lastInfSpawnTime = 0; // last time infinity fern spawned (if it's there now) or was picked up (if it's not there now)
this.lastInfTakeTime = 0; // last time infinity fern spawned (if it's there now) or was picked up (if it's not there now)
this.infspawnGraceTime = 0; // duration added or subtracted to balance amount of infspawns that appear to be 1 per 24h even if player takes some too late. A positive value means you get the infspawn sooner than normal, negative value that you get it slower than normal
// field size in amount of cells
this.numw = 5;
this.numh = 5;
//what's planted and their state of the main field
this.field = [];
this.crops = [];
for(var i = 0; i < registered_crops.length; i++) {
this.crops[registered_crops[i]] = new CropState();
}
this.upgrades = [];
for(var i = 0; i < registered_upgrades.length; i++) {
this.upgrades[registered_upgrades[i]] = new UpgradeState();
}
this.medals = [];
for(var i = 0; i < registered_medals.length; i++) {
this.medals[registered_medals[i]] = new MedalState();
}
this.challenges = []; // array of ChallengeState
for(var i = 0; i < registered_challenges.length; i++) {
this.challenges[registered_challenges[i]] = new ChallengeState();
var c = challenges[registered_challenges[i]];
var c2 = this.challenges[registered_challenges[i]];
if(c.cycling > 1) {
c2.maxlevels = [];
c2.besttimes = [];
c2.besttimes2 = [];
for(var j = 0; j < c.cycling; j++) {
c2.maxlevels[j] = 0;
c2.besttimes[j] = 0;
c2.besttimes2[j] = 0;
}
}
}
// also have run stats for no-challenge (index 0)
this.challenges[0] = new ChallengeState();
// whether the challenge was completed (or multiple objective completed for higher values) during this run
// this as opposed to challenge.completed which is global across runs
// only used for challenge with non-targetlevel based objective, for the targetlevel ones the tree level already indicates this
this.challenge_completed = 0;
this.towerdef = new TowerDefenseState();
// ethereal field and crops
this.numw2 = 5;
this.numh2 = 5;
this.field2 = [];
this.crops2 = [];
for(var i = 0; i < registered_crops2.length; i++) {
this.crops2[registered_crops2[i]] = new Crop2State();
}
this.treelevel2 = 0;
this.upgrades2 = [];
for(var i = 0; i < registered_upgrades2.length; i++) {
this.upgrades2[registered_upgrades2[i]] = new Upgrade2State();
}
// infinity field and crops
this.numw3 = 5;
this.numh3 = 5;
this.field3 = [];
this.crops3 = [];
for(var i = 0; i < registered_crops3.length; i++) {
this.crops3[registered_crops3[i]] = new Crop3State();
}
// pond grid
this.pondw = 5;
this.pondh = 5;
this.pond = []; // equivalent to field
this.fishes = []; // equivalent to crops
for(var i = 0; i < registered_fishes.length; i++) {
this.fishes[registered_fishes[i]] = new FishState();
}
// minimum multiplier to resin/twigs from fishes seen during this run, which is kept track of and used as actual multiplier to prevent fish-swapping strategies during the game
this.fish_resinmul_weighted = Num(-1); // time-weighted average
this.fish_resinmul_last = Num(0); // last computed fishresin, at fish_resinmul_time
this.fish_resinmul_time = 0; // time since last fish_resinmul_weighted change, as time since start of run (can also be negative)
this.fish_resinmul_time_shift = 0;
this.fish_twigsmul_weighted = Num(-1); // time-weighted average
this.fish_twigsmul_last = Num(0); // last computed fishtwigs, at fish_twigsmul_time
this.fish_twigsmul_time = 0; // time since last fish_twigsmul_weighted change, as time since start of run (can also be negative)
this.fish_twigsmul_time_shift = 0;
// minimum multiplier for infinity-field-to-basic-field effects (including from pond), which is kept track of and used as actual multiplier to prevent fish-swapping and infinity crop-swapping strategies during the game (which is intended as a good thing, less manual work needed)
// infinity field to basic field production multiplier
this.infinity_prodboost_weighted = Num(-1); // time-weighted average
this.infinity_prodboost_last = Num(0); // last computed bonus value, at infinity_prodboost_time
this.infinity_prodboost_time = 0; // time since last infinity_prodboost_weighted change, as time since start of run (can also be negative)
this.infinity_prodboost_time_shift = 0;
// Computed in a similar way as infinity_prodboost_weighted etc... above, however these are actually used for deciding if the time delay from the above ones is used at all or not, and not for delaying any actual infinity-seeds / infinity-spores production itself
this.infinity_infprod_weighted = Res();
this.infinity_infprod_last = Res();
this.infinity_infprod_time = -Infinity;
this.squirrel_evolution = 0;
this.squirrel_stages = []; // must be inited with initSquirrelStages at appropriate times
this.squirrel_upgrades_spent = Num(0); // nuts spent on squirrel_upgrades, can be given back on respec
this.nuts_before = Num(0); // nuts before evolution change
this.just_evolution = false; // if true, did squirrel evolution during this run so no nuts are produced
this.seen_evolution = false; // to no longer color tab red if only upgrade is evolution
// squirrel upgrades
// NOT saved, squirrel_stages is saved instead. But does give the info of upgrades from stages, including "count" if an upgrade appears multiple times in various stages. This one is kept in sync, not computed by computeDerived.
this.squirrel_upgrades = [];
for(var i = 0; i < registered_squirrel_upgrades.length; i++) {
this.squirrel_upgrades[registered_squirrel_upgrades[i]] = new SquirrelUpgradeState();
}
this.misttime = 0; // mist is unlocked if state.upgrades[upgrade_mistunlock].count
this.suntime = 0; // similar
this.rainbowtime = 0; // similar
this.lastWeather = 0; // last clicked weather ability, if multiple are active according to the timer, only the one matching this counts as active
this.lastPermaWeather = 0; // Indicates which perma weather is active. Similar to lastWeather and usually set to the same as it, but can be set to something else to change which perma weather will get activated when the true weather ended.
this.lastLightningTime = 0; // for the stormy challenge
this.lastBackupWarningTime = 0;
// misc
this.delete2tokens = 4; // obsolete but for now still present in case the tokens need to come back
this.squirrel_respec_tokens = squirrel_respec_initial; // a resource, though not part of the Res() resources object since it's more its own special purpose thing
this.paused = false;
this.paused_while_heavy_computing = false; // if this is true (while paused is also true), it will pause the heavy computing itself but not the game
this.lastEtherealDeleteTime = 0; // This was used for the ethereal delete limitations, which were removed in november 2022. Not yet removed for now incase the system needs to come back in some form, but may be obsolete
this.lastEtherealPlantTime = 0; // idem
this.lastHumanActionTime = 0; // last time a human action (rather than an automated action) was done
this.numLastAutomaticTranscends = 0; // amount of automatic transcends sinze the last manual transcend
this.numAutomaticTranscendsSinceHumanAction = 0; // amount of automatic transcends sinze the last manual action of any kind (not just transcend)
this.automaticTranscendRes = new Res(); // amount of resources gotten from the last streak of streak of auto-transcends.
this.runHadAnyHumanAction = true; // if false, this run was started by auto-transcend and no other human actions happened so far either
// fruit
this.fruit_seed = -1; // random seed for creating random fruits
this.fruit_seen = false; // whether seen latest fruit drop (for red color)
this.fruit_active = 0; // index in fruit_stored of currently active fruit, or -1 to explicitely disable fruit
this.fruit_stored = []; // fruits in storage that stay after transcension
this.fruit_slots = 3; // amount of slots for fruit_stored
this.fruit_sacr = []; // fruits outside of storage that will be sacrificed on transcension
this.seen_seasonal_fruit = 0; // bit flags: 1=spring fruit, 2=summer fruit, 4=autumn fruit, 8=winter fruit, and so on for higher fruit types. For each flag, if false means never seen a seasonal fruit of that type yet. Some events here give an extra fruit slot.
this.fruit_recover = []; // fruits from previous run that can be recovered
// settings / preferences
this.notation = Num.N_LATIN; // number notation
this.precision = 3; // precision of the numeric notation
this.roman = 1; // use roman numbers in various places (upgrade levels, ...). 1: all roman, 0: roman up to 12, 2: no roman
this.mobilemode = false;
this.saveonaction = true; // save when doing player actions, and with the window unload event (this is different than the interval based autosave)
this.tooltipstyle = 1;
this.disableHelp = false; // disable all popup help dialogs
this.uistyle = 1; // 0=default (1), 1=light, 2=dark, 3=darkest
this.sidepanel = 1; // 0=disabled, 1=automatic
this.notificationsounds = [0, 0]; // index0: fern sound, index1: fullgrown sound
this.volume = 1; // for notification sounds
this.messagelogenabled = [1, 1, 1, 1, 1, 1]; // 0: "game saved" message log messages, 1: tree leveling, 2: upgrades available, 3: abbreviated help, 4: pause/resumed messages, 5: fruit drops
this.cancelbuttonright = true; // whether cancel buttons in dialogs appear on the leftmost or the rightmost side of a group of buttons (the group of button always starts from the right either way). If on the right side, all cancel or back buttons are in the same right corner of the screen. If false, then the right corner of the screen gets the "do the action" button, and cancel/back buttons are to the left.
// each of the following keys has the following meanings: 0=nothing, 1=weather (1-3, does not work for brackets), 2=tabs, 3=active fruit
this.keys_numbers = 3;
this.keys_numbers_shift = 1; // there is none for ctrl, ctrl+numbers makes browsers change their tab, can't use this as a shortcut key in the game
this.keys_brackets = 2;
this.keepinterestingfruit = false;
// help dialog related
this.help_seen = {}; // ever seen this help message at all as dialog
this.help_seen_text = {}; // ever seen this help message at all as text
this.help_disable = {}; // disabled this help message (available once seeing it the second time)
// automaton
this.automaton_enabled = true; // default is true, but is not active until you actually placed the automaton
this.automaton_autochoice = 0;
/*
for each choice upgrade:
0 (or array too short): not yet unlocked for this choice upgrade, so nothing to set
1: manual mode, player chooses to do the choice upgrades manually and not let automaton do it
2: auto choose first choice
3: auto choose second choice
*/
this.automaton_choices = [];
/*
0: auto upgrades disabled
1: auto upgrades enabled
*/
this.automaton_autoupgrade = 1;
/*
fraction of resources the automaton is allowed to use for auto-upgrades, e.g. 0.1 to allow to use up to 10% of resources for auto upgrades
meaning of each index:
0: global, when there is no distinction made between plant types
1: other / challenge-specific crops
2: watercress
3: berry
4: mushroom
5: flower
6: nettle
7: beehive
8: mistletoe (not used for upgrade, but used for auto unlock for example in other fraction arrays below)
9: nuts
*/
this.automaton_autoupgrade_fraction = [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5];
/*
0: auto plant disabled
1: auto plant enabled
*/
this.automaton_autoplant = 1;
/*
fraction of resources automation is allowed to use for auto-plant
the indices are the same as for automaton_autoupgrade_fraction, even though some are unused (e.g. watercress)
*/
this.automaton_autoplant_fraction = [1, 0.5, 0.5, 1, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5];
/*
0: autounlock disabled
1: autounlock enabled, but only if also autoplant is enabled
*/
this.automaton_autounlock = 1;
this.automaton_autounlock_copy_plant_fraction = false;
this.automaton_autounlock_fraction = [1, 0.5, 0.5, 1, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5];
this.automaton_autounlock_max_cost = Num(0);
/*
0: autoprestige disabled
1: autoprestige enabled, but only if also autoplant is enabled
*/
this.automaton_autoprestige = 1;
/*
auto action = automatically do auto-actions, such as plant a blueprint, at certain programmable points
0: auto action globally disabled
1: auto action enabled
2: auto action disabled for now, but enable again next run
*/
this.automaton_autoaction = 0;
this.automaton_autoactions = []; // array of AutoActionState. The first one (index 0) is special: that one is always activated at start of a run (time 0), so has no configurable trigger condition
// challenges
this.challenge = 0;
this.challenge_autoaction_warning = false; // has autoaction_warning already been applied during this challenge run
// saved stats, global across all runs
this.g_numresets = 0; // amount of soft resets done, non-challenge
this.g_numsaves = 0;
this.g_numautosaves = 0;
this.g_numloads = 0;
this.g_numimports = 0;
this.g_numexports = 0;
this.g_lastexporttime = 0; // last save export time, to give warnings
this.g_lastimporttime = 0; // last save import time, for those same warnings
this.g_nummedals = 0;
this.g_treelevel = 0; // max tree level of any run
this.g_numplanted2 = 0;
this.g_numunplanted2 = 0;
this.g_numupgrades2 = 0;
this.g_numupgrades2_unlocked = 0;
this.g_numfullgrown2 = 0;
this.g_seasons = 1; // amount of seasons had (amount of season changes + the initial one at start of game) (does not count infernal challeng's season)
this.g_season_changes_seen = 0; // season changes actually seen (a savegame from multiple days ago counts as only 1)
this.g_resin_from_transcends = Num(0); // this is likely the same value as g_res.resin, but is a separate counter for amount of resin ever earned from transcends in case it's needed for recovery in transcension-changing game updates
this.g_delete2tokens = 0; // obsolete but for now still present in case the tokens need to come back
this.g_fastestrun = 0; // runtime of fastest transcension
this.g_slowestrun = 0; // runtime of slowest transcension
this.g_fastestrun2 = 0; // as measured on wall clock instead of the runtime that gets deltas added each time
this.g_slowestrun2 = 0;
this.g_numresets_challenge = 0; // amount of soft resets done after a challenge, excluding g_numresets_challenge_0
this.g_numresets_challenge_0 = 0; // amount of challenges quit immediately, before tree leveled even to level 1, so these do not count for stats, not even num runs of a challenge
this.g_numresets_challenge_10 = 0; // amount of soft resets done after a challenge where at least level 10 was reached, so that it can be counted as at least as good as a regular g_numresets value
this.g_p_treelevel = 0; // max tree level of any run, but not including the current run
this.g_num_squirrel_upgrades = 0;
this.g_num_squirrel_respec = 0;
this.g_amberdrops = 0;
this.g_amberbuy = [0, 0, 0, 0, 0, 0, 0]; // amount bought of amber upgrades (using amber effect action indices, e.g. #0=squirrel respec, #1=prod, #2=lengthen season, ...)
this.g_max_res_earned = Res(); // max total resources earned during a run (excluding current one), includes best amount of total resin and twigs earned during a single run, but excludes resin/(twigs if implemented) earned from extra bushy ferns and infinity symbols
this.g_fernres = Res(); // total resources gotten from ferns
this.g_numpresents = [0, 0]; // order: presents '21-'22, eggs '22 + eggs '23 + presents '22-'23. Starting from index 2 with regularity: presents '23-'24, eggs '24, presents '24-'25, eggs '25, etc...
this.g_nummistletoeupgradesdone = 0;
this.g_nummistletoeupgrades = 0; // started or continued
this.g_nummistletoecancels = 0;
this.g_mistletoeidletime = 0;
this.g_mistletoeupgradetime = 0; // this is the effective upgrade time, that is, idle time that was used for upgrades is also counted (e.g. if a single 1 day worth upgrade is done in total, the value of this will be exactly 1d)
this.g_numplanted3 = 0;
this.g_numunplanted3 = 0;
this.g_numfullgrown3 = 0;
this.g_numwither3 = 0;
this.g_numamberkeeprefunds = 0;
this.g_max_infinityboost = Num(0); // max boost to basic field ever seen from infinity field
this.g_fruits_recovered = 0;
this.g_numplanted_fish = 0;
this.g_numunplanted_fish = 0;
this.g_td_highest_wave_ever = 0; // also used for skipping easy waves
this.g_num_auto_resets = 0; // amount of non-manual resets, done by auto-action
this.g_num_infspawns = 0;
this.g_infspawnres = Res();
this.g_starttime = 0; // starttime of the game (when first run started)
this.g_runtime = 0; // this would be equal to util.getTime() - state.g_starttime if game-time always ran at 1x, however paused time is not included so it may differ
this.g_numticks = 0;
this.g_res = Res(); // total resources gained, all income ever without subtracting costs, including both production and one-time income
this.g_max_res = Res(); // max resources ever had
this.g_max_prod = Res(); // max displayed resource/second gain ever had (production, this excludes immediate resources such as from ferns)
this.g_numferns = 0;
this.g_numplantedbrassica = 0; // amount of short-lived plants planted
this.g_numplanted = 0; // amount of plants planted on a field
this.g_numfullgrown = 0; // very similar to numplanted, but for full grown plants
this.g_numunplanted = 0; // amount of plants deleted from a field
this.g_numupgrades = 0; // upgrades performed
this.g_numupgrades_unlocked = 0; // upgrades unlocked but not yet necessarily performed
this.g_numabilities = 0; // weather abilities ran
this.g_numfruits = 0; // fruits received
this.g_numfruitupgrades = 0;
this.g_numautoupgrades = 0; // upgrades done with automaton. These are also counted in the standard g_numupgrades as well.
this.g_numautoplant = 0;
this.g_numautodelete = 0;
this.g_numfused = 0;
this.g_res_hr_best = Res(); // best resource/hr ever had