-
Notifications
You must be signed in to change notification settings - Fork 11
/
utils_save.js
1429 lines (1296 loc) · 45.3 KB
/
utils_save.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/>.
*/
// all parsing and serializing is directly to base64, that is, not bytes but base64 units
// reader is object with inside of it:
// a string "s" with base64 values
// a position "pos", the pos can be incremented whenever using a base64 character
// an error indicator error, if truthy indicates something went wrong
// e.g.: {s:'', pos:0, error:false}
// for standard base64, last to characters are +/ (we don't use the ='s though)
// for URL-compatible base64, last two characters are -_
// choose the standard one: this is more recognizable for users, so less probability they accidently forget to copypaste a '-' along with their savegame
var toBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var fromBase64 = {};
function initBase64() {
for(var i = 0; i < 64; i++) fromBase64[toBase64[i]] = i;
}
initBase64();
function isBase64(s) {
for(var i = 0; i < s.length; i++) {
if(fromBase64[s[i]] == undefined) {
return false;
}
}
return true;
}
function encLZ77(s) {
var lz77MatchLen = function(s, i0, i1) {
var l = 0;
while(i1 + l < s.length && s[i1 + l] == s[i0 + l]) l++;
return l;
};
var result = '';
var map = {};
var lit = '';
for(var i = 0; i < s.length; i++) {
var len = 0;
var dist = 0;
var sub = map[s.substr(i, 4)];
if(sub) {
for(var j = sub.length - 1; j >= 0; j--) {
var i2 = sub[j];
var d = i - i2;
if(d > 4096) break;
var l = lz77MatchLen(s, i2, i);
if(l > len) {
len = l;
dist = d;
if(l > 63) break; // good enough
}
}
}
if(!(len > 5 || (len > 4 && dist < 4096) || (len > 3 && dist < 32))) {
len = 1;
}
for(var j = 0; j < len; j++) {
var sub = s.substr(i + j, 4);
if(!map[sub]) map[sub] = [];
if(map[sub].length > 1000) map[sub] = []; // prune
map[sub].push(i + j);
}
i += len - 1;
if(len >= 3) {
result += encUint(lit.length);
result += encUint(len);
result += encUint(dist);
result += lit;
lit = '';
} else {
lit += s[i];
}
}
if(lit.length) {
result += encUint(lit.length);
result += encUint(0);
result += encUint(0);
result += lit;
}
return result;
}
function decLZ77(s) {
var reader = {s:s, pos:0};
var result = '';
while(reader.pos < s.length) {
var num = decUint(reader);
var len = decUint(reader);
if(result.length + num + len > 100000) {
return null; // too suspiciously long
}
var dist = decUint(reader);
if(reader.error) {
return null;
}
for(var i = 0; i < num; i++) {
if(reader.pos >= s.length) {
return null;
}
result += s[reader.pos++];
}
if(dist > result.length) {
return null;
}
for(var i = 0; i < len; i++) {
result += result[result.length - dist];
}
}
return result;
}
function compress(s) {
//return s;
return encLZ77(s);
}
function decompress(s) {
//return s;
return decLZ77(s);
};
function encInt(i) {
//i = Math.floor(i);
if(isNaN(i)) i = 0; // avoid infinite loops
if(i < -9007199254740992) i = -9007199254740992; // avoid infinite loops
if(i > 9007199254740992) i = 9007199254740992; // avoid infinite loops
var s = 0;
if(i < 0) {
s = 1;
i = -i;
}
var b = i & 15;
i = Math.floor(i / 16); // too bad doing ">> 4" doesn't work for more than 32 bits with JS
var result = toBase64[b | (s << 4) | (i ? 32 : 0)];
while(i) {
b = i & 31;
i = Math.floor(i / 32);
result += toBase64[b | (i ? 32 : 0)];
}
return result;
}
function readBase64(reader) {
if(reader.pos >= reader.s.length) reader.error = true;
var v = fromBase64[reader.s[reader.pos++]];
if(v == undefined) reader.error = true;
return v;
}
function decInt(reader) {
var olderror = reader.error;
var v = readBase64(reader);
var result = v & 15;
var s = (v & 16) ? 1 : 0; // if 1, sign is negative
var mul = 16; // cannot use shift because JS only support << for up to 32-bit integers
for(;;) {
if(!(v & 32)) break;
if(mul > 9007199254740992) { reader.error = true; break; }
v = readBase64(reader);
result += (v & 31) * mul;
mul *= 32;
}
if(s) result = -result;
if(reader.error && !olderror) return NaN;
return result;
}
function encBool(b) {
return b ? 'B' : 'A';
}
function decBool(reader) {
var v = readBase64(reader);
if(v > 1) reader.error = true; // unknown bool value
return (v == 1);
}
function encUint(i) {
//i = Math.floor(i);
if(i < 0 || isNaN(i)) i = 0; // avoid infinite loops
if(i > 9007199254740992) i = 9007199254740992; // avoid infinite loops
var b = i & 31;
i = Math.floor(i / 32); // too bad doing ">> 5" doesn't work for more than 32 bits with JS
var result = toBase64[b | (i ? 32 : 0)];
while(i) {
b = i & 31;
i = Math.floor(i / 32);
result += toBase64[b | (i ? 32 : 0)];
}
return result;
}
function decUint(reader) {
var olderror = reader.error;
var v = readBase64(reader);
var result = v & 31;
var mul = 32; // cannot use shift because JS only support << for up to 32-bit integers
for(;;) {
if(!(v & 32)) break;
if(mul > 9007199254740992) { reader.error = true; break; }
v = readBase64(reader);
result += (v & 31) * mul;
mul *= 32;
}
if(reader.error && !olderror) return NaN;
return result;
}
// encodes an integer with max value 2**53 (inclusive) into max 9 chars
// NOTE: while encUint(i) also supports max value 2**53 due JS limitations, its encoding can in rare cases be slightly more efficient for lower values, which is why both encodings exist. encUint53 is good for mantissas of floating point values.
function encUint53(i) {
if(i < 0 || isNaN(i)) i = 0; // avoid infinite loops
if(i > 9007199254740992) i = 9007199254740992; // avoid infinite loops
if(i > 2199023255552) {
// starting at 2199023255552, the varint method outputs 9 chars, so then we
// can use 9 chars directly, which provide 53 bits of precision (excluding
// the one bit to indicate this case), exactly enough for 53-bit int
i--; // support the value 2**53 itself too
var result = toBase64[1 | ((i & 31) << 1)];
i = Math.floor(i / 32);
for(var j = 0; j < 8; j++) {
result += toBase64[i & 63];
i = Math.floor(i / 64);
}
return result;
}
i *= 2; // indicate with LSB 0 that it's not the direct case above.
var b = i & 31;
i = Math.floor(i / 32); // too bad doing ">> 5" doesn't work for more than 32 bits with JS
var result = toBase64[b | (i ? 32 : 0)];
while(i) {
b = i & 31;
i = Math.floor(i / 32);
result += toBase64[b | (i ? 32 : 0)];
}
return result;
}
function decUint53(reader) {
var olderror = reader.error;
var v = readBase64(reader);
var result;
if(v & 1) {
result = (v & 62) >> 1;
var mul = 32;
for(var j = 0; j < 8; j++) {
v = readBase64(reader);
result += v * mul;
mul *= 64;
}
result++;
} else {
result = (v & 30) >> 1;
var mul = 16; // cannot use shift because JS only support << for up to 32-bit integers
for(;;) {
if(!(v & 32)) break;
if(mul > 9007199254740992) { reader.error = true; break; }
v = readBase64(reader);
result += (v & 31) * mul;
mul *= 32;
}
}
if(reader.error && !olderror) return NaN;
return result;
}
// Why encUint16 exists compared to just using encUint: uint16 has 16 bits so is guaranteed to fit in 3 characters, while the standard encUint varint may use 4 characters for uint16.
// encUint16 still also acts like varint, just the third character is guaranteed to use all 6 base64-bits and have no flag bit.
// So this is slightly more efficient than encUint for values expected to be low, and significantly more efficient (towards 25%) than encUint for uniform random uint16 values.
// Note that this can actually encode values from 0-66591, not just 0-65535.
function encUint16(i) {
if(i < 0 || isNaN(i)) i = 0; // avoid infinite loops
if(i > 66591) i = 66591; // avoid infinite loops
// 1 char: 5 bits: 0-31
// 2 chars: 10 bits: 32-1055
// 3 chars: 16 bits: 1056-66591
if(i < 32) {
return toBase64[i];
}
if(i < 1056) {
i -= 32;
return toBase64[32 | (i & 31)] + toBase64[((i >> 5) & 31)];
}
i -= 1056;
return toBase64[32 | (i & 31)] + toBase64[32 | ((i >> 5) & 31)] + toBase64[((i >> 10) & 63)];
}
// returns value in range 0-66591 reading up to 3 chars
function decUint16(reader) {
var olderror = reader.error;
var v = readBase64(reader);
var result = v & 31;
if(v & 32) {
result += 32;
v = readBase64(reader);
result += ((v & 31) << 5);
}
if(v & 32) {
result += 1024;
v = readBase64(reader);
result += ((v & 63) << 10);
}
if(reader.error && !olderror) return NaN;
return result;
}
// Encodes any value 0-63 in one symbol (unlike the varint encUint which may take 2 symbols for some)
function encUint6(i) {
return toBase64[i & 63];
}
// returns value in range 0-63 reading 1 chars
function decUint6(reader) {
var olderror = reader.error;
var v = readBase64(reader);
if(reader.error && !olderror) return NaN;
return v;
}
// For unicode codepoint values
function encUint21(i) {
if(i < 0 || isNaN(i)) i = 0; // avoid infinite loops
if(i > 2130975) i = 2130975; // avoid infinite loops
// 1 char: 5 bits: 0-31
// 2 chars: 10 bits: 32-1055
// 3 chars: 15 bits: 1056-33823
// 4 chars: 21 bits: 33824-2130975
if(i < 32) {
return toBase64[i];
}
if(i < 1056) {
i -= 32;
return toBase64[32 | (i & 31)] + toBase64[((i >> 5) & 31)];
}
if(i < 33824) {
i -= 1056;
return toBase64[32 | (i & 31)] + toBase64[32 | ((i >> 5) & 31)] + toBase64[((i >> 10) & 31)];
}
i -= 33824;
return toBase64[32 | (i & 31)] + toBase64[32 | ((i >> 5) & 31)] + toBase64[32 | ((i >> 10) & 31)] + toBase64[((i >> 15) & 63)];
}
// returns value in range 0-2130975 reading up to 4 chars
function decUint21(reader) {
var olderror = reader.error;
var v = readBase64(reader);
var result = v & 31;
if(v & 32) {
result += 32;
v = readBase64(reader);
result += ((v & 31) << 5);
}
if(v & 32) {
result += 1024;
v = readBase64(reader);
result += ((v & 31) << 10);
}
if(v & 32) {
result += 32768;
v = readBase64(reader);
result += ((v & 63) << 15);
}
if(reader.error && !olderror) return NaN;
return result;
}
function mirrorBits(i, num) {
var res = 0;
// not using shifts because JS only supports those up to 32-bit integers
var mul = 1;
for(var j = 0; j < num; j++) {
res += (i & 1);
res *= 2;
i = Math.floor(i / 2);
}
return res;
}
// encodes a 52-bit mantissa in max 9 chars
function encMantissa(mantissa) {
// try inverting bits of mantissa, because floating point values of integers usually have their set mantissa bits only in the MSB's of the mantissa. Encoding as varint is smaller if the value is smaller, so invert the bits to bring the MSBs to the LSBs.
// but also consider not inverting mantissa, that encodes a value like 1.0000001 better
// of course for arbitrary floats like 1.5646545 it doesn't matter either way, but both integers, and values close to 1, can be common. This also avoids long streaks of 0, showing up as ggggg in the varint.
var m2 = mirrorBits(mantissa, 52);
var invert = false;
if(m2 < mantissa) {
mantissa = m2;
invert = true;
}
mantissa *= 2;
mantissa += invert ? 1 : 0;
return encUint53(mantissa);
}
function decMantissa(reader) {
var mantissa = decUint53(reader);
var invert = mantissa & 1;
mantissa = Math.floor(mantissa / 2);
if(invert) mantissa = mirrorBits(mantissa, 52);
return mantissa;
}
function encFloat(f) {
// single-char result for these common values
var d = util.dissectfloat(f, 11, 52); // [sign, mantissa, exponent]
if(f == 0 && !d[0]) return encUint(0);
if(f == 1) return encUint(1);
var sign = d[0];
var exp = d[1];
var mantissa = d[2];
var result = '';
// Make the varint encoding of low exponents more efficient, the exponent for value 1 is 1023, also include a few exponents for smaller-than-one in this (hence 1020 is used)
// Note: that's a modulo division, not mask, on purpose
if(exp != 0) exp = 1 + ((2047 + (exp - 1) - 1020) % 2047);
exp = (exp << 1) | sign; // store the mantissa sign information in the exponent
result += encUint(exp + 2); // + 2 due to the two shortcuts for 0 and 1 above
result += encMantissa(mantissa);
return result;
}
function decFloat(reader) {
var olderror = reader.error;
var exp = decUint(reader);
if(exp == 0) return 0;
if(exp == 1) return 1;
exp -= 2;
var mantissa = decMantissa(reader);
var sign = (exp & 1) ? 1 : 0;
exp >>= 1;
if(exp != 0) exp = 1 + ((2047 + (exp - 1) + 1020) % 2047);
var result = util.createfloat(sign, exp, mantissa, 11, 52);
if(reader.error && !olderror) return NaN;
return result;
}
// alternative float encoding that is a bit better for range ~0..2 but a bit worse for other ranges. Has following properties:
// -a value in range [0.25..2) will be encoded in max 9 characters, 1 less than encFloat on average
// -a value in range (-2..-0.5] and [~0.01..0.25] will be encoded in max 10 characters, similar to encFloat on average
// -values 0 and 1 are encoded in 1 character, just like encFloat does
// -other values are on average no more than 1 char bigger than encFloat (but +2 is possible for very specific values)
// -the max output size is 12 chars, same as encFloat's
// This function is good for encoding the mantissa of encNum (which is usually
// in range 1..2), and for encoding growth, percentages (scaled), and other such
// progress value that are usually in range 0..1. It'll typically save at least
// 1 character compared to encFloat for those.
// For generic floats, that is values with arbitrary exponents, negative values,
// 'round' values like exact 0.25 or 1.5, and values are not typically in
// roughly range 0..2, the standard encFloat is better since encFloat2 will
// often cost one character more for those.
function encFloat2(f) {
// single-char result for these common values
var d = util.dissectfloat(f, 11, 52); // [sign, mantissa, exponent]
var sign = d[0];
var exp = d[1];
var mantissa = d[2];
var e = exp - 1023;
var c;
var result = '';
if(f == 0 && !sign) {
result = toBase64[0];
} else if(f == 1) {
result = toBase64[1];
} else {
// 6 specific shorter exp values: 1 to represent also values 2.0..4.0 somewhat shorter, and -3..-7 for values roughly ~0.01..0.125
if(!sign && (e >= -7 && e <= 1)) {
// values of c=0 and c=1 already taken by direct values 0 and 1.
// exponents 0, -1 and -2 already have other shortcut further below, but can still be useful in combination with the possibly shorter mantissa encoding here
if(e == 1) c = 2;
else if(e == 0) c = 3;
else if(e == -1) c = 4;
else if(e == -2) c = 5;
else if(e == -3) c = 6;
else if(e == -4) c = 7;
else if(e == -5) c = 13;
else if(e == -6) c = 14;
else if(e == -7) c = 15;
result += toBase64[c];
} else if(sign && (e >= -1 && e <= 1)) {
if(e == 1) c = 10;
else if(e == 0) c = 11;
else if(e == -1) c = 12;
result += toBase64[c];
} else if(e >= -15 && e <= 16) {
result += toBase64[8]; // indicates 1-char exponent+sign
result += toBase64[(e + 15) | (sign ? 32 : 0)];
} else {
result += toBase64[9]; // indicates 2-char exponent+sign
result += toBase64[(exp & 31) | (sign ? 32 : 0)];
result += toBase64[exp >> 5];
}
result += encMantissa(mantissa);
}
exp = d[1];
if(!sign && (e >= -2 && e <= -0) && result.length > 9) {
// exponent -2 for 0.25..0.5, -1 for 0.5..1 and 0 for 1..2
result = '';
c = (e == 0) ? 16 : ((e == -1) ? 32 : 48);
c |= (mantissa & 15);
result += toBase64[c];
mantissa = Math.floor(mantissa / 16);
for(var i = 0; i < 8; i++) {
result += toBase64[mantissa & 63];
mantissa = Math.floor(mantissa / 64);
}
}
return result;
}
function decFloat2(reader) {
var olderror = reader.error;
var v = readBase64(reader);
var result;
if(v == 0) {
result = 0;
} else if(v == 1) {
result = 1;
} else if((v & 48) == 0) {
var exp, sign;
if(v == 8) {
var w = readBase64(reader);
sign = (w & 32) ? 1 : 0;
exp = (w & 31) - 15 + 1023;
} else if(v == 9) {
var w = readBase64(reader);
sign = (w & 32) ? 1 : 0;
exp = (w & 31);
exp += readBase64(reader) << 5;
} else if(v == 10 || v == 11 || v == 12) {
sign = 1;
var e;
if(v == 10) e = 1;
else if(v == 11) e = 0;
else if(v == 12) e = -1;
exp = e + 1023;
} else {
sign = 0;
var e;
if(v == 2) e = 1;
else if(v == 3) e = 0;
else if(v == 4) e = -1;
else if(v == 5) e = -2;
else if(v == 6) e = -3;
else if(v == 7) e = -4;
else if(v == 13) e = -5;
else if(v == 14) e = -6;
else if(v == 15) e = -7;
exp = e + 1023;
}
var mantissa = decMantissa(reader);
result = util.createfloat(sign, exp, mantissa, 11, 52);
} else {
var c = (v & 48);
var sign = 0;
var exp = (c == 16) ? 0 : ((c == 32) ? -1 : -2);
exp += 1023;
var mantissa = (v & 15);
var mul = 16;
for(var i = 0; i < 8; i++) {
mantissa += readBase64(reader) * mul;
mul *= 64;
}
result = util.createfloat(sign, exp, mantissa, 11, 52);
}
if(reader.error && !olderror) return NaN;
return result;
}
function encNum(num) {
// 1-char for the common values of 0 and 1.
// Other integers do not have shortcuts, use encInt/encUint rather than Num for integer values (counting stats, ...) if efficient size is desired, Num cannot represents them with more accuracy anyway (has same 53-bit mantissa as JS number has)
if(num.eqr(0)) return encInt(0);
if(num.eqr(1)) return encInt(1);
var e = num.e > 1 ? num.e : (num.e - 2);
if(isNaN(e)) e = -2;
return encInt(e) + encFloat2(num.b);
}
function decNum(reader) {
var e = decInt(reader);
if(e == 0) return new Num(0, 0);
if(e == 1) return new Num(1, 0);
var result = new Num(0, 0);
result.e = e;
result.b = decFloat2(reader);
if(result.e <= 1) result.e += 2;
return result;
}
// converts array of unicode codepoints to JS string
function arrayToString(a) {
var s = '';
for(var i = 0; i < a.length; i++) {
var c = a[i];
if (c < 0x10000) {
s += String.fromCharCode(c);
} else if (c <= 0x10FFFF) {
s += String.fromCharCode((c >> 10) + 0xD7C0);
s += String.fromCharCode((c & 0x3FF) + 0xDC00);
} else {
s += ' ';
}
}
return s;
}
// converts JS string to array of unicode codepoints
function stringToArray(s) {
var a = [];
for(var i = 0; i < s.length; i++) {
var c = s.charCodeAt(i);
if (c >= 0xD800 && c <= 0xDBFF && i + 1 < s.length) {
var c2 = s.charCodeAt(i + 1);
if (c2 >= 0xDC00 && c2 <= 0xDFFF) {
c = (c << 10) + c2 - 0x35FDC00;
i++;
}
}
a.push(c);
}
return a;
}
function swapFrequent(a) {
// give likely more frequent characters the 32 1-character values
for(var i = 0; i < a.length; i++) {
// 'a'-'z'
if(a[i] >= 97 && a[i] <= 122) a[i] -= 97;
else if(a[i] >= 0 && a[i] <= 25) a[i] += 97;
// ' '
else if(a[i] == 32) a[i] = 26;
else if(a[i] == 26) a[i] = 32;
// '.'
else if(a[i] == 46) a[i] = 27;
else if(a[i] == 27) a[i] = 46;
// ','
else if(a[i] == 44) a[i] = 28;
else if(a[i] == 28) a[i] = 44;
// 'A'
else if(a[i] == 65) a[i] = 29;
else if(a[i] == 29) a[i] = 65;
// 'S'
else if(a[i] == 83) a[i] = 30;
else if(a[i] == 30) a[i] = 83;
// 'T'
else if(a[i] == 84) a[i] = 31;
else if(a[i] == 31) a[i] = 84;
}
}
function encString(s) {
var a = stringToArray(s);
swapFrequent(a);
var result = encUint(a.length);
for(var i = 0; i < a.length; i++) {
result += encUint21(a[i]);
}
return result;
}
function decString(reader) {
var len = decUint(reader);
if(reader.error) return '';
var a = [];
for(var i = 0; i < len; i++) {
a[i] = decUint21(reader);
if(reader.error) return '';
}
swapFrequent(a);
return arrayToString(a);
}
var date_base = 1600000000; // september 2020 instead of 1970 unix epoch, for smaller numbers to encode
// similar to encFloat, but for values representing times, either unix timestamps or durations; it removes some precision, but good enough sub-second precision for timestamps, so is more efficient than encFloat for this purpose
function encTime(s) {
// a few special values
if(s == 0) return encUint6(0);
if(s == -Infinity) return encUint6(13);
if(s == Infinity) return encUint6(14);
if(isNaN(s)) return encUint6(15);
var i = Math.floor(s);
var m = Math.round((s - i) * 64); // sub-second precision
if(m > 63) {
i++;
m = 0;
}
var e = 1;
if(i >= date_base) {
e = 2;
i -= date_base;
} else if(i < 0) {
e = 3;
i = -i;
}
var a = encUint6((e << 4) | (i % 16));
i = Math.floor(i / 16); // NOTE: can't use bitshift if i bigger than 31 bits in JS
var b = encUint6(m);
var c = '';
if(e == 2) {
// typical timestamps have at least this many bits, so encode part as fixed integer rather than spend more varint bits
c += encUint6(i % 64);
i = Math.floor(i / 64);
c += encUint6(i % 64);
i = Math.floor(i / 64);
c += encUint6(i % 64);
i = Math.floor(i / 64);
}
c += encUint(i);
return a + b + c;
}
function decTime(reader) {
var a = decUint6(reader);
var e = (a >> 4) & 3;
if(e == 0) {
if(a == 0) return 0;
if(a == 13) return -Infinity;
if(a == 14) return Infinity;
if(a == 15) return NaN;
reader.error = true;
return 0;
}
var m = decUint6(reader);
var i = 0;
if(e == 2) {
i += decUint6(reader);
i += decUint6(reader) * 64;
i += decUint6(reader) * 4096;
i += decUint(reader) * 262144;
} else {
i = decUint(reader);
}
i = (i * 16) + (a % 16);
if(e == 2) {
i += date_base;
} else if(e == 3) {
i = -i;
}
return i + m / 64.0;
}
// encode the array of a single resources object
function encResArray(arr) {
var result = '';
var first = 0, last = -1;
for(var i = 0; i < arr.length; i++) {
if(arr[i].neqr(0)) {
if(last == -1) first = i;
last = i;
}
}
// encode with a single number what range of resources is being encoded (first to last index), so that if there's e.g. only 1 type of non-zero resource, it doesn't encode a zero character for all the others
// but also future proof this so that if new resource types are added it can still be represented
var code = 0;
if(last >= 0) {
var t = last * (last + 1) / 2; // triangular number
code = 1 + t + first; // + 1 since code 0 means no resources at all are encoded
}
if(code < 56) {
// up to 10 distinct resources can have this combination encoded in 1 character
result += encUint6(code);
} else {
result += encUint6(56 + (code & 7));
result += encUint(code >> 3);
}
for(var i = first; i <= last; i++) {
result += encNum(arr[i]);
}
return result;
}
// decode the array of a single resources object
function decResArray(reader) {
var arr = [];
var code = decUint6(reader);
if(code >= 56) {
code = (code & 7) | (decUint(reader) << 3);
}
if(reader.error) return undefined;
var first = 0, last = -1;
if(code > 0) {
code--;
var n = Math.floor(Math.sqrt(0.25 + 2 * code) - 0.5); // inverse of triangular number
if((n + 1) * (n + 2) / 2 - 1 < code) {
n++; // fix potential numerical imprecision
}
var t = n * (n + 1) / 2;
last = n;
first = code - t;
}
for(var i = first; i <= last; i++) {
arr[i] = decNum(reader);
}
return arr;
}
// encode a fraction from a dropdown for storing choices such as "how much fraction of resource may auto upgrade max consume per upgrade"
// fraction to uint6 (uint6 value 0 means fraction=1 or 100%, higher uint6 values mean lower fraction, towards 0)
// e is integer encoding in range 0..63. examples of e:f pairs:
// 63:1, 62:0.5, 61:0.2, 60:0.1, 59:0.05, 58:0.02, 57:0.01, 56:0.005, ..., 3:0.00000000000000000001, 2:0.000000000000000000005, 1:0.000000000000000000002, 0:0
function encFractionChoice(f) {
if(f <= 0) return 0;
if(f >= 1) return 63;
var l = -Math.log(f) / 2.302585092994046;
l = Math.floor(l);
var p = Math.pow(10, -l);
var r = f / p; // 0.1, 0.2 or 0.5 for the typical inputs, but may due to rounding also become 1, in which case we go to the previous log bucket
var q = (r < 0.15) ? 3 : ((r < 0.35) ? 2 : ((r < 0.75) ? 1 : 0)); // 3 for 0.1, 2 for 0.2, 1 for 0.5, 0 for 1 to go to the previous log bucket
return 63 - (l * 3 + q);
}
function decFractionChoice(e) {
e = 63 - Math.round(e);
if(e <= 0) return 1;
if(e >= 63) return 0;
var q = (e % 3);
var p = Math.floor(e / 3);
var l = Math.pow(10, -p);
return l * [1, 0.5, 0.2][q];
}
////////////////////////////////////////////////////////////////////////////////
var approx_num_base = 1.15;
var approx_num_exact = 11;
var approx_num_skipped = 6; // chosen such that the encoding of values above approx_num_exact begin at encoding approx_num_exact + 1. This is log(approx_num_exact) / log(approx_num_base) - approx_num_exact, that is, Math.floor(Num(approx_num_exact).rlogr(approx_num_base) - approx_num_exact)
// Encodes a positive Num in approximate form.
// The precision whichever is highest of absolute 1, or relative around 15%
function encApproxNum(f) {
if(f.ltr(0.5)) return 0;
if(f.lter(approx_num_exact)) return Math.round(f.valueOf());
var l = f.rlogr(approx_num_base) - approx_num_skipped;
if(l > 9007199254740992) return 9007199254740992;
return Math.round(l);
}
function decApproxNum(e) {
if(e <= approx_num_exact) return Num(e);
var f = Num.pow(Num(approx_num_base), Num(e + approx_num_skipped));
return f;
}
////////////////////////////////////////////////////////////////////////////////
var approx2_num_base = 1.005;
var approx2_num_exact = 200;
var approx2_num_skipped = 862; // chosen such that the encoding of values above approx_num_exact begin at encoding approx_num_exact + 1. This is log(approx2_num_exact) / log(approx2_num_base) - approx2_num_exact, that is, Math.floor(Num(approx2_num_exact).rlogr(approx2_num_base) - approx2_num_exact)
// Encodes a positive Num in approximate form.
// The precision whichever is highest of absolute 1, or relative around 0.1%
function encApprox2Num(f) {
if(f.ltr(0.5)) return 0;
if(f.lter(approx2_num_exact)) return Math.round(f.valueOf());
var l = f.rlogr(approx2_num_base) - approx2_num_skipped;
if(l > 9007199254740992) return 9007199254740992;
return Math.round(l);
}
function decApprox2Num(e) {
if(e <= approx2_num_exact) return Num(e);
var f = Num.pow(Num(approx2_num_base), Num(e + approx2_num_skipped));
return f;
}
////////////////////////////////////////////////////////////////////////////////
var approx3_num_base = 1.001;
var approx3_num_exact = 1000;
var approx3_num_skipped = 5911; // chosen such that the encoding of values above approx_num_exact begin at encoding approx_num_exact + 1. This is log(approx3_num_exact) / log(approx3_num_base) - approx3_num_exact, that is, Math.floor(Num(approx3_num_exact).rlogr(approx3_num_base) - approx3_num_exact)
// Encodes a positive Num in approximate form.
// The precision whichever is highest of absolute 1, or relative around 0.5%
function encApprox3Num(f) {
if(f.ltr(0.5)) return 0;
if(f.lter(approx3_num_exact)) return Math.round(f.valueOf());
var l = f.rlogr(approx3_num_base) - approx3_num_skipped;
if(l > 9007199254740992) return 9007199254740992;
return Math.round(l);
}
function decApprox3Num(e) {
if(e <= approx3_num_exact) return Num(e);
var f = Num.pow(Num(approx3_num_base), Num(e + approx3_num_skipped));
return f;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Type is encoded in code together with a few other bits. Code is a value in range [0..63].
// Types of inclusive range [0..11] are supported, and the same 12 more, that is [12..23] for the array types.
var type_index = 0;
var TYPE_BOOL = type_index++; // boolean true/false value
var TYPE_UINT6 = type_index++; // single base64 character
var TYPE_INT = type_index++; // signed integer, variable length encoded
var TYPE_UINT = type_index++; // unsigned integer, variable length encoded
var TYPE_UINT16 = type_index++; // 16-bit integer, variable length encoded
var TYPE_FLOAT = type_index++; // floating point value, variable length encoded
var TYPE_FLOAT2 = type_index++; // floating point value, alternative variable length encoding that is shorter for values in range 0.25..2
var TYPE_NUM = type_index++; // large number with large exponent
var TYPE_STRING = type_index++; // unicode text
var TYPE_RES = type_index++; // resources (array of resources from the game, such as seeds, ...)
var TYPE_TIME = type_index++; // encodes durations or times since unix epoch in smaller size than using float, with a precision of 1/64th seconds. Only can encode time values that are within reasonable bounds (millenia, but the given sub second precision is only guaranteed up to 1e14 seconds, and being able to encode it at all only from around -1e17 to 1e22 seconds).
// arrays for all the same types
type_index = 12;
var TYPE_ARRAY_BOOL = type_index++; // bits packed
var TYPE_ARRAY_UINT6 = type_index++; // array of base64 characters, useable as blob
var TYPE_ARRAY_INT = type_index++;
var TYPE_ARRAY_UINT = type_index++;
var TYPE_ARRAY_UINT16 = type_index++;
var TYPE_ARRAY_FLOAT = type_index++;
var TYPE_ARRAY_FLOAT2 = type_index++;
var TYPE_ARRAY_NUM = type_index++;
var TYPE_ARRAY_STRING = type_index++;
var TYPE_ARRAY_RES = type_index++;
var TYPE_ARRAY_TIME = type_index++;
// this type only exists as array, and represents one or more nested instances of a "struct", where struct means an entire set of fields just like the top level main save is, with its own id's, sections, ...
// this will use two different types of encodings internally depending on the nested fields:
// -if all instances are exactly the same (exactly same amount of fields with example same id and section), will use "fixed" encoding, which is as efficient as a transposed struct array encoding
// -if instances have different fields (e.g. field present on one instances, but not in other, or different ids), then "flexible" encoding is used, which us recursively the same format as the top level save, but more expensive, since every field will have the id and type for every instance, and section numbers are encoded
// if the goal is to encode many instances of the same object, say e.g. 4 instances of a automaton configuration for 4 seasons, ensure to fill in the same fields in every instance to get the efficient coding (and don't e.g. leave out an optional field in one but not another instance)
// if the goal instead is to encode multiple instances of a complex variable sized structure, or to encode only a single instance of a nested save for example, then using flexible is likely better
// whether fixed or flexible used is determined automatically, so to get fixed, ensure its conditions are satisfied.
var TYPE_ARRAY_STRUCT = 60;
var compactBool = true;
function Token(value, type, id) {
// Allow calling it without new, but act like new
if(!(this instanceof Token)) {
return new Token(value, type, id);
}
this.id = id; // section_id*64 + subid, where subid is id within section, so there can be max 64 unique tokens within 1 section (but a token can be an array of data)
this.value = value; // once filled in, must be relevant for the type
this.type = type;
};
function encTokenValue(value, type) {
switch(type) {
case TYPE_BOOL: return encBool(value);
case TYPE_UINT6: return encUint6(value);
case TYPE_INT: return encInt(value);