-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
982 lines (916 loc) · 69 KB
/
index.html
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quar Battleforce Builder</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-vue@latest/dist/bootstrap-vue.css" rel="stylesheet">
<link rel="stylesheet" href="./style.css">
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css">
<link rel="stylesheet" href="https://cdn.datatables.net/responsive/3.0.2/css/responsive.dataTables.css">
<!-- DataTables Bootstrap 4 CSS -->
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.4/css/dataTables.bootstrap4.min.css">
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/js/bootstrap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-vue@latest/dist/bootstrap-vue.js"></script>
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<script src="https://https://cdn.datatables.net/responsive/3.0.2/js/dataTables.responsive.js"></script>
<!-- DataTables Bootstrap 4 JS -->
<script src="https://cdn.datatables.net/1.13.4/js/dataTables.bootstrap4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.min.js"></script>
</head>
<body>
<div id="app">
<div class="back-img no-print"></div>
<div class="no-print d-flex" style="margin-top: -25px;">
<div class="d-flex flex-column mx-auto">
<h2 class="mx-auto text-monospace">Quar Battleforce Builder</h2>
<small class="mx-auto">Mobile support is a WIP</small>
<small class="mx-auto">All rules and information based on the
<a href="https://www.rhyfler.com/files/ClashOfRhyflesQuickStart.pdf">Clash of Rhyfles V2 Rulebook </a>
-
<a href="https://www.rhyfler.com/files/ClashofRhyflesAddendumv2.pdf">Addendum V2</a>
. Images are the property of
<a href="https://zombiesmith.com/">ZombieSmith</a> and @shemp2771 on Discord.</small>
</div>
</div>
<div class="main-container d-flex">
<div class="d-flex flex-column">
<div style="width: 275px; padding: 5px;">
Faction:
<select id="faction-select" class="form-control" v-model="selectedFaction" @change="clearSelectedUnits">
<option v-for="(units, faction) in availableUnits" :key="faction" :value="faction">
{{ faction }}
</option>
</select>
</div>
<div class="unit-selection no-print">
<div v-for="(unit, index) in filteredUnits" :key="index" class="unit-card">
<div>
<div class="points-box" :class="[selectedFaction]">{{ unit.points }}</div>
<div class="unit-name-box" :class="[selectedFaction]">
<span v-if="unit.allowed">({{unit.allowed}})</span>
{{ unit.name }}
</div>
<div class="add-btn no-print" @click="addToArmy(unit)">+</div>
</div>
<div class="unit-stats flex" style="margin-top: 37px; margin-left: 3px;">
<div class="w-50 my-auto">
<img :src="getImageSrc(unit.name)" :class="{ 'fallback-image': isFallback(unit.name) }" style="object-fit: scale-down; width: 100px; height: 100px; margin-bottom: 5px;" @error="setFallbackImage($event, unit.name)">
</div>
<div class="flex-grow-1 my-auto">
<div class="d-flex flex-column flex-grow-1">
<div class="font-weight-bold">Skill:
</div>
<div>{{ unit.sk }}</div>
</div>
<div class="d-flex flex-column flex-grow-1">
<div class="font-weight-bold">Might:
</div>
<div>{{ unit.mt }}</div>
</div>
</div>
<div class="flex-grow-1 my-auto">
<div class="d-flex flex-column flex-grow-1">
<div class="font-weight-bold">Move:
</div>
<div>{{ unit.ma }}</div>
</div>
<div class="d-flex flex-column flex-grow-1">
<div class="font-weight-bold">Tough:
</div>
<div>{{ unit.toughness }}</div>
</div>
</div>
</div>
<div v-if="unit.specialAbilities.length > 0" class="unit-stats my-auto pb-2">
<div class="d-flex flex-column flex-grow-1" style="margin-left: 3px;">
<div class="font-weight-bold mx-auto">Special Abilities:
</div>
<span class="mx-auto" v-for="ability in unit.specialAbilities" :key="ability">
<span class="ability" v-b-tooltip.html="{title: formatAbilityTooltip(ability), placement: 'right', interactive: false, trigger: 'hover', boundary: 'window'}">
{{ ability }}
</span>
</span>
<div v-if="unit.name == 'PykPyks (3)'">
<br>
</div>
</div>
</div>
<div v-else="" class="unit-stats my-auto">
<!-- <div class="d-flex flex-column flex-grow-1" style="margin-left: 3px;">
<div class="font-weight-bold mx-auto">Special Abilities: </div>
<div class="font-italic font-weight-light mx-auto">No Special Abilities</div>
</div> -->
</div>
<div v-if="unit.equipment && unit.name != 'PykPyks (3)'" class="unit-stats mt-auto">
<div class="w-100">
<div v-for="equip in unit.equipment" class="value" data-toggle="tooltip" style="border: 2px solid rgba(0, 0, 0, 0.17); border-radius: 5px; margin-bottom: 2px;">
<div class="d-flex flex-column" style="background-color:#f0f2f4; border-radius: 5px; padding: 2px; margin-bottom: 2px;">
<div class="d-flex">
<div class="d-flex flex-column">
<div class="equipment">{{ equip }}</div>
<img :src="'images/' + equip + '.png'" style="object-fit: scale-down; width: 100px; height: 28px; margin-bottom: 5px;">
</div>
<div v-if="equipmentData[equip]?.Abilities.length" class="d-flex flex-column flex-grow-1" style="background-color:#f0f2f4; border-radius: 5px; padding: 2px; margin-bottom: 2px;">
<div class="font-weight-bold font-italic ml-4">Abilities:
</div>
<div class="d-flex flex-column ml-4">
<div class="ability" v-for="ability in equipmentData[equip]?.Abilities">
<span v-b-tooltip.html="{ title: formatAbilityTooltip(ability), placement: 'right', interactive: false, trigger: 'hover', boundary: 'window' }">
{{ ability }}
</span>
</div>
</div>
</div>
</div>
<div v-if="equipmentData[equip]" class="d-flex flex-column mt-2 px-3 py-1" style="color: white;background-color: #8b9192; border-radius: 5px;">
<div class="d-flex">
<div class="d-flex mr-auto">
<div class="d-flex">
<span class="mr-1 my-auto font-weight-bold">
Range:
<!-- <img style="filter: invert(1);" src="images/range.svg" width="20px" height="20px"> -->
</span>
<span class="my-auto">{{ equipmentData[equip]?.R }}</span>
</div>
</div>
<div v-if="equipmentData[equip]?.Mt !== '-'" class="d-flex">
<div>
<span class="mr-1 font-weight-bold">
Mt:
<!-- <img style="filter: invert(1);" src="images/might.svg" width="20px" height="20px"> -->
</span>
<span class="my-auto">{{ equipmentData[equip]?.Mt }}</span>
</div>
</div>
<div class="d-flex ml-auto">
<div>
<span class="mr-1 font-weight-bold">
Snap:
<!-- <img style="filter: invert(1);" src="images/snapshot.svg" width="20px" height="20px"> -->
</span>
<span class="my-auto">{{ equipmentData[equip]?.Sn }}</span>
</div>
</div>
</div>
</div>
<div v-if="!equipmentData[equip]" class="font-weight-light font-italic mt-2">Use unit stats for melee.</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="selected-units" id="selected-units-container">
<div class="mx-5">
<div class="d-flex justify-content-between">
<img :src="'images/' + selectedFaction + '.png'" style="object-fit: scale-down; width: 126px; height: 80px;">
<h1 class="my-auto">{{selectedFaction}}</h1>
<img :src="'images/' + selectedFaction + '.png'" style="object-fit: scale-down; width: 126px; height: 80px;">
</div>
<div class="d-flex flex-column">
<div class="d-flex justify-content-between no-print">
<div class="d-flex flex-column">
<div class="d-flex">
<b-form-checkbox v-model="GroupUnits" unchecked-value="false">
Group Duplicates
</b-form-checkbox>
<b-form-checkbox v-model="ShowUnitCards" class="ml-2" unchecked-value="false" >
Show Unit Cards
</b-form-checkbox>
</div>
<div class="d-flex">
<div style="width: 220px;">
<b-form-file
variant="primary"
@change="importArmy"
accept=".json"
placeholder="Import list..."
drop-placeholder=""
no-drop>
Import
</b-form-file>
</div>
<b-button style="color: #495057; border: 1px solid #ced4da; background-color: #e9ecef; border-radius: .25rem;"
@click="exportArmy" class="ml-2">Export</b-button>
</div>
</div>
<div class="d-flex flex-column" style="margin-right: 56px;"><!-- hacky AF lmao, fix this -->
<div class="no-print mb-2" style="width: 200px">
<input type="range" id="points-slider" class="form-control-range" v-model="selectedPoints" min="100" max="600" step="25">
</div>
<div class="d-flex justify-content-between no-print mb-2" style="width: 200px">
<button class="button-74" role="button" @click="adjustPoints(-25)">-</button>
<div>{{ totalPoints }} / {{ selectedPoints }}</div>
<button class="button-74" role="button" @click="adjustPoints(25)">+</button>
</div>
</div>
<div class="d-flex flex-column">
<div class="flex-grow-1 d-flex ml-auto">
<b-form-checkbox size="sm" v-model="printTables" class="my-auto">Include Reference Tables</b-form-checkbox>
<button class="btn ml-2 my-auto" @click="printTable" style="color: rgb(73, 80, 87);
border: 1px solid rgb(206, 212, 218);
background-color: rgb(233, 236, 239);
border-radius: 0.25rem;">Print</button>
</div>
<div class="ml-auto my-auto badge badge-pill badge-light badge-custom" @click="clearAll()">
Clear All
</div>
</div>
</div>
</div>
<div class="table-responsive">
<table id="selected-units-table" style="width:100%" class="table table-bordered">
</table>
</div>
<div>
<div v-for="(def, key) in getKeywords" :key="key">
<div class="font-weight-bold">{{key}}:</div>
<div class="font-weight-light">{{def}}</div>
</div>
</div>
<div id="printableCards" class="flex-wrap" :class="{ 'd-none': ShowUnitCards == 'false', 'd-flex': ShowUnitCards == true}">
<div class="printable-unit-card justify-content-between" :class="selectedFaction + '-bg'" v-for="(unit, index) in filteredArmy" :key="index">
<div class="d-flex flex-column" style="width: 100%;">
<div class="text-center font-weight-bold" style="font-size: 1.4em; background-color: rgba(0, 0, 0, 0.65); color: white; padding: 2px; padding-left: 5px; padding-bottom:5px; width: 100%">
({{unit.points}}) {{unit.name}}
</div>
<div class="d-flex w-100" style="height: 150px;">
<img :src="getImageSrc(unit.name)" class="mx-auto my-auto" :class="{ 'fallback-image': isFallback(unit.name) }" style="object-fit: scale-down; width: 140px; height: 140px; filter: drop-shadow(0 0 0.1rem black)" @error="setFallbackImage($event, unit.name)">
</div>
<div class="d-flex flex-column my-auto flex-grow-1">
<div class="mx-auto font-italic" style="text-shadow: 0px 0px 0px black !important;">
{{unit.specialAbilities.join(', ')}}
</div>
<!-- <div v-if="unit.specialAbilities.length > 0" class="mx-auto font-weight-bold font-italic" style="text-shadow: 0px 0px 0px black !important;" v-for="unitAbility in unit.specialAbilities">
{{unitAbility}}
</div> -->
</div>
<div class="flex-grow-1">
<div class="d-flex flex-column w-100">
<div class="d-flex flex-column justify-content-between mt-auto">
<div class="d-flex printable-unit-card-item my-1" v-for="item in unit.equipment">
<div class="d-flex flex-column">
<div class="font-weight-bold my-auto" style="font-size: 1.2em;">
<img class="my-auto" :src="'images/' + item + '.png'" style="object-fit: scale-down; max-width: 50px; height: 21px; margin-bottom: 5px;">
<span class="my-auto">
{{getShortItemName(item)}}
</span>
</div>
<div class="d-flex flex-column mx-auto">
<div v-if="equipmentData[item]?.Abilities > 0" class="font-weight-bold mx-auto">Abilities</div>
<!-- {{equipmentData[item]?.Abilities.join(', ')}} -->
<div v-for="(ability, i) in equipmentData[item]?.Abilities">
{{ability}}
</div>
</div>
</div>
<div class="d-flex flex-column ml-auto my-auto" v-if="equipmentData[item]">
<div class="d-flex">
<div class="d-flex flex-column flex-grow-1 justify-content-center mr-2">
<div class="mx-auto">Range</div>
<div class="mx-auto stat-number">{{equipmentData[item]?.R}}</div>
</div>
<div class="d-flex flex-column flex-grow-1 justify-content-center">
<div class="mx-auto">Snap</div>
<div class="mx-auto stat-number">{{equipmentData[item]?.Sn}}
</div>
</div>
</div>
<div class="d-flex flex-column flex-grow-1 justify-content-center mt-2" v-if="equipmentData[item]?.Mt != '-'">
<div class="mx-auto">Might:
</div>
<div class="mx-auto stat-number">{{equipmentData[item]?.Mt}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<div class="d-flex flex-column justify-content-between pt-2" style="background-color: rgba(0, 0, 0, 0.65);color: white;padding-right: 6px;">
<div class="d-flex flex-column justify-content-between w-100 mb-1">
<div class="d-flex">
<div class="w-50 d-flex flex-column">
<!-- <img :src="'images/range.svg'" style="object-fit: scale-down; width: 16px; height: 16px; margin-bottom: 5px;"> -->
<div class="mx-auto">Skill</div>
<div class="mx-auto stat-number">{{unit.sk}}</div>
</div>
<div class="w-50 d-flex flex-column">
<!-- <img :src="'images/movement.svg'" style="object-fit: scale-down; width: 16px; height: 16px; margin-bottom: 5px;"> -->
<div class="mx-auto">Movement</div>
<div class="mx-auto stat-number">{{unit.ma}}</div>
</div>
<div class="w-50 d-flex flex-column">
<!-- <img :src="'images/might.svg'" style="object-fit: scale-down; width: 16px; height: 16px; margin-bottom: 5px;"> -->
<div class="mx-auto">Might</div>
<div class="mx-auto stat-number">{{unit.mt}}</div>
</div>
<div class="w-50 d-flex flex-column">
<!-- <img :src="'images/toughness.svg'" style="object-fit: scale-down; width: 16px; height: 16px; margin-bottom: 5px;"> -->
<div class="mx-auto">Toughness</div>
<div class="mx-auto stat-number">{{unit.toughness}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="referenceTables" style="width: 1000px !important;">
<div :class="{'no-print': !printTables}" style="break-before: auto;">
<div class="d-flex ml-1 font-italic">
<div class="my-auto">Grenade throws require 2 actions.</div>
<div class="d-flex flex-column ml-auto">
<div>10" range, roll 2D6 and check the outcome table below.</div>
<div>Blind frags roll 3D6 and the opposing commander chooses two.</div>
</div>
</div>
<hr>
<h3>Frag Grenade Table</h3>
<div id="fragmentationGrenadeOutcomes" class="data-table-container mb-2"></div>
<h3>Smoke Grenade Table</h3>
<ul class="list-unstyled">
<ul>A smoke marker matches the size of a rhyfler's base.</ul>
<ul>Imposes a -3 Skill penalty on ranged attacks tracing LOS through it, and a -1 penalty for attacks within 1” of it.</ul>
<ul>Remove smoke markers when the Activation Deck is reshuffled.</ul>
</ul>
<div id="smokeGrenadeOutcomes" class="data-table-container mb-2"></div>
<h3 class="page-break">Infiltrate Table</h3>
<ul class="list-unstyled">
<ul>A rhyfler with this ability can start off the battlefield.
</ul>
<ul>When the commander brings the rhyfler onto the field, they choose a point at least 4” away from enemy rhyflers, spend an Action, and roll on the Infiltrate table.</ul>
</ul>
<div id="infiltrateOutcomes" class="data-table-container mb-2"></div>
<h3>Volley Table</h3>
<div id="volleyOutcomes" class="data-table-container"></div>
</div>
</div>
</div>
</div>
</div>
<!-- <script src='https://storage.ko-fi.com/cdn/scripts/overlay-widget.js'></script>
<script>
kofiWidgetOverlay.draw('thesmarf', {
'type': 'floating-chat',
'floating-chat.donateButton.text': 'Support Me!',
'floating-chat.donateButton.background-color': '#ffffff',
'floating-chat.donateButton.text-color': '#323842',
'floating-chat.display': 'none',
});
</script> -->
<script>
$(document).ready(function() {
function renderTable(containerId, data) {
const tableHtml = `
<table id="${containerId}Table" class="display">
<thead>
<tr>
<th>Roll Outcome</th>
<th>Description</th>
</tr>
</thead>
<tbody>
${Object.entries(data).map(([key, value]) => `
<tr>
<td>${key}</td>
<td>${value}</td>
</tr>
`).join('')}
</tbody>
</table>
`;
$(`#${containerId}`).html(tableHtml);
$(`#${containerId}Table`).DataTable({
searching: false,
paging: false,
info: false,
sorting: false,
columns: [
{ width: '20%' }, // Width for Roll Outcome column
{ width: '80%' } // Width for Description column
]
});
}
const fragmentationGrenadeOutcomes = {
"6/6": "Target suffers a 3d6 Might attack; rhyflers within 3” suffer a 2d6 Might attack.",
"6/-": "Target suffers a 2d6 Might attack; rhyflers within 2” suffer a 2d6 Might attack.",
"6/1": "Target suffers a 2d6+1 Might attack; thrower is Gobsmacked.",
"-/-": "Target suffers a 1d6 Might attack.",
"1/-": "Grenade bounces away and is harmless.",
"1/1": "Thrower suffers a 2d6 Might attack."
};
const smokeGrenadeOutcomes = {
"6/6": "Place a smoke marker at the designated location, and a second smoke marker 1” away.",
"6/-": "Place a smoke marker at the designated location.",
"6/1": "Place a smoke marker at the designated location, and the opposing commander places a second smoke marker up to 2” away.",
"-/-": "Opposing commander places a smoke marker up to 3” away from the designated location.",
"1/-": "Grenade bounces and produces little smoke.",
"1/1": "Place a smoke marker immediately in front of the thrower."
};
const infiltrateOutcomes = {
"6/6": "Psst! Behind you! – Place the rhyfler anywhere on the battlefield, including Engaged with an enemy rhyfler. This is a free action and does not expend an Action from the current Card.",
"6/-": "Where did he come from?!? – Place the rhyfler anywhere within 4” of the designated point, including Engaged with an enemy rhyfler.",
"6/1": "Stepped on a branch! – Place the rhyfler at the designated point. This is a free action and does not expend an Action from the current Card.",
"-/-": "Sneaky bugger! – Place the rhyfler at the designated point.",
"1/-": "Wait, where am I? – Opponent places rhyfler within 4” of the designated point, including Engaged with an enemy rhyfler of his choice.",
"1/1": "Should have turned left at... – Opponent places the rhyfler anywhere on the battlefield, including Engaged with an enemy rhyfler of his choice."
};
const volleyOutcomes = {
"6/6": "Devastating Volley! – All rhyflers in Zone are attacked at +2 Skill.",
"6/-": "Successful Volley! – All rhyflers in Zone are attacked at +1 Skill.",
"6/1": "That kicks! – Attacks available targets in Zone at +1 Skill and the Scattergunner is Gobsmacked.",
"-/-": "Right on target! – Attacks rhyflers in Zone as normal.",
"1/-": "Breach Fire! – The weapon malfunctions, and the Scattergunner is Gobsmacked.",
"1/1": "Breach Explosion! – The weapon explodes, and the Scattergunner is OOA. Weapon is destroyed."
};
renderTable('fragmentationGrenadeOutcomes', fragmentationGrenadeOutcomes);
renderTable('smokeGrenadeOutcomes', smokeGrenadeOutcomes);
renderTable('infiltrateOutcomes', infiltrateOutcomes);
renderTable('volleyOutcomes', volleyOutcomes);
$('#referenceTables').addClass('onlyPrint');
});
var QuarBFB = new Vue({
el: '#app',
data: {
selectedFaction: 'Crusader',
selectedPoints: 200,
fallbackImages: [],
GroupUnits: false,
ShowUnitCards: 'false',
availableUnits: {
"Crusader": [
{ "name": "Rhyfler (Bogen)", "sk": 12, "mt": "1d6+2", "ma": 5, "toughness": 4, "equipment": ["Bogen"], "specialAbilities": ["Support"], "points": 25 },
{ "name": "Rhyfler (Ryshi)", "sk": 12, "mt": "2d6-1", "ma": 5, "toughness": 4, "equipment": ["Ryshi"], "specialAbilities": [], "points": 30 },
{ "name": "LMG Rhyfler", "sk": 12, "mt": "1d6+2", "ma": 5, "toughness": 4, "equipment": ["H-11a"], "specialAbilities": [], "points": 35, "allowed": 1 },
{ "name": "Sharpshooter", "sk": 13, "mt": "1d6+2", "ma": 5, "toughness": 4, "equipment": ["Bogen"], "specialAbilities": ["Dead-eye"], "points": 35, "allowed": 2 },
{ "name": "Milwer", "sk": 12, "mt": "1d6+2", "ma": 5, "toughness": 4, "equipment": ["Bogen"], "specialAbilities": ["Leader (2/3)"], "points": 28, "allowed": 1 },
{ "name": "Yawdryl", "sk": 13, "mt": "1d6+2", "ma": 5, "toughness": 4, "equipment": ["Bogen"], "specialAbilities": ["Leader (3/4)"], "points": 30, "allowed": 1 },
{ "name": "is-Caerten", "sk": 12, "mt": "1d6+2", "ma": 5, "toughness": 4, "equipment": ["Grifkis Shotgun"], "specialAbilities": ["Leader (2/5)"], "points": 30, "allowed": 1 },
{ "name": "Trench Raider", "sk": 13, "mt": "2d6+1", "ma": 6, "toughness": 4, "equipment": ["Sio-Cly Derringer", "Mace"], "specialAbilities": ["Wallop", "Infiltrate"], "points": 42, "allowed": 3 },
{ "name": "Trench Raider Yawdryl", "sk": 13, "mt": "2d6+1", "ma": 6, "toughness": 4, "equipment": ["Sio-Cly Derringer", "Mace"], "specialAbilities": ["Infiltrate", "Leader (3/4)"], "points": 50, "allowed": 1 },
{ "name": "Torpedo Rhyfler", "sk": 12, "mt": "1d6+1", "ma": 5, "toughness": 4, "equipment": ["Splagen Torpedo"], "specialAbilities": [], "points": 39, "allowed": 2 },
{ "name": "Standard Bearer", "sk": 12, "mt": "1d6+2", "ma": 5, "toughness": 4, "equipment": ["Bogen"], "specialAbilities": ["Standard"], "points": 30, "allowed": 1 },
{ "name": "Squirrel Handler", "sk": 12, "mt": "1d6+2", "ma": 4, "toughness": 4, "equipment": ["Bogen"], "specialAbilities": ["PykPyk(3)"], "points": 30, "allowed": 1 },
{ "name": "PykPyks (3)", "sk": "-", "mt": "-", "ma": "-", "toughness": "-", "equipment": [], "specialAbilities": ["PykPyk(3)"], "points": 5 }
],
"Coftyran": [
{ "name": "Rhyfler (Harlech)", "sk": 12, "mt": "2d6-1", "ma": 5, "toughness": 4, "equipment": ["Harlech"], "specialAbilities": ["Support"], "points": 25 },
{ "name": "Rhyfler (Cryfen)", "sk": 12, "mt": "1d6+2", "ma": 5, "toughness": 4, "equipment": ["Cryfen"], "specialAbilities": [], "points": 32, "allowed": 1 },
{ "name": "Sharpshooter", "sk": 13, "mt": "1d6+2", "ma": 5, "toughness": 4, "equipment": ["Harlech"], "specialAbilities": ["Dead-eye"], "points": 35, "allowed": 2 },
{ "name": "Milwer", "sk": 12, "mt": "1d6+2", "ma": 5, "toughness": 4, "equipment": ["Harlech"], "specialAbilities": ["Leader (2/3)"], "points": 28, "allowed": 1 },
{ "name": "Yawdryl", "sk": 12, "mt": "1d6+2", "ma": 5, "toughness": 4, "equipment": ["Doru"], "specialAbilities": ["Leader (3/4)"], "points": 33, "allowed": 1 },
{ "name": "is-Caerten", "sk": 12, "mt": "2d6", "ma": 5, "toughness": 4, "equipment": ["Pistol", "Sword"], "specialAbilities": ["Leader (2/5)"], "points": 28, "allowed": 1 },
{ "name": "Trench Raider", "sk": 13, "mt": "2d6+1", "ma": 6, "toughness": 4, "equipment": ["Doru", "Sword"], "specialAbilities": ["Killing Strike", "Infiltrate"], "points": 42, "allowed": 3 },
{ "name": "Trench Raider Yawdryl", "sk": 13, "mt": "2d6+1", "ma": 6, "toughness": 4, "equipment": ["Doru", "Sword"], "specialAbilities": ["Infiltrate", "Leader (3/4)"], "points": 50, "allowed": 1 },
{ "name": "Gunner", "sk": 12, "mt": "1d6+1", "ma": 5, "toughness": 4, "equipment": ["RCO m60"], "specialAbilities": [], "points": 70, "allowed": 1 },
{ "name": "Standard Bearer", "sk": 12, "mt": "1d6+2", "ma": 5, "toughness": 4, "equipment": ["Doru"], "specialAbilities": ["Standard"], "points": 35, "allowed": 1 },
{ "name": "Squirrel Handler", "sk": 12, "mt": "1d6+2", "ma": 4, "toughness": 4, "equipment": ["Harlech"], "specialAbilities": ["PykPyk(3)"], "points": 30, "allowed": 1 },
{ "name": "PykPyks (3)", "sk": "-", "mt": "-", "ma": "-", "toughness": "-", "equipment": [], "specialAbilities": ["PykPyk(3)"], "points": 5 }
],
"Gwynt": [
{ "name": "Rhyfler", "sk": 12, "mt": "2d6-1", "ma": 5, "toughness": 4, "equipment": ["Harlech"], "specialAbilities": ["Support"], "points": 25 },
{ "name": "Milwer", "sk": 12, "mt": "1d6+1", "ma": 5, "toughness": 4, "equipment": ["Doru"], "specialAbilities": ["Leader (2/3)"], "points": 30, "allowed": 1 },
{ "name": "Yawdryl", "sk": 13, "mt": "2d6", "ma": 5, "toughness": 4, "equipment": ["Doru", "Blade"], "specialAbilities": ["Steady Under Fire (2)", "Leader (3/4)"], "points": 38, "allowed": 1 },
{ "name": "Grenadier", "sk": 12, "mt": "1d6+2", "ma": 5, "toughness": 4, "equipment": ["Harlech"], "specialAbilities": ["Grenadier"], "points": 28 , "allowed": 2},
{ "name": "Scattergunner", "sk": 12, "mt": "1d6+2", "ma": 5, "toughness": 4, "equipment": ["SG-3 Scattergun"], "specialAbilities": [], "points": 34 , "allowed": 2},
{ "name": "RCO Gunner", "sk": 12, "mt": "1d6+1", "ma": 5, "toughness": 4, "equipment": ["RCO m60"], "specialAbilities": [], "points": 70, "allowed": 1 },
{ "name": "is-Caerten", "sk": 12, "mt": "2d6", "ma": 5, "toughness": 4, "equipment": ["Pistol", "Sword"], "specialAbilities": ["Leader (2/5)"], "points": 28, "allowed": 1 },
{ "name": "Standard Bearer", "sk": 12, "mt": "1d6+2", "ma": 5, "toughness": 4, "equipment": ["Doru"], "specialAbilities": ["Standard"], "points": 35, "allowed": 1 },
{ "name": "Squirrel Handler", "sk": 12, "mt": "1d6+2", "ma": 4, "toughness": 4, "equipment": ["Harlech"], "specialAbilities": ["PykPyk(3)"], "points": 30, "allowed": 1 },
{ "name": "PykPyks (3)", "sk": "-", "mt": "-", "ma": "-", "toughness": "-", "equipment": [], "specialAbilities": ["PykPyk(3)"], "points": 5 }
],
"Toulmore": [
{ "name": "Rhyfler (Bogen)", "sk": 13, "mt": "1d6+2", "ma": 5, "toughness": 4, "equipment": ["Bogen"], "specialAbilities": ["Support"], "points": 27 },
{ "name": "Rhyfler (Ryshi)", "sk": 13, "mt": "2d6-1", "ma": 5, "toughness": 4, "equipment": ["Ryshi"], "specialAbilities": [], "points": 30 },
{ "name": "LMG Rhyfler", "sk": 13, "mt": "1d6+2", "ma": 5, "toughness": 4, "equipment": ["H-11a"], "specialAbilities": [], "points": 37, "allowed": 1 },
{ "name": "Sharpshooter", "sk": 13, "mt": "1d6+2", "ma": 5, "toughness": 4, "equipment": ["Harlech"], "specialAbilities": ["Veteran", "Dead-eye"], "points": 37, "allowed": 2 },
{ "name": "Mortar Rhyfler", "sk": 13, "mt": "1d6", "ma": 4, "toughness": 4, "equipment": ["MTR(22)"], "specialAbilities": [], "points": 40, "allowed": 1 },
{ "name": "Milwer", "sk": 13, "mt": "1d6+2", "ma": 5, "toughness": 4, "equipment": ["Bogen"], "specialAbilities": ["Leader (2/3)"], "points": 30, "allowed": 1 },
{ "name": "Yawdryl", "sk": 13, "mt": "1d6+2", "ma": 5, "toughness": 4, "equipment": ["Bogen"], "specialAbilities": ["Veteran", "Leader (3/4)", "Élan"], "points": 35, "allowed": 1 },
{ "name": "is-Caerten", "sk": 12, "mt": "1d6+2", "ma": 5, "toughness": 4, "equipment": ["Grifkis Shotgun"], "specialAbilities": ["Leader (2/5)"], "points": 30, "allowed": 1 },
{ "name": "Standard Bearer", "sk": 12, "mt": "1d6+2", "ma": 5, "toughness": 4, "equipment": ["Bogen"], "specialAbilities": ["Standard"], "points": 30, "allowed": 1 },
{ "name": "Squirrel Handler", "sk": 12, "mt": "1d6+2", "ma": 4, "toughness": 4, "equipment": ["Bogen"], "specialAbilities": ["PykPyk(3)"], "points": 30, "allowed": 1 },
{ "name": "PykPyks", "sk": "-", "mt": "-", "ma": "-", "toughness": "-", "equipment": [], "specialAbilities": ["PykPyk(3)"], "points": 5 }
]
},
equipmentData: {
"Harlech": { R: 8, Sn: -3, Abilities: [], Mt: '-' },
"Cryfen": { R: 8, Sn: -3, Abilities: ['Automatic', 'Loader'], Mt: '-' },
"Doru": { R: 4, Sn: -1, Abilities: ['Automatic'], Mt: '-' },
"Pistol": { R: 4, Sn: -1, Abilities: [], Mt: '-' },
"RCO m60": { R: 12, Sn: -4, Abilities: ['Blast(2)', 'Slow Reload', 'Crew(2)', 'Gun Shield', 'Heavy'], Mt: '2d6' },
"Bogen": { R: 6, Sn: -2, Abilities: [], Mt: '-' },
"Ryshi": { R: 8, Sn: -3, Abilities: ["Large Caliber"], Mt: '-' },
"H-11a": { R: 10, Sn: -3, Abilities: ['Automatic', 'Loader'], Mt: '-' },
"Grifkis Shotgun": { R: 4, Sn: -1, Abilities: ['Shotgun'], Mt: '-' },
"Sio-Cly Derringer": { R: 4, Sn: -1, Abilities: ['Shotgun'], Mt: '-' },
"Splagen Torpedo": { R: 6, Sn: "-", Abilities: ['Blast(1)'], Mt: '2d6-1' },
"SG-3 Scattergun": { R: 16, SN: "-", Abilities: ["Scattergun", "Large Caliber", "Volley Fire"], Mt: '-'},
"MTR(22)": {R:10, SN: "-", Abilities: ["Blast(1)", "Braced"], Mt: "2d6"}
},
specialAbilities: {
"Automatic": "Automatic weapons may perform the Area Fire action which can target figures within a 4” circle. Each extra target adds -1 to the Skill roll. Use the best cover available (light cover does not apply), and apply modifiers based on targets’ actions (Dive for Cover, return fire, or move).",
"Braced": "Weapons with bipods and heavy rifles sometimes require physical support to fire accurately. When these weapons fire they subtract -1 from Skill unless the firing unit is Prone or base-to-base with terrain providing cover.",
"Cavalry": "May perform a combat action as part of a normal move. The attack may be performed at any point during the rhyfler’s movement. Ranged attacks performed with this ability are at an additional -2 to the Skill Check, but Melee attacks grant the attacker the Wallop ability.",
"Communications": "After the opponent draws their activation card, their commander may spend a point of Pluck to reveal its value.",
"Dead-eye": "Rhyflers with this ability may perform Aimed Shot for1 Action instead of the required 2.",
"Élan": "Rhyflers within command range of this unit gain the Veteran ability",
"Grenadier": "The unit may spend Pluck to adjust a roll on the Grenade charts up or down 1 line.",
"Hardened": "Ignore the need to go Prone when hit by ranged fire. Not Gobsmacked when he dives for cover.",
"Hero": "Heroes have the Hardened and Veteran abilities. May only include one Hero for every 100 points gathered, with a maximum of four allowed.",
"Infiltrate": "A rhyfler with this ability may start the Skirmish off the battlefield. When his commander chooses to bring this rhyfler on the field he designates a point anywhere on the battlefield, no closer than 4” to an enemy rhyfler. He then spends an Action, and rolls on the Infiltrate table.",
"Killing Strike": "May reduce Skill for a melee attack to increase might. -2 Skill +2 Might.",
"Large Caliber": "Weapons that use larger rounds are more effective versus rhyflers in cover, Tractors, and other obstacles. Large Caliber weapons ignore Light Cover and reduce standard Cover by -1. Tractors hit by Large Caliber weapons read one row higher on Damage Tables.",
"Leader (X/Y)": "Leaders allow a commander to better control his troops through their abilities to activate multiple rhyflers at once. A rhyfler with this ability may perform a Leadership action with a number of rhyflers equal to his Leadership value (X) and within his Leadership range (Y), in inches.",
"Leap (X)": "When jumping during a movement action, this rhyfler multiplies the distance allowed, both horizontally and vertically, by their Leap value. This allows the rhyfler to ignore certain barricades and obstacles, as well as enemy rhyflers.",
"Loader": "If the firing rhyfler is in base contact with another friendly rhyfler, increase the roll needed to pass any Skill Check by +1.",
"Medic (X)": "Trained to patch up minor wounds and get rhyflers back on the battlefield, Medics can do a better job of Tend Wounded by subtracting their level to their skill for the Tend Wounded roll.",
"Parry": "Armed with advanced training in hand-to-hand combat, this rhyfler, when Gobsmacked by a melee attack, may attempt a Skill check to avoid the change in Status.",
"Pathfinder": "This rhyfler is extremely adept at surprising his opponents. The rhyfler has the Infiltrate ability, and when rolling on the Infiltrate table, this he rolls three dice and chooses which two to apply.",
"Ranged/Melee (X)": "Rhyflers receive extensive training in the weapons they are issued, allowing them to add this value to the target number when performing the listed combat action.",
"Resilient": "Tougher than his companions, a Resilient rhyfler can do a Tend Wounded action on himself.",
"Scattergun": " Scatterguns may be fired as normal but not beyond one Range Band. A Scattergun may also be Volley Fired.",
"Shotgun": "Shotguns fire a spread of projectiles. In the first range band, they hit the target with +1 Skill. In the second band, they hit the target and one rhyfler within 1”. In the third band, they hit the target and two rhyflers within 2”. They cannot fire beyond three range bands.",
"Skirmisher": "Skirmishers do not suffer movement penalties from difficult terrain, and increase modifiers from cover by 1.",
"Slow Reload": "These weapons require an action to reload after each attack. If not reloaded immediately, the rhyfler must be marked as carrying an unloaded weapon.",
"Sniper Scope": "Equipped with telescopic sights, these weapons only apply range penalties up close. A weapon with a Sniper Scope suffers a -2 penalty within its first range band, and a -1 within its second. The third range band for the weapon is at no penalty, and further penalties are then calculated from that point, thus the fourth range band is at a -1.",
"Standard": "Rhyflers within 2” of their side's standard bearer gain +1 Skill. If the standard bearer is Gobsmacked, he clings to the standard. If Killed, the standard falls and can be picked up by an adjacent rhyfler with an action. A rhyfler who picks up their own side’s standard gains the Standard Special Ability. If an enemy picks it up, the losing side gives 3 Pluck (or all they have) to the gaining side. The original side regains 3 Pluck (or all that remains) if they recover their standard.",
"Steady Under Fire (X)":"The unit may use its Leadership values (X/Y) to remove Gobsmacked status from X number of units at range Y. This does not count as an action for the targeted units.",
"Support": "Rhyflers with this ability may perform Supporting Fire when they meet the requirements of the Action. They may only Support and be Supported by rhyflers with this ability.",
"Veteran": "Trained to shrug off battlefield conditions that leave other rhyflers subdued, a successful recovery does not count as one of a Veteran rhyfler’s two allowed actions.",
"Wallop": "A rhyfler with the Wallop ability may perform a powerful blow attack by spending a point of Pluck instead of the second action normally required.",
"Heavy": "Heavy objects move 1 inch per rhyfler in base-to-base contact. These rhyflers may conduct a Group Movement action as if they have a leader.",
"Crew(X)": "For a crewed weapon X is the number of rhyflers required to operate the weapon. All crew must be Ready or on Overwatch and base-to-base with the weapon. If active crew drop below X the Skill of the Rhyfler operating the weapon is at -2. Firing the Crewed weapon counts as a combat action for the entire crew. Crew Move - Weapon Crew may perform a Group Movement action as if they have a leader.",
"Braced": "Automatic weapons with bipods and heavy rifles sometimes require physical support to fire accurately. When these weapons fire they subtract -1 from Skill unless the firing unit is Prone or base-to-base with terrain providing cover.",
"Gun Shield": "Some crewed weapons are equipped with a gun shield to protect the crew from incoming fire. A gun shield provides partial cover for up to Crew(X) of the weapon.",
"Blast(X)": "This weapon affects an area instead of a single target. The firing rhyfler declares a target point, which may be either an enemy rhyfler or point on the battlefield, then makes a Ranged Attack check to hit the target. Target units may not react to Blast attacks. Any infantry model within one Blast distance (X) of the point of impact is automatically knocked Prone. Conduct a Might roll against each unit within the Blast area. Units with partial cover to the Blast center half the Might roll against them. Any units between the blast radius and twice the blast radius must make a skill check or become Prone, but are not attacked. If the check is failed, the firer rolls on the Dispersion table.",
"PykPyk(X)": "Pykpyk Squirrels are treated as tokens and placed on the side of the play area. They need not be placed on the board with their handler, do not need to be activated and may not be attacked. A Pykpyk may be exchanged to reveal the current activation card, reroll any of the player’s die rolls or to perform a Tend Wounded action on an Out Of Action Rhyfler. Pykpyks are not removed if their Handler is Out Of Action. (X) is the number of Pykpyks included with the Handler. Additional Pykpyks may be purchased during the Gathering of Forces.",
"Volley Fire": "(Requires 2 actions) This attack targets all rhyflers, friend or foe, in the weapon’s effect area, or Zone. This Zone is one inch wide and is a line that extends a single Range Band from the Rhyfler’s base to the fnal target. Consult the Scattergun Outcome table before any to-hit rolls are made. If the fring model is still active, conduct individual Ranged Attacks against each model in the line of fre. Volley Fire may only be Reacted to by Diving for Cover."
},
selectedArmy: [],
table: '',
printTables: false
},
mounted: function(){
// Initialize DataTable
this.table = $('#selected-units-table').DataTable({
searching: false,
paging: false,
info: false,
data: this.getData(),
autoWidth: true,
responsive: true,
oLanguage: {
sEmptyTable: "No units added."
},
columns: [
{
title: 'Quantity',
data: 'quantity',
render: function(data, type, row, meta) {
return `
<div class="d-flex">
<div class="my-auto badge badge-pill badge-light badge-custom no-print">X</div>
<div class="mx-auto">${data}</div>
</div>`;
}
},
{
title: 'Unit',
data: 'name',
width: '20%'
},
{ title: 'Skill', data: 'sk', className: 'text-center' },
{ title: 'Might', data: 'mt', className: 'text-center'},
{ title: 'Movement', data: 'ma', className: 'text-center' },
{ title: 'Toughness', data: 'toughness', className: 'text-center' },
{ title: 'Special Abilities', data: 'specialAbilities' },
{
title: 'Points',
data: 'points',
className: 'text-center',
render: function(data, type, row, meta) {
if(row['quantity'] > 1){
return data + " (" + parseInt(data * row['quantity']) + ")";
}
return data;
}
}
],
initComplete: function() {
var vueInstance = this;
$('#selected-units-table').on('click', '.badge-custom', function() {
var row = $(this).closest('tr');
var rowIndex = vueInstance.table.row(row).index();
var unitName = vueInstance.selectedArmy[rowIndex].name;
// Remove the row from DataTable
vueInstance.table.row(row).remove();
// Remove the corresponding item from Vue dataset
vueInstance.removeFromArmy(rowIndex);
// If the removed unit is "Squirrel Handler", remove all "Pykpyk" units
if (unitName === "Squirrel Handler") {
for (var i = vueInstance.selectedArmy.length - 1; i >= 0; i--) {
if (vueInstance.selectedArmy[i].name == 'PykPyks (3)') {
vueInstance.table.row(i).remove().draw();
vueInstance.removeFromArmy(i);
}
}
}
var _data = QuarBFB.getData();
QuarBFB.table.clear().draw();
QuarBFB.table.rows.add(_data); // Add new data
QuarBFB.addTableChildRows(_data);
});
$(window).on('resize', function() {
vueInstance.table.columns.adjust().draw(false);
});
}.bind(this) // Ensure `this` is bound to the Vue instance
});
},
watch:{
selectedFaction: function(old, _new){
this.clearAll();
QuarBFB.fallbackImages.splice(0);
},
GroupUnits: function(){
var _data = this.getData();
QuarBFB.table.clear().draw();
QuarBFB.table.rows.add(_data); // Add new data
QuarBFB.addTableChildRows(_data);
}
},
computed: {
filteredArmy(){
if(this.GroupUnits == true){
return this.selectedArmy.reduce((acc, current) => {
let existing = acc.find(item => item.name === current.name);
if (!existing) {
acc.push(current);
}
return acc;
}, []);
}
return this.selectedArmy
},
filteredUnits() {
return this.availableUnits[this.selectedFaction];
},
totalPoints() {
return this.selectedArmy.reduce(function(acc, unit) {
return acc + unit.points;
}, 0);
},
getKeywords(){
var obj = {};
for(var unit of this.selectedArmy){
for(var i in unit.specialAbilities){
var def = this.formatAbilityTooltip(unit.specialAbilities[i]).split(":");
obj[def[0]] = def[1];
}
var equipmentData = this.equipmentData[unit.equipment[0]]?.Abilities;
for (var j in equipmentData){
var def = this.formatAbilityTooltip(equipmentData[j]).split(":");
obj[def[0]] = def[1];
}
}
// Sorting the object by keys
var sortedObj = {};
var keys = Object.keys(obj).sort();
for (var key of keys) {
sortedObj[key] = obj[key];
}
return sortedObj;
}
},
methods: {
getShortItemName: function(item){
switch(item){
case "Sio-Cly Derringer":
return "Sio-Clay";
case "Grifkis Shotgun":
return "Grifkis";
case "Splagen Torpedo":
return "Splagen";
case "SG-3 Scattergun":
return "SG-3";
}
return item;
},
exportArmy: function() {
// Export selected army to JSON
const armyData = JSON.stringify(this.selectedArmy);
const blob = new Blob([armyData], { type: 'application/json' });
const url = URL.createObjectURL(blob);
// Create a temporary link to download the JSON file
const link = document.createElement('a');
link.href = url;
link.download = 'selected_army.json';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
},
importArmy: function(event) {
// Import selected army from a JSON file
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
try {
// Ensure reactivity
var _data = JSON.parse(e.target.result);
this.$set(this, 'selectedArmy', _data);
QuarBFB.table.clear().draw();
QuarBFB.table.rows.add(_data); // Add new data
QuarBFB.addTableChildRows(_data);
var _points = 0;
for(var i = 0; i < _data.length; i++){
_points += _data[i].points;
}
this.selectedPoints = Math.ceil(_points / 25) * 25;
} catch (error) {
alert('Failed to import army: Invalid JSON format.');
}
};
reader.readAsText(file);
}
},
getData(){
if(this.GroupUnits == true){
return this.getGroupedData();
}
return this.selectedArmy;
},
getGroupedData() {
// Create an object to store grouped units with their quantities
const groupedData = {};
// Iterate through each item in the selectedArmy array
this.selectedArmy.forEach(unit => {
// Check if the unit name already exists in groupedData
if (groupedData[unit.name]) {
// Increment the quantity for this unit name
groupedData[unit.name].quantity++;
} else {
// Add new entry for this unit name with a quantity of 1
groupedData[unit.name] = { ...unit, quantity: 1 };
}
});
// Convert the groupedData object back to an array
return Object.values(groupedData);
},
adjustPoints(amount) {
const newPoints = parseInt(this.selectedPoints) + parseInt(amount);
if (newPoints >= 100 && newPoints <= 600) {
this.selectedPoints = newPoints;
}
},
getImageSrc(unitName) {
return `images/${this.selectedFaction}/${unitName}.png`;
},
setFallbackImage(event, unitName) {
event.target.src = 'images/Generic/quar.png';
event.target.classList.add('fallback-image');
this.fallbackImages.push(unitName);
},
isFallback(unitName) {
return this.fallbackImages.includes(unitName);
},
clearAll: function(){
var vueInstance = this;
vueInstance.table.rows().every(function(rowIdx, tableLoop, rowLoop) {
// Get the current row
var row = this.node();
// Remove the row from DataTable
vueInstance.table.row(row).remove().draw(false); // Use draw(false) to prevent complete redraw after each removal
// Remove the corresponding item from Vue dataset
vueInstance.removeFromArmy(rowIdx);
});
Vue.set(vueInstance, vueInstance.selectedArmy, []);
},
addToArmy: function(unit) {
// Verify if "PykPyks" can be added only if "Squirrel Handler" is already in the army
if (unit.name == "PykPyks (3)") {
var squirrelHandlerExists = this.selectedArmy.some(selectedUnit => selectedUnit.name === "Squirrel Handler");
if (!squirrelHandlerExists) {
alert('You must have a "Squirrel Handler" in your army to add "PykPyks".');
return;
}
}
var potentialTotalPoints = parseInt(this.totalPoints) + parseInt(unit.points);
// Check if adding this unit would exceed the total points allowed
if (potentialTotalPoints > this.selectedPoints) {
alert('Adding this unit will exceed the total points available.');
return;
}
// Check if the unit has an 'allowed' property
if (unit.allowed) {
// Count how many units of this type are already in the army
var unitCount = this.selectedArmy.filter(selectedUnit => selectedUnit.name === unit.name).length;
// Multiplier based on selectedPoints
var multiplier = this.selectedPoints <= 300 ? 1 : this.selectedPoints <= 600 ? 2 : null;
// If the number of this unit type is less than the allowed amount times the multiplier, add the unit
if (unitCount < unit.allowed * multiplier) {
unit["quantity"] = 1;
this.selectedArmy.push(unit);
} else {
alert(`You can only add ${unit.allowed * multiplier} of ${unit.name}.`);
}
} else {
// If the unit doesn't have an 'allowed' property, just add it
unit["quantity"] = 1;
this.selectedArmy.push(unit);
}
this.$nextTick(function() {
var _data = this.getData();
this.addTableChildRows(_data);
});
},
addTableChildRows: function(_data){
this.table.clear();
this.table.rows.add(_data);
this.table.draw();
this.table.rows().every(function () {
var row = this;
var tr = $(row.node());
// Check if the row has a child row
if (!this.child.isShown()) {
var data = row.data();
var childHTML = '';
var R, Sn, Mt, Abilities;
for (i in data.equipment) {
// Get equipment data with fallback values
var R = QuarBFB.equipmentData[data.equipment[i]]?.R ?? '';
var Sn = QuarBFB.equipmentData[data.equipment[i]]?.Sn ?? '';
var Mt = QuarBFB.equipmentData[data.equipment[i]]?.Mt ?? '';
var Abilities = QuarBFB.equipmentData[data.equipment[i]]?.Abilities ?? [];
// Conditionally add 'mt-1' class if 'i' is greater than 0
var marginClass = (parseInt(i) > 0) ? 'mt-1' : '';
// Construct the HTML string with conditional class
childHTML += `
<div class="d-flex border rounded ${marginClass}" style="float: right;background-color: rgb(240, 242, 244);">
<div class="d-flex flex-column ml-2 mr-2 my-auto font-italic" style="width: 120px">
<div>${data.equipment[i]}</div>
<img src="images/${data.equipment[i]}.png" style="object-fit: scale-down;width: 100px; height: 38px; margin-bottom: 5px;">
</div>
<div class="d-flex flex-column border-left" style="width: 120px">
<div class="ml-1 border-bottom">Range: </div>
<div class="mx-auto my-auto">${R}</div>
</div>
<div class="d-flex flex-column border-left" style="width: 120px">
<div class="ml-1 border-bottom">Snapshot: </div>
<div class="mx-auto my-auto">${Sn}</div>
</div>
<div class="d-flex flex-column border-left" style="width: 120px">
<div class="ml-1 border-bottom">Might: </div>
<div class="mx-auto my-auto">${Mt}</div>
</div>
<div class="d-flex flex-column border-left" style="width: 120px">
<div class="ml-1 border-bottom">Abilities: </div>
<div class="ml-1 d-flex flex-column">
${Abilities.map(ability => `<div>${ability}</div>`).join('')}
</div>
</div>
</div>
`;
}
if(childHTML.length > 0){
this.child(childHTML).show();
}
// Open the child row
this.child.show();
}
});
},
removeFromArmy: function(index) {
this.selectedArmy.splice(index, 1);
},
clearSelectedUnits: function() {
this.selectedArmy = [];
},
formatAbilityTooltip: function(ability) {
// Replace fractions with X/Y
ability = ability.replace(/\b\d+\/\d+\b/g, 'X/Y');
// Replace integers with X
ability = ability.replace(/\b\d+\b/g, 'X');
var tooltip = this.specialAbilities[ability];
if (tooltip) {
return ability + ": " + tooltip;
}
return '';
},
printTable: function() {
window.print();
}
}
});
</script>
</body>
</html>