-
Notifications
You must be signed in to change notification settings - Fork 8
/
all.js
15783 lines (13566 loc) · 450 KB
/
all.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
"use strict";
// preload.js
new Image().src = "img/welcome.png";
new Image().src = "img/avatars.png";
new Image().src = "img/home.png";
new Image().src = "img/ice.png";
new Image().src = "img/items.png";
new Image().src = "img/nodeMisc.png";
new Image().src = "img/software.png";
new Image().src = "img/nodeBgCOP.png";
new Image().src = "img/nodeBgCPU.png";
new Image().src = "img/nodeBgDS.png";
new Image().src = "img/nodeBgIO.png";
new Image().src = "img/nodeBgJunc.png";
new Image().src = "img/nodeBgPortal.png";
new Image().src = "img/nodeBgSPU.png";
// random.js
// Decker's RNG functions
function Random(nMax) {
return Rand(nMax);
}
function Random2(nCount, nMax) {
var nData = 0;
while (nCount--)
nData += Random(nMax);
return nData;
}
function ChooseRot() {
return Random(2)*2-1; // 1:Clockwise, -1:Counterclockwise
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Seedable and Feedable PRNG
var Rand = function(p1,p2) {
if (p2 === undefined)
return Rand.next() % p1;
else
return p1 + (Rand.next() % (p2 - p1 + 1));
}
{
let food = [];
Rand.seed = function(s) {
if (s === undefined)
s = new Date().getTime();
s = Math.floor(s);
if (Number.isNaN(s)) s = 0;
if (s < 0) s = -s;
s %= 0x7fffffff;
if (s < 0) s = 0;
console.info("random seed: "+s);
Rand._seed = s;
}
Rand.getSeed = function() {
return Rand._seed;
}
Rand.feed = function(arr) {
food = arr;
}
Rand.next = function() {
if (food.length) return food.shift();
return (Rand._seed = (Rand._seed * 1000003 + 1) % 0x7fffffff);
}
Rand.seed();
}
// basics.js
// Class to simulate pass by reference
function Numero(n) {
this.value = n;
}
Numero.prototype.get = function() {
return this.value;
}
Numero.prototype.set = function(n) {
this.value = n;
}
Numero.prototype.inc = function(n) {
this.value++;
}
function Point(x,y) {
this.x = x;
this.y = y;
}
Point.dirCoord = [
new Point( 0,-1), // 0 = North (Coordinate plane is from upper left)
new Point( 1, 0), // 1 = East
new Point( 0, 1), // 2 = South
new Point(-1, 0), // 3 = West
];
Point.prototype.move = function(dir) {
let p = Point.dirCoord[dir];
return new Point( this.x+p.x, this.y+p.y );
}
Point.prototype.sameAs = function(p) {
return this.x===p.x && this.y===p.y;
}
Point.prototype.copy = function() {
return new Point(this.x,this.y);
}
function escapeHTML(txt) {
return (""+txt).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
}
// remove item from array, by value
Array.prototype.remove = function(elem) {
for (let i=0; i<this.length; i++) {
if (this[i] !== elem) continue;
this.splice(i,1);
return true;
}
return false;
}
// shuffle elements of an array. Fisher-Yates algorithm
Array.prototype.shuffle = function() {
for (let i = this.length-1; i>0; i--) {
let j = Random(i+1);
[ this[i],this[j] ] = [ this[j],this[i] ];
}
}
// const.js
/* MY GLOBALS *************************************************/
const DBLCLICK_DELAY = 500; // how much to wait for a second click to consider it a double click. Used in tableList
const CHARNAME_MAXSIZE = 30; // maximum amount of characters for the player's name
const PROGNAME_MAXSIZE = 30; // maximum amount of characters for a program's name
const ICE_PER_ROW = 10; // images per row on ice.png
const MAX_MESSAGES = 100; // maximum amount of messages in Matrix (0 for no messages, -1 for no limit)
const MAX_AVATAR = 17;
/* Globals.h **************************************************/
// colors
const BLACK = "#000000";
const BLUE = "#0000FF";
const GREEN = "#00FF00";
const RED = "#FF0000";
const YELLOW = "#FFFF00";
const DK_BLUE = "#000080";
const DK_GREEN = "#008000";
const ORANGE = "#FF8000";
const PURPLE = "#FF00FF";
// Reputation level factor - # points to gain level (relative)
const REP_LEVEL_FACTOR = 5;
const LIFESTYLE_UPGRADE_FACTOR = 3;
// Maximum health
const MAX_HEALTH = 20;
const HEALTH_INCREMENT = 5;
// Global Variables
const g_szMonthNames = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
];
const g_nDaysPerMonth = [
31, //"January",
28, //"February",
31, //"March",
30, //"April",
31, //"May",
30, //"June",
31, //"July",
31, //"August",
30, //"September",
31, //"October",
30, //"November",
31, //"December"
];
const g_szLifestyleString = [
"Poverty","Lower Class","Middle Class","Upper Class","Elite"
];
const g_nLifestyleMonthlyCost = [
500,1000,2000,4000,10000
];
const g_szRepLevelString = [
"Nobody",
"Wannabe",
"Cyber Surfer",
"Matrix Runner",
"Newbie Hacker",
"Journeyman Hacker",
"Competent Hacker",
"Experienced Hacker",
"Hacker Extraordinaire",
"Cyber Thief",
"Cyber Sleuth",
"Cyber Warrior",
"Cyber Wizard",
"Ice Crusher",
"Node Master",
"System Master",
"Ghost in the Machine",
"Digital Dream",
"Digital Nightmare",
"Master of the Matrix",
"Matrix God",
];
/* Character.h **************************************************/
// Lifestyles
const LS_STREET = 0;
const LS_LOW = 1;
const LS_MED = 2;
const LS_HIGH = 3;
const LS_ELITE = 4;
const MAX_LIFESTYLE = 4;
// Item Types
const IT_SOFTWARE = 0;
const IT_CHIP = 1;
const IT_HARDWARE = 2;
// Chip types
const CHIP_CPU = 0;
const CHIP_ATTACK = 1;
const CHIP_DEFENSE = 2;
const CHIP_STEALTH = 3;
const CHIP_ANALYSIS = 4;
const CHIP_COPROCESSOR = 5;
const NUM_CHIPS = 6;
// Hardware types
const HW_CHIP_BURNER = 0;
const HW_SURGE_SUPP = 1;
const HW_NEURAL_DAMPER = 2;
const HW_TRACE_MONITOR = 3;
const HW_BIO_MONITOR = 4;
const HW_HIGH_BW_BUS = 5;
const HW_MAPPER = 6;
const HW_DESIGN_ASSIST = 7;
const HW_PROXY = 8;
const NUM_HW = 9;
const NUM_HW_RESERVED = 20; // Reserved space for future expansion
const NUM_HW_0_1 = 6; // Hardware in version 0.1
// Ratings for GetEffectiveRating
const RATING_ATTACK = 0;
const RATING_DEFENSE = 1;
const RATING_STEALTH = 2;
const RATING_ANALYSIS = 3;
// Load status
const LS_LIGHT = 0;
const LS_NORMAL = 1;
const LS_HEAVY = 2;
// Maximum decoys
const MAX_DECOYS = 5;
// Starting Bonus
const BONUS_NONE = 0;
const BONUS_SKILLS = 1;
const BONUS_HARDWARE = 2;
const BONUS_SOFTWARE = 3;
const BONUS_MONEY = 4;
// Run flags
const CRF_ALARMS_SET = 0x00000001; // Alarms have been set
/* Contract.h **************************************************/
// Number of corporation defined in the database
const NUM_CORP = 100;
// Contract Types
const CONT_STEAL = 0;
const CONT_STEAL_ERASE = 1;
const CONT_ERASE = 2;
const CONT_EDIT = 3;
const CONT_DEACTIVATE_IO = 4;
const CONT_ACTIVATE_IO = 5;
const CONT_SABOTAGE_IO = 6;
const CONT_CRASH_SYS = 7;
const CONT_BACKDOOR = 8;
const CONT_RUN_PROGRAM = 9;
// Contract Targets
// Datastore targets
const CT_RESEARCH_DATA = 0;
const CT_CHEM_FORMULA = 1;
const CT_PERSONNEL_FILES = 2;
const CT_FINANCIAL_DATA = 3;
const CT_GRADE_REPORTS = 4;
const CT_SECURITY_FILES = 5;
const CT_SECURITY_CAM_REC = 6;
const CT_BLUEPRINTS = 7;
const CT_EMPLOYEE_EVAL = 8;
const CT_PRODUCT_INFO = 9;
const CT_MEDICAL_RECORDS = 10;
const CT_ILLEGAL_ACTIVITY = 11;
const CT_TEST_RESULTS = 12;
const CT_INVENTORY_DATA = 13;
// Deactivate IO targets
const CT_DOORLOCKS = 14;
const CT_SECURITY_CAMERAS = 15;
const CT_ALARM_SYSTEMS = 16;
// Activate IO targets
const CT_FIRE_ALARMS = 17;
const CT_SECURITY_ALARMS = 18;
const CT_RADIATION_ALARMS = 19;
const CT_BIOHAZARD_ALARMS = 20;
const CT_CHEM_ALARMS = 21;
// Sabotage targets
const CT_MANUF_CONTROLS = 22;
const CT_CHEM_PRODUCTION = 23;
const CT_VAULT_CONTROLS = 24;
// Crash system, backdoor target
const CT_CPU = 25;
// Run Program targets
const CT_NODE_IO = 26;
const CT_NODE_DS = 27;
const CT_NODE_CPU = 28;
// Contract flags
const CF_NO_ALARMS = 0x00000001; // Can't set off an alarm
const CF_TIMED = 0x00000002; // Limited time to complete
/* DSFile.h **************************************************/
// File types
const FT_USELESS = 0;
const FT_VALUABLE = 1;
const FT_PASSCODE = 2;
const FT_CLUE = 3;
const FT_QUEST = 4;
const FT_PROGRAM = 5;
const FT_P_SOURCE = 6;
const FT_C_SOURCE = 7;
// States (flags)
const STATE_IN_NODE = 0x01; // It is in the node
const STATE_IN_DECK = 0x02; // It is in the deck (downloaded)
const STATE_EDITED_N = 0x04; // It has been edited in the node
const STATE_EDITED_D = 0x08; // It has been edited in the deck (useless)
const STATE_SCAN = 0x10; // It has been scanned (contents known)
const STATE_EVAL = 0x20; // It has been evaluated (value known)
/* FileAccessDlg.h **************************************************/
// Types of operations for this window
const FO_VIEW = 0; // Just show the files
const FO_GET = 1; // Get (download) a file
const FO_EDIT = 2; // Edit a file
const FO_ERASE = 3; // Erase a file
/* Ice.h **************************************************/
// ICE Classes
// White
const ICE_GATEWAY = 0;
const ICE_PROBE = 1;
const ICE_GUARDIAN = 2;
const ICE_TAPEWORM = 3;
// Black
const ICE_ATTACK = 4;
const ICE_TRACE = 5;
// Maximum white ice number
const MAX_WHITE = 3;
// Attack ice sub-types
// Attacks deck
const AST_NORMAL = 0x0000;
const AST_HARDENED = 0x0001;
const AST_PHASING = 0x0002;
const AST_CRASH = 0x0004;
// Attacks decker
const AST_KILLER = 0x1000;
const AST_KILLER_H = 0x1001;
const AST_KILLER_P = 0x1002;
const AST_KILLER_C = 0x1004;
const AST_MASK_KILLER = 0x1000;
const AST_MASK_HARDENED = 0x0001;
const AST_MASK_PHASING = 0x0002;
const AST_MASK_CRASH = 0x0004;
// Trace ice sub-types
const TST_NORMAL = 0; // Normal trace
const TST_DUMP = 1; // Extended trace & dump
const TST_FRY = 2; // Extended trace & dump & fry
// Tapeworm ice subtypes
const TWST_NORMAL = 0; // Normal tapeworm
const TWST_DATABOMB = 1; // Data bomb
// Rating Types
const RATING_NORMAL = 0; // Base rating
const RATING_COMBAT = 1; // Combat rating
const RATING_SENSORS = 2; // Sensor rating
// ICE States
// Non-hostile
const STATE_INACTIVE = 0x00; // Black ice which is not active
const STATE_GUARDING = 0x01; // Normal ice which are sitting in their home node
const STATE_FOLLOWING = 0x02; // Following player to query
const STATE_MOVING = 0x03; // Going to a target node. (Black only)
// Response: Going to search.
// Other: Going home to guard.
const STATE_SEARCHING = 0x04; // Searching for intruders (Black/Probe)
const STATE_DESTROYING = 0x05; // Destroying a datafile - tapeworm only
const STATE_QUERIED1 = 0x06; // Queried player, waiting for response
const STATE_QUERIED2 = 0x07; // Queried player, waiting for response
const STATE_QUERIED3 = 0x08; // Queried player, waiting for response
// Hostile
const STATE_GUARDING_H = 0x11; // Guarding, but knows hostile
const STATE_ATTACKING = 0x12; // Black ice attacking/chasing the player,
const STATE_MOVING_H = 0x13; // White ice returning to home node
const STATE_MASK_HOSTILE = 0x10; // Mask to show hostility
/* MatrixView.h **************************************************/
// Reasons for player dumping
const DUMP_DECK_DAMAGE = 0; // Dumped due to normal (deck) damage
const DUMP_UNCONS = 1; // Dumped due to unconsciousness
const DUMP_DEATH = 2; // Dumped due to death
const DUMP_TRACE = 3; // Dumped due to a trace completing
const DUMP_TRACE_FRY = 4; // Dumped due to a trace completing, get to fry a chip
const DUMP_SYS_CRASH = 5; // Dumped due to the decker crashing the system
const DUMP_SYS_OFFLINE = 6; // Dumped due to system going offline from a red alert
const DUMP_DISCONNECT = 7; // Decker disconnected
// Health bars to update
const BAR_ALL = 100;
const BAR_DECK = 0; // Deck damage
const BAR_MENTAL = 1; // Stun damage
const BAR_LETHAL = 2; // Lethal damage
const BAR_SHIELD = 3; // Shield bar
const BAR_TRANSFER = 4; // Upload/download completion bar
const BAR_TRACE = 5; // Trace completion bar
const BAR_ICE = 6; // Ice health
/* Node.h **************************************************/
// Node Types
const NT_CPU = 0; // Central Processing Unit
const NT_SPU = 1; // Sub Processing Unit
const NT_COP = 2; // Coprocessor
const NT_DS = 3; // Data store
const NT_IO = 4; // I/O Controller
const NT_JUNC = 5; // Junction
const NT_PORTAL_IN = 6; // Portal
const NT_PORTAL_OUT = 7; // Portal
// Directions
const DIR_NORTH = 0;
const DIR_EAST = 1;
const DIR_SOUTH = 2;
const DIR_WEST = 3;
const DIR_NONE = -1;
const DIR_CENTER = -2;
// Macro to get the opposite direction
function OppDir(n) { return ((n+2)%4); }
// Node subtypes
const NST_IO_USELESS = 0; // Useless IO node
const NST_IO_QUEST_NODE = 1; // This is the quest node
const NST_IO_ALARM = 2; // IO Node - alarm
const NST_IO_ICE_PORT = 3; // IO Node - ICE port - ice respawn here (not impl.)
const NST_IO_MATRIX = 4; // High-speed matrix port
const NST_DS_NORMAL = 0;
const NST_DS_QUEST_NODE = 1;
const NST_COP_NORMAL = 0;
const NST_COP_SECURITY = 1; // FSO 12-17-01
// Node special images
const NSI_COP_SECURITY = 0;
const NSI_DS_QUEST = 0;
const NSI_IO_QUEST = 0;
const NSI_IO_ALARM = 1;
const NSI_IO_ICE_PORT = 2;
const NSI_IO_MATRIX = 3;
/* Program.h **************************************************/
// Program Types
const PROGRAM_ATTACK = 0;
const PROGRAM_ATTACK_A = 1;
const PROGRAM_ATTACK_P = 2;
const PROGRAM_SLOW = 3;
const PROGRAM_VIRUS = 4;
const PROGRAM_SILENCE = 5;
const PROGRAM_CONFUSE = 6;
const PROGRAM_WEAKEN = 7;
const PROGRAM_SHIELD = 8;
const PROGRAM_SMOKE = 9;
const PROGRAM_DECOY = 10;
const PROGRAM_MEDIC = 11;
const PROGRAM_ARMOR = 12;
const PROGRAM_HIDE = 13;
const PROGRAM_DECEIVE = 14;
const PROGRAM_RELOCATE = 15;
const PROGRAM_ANALYZE = 16;
const PROGRAM_SCAN = 17;
const PROGRAM_EVALUATE = 18;
const PROGRAM_DECRYPT = 19;
const PROGRAM_REFLECT = 20;
const PROGRAM_ATTACK_BOOST = 21;
const PROGRAM_DEFENSE_BOOST = 22;
const PROGRAM_STEALTH_BOOST = 23;
const PROGRAM_ANALYSIS_BOOST = 24;
const PROGRAM_CLIENT = 25; // Special program for missions only
const NUM_PROGRAMS = 26;
/* System.h **************************************************/
// Alert Levels
const ALERT_GREEN = 0;
const ALERT_YELLOW = 1;
const ALERT_RED = 2;
// globals.js
// Note on this function:
// It returns the success level of running a program.
// -1 = Catastrophic failure
// 0 = Failure
// 1+ = Success level
function DoDieRoll(iTargetNumber) {
// Clamp off the target value
if (iTargetNumber > 20) iTargetNumber = 20;
//else if (iTargetNumber < 2) iTargetNumber = 2;
// Roll the die
let iRoll = Rand(1,20);
// Check for critical failure
if (iRoll === 1)
return -1;
// Calculate the success level and return it
let iDiff = iRoll - iTargetNumber;
if (iDiff < 0)
return 0; // Failure
// FSO Changing the way success is calculated
//if (iDiff <= 1)
// return 1;
//else if (iDiff <= 4)
// return 2;
//else if (iDiff <= 8)
// return 3;
//else if (iDiff <= 13)
// return 4;
//else // iDiff <= 19
// return 5;
let iSuccess = Math.floor( (iDiff+4) / 4 );
if (iSuccess > 5)
iSuccess = 5;
return iSuccess;
}
function GetConditionModifier(nCondition) {
//if (nCondition<=4)
// return -6;
//else if (nCondition<=7)
// return -4;
//else if (nCondition<=9)
// return -2;
//else
// return 0;
return Math.floor( (nCondition - MAX_HEALTH) / 4 );
}
function CalcRepPointsForNextLevel(nCurrentLevel) {
let x = (nCurrentLevel+1);
x = REP_LEVEL_FACTOR * x * (x+1);
return Math.floor(x/2);
}
function GetDays(nMonth, nYear) {
let nDays = g_nDaysPerMonth[nMonth];
// Check for leap year
if (nMonth === 1 && (nYear%4)===0)
nDays++;
return nDays;
}
function ComputeDamage(iSuccess) {
// Original: Damage = success (too low)
// Now: Damage = success squared (too high)
return (iSuccess * iSuccess);
}
// tableList.js
/*
var X = new tableList(div_node, height, className, allowDeselect=true);
X.onClick = function : called with row number as parameter, starting at 1. Second parameter if it was a 'true' click
X.onDblClick = function : called with row number as parameter, starting at 1
X.onSort = function : called with column number as parameter, starting at 0
X.markRow(i) : colorizes the row and scrolls if necessary to make it visible
X.setColumns(arr) : configures columns. Each 'arr' element is a pair (column_name/function)
the function is called for each row, each column, and prints the result
if the column name is null, it represents an icon for the next column. In this case, the value can be an array, with the second element being the outline color
X.setIgnoreColumns(...columns): ignores the indicated columns
X.setContents(arr) : fills the table with the data, using the columns information
X.redraw() : redraws table, keeping the selected value. Use only if the elements haven't changed; just their display
*/
function tableList(obj, height, className, allowDeselect=true) {
let mainTable = document.createElement("table");
mainTable.className = className;
this.tHead = document.createElement("thead");
this.tBody = document.createElement("tbody");
mainTable.appendChild(this.tHead);
mainTable.appendChild(this.tBody);
let heightDiv = document.createElement("div");
if (height !== null)
heightDiv.style.height = height+"px";
else
heightDiv.style.height = "100%";
heightDiv.appendChild(mainTable);
obj.appendChild(heightDiv);
if (allowDeselect) {
Popup.onclick(heightDiv, e => {
if (e.target === heightDiv)
this.markRow(0, true, true);
});
}
let dblClickTimer = null;
this.onClick = function(){};
this.onDblClick = null;
Popup.onclick(this.tBody, e => {
e.stopPropagation();
let tr = e.target.parentNode;
if (tr.parentNode !== this.tBody) return; // shouldn't happen, but just in case...
let row = 0;
while ( (tr = tr.previousSibling) !== null )
row++;
// if desired, find out if it is a double click
if (this.onDblClick !== null) {
if (row === this.selected && dblClickTimer) {
clearTimeout(dblClickTimer);
dblClickTimer = null;
// double click!
this.onDblClick(row);
return;
} else {
clearTimeout(dblClickTimer);
dblClickTimer = setTimeout( () => dblClickTimer=null, DBLCLICK_DELAY);
}
}
this.markRow(row, true, true);
});
this.onSort = null;
Popup.onclick(this.tHead, e => {
e.stopPropagation();
if (!this.onSort) return;
let th = e.target;
if (th.parentNode.parentNode !== this.tHead) return; // shouldn't happen, but just in case...
let column = 0;
while ( (th = th.previousSibling) !== null )
column++;
this.onSort(column);
});
this.selected = 0;
this.data = [];
this.ignore = []; // columns to ignore
}
tableList.prototype.has = function(p) {
return this.data.indexOf(p) >= 0;
}
tableList.prototype.clear = function() {
this.data = [];
this.selected = 0;
// doesn't redraw, since this is called only for the date to be refilled
}
tableList.prototype.markRow = function(i, call_onclick=true, was_true_click=false) {
// remove color from the previously selected row, if any
if (this.selected !== 0)
this.tBody.children[this.selected].classList.remove("marked");
// store new selection
this.selected = i;
// if something is selected, mark it
if (i > 0) {
// pick the corresponding row
let row = this.tBody.children[i];
// colorize it
row.className = "marked";
// scroll if necessary to ensure the row is fully visible
this.scrollIfNeeded(i);
}
if (call_onclick)
this.onClick(i ? this.data[i-1] : null, was_true_click);
}
tableList.prototype.visibles = function() {
let boxRect = this.tBody.parentNode.parentNode.getBoundingClientRect();
let headerSize = this.tHead.children[0].clientHeight; // beware of the header!
let first = null, last = null;
for (let r=1; r<=this.data.length; r++) {
let row = this.tBody.children[r];
if ( first === null && row.getBoundingClientRect().top >= boxRect.top + headerSize )
first = r;
if ( row.getBoundingClientRect().bottom <= boxRect.bottom+2 ) // +2 : weird fix
last = r;
}
return [first,last];
}
tableList.prototype.scrollIfNeeded = function(i, measureOnly) {
// pick the corresponding row
let row = this.tBody.children[i];
// check if scroll is necessary
let box = row.parentNode.parentNode.parentNode;
let rowRect = row.getBoundingClientRect();
let boxRect = box.getBoundingClientRect();
let headerSize = this.tHead.children[0].clientHeight; // beware of the header!
let R = 0;
if (rowRect.bottom > boxRect.bottom)
R = boxRect.bottom - rowRect.bottom;
if (rowRect.top < boxRect.top + headerSize)
R = boxRect.top - rowRect.top + headerSize;
let oldScroll = box.scrollTop;
box.scrollTop -= R;
R = oldScroll - box.scrollTop;
if (measureOnly) box.scrollTop = oldScroll;
return R;
}
tableList.prototype.setColumns = function(arr) { // each element is a pair column_name/function
this.cols = arr;
}
tableList.prototype.setIgnoreColumns = function(...ignore) {
this.ignore = ignore;
}
tableList.prototype.setContents = function(data, call_onclick=true) {
let oldSelected = this.selected ? this.data[this.selected-1] : null;
this.data = data.slice(0);
this.selected = 0;
this.redraw();
if (oldSelected)
this.markRow( this.data.indexOf(oldSelected) + 1, call_onclick );
}
tableList.prototype.redraw = function() {
let html = "";
html += "<tr>";
this.cols.forEach((col,i) => {
if (this.ignore.indexOf(i) >= 0) return; // ignore this column number
if (col[0] === null) return; // icon
html += "<th>" + escapeHTML(col[0]) + "</th>";
});
html += "</tr>";
this.data.forEach(row => {
html += "<tr>";
let icon = "";
this.cols.forEach((col,i) => {
if (this.ignore.indexOf(i) >= 0) return; // ignore this column number
let content = col[1](row);
if (col[0] === null) {
if (Array.isArray(content)) {
let color = content[1]+" 1px, transparent 1px";
icon = "<icon style='margin-left:2px;background:linear-gradient(0deg, "+color+"), linear-gradient(90deg, "+color+"), linear-gradient(180deg, "+color+"), linear-gradient(270deg, "+color+")'>";
icon += "<icon style='background-position-x:"+(-content[0]*16)+"px'></icon>";
icon += "</icon>";
} else {
icon = "<icon style='margin-left:2px;background-position-x:"+(-content*16)+"px'></icon>";
}
} else {
html += "<td>" + icon + escapeHTML(content) + "</td>";
icon = "";
}
});
html += "</tr>";
});
this.tHead.innerHTML = html;
this.tBody.innerHTML = html;
this.markRow(this.selected, false);
}
tableList.prototype.getSelected = function() {
if (!this.selected) return null;
return this.data[this.selected-1];
}
tableList.prototype.keyBindings = function() {
return {
"ArrowUp": this.keyUp.bind(this),
"ArrowDown": this.keyDown.bind(this),
"PageUp": this.keyPUp.bind(this),
"PageDown": this.keyPDown.bind(this),
"Home": this.keyHome.bind(this),
"End": this.keyEnd.bind(this),
};
}
tableList.prototype.keyUp = function(event) {
event.preventDefault();
if (!this.data.length) return; // table empty
if (!this.selected) this.markRow(this.data.length);
else if (this.selected > 1) this.markRow(this.selected-1);
else this.markRow(this.selected);
}
tableList.prototype.keyDown = function(event) {
event.preventDefault();
if (!this.data.length) return; // table empty
if (!this.selected) this.markRow(1);
else if (this.selected < this.data.length) this.markRow(this.selected+1);
else this.markRow(this.selected);
}
tableList.prototype.keyHome = function(event) {
event.preventDefault();
if (!this.data.length) return; // table empty
this.markRow(1);
}
tableList.prototype.keyEnd = function(event) {
event.preventDefault();
if (!this.data.length) return; // table empty
this.markRow(this.data.length);
}
tableList.prototype.keyPUp = function(event) {
event.preventDefault();
if (!this.data.length) return; // table empty
if (!this.selected) return this.markRow(this.data.length); // nothing selected, so select last
if (this.selected === 1) return this.markRow(this.selected); // first row already selected. Scroll to it if needed, and done
let [first,last] = this.visibles();
// unless selected row is fully visible, and is not the first one visible, scroll so that the selected row is at the bottom
if ( !(this.selected > first && this.selected <= last) ) {
this.scrollIfNeeded(0); // scroll to the first...
this.scrollIfNeeded(this.selected); // then to the row...
}
// and select the first visible row
[first,last] = this.visibles();
this.markRow(first);
}
tableList.prototype.keyPDown = function(event) {
event.preventDefault();
if (!this.data.length) return; // table empty
if (!this.selected) return this.markRow(1); // nothing selected, so select first
if (this.selected === this.data.length) return this.markRow(this.selected); // last row already selected. Scroll to it if needed, and done
let [first,last] = this.visibles();
// unless selected row is fully visible, and is not the last one visible, scroll so that the selected row is at the top
if ( !(this.selected >= first && this.selected < last) ) {
this.scrollIfNeeded(this.data.length); // scroll to the last...
this.scrollIfNeeded(this.selected); // then to the row...
}
// and select the last visible row
[first,last] = this.visibles();
this.markRow(last);
}
// config.js
var Config = {};
{
// property savename range default
addConfigBool("mute", "Mute", true);
addConfigInt( "volume", "Vol", [0,100], 20);
addConfigInt( "difficulty", "Diff", [0,1], 0);
addConfigBool("viewice", "VIce", true); // show popup when analyzing an ICE
addConfigBool("warnclose", "WCl", true); // show warning when closing the game
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
let Callbacks = {};
Config.onchange = function(propName, cb) {
Callbacks[propName] = cb;
}
function addConfigBool(propName, cfgName, def) {
cfgName = "DeckerCfg" + cfgName;
let value = localStorage.getItem(cfgName);
if (value === null) value = def;
else value = (value !== "");
Object.defineProperty(Config, propName, {
get: function() {
return value;
},
set: function(val) {
value = !!val;
localStorage.setItem(cfgName, value ? "1" : "");
if (Callbacks[propName]) Callbacks[propName](value);
},
});
}
function addConfigInt(propName, cfgName, [min,max], def) {