-
Notifications
You must be signed in to change notification settings - Fork 2
/
Upgrades Bag.ttslua
2095 lines (1944 loc) · 80.8 KB
/
Upgrades Bag.ttslua
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
-- ~~~~~~
-- Script by dzikakulka
-- Issues, history at: https://github.com/tjakubo2/TTS_xwing
-- ~~~~~~
--[[
, ,
/ \/ \
(/ //_ \_
.-._ \|| . \
\ '-._ _,:__.-"/---\_ \
______/___ '. .--------------------'~-'--.)__( , )\ \
`'--.___ _\ / | Here ,' \)|\ `\|
/_.-' _\ \ _:,_ Be Dragons " || (
.'__ _.' \'-/,`-~` |/
'. ___.> /=,| Abandon hope all ye who enter |
/ .-'/_ ) '---------------------------------'
)' ( /(/
\\ "
'=='
This bag serves as a main source for list spawner and item browser with kind of piggybacked
functionaliy for collection generation. Includes how to (not really) do a state machine, give
up on it halfway and *still* make it work. I could really use someone deleting all of it
and forcing me to rewrite this from scratch.
For the most part though, it works out just fine.
--]]
--------
-- SPAWNER MODULE
-- Items available for spawn are added buy passing bags (gets their contents)
-- Items added once can be spawned any number of times, as long as previously spawned
-- items are not deleted
-- Allows for a search it item is avaialable for spawn
-- Items are keyed by name, case-indifferent
-- Passing items with same names not allowed - modify names if necesary (e.g. prefix option)
-- No connection to builder module, no elaborate error handling ( <- I wish this was still true )
Spawner = {}
-- Key: raw name, Value: {source='bag'|'obj', ref=objRef|bagRef, bGUID=bagGuid}
Spawner.items = {}
-- Add all contents of bagRef bag for spawning
-- If prefix is provided, concatenate added items names with it
Spawner.Fill = function(bagRef, prefix)
if prefix == nil then prefix = '' end -- prefix the names
local items = bagRef.getObjects()
for k,info in pairs(items) do
-- Add item with LOWERCASE name, warn and replace if it's double
if Spawner.items[string.lower(prefix .. info.name)] ~= nil then
print('WARNING: Double item \'' .. string.lower(prefix .. info.name) .. '\' from \'' .. bagRef.getName() .. '\' bag')
end
local desc = nil
--if Spawner.extraInfo[info.name] ~= nil then
-- desc = Spawner.extraInfo[info.name]
--end
Spawner.items[string.lower(prefix .. info.name)] = {source='bag', ref=bagRef, bGUID=info.guid, name=info.name}--, desc=info.description}
end
end
-- Spawn an item keyed as itemName at provided postion and rotation
-- Return the spawned item reference, nil if not found
Spawner.Spawn = function(itemName, pos, rot)
-- Item not found
if Spawner.items[string.lower(itemName)] == nil then
print('ERROR: Attempt to spawn unknown item \'' .. string.lower(itemName) .. '\'')
return nil
end
-- Fill variables
if pos == nil then pos = {0, 0, 0} end
if rot == nil then
rot = {0, 0, 0}
elseif type(rot) == 'number' then
rot = {0, rot, 0}
end
-- Take LOWERCASE name
local item = Spawner.items[string.lower(itemName)]
local newObj = nil
-- If it was not yet spawned (still in bag)
if item.source == 'bag' then
-- Take the object
newObj = item.ref.takeObject({guid=item.bGUID, position=pos, rotation=rot})
-- Set its origin to the newly spawned object
Spawner.items[string.lower(itemName)].source = 'obj'
Spawner.items[string.lower(itemName)].ref = newObj
Spawner.items[string.lower(itemName)].bGUID = nil
else
-- If origin object was deleted
if item.ref == nil then
print('ERROR: Origin item \'' .. string.lower(itemName) .. '\' ref nil')
return nil
else
-- If origin object exists, clone it
newObj = item.ref.clone({position=pos})
end
end
newObj.setPosition(pos)
newObj.setRotation(rot)
return newObj
end
-- Spawn an item keyed as itemName at provided postion and rotation
-- Return the spawned item reference, nil if not found
-- Instead of switching the item to object source mode, put it back in the bag if taken from bag
Spawner.SpawnReturn = function(itemName, pos, rot)
-- Item not found
if Spawner.items[string.lower(itemName)] == nil then
print('ERROR: Attempt to spawn unknown item \'' .. string.lower(itemName) .. '\'')
return nil
end
-- Fill variables
if pos == nil then pos = {0, 0, 0} end
if rot == nil then
rot = {0, 0, 0}
elseif type(rot) == 'number' then
rot = {0, rot, 0}
end
-- Take LOWERCASE name
local item = Spawner.items[string.lower(itemName)]
local newObj = nil
-- If it was not yet spawned (still in bag)
if item.source == 'bag' then
-- Take the object
newObj = item.ref.takeObject({guid=item.bGUID, position=pos, rotation=rot})
local cloneObj = newObj.clone({})
cloneObj.lock()
cloneObj.setPosition(Builder.LocalPos({0, -3, 0}, item.ref))
item.ref.putObject(cloneObj)
else
-- If origin object was deleted
if item.ref == nil then
print('ERROR: Origin item \'' .. string.lower(itemName) .. '\' ref nil')
return nil
else
-- If origin object exists, clone it
newObj = item.ref.clone({position=pos})
end
end
newObj.setPosition(pos)
newObj.setRotation(rot)
return newObj
end
-- Return true if queried name exists, false if it doesn't
Spawner.Find = function(itemName)
if Spawner.items[string.lower(itemName)] == nil then return false
else return true end
end
-- Return item matches
-- Arg: {word1, word2, ... , wordN}
-- Return: {ships={entry1, entry2, ...}, upgrades={entry1, entry2, ...}, misc={entry1, entry2, ...}}
-- Entry: {name=prettyItemName, key=itemSpawnKey}
Spawner.ReturnMatches = function(searchWords)
local matches = {ships={}, upgrades={}, misc={}}
for k,word in pairs(searchWords) do
local acronym = true
for i=1,word:len() do
if word:sub(i,i) ~= string.upper(word:sub(i,i)) and tonumber(word:sub(i,i)) == nil then
acronym = false
end
end
if acronym then
for itemName,itemInfo in pairs(Spawner.items) do
local nameWords = {}
for nameWord in itemInfo.name:gmatch('[^%s-]+') do
table.insert(nameWords, nameWord)
end
local match = true
if #nameWords ~= word:len() then
match = false
end
if match then
for k,nameWord in pairs(nameWords) do
if string.lower(nameWord:sub(1,1)) ~= string.lower(word:sub(k,k)) then
match = false
break
end
end
end
if match then
if itemName:sub(1,8) == 'upgrade:' then
table.insert(matches.upgrades, {name=itemInfo.name, key=itemName})
elseif itemName:sub(1,5) == 'ship:' then
local listName = itemInfo.name
--print(itemName)
--if itemName:find(' v[1-9]') ~= nil then
-- listName = listName:sub(1, -3) .. '(' .. itemInfo.desc .. ')'
-- print('hit')
--end
table.insert(matches.ships, {name=listName, key=itemName})
elseif itemName:find('refcard') ~= nil or itemName:find('dials') ~= nil then
table.insert(matches.misc, {name=itemInfo.name, key=itemName})
end
end
end
elseif word:len() > 2 then
for itemName,itemInfo in pairs(Spawner.items) do
if itemName:find(string.lower(word)) ~= nil then
if itemName:sub(1,8) == 'upgrade:' then
table.insert(matches.upgrades, {name=itemInfo.name, key=itemName})
elseif itemName:sub(1,5) == 'ship:' then
local listName = itemInfo.name
--print(itemName)
--if itemName:find(' v[1-9]') ~= nil then
-- listName = listName:sub(1, -3) .. '(' .. itemInfo.desc .. ')'
-- print('hit')
--end
table.insert(matches.ships, {name=listName, key=itemName})
elseif itemName:find('refcard') ~= nil or itemName:find('dials') ~= nil then
table.insert(matches.misc, {name=itemInfo.name, key=itemName})
end
end
end
end
end
return matches
end
-- END SPAWNER MODULE
--------
--------
-- BUILDER MODULE
-- Builds lists, usually.
Builder = {}
-- Initial textfield note
Builder.initialNote =
[[\n\n\n\n\n\nRemove all of this text and paste your list snippet here.\nClick the T tool on left bar (F8 shorcut),\nthen click this field to edit it.\n\n\n\n\n\n]]
-- Initialize this bag in squadron builder mode
function startAsBuilder()
init()
local rot = {self.getRotation()[1], self.getRotation()[2]+180, self.getRotation()[3]}
--Builder.noteObj = Spawner.Spawn('Spawn Me', Builder.LocalPos({-15, 0.5, 2}), rot)
Builder.noteObj = Builder.textToolTemplate.clone({position = Builder.LocalPos({-15, 0, 8}, self, 0.75)})
Builder.noteObj.setValue(Builder.initialNote)
-- Change button to spawning function:
self.clearButtons()
Builder.generalButton.click_function = 'start'
Builder.generalButton.label = 'Spawn it!'
Builder.generalButton.width = 3500
self.createButton(Builder.generalButton)
end
-- Start the spawn routine
function start()
Builder.ParseInput(note)
end
-- Elements placement config
Builder.config = {
-- TOP VIEW
-- ?? (y axis)
-- |
-- ??? --- > (x axis)
-- (z axis)
-- Cards dimensions WxH:
-- 2.35 x 3.25 -- (1.175 x 1.625) PC
-- 2.1 x 1.4 -- (1.05 x 0.7) UG
global_z = 0.5,
refcardOffset_y = 0,
refcardOffset_x = -12,
refcardSpacing_x = 3,
dialOffset_y = -3,
dialOffset_x = -12,
dialSpacing_x = 3,
shipOffset_y = -4,
shipSpacing_x = 5.5,
pilotOffset_y = -4,
pilotSpacing_x = 5.5,
upgOffset_y = -1*((3.25/2)+(2.1/2)),
upgSpacing_x = 1.4,
upgSpacing_y = -2.1,
upgRowCount = 3,
shieldInitOffset = {1.65, 0.1, 1},
shieldColCount = 3,
shieldSpacing_x = 0.8,
shieldSpacing_y = -0.9,
shieldEvenColInset_y = -0.45
}
-- Shorthand for accesing config elements
local BC = Builder.config
-- Initial position for refcards
Builder.RefcardInitPos = function(refcardCount)
return {-1*(BC.refcardSpacing_x/2)*(refcardCount-1)+BC.refcardOffset_x, BC.global_z, BC.refcardOffset_y}
end
-- Step refcard position
Builder.RefcardStep = function(currPos)
return {currPos[1]+BC.refcardSpacing_x, currPos[2], currPos[3]}
end
-- Initial position for dial bags
Builder.DialInitPos = function(dialCount)
return {-1*(BC.dialSpacing_x/2)*(dialCount-1)+BC.dialOffset_x, BC.global_z, BC.refcardOffset_y+BC.dialOffset_y}
end
-- Step dial bag position
Builder.DialStep = function(currPos)
return {currPos[1]+BC.dialSpacing_x, currPos[2], currPos[3]}
end
-- Initial position for ship models
Builder.ShipInitPos = function(shipCount)
return {-1*(BC.shipSpacing_x/2)*(shipCount-1), BC.global_z, BC.refcardOffset_y+BC.dialOffset_y+BC.shipOffset_y}
end
-- Step ship model position
Builder.ShipStep = function(currPos)
return {currPos[1]+BC.shipSpacing_x, currPos[2], currPos[3]}
end
-- Pilot card position for given ship model position
Builder.PilotForShip = function(shipPos)
return {shipPos[1], shipPos[2], shipPos[3]+BC.pilotOffset_y}
end
-- Initial upgrade card position for given pilot cad position
Builder.UpgForPilotInit = function(pilotPos, upgCount)
if upgCount > BC.upgRowCount then upgCount = BC.upgRowCount end
return {pilotPos[1]-(BC.upgSpacing_x/2)*(upgCount-1), pilotPos[2], pilotPos[3]+BC.upgOffset_y}
end
-- Step upgrade card position
Builder.UpgStep = function(currPos, currUpgNumber, upgCount)
upgCount = upgCount or 100 -- Yeah, this
if currUpgNumber%BC.upgRowCount == 0 then
local insetCount = upgCount - currUpgNumber
if insetCount > BC.upgRowCount then insetCount = BC.upgRowCount end
insetCount = insetCount + 0.5*(BC.upgRowCount - insetCount)
return {currPos[1]-((insetCount-1)*BC.upgSpacing_x), currPos[2], currPos[3]+BC.upgSpacing_y}
else
return {currPos[1]+BC.upgSpacing_x, currPos[2], currPos[3]}
end
end
-- Initial shield token position in the pilot card frame
Builder.ShieldInPilotFrameInit = function()
return BC.shieldInitOffset
end
-- Step shield position
Builder.ShieldInPilotFrameStep = function(currPos, shieldNumber)
if shieldNumber%BC.shieldColCount == 0 then
local column = math.floor(shieldNumber/BC.shieldColCount)+1
local colOffset_y = 0
if column%2 == 0 then
colOffset_y = BC.shieldEvenColInset_y
end
return {currPos[1]+BC.shieldSpacing_x, currPos[2], currPos[3]-2*BC.shieldSpacing_y+colOffset_y}
else
return {currPos[1], currPos[2], currPos[3]+BC.shieldSpacing_y}
end
end
-- Adjust config for low amount of ships
Builder.config.SparseAdjust = function()
local shipNum = #Builder.pilots
if shipNum == 2 then
BC.shipSpacing_x = 2*BC.shipSpacing_x
BC.pilotSpacing_x = 2*BC.pilotSpacing_x
BC.upgRowCount = 5
elseif shipNum == 3 then
BC.shipSpacing_x = 1.5*BC.shipSpacing_x
BC.pilotSpacing_x = 1.5*BC.pilotSpacing_x
BC.upgRowCount = 4
end
end
-- Main pilots table
-- Key: numerical, Value:{name=pilotName, pRef=pilotRef, sRef=shipRef, upgrades=upgTable, shipName=shipName}
-- [[upgTable]] Key: numerical, Value: {name=upgName, ref=upgRef}
Builder.pilots = {}
-- Table containing pilots indexes grouped by name
-- Key: pilotName, Value:{keyInPilots1, keyInPilots2, ... , keyInPilotsN}
Builder.pilotCount = {}
-- Table containing pilots indexes grouped by ship type
-- Key: shipName, Value:{keyInPilots1, keyInPilots2, ... , keyInPilotsN}
Builder.ships = {}
-- Above table entry count (it's string keyed so # operator doesn't work)
Builder.shipsCount = 0
-- Table containing references for accesories per each ship type
-- Key: shipName, Value:{dRef=dialRef, rcRef=refcardRef}
Builder.accesories = {}
-- Table with miscellaneous items spawned (subclasses)
Builder.misc = {}
-- Table with spawned shield sets refs
-- Key: numericalFromPilots {sRefs={shRef1, shRef2, ... , shRefN}}
Builder.misc.shields = {}
-- Table with any tokens spawned + parent item if applicable
-- Key: numerical {pRef=parentRef, tRef=tokenRef}
Builder.misc.tokens = {}
-- Table for any other spawned items
-- Key: numerical {ref=object, com=comment}
Builder.misc.other = {}
-- Table with user choices between same name pilot spawns
-- Key: numerical, Value:{cName=commonName, pCards={pilotCard1ref, pilotCard2ref, ... , pilotCardNref},
-- pilotsIndex=indexInPilotsTable, sPos=shipPos, pPos=pilotPos, resolved=true/false}}
Builder.choices = {}
-- Create an interactive user choice - spawn cards with buttons, fill choices table
Builder.CreateChoice = function(commonName, shipPos, pilotsIndex)
-- Create a new choice table
local cTable = {}
cTable.resolved = false
cTable.pilotsIndex = pilotsIndex
cTable.cName = commonName
cTable.sPos = shipPos
cTable.pPos = Builder.PilotForShip(shipPos)
cTable.rot = self.getRotation()
cTable.pCards = {}
local choiceButton = {
click_function = 'Click_resolveChoice',
function_owner = self,
label = 'Choose',
position = {0, 0.5, 2},
rotation = {0, 0, 0},
width = 1000,
height = 400,
font_size = 200
}
-- Get all the choice possibilities
local choiceNames = {}
for k=1,3,1 do
if Spawner.Find(commonName .. ' v' .. k) then
table.insert(choiceNames, commonName .. ' v' .. k)
if Spawner.Find('Ship: ' .. commonName .. ' v' .. k) ~= true then
Builder.Log('Ship model \'' .. commonName .. ' v' .. k .. '\' not found... Stop')
Builder.DisplayLog()
return
end
end
end
-- Spawn choice cards, create buttons, tag with choice ID
for k,name in pairs(choiceNames) do
local newCardPos = {cTable.sPos[1]-(2.35/2)*(#choiceNames-1)+(k-1)*(2.35), cTable.sPos[2], cTable.sPos[3]}
local newCard = Spawner.Spawn(name, Builder.LocalPos(newCardPos), cTable.rot)
table.insert(cTable.pCards, newCard)
newCard.setVar('choiceID', #Builder.choices+1)
newCard.createButton(choiceButton)
end
table.insert(Builder.choices, cTable)
end
-- Resolve choice avaialble as click function
function Click_resolveChoice(object, dummy)
Builder.ResolveChoice(object)
end
-- Called when one of the choice cards is clicked
Builder.ResolveChoice = function(clickedCard)
local corrPilot = clickedCard
local corrShip = nil
local cTable = Builder.choices[corrPilot.getVar('choiceID')]
for k,choice in pairs(cTable.pCards) do
-- Delete other choice cards
if choice ~= corrPilot then
choice.destruct()
else
-- Set this card in correst position, spawn its ship
choice.clearButtons()
choice.setPosition(Builder.LocalPos(cTable.pPos))
if Spawner.Find('Ship: ' .. corrPilot.getName()) == true then
corrShip = Spawner.Spawn('Ship: ' .. corrPilot.getName(), Builder.LocalPos(cTable.sPos), cTable.rot)
else
Builder.Die('B26', 'Spawn choice \'' .. corrPilot.getName() .. '\' ship not found')
end
end
end
-- Fill pilots table, rename correctly
Builder.pilots[cTable.pilotsIndex].pRef = corrPilot
Builder.pilots[cTable.pilotsIndex].sRef = corrShip
corrPilot.setName(Builder.pilots[cTable.pilotsIndex].name)
corrShip.setName(Builder.pilots[cTable.pilotsIndex].name)
--corrShip.setDescription('')
cTable.resolved = true
-- Advance spawn state if that was the last choice
if Builder.AllResolved() == true then Builder.AdvanceState(Builder.states.CoreSpawned) end
end
-- Are all choices resolved? Returns true/false
Builder.AllResolved = function()
local allResolved = true
for k,cInfo in pairs(Builder.choices) do
if cInfo.resolved ~= true then allResolved = false end
end
return allResolved
end
-- Small logging module for user-friendly errors
-- Log string
Builder.log = ''
-- Add some message (new line) to the log
Builder.Log = function(logMsg)
Builder.log = Builder.log .. logMsg .. '\n'
end
-- Display the log on note if it exists, highlight it
Builder.DisplayLog = function(color)
if color == nil then color = {1, 0, 0} end
if Builder.noteObj ~= nil then
Builder.noteObj.setValue(Builder.log)
Builder.noteObj.highlightOn(color, 6)
end
end
-- Add a blank (name only) entry to the main pilots table
Builder.AddPilot = function(pilotName)
pilotName = Builder.ErrataPass(pilotName)
if Builder.pilotCount[pilotName] == nil then
Builder.pilotCount[pilotName] = {}
end
table.insert(Builder.pilotCount[pilotName], #Builder.pilots + 1)
table.insert(Builder.pilots, {name = pilotName, pRef = nil, sRef = nil, upgrades = {}, count = #Builder.pilotCount[pilotName]})
end
-- Duplicate some pilot with his upgrades
-- No index passed = last pilot duplicated
Builder.DuplicatePilot = function(pilotIndex)
if pilotIndex == nil or pilotIndex > #Builder.pilots then
pilotIndex = #Builder.pilots
end
local origPilot = Builder.pilots[pilotIndex]
Builder.AddPilot(origPilot.name)
for k,uTable in pairs(origPilot.upgrades) do
Builder.AddUpgrade(uTable.name)
end
end
-- Add an empty upgrade entry (name only) to the pilot at given index
-- If index not provided, adds to the last pilot
Builder.AddUpgrade = function(upgName, pilotIndex)
upgName = Builder.ErrataPass(upgName)
if pilotIndex == nil or pilotIndex > #Builder.pilots then
pilotIndex = #Builder.pilots
end
table.insert(Builder.pilots[pilotIndex].upgrades, {name=upgName, ref=nil})
end
-- Get the upgrade table for pilot of given index
Builder.GetUpgrades = function(pilotIndex)
if Builder.pilots[pilotIndex] ~= nil then
return Builder.pilots[pilotIndex].upgrades
end
end
-- Check if pilot at some index has specified upgrade (not neccesarily spawned already)
-- pilotIndex can be 'any' to check all pilots
-- Returns false/true
Builder.HasUpgrade = function(upgName, pilotIndex)
if type(pilotIndex) == 'number' then
local upgrades = Builder.GetUpgrades(pilotIndex)
local found = false
for k,uTable in pairs(upgrades) do
if string.lower(uTable.name) == string.lower(upgName) then found = true end
end
return found
elseif string.lower(pilotIndex) == 'any' then
local found = false
for k=1,#Builder.pilots,1 do
if Builder.HasUpgrade(upgName, k) == true then found = true end
end
return found
end
end
-- Delete all pilots/upgrades records
Builder.ClearList = function()
Builder.pilots = {}
Builder.pilotCount = {}
Builder.ships = {}
Builder.shipsCount = 0
end
-- Prints squad, judt for debugging
Builder.PrintSquad = function()
print('--- SQUAD ---')
for k,pilotTable in pairs(Builder.pilots) do
print(' - P: \'' .. pilotTable.name .. '\'')
for k2,upgTable in pairs(pilotTable.upgrades) do
print(' - - U: \'' .. upgTable.name .. '\'')
end
end
print('--- END SQUAD ---')
end
-- Trim a string of whitespaces
Builder.TrimWord = function(word)
-- return word:match("^%s*(.-)%s*$")
return (word:gsub("^%s+", ""):gsub("%s+$", ""))
end
-- Create notebook "Spawn Me" tab for parsing long lists
-- Removes any that already exist
Builder.CreateNotebookTab = function(playerName)
Builder.RemoveNotebookTab()
local tabBody = 'Delete all this, paste your list here and press the "Parse Notebook" button'
if playerName ~= nil and type(playerName) == 'string' then
tabBody = tabBody .. '\nThis tab was created by ' .. playerName .. '\'s too long list snippet spawn attempt'
end
addNotebookTab({title='Spawn Me', body=tabBody})
end
-- Parse the text from created "Spawn Me" notebook tab
-- If it still contains initial message, return empty string
-- If none exists, return empty string
Builder.ParseFromNotebookTab = function()
local tabs = getNotebookTabs()
local body = ''
for k,tTable in pairs(tabs) do
if tTable.title == 'Spawn Me' then
body = tTable.body
end
end
if body:sub(1,15) == 'Delete all this' then
body = ''
end
return body
end
-- Remove all created "Spawn Me" notebook tabs
Builder.RemoveNotebookTab = function()
local tabs = getNotebookTabs()
local tabsToRemove = {}
for k, tTable in pairs(tabs) do
if tTable.title == 'Spawn Me' then
table.insert(tabsToRemove, tTable.index)
end
end
for k,ind in pairs(tabsToRemove) do
removeNotebookTab(ind)
end
end
-- Button definition for the main bag
-- Lacks click_function, width, label
Builder.generalButton = {
function_owner = self,
position = {0, 0.5, 3},
rotation = {0, 0, 0},
height = 800,
font_size = 20000,
click_function='dummy'
}
-- Note object
Builder.noteObj = nil
Builder.errorNote = '[FF3333]Your list snippet appears to be in\n a wrong format, incomplete or including typos.\n[FFFF33]After verifying, you can paste it here and click \'Spawn it!\' again.\n\n[FF3333]Please make sure you are copying it *exactly*\n like instructed (not omitting any parts)\nReport at github.com/tjakubo2/TTS_xwing/issues if this persists.\n\n'
-- Parse the text list and proceed if it passed the precheck
Builder.ParseInput = function()
local input = Builder.noteObj.getValue()
local format = Builder.ProcessInput(input)
local passState = Builder.PrecheckPass()
if format ~= 'unknown' and passState == true then
self.clearButtons()
Builder.Log('Input parsed')
Builder.AdvanceState(Builder.states.ListParsed)
else
local errorDetail = 'Format: ' .. format
if passState ~= true then
errorDetail = errorDetail .. '\nError: ' .. passState .. ' not found'
end
Builder.noteObj.setValue(Builder.errorNote .. errorDetail)
Builder.ClearList()
end
end
-- Check what format the processed list is in
-- SUPPORTED FORMATS:
-- xwing-builder.co.uk/browse - uk_browse
-- xwing-builder.co.uk/build, Forum or email -> Plain text - uk_Plain
-- xwing-builder.co.uk/build, Forum or email -> Plain text (brief) - uk_PlainBrief
-- geordanr.github.io/xwing, Print/View as text -> BB Code - geordanr_BB
Builder.CheckListFormat = function(input)
-- Geordanr builder is identified by his website in footer
if input:find('geordanr.github.io') ~= nil then
if input:find('<a href=') ~= nil then
return ''
else
return 'geordanr_BB'
end
end
-- co.uk builder "Forum or email" formats contain a
--> Pilots
--> ------
-- snippet, brief has brackets
if input:find('Pilots[%s]+%-%-%-%-%-%-') ~= nil then
if input:find('%] %(') ~= nil then
return 'uk_PlainBrief'
else
return 'uk_Plain'
end
else
-- If there's no Pilots-dashes snippet but there are numbers
-- in parentheses, we take it as a co.uk browse format
if input:find('%([%d]+%)') ~= nil then
return 'uk_Browse'
end
end
return ''
end
-- Check list format, parse squad using an approriate function, advance spawn state
Builder.ProcessInput = function(input)
local listFormat = Builder.CheckListFormat(input)
if listFormat ~= '' then
Builder.Log('Recognized ' .. listFormat .. ' format')
else
--Builder.Log('Unrecognized list format... Stop')
--Builder.DisplayLog()
return 'unknown'
end
Builder.ParseSquad[listFormat](input)
return listFormat
end
-- Table containing parse (decode list into table entries) for each supported format
Builder.ParseSquad = {}
Builder.ParseSquad.uk_Browse = function(input)
-- Strip parentheses with text inside
input = input:gsub('[%s]%([^%d][%w%s\']+%)', '')
-- Flag if the next name is a pilot
local newShip = true
-- Separate 'words' by opening parentheses OR plus sign
for word in input:gmatch('[“”’\'\"/%w%s%-%.]+[%s][+%(]') do
word = Builder.TrimWord(word)
-- Replace special characters
word = word:gsub('[“”]', '"')
word = word:gsub('’', '\'')
local itemName = word:sub(1, -3)
local delim = word:sub(-1, -1)
-- Add new pilot if it is the time
if newShip == true then
Builder.AddPilot(Builder.TrimWord(itemName))
else
Builder.AddUpgrade(Builder.TrimWord(itemName))
end
-- Update the next item type flag
if delim == '(' then
newShip = true
else
newShip = false
end
end
end
Builder.ParseSquad.uk_Plain = function(input)
-- Cut all the names/headers from input
local p_b,p_e = input:find('Pilots[%s]+%-%-%-%-%-%-')
local cutInput = input:sub(p_e+1, -1)
local prefix = input:sub(1, p_e)
-- Strip parentheses with text inside
cutInput = cutInput:gsub('[%s]%([^%d][%w%s\']+%)', '')
-- Convert:
--> Pilot (cost) [x times]
--> ShipName (cost), Upg1 (cost), Upg2 (cost), ... , UpgN (cost)
-- into
--> Pilot [upg1, upg2, ... , upgN] (cost)
-- which is simply co.uk Plain Brief format
local lines = {}
for line in string.gmatch(cutInput,'[^\r\n]+') do
-- If it's just pilot line, add it
if line:find(',') == nil then
table.insert(lines, line)
else
-- If it's ship and upgrades line
line = line:gsub('[%s]%([%d]+%)', '') -- cut out point costs
line = Builder.TrimWord(line)
local firstComma = line:find(',') -- cut out ship name
line = line:sub(firstComma+2, -1)
line = ' [' .. line .. ']' -- add brackets
local e_b,e_e = lines[#lines]:find('[%s]%([%d]+%)')
local ending = lines[#lines]:sub(e_b, -1)
lines[#lines] = lines[#lines]:sub(1, e_b-1) .. line .. ending -- cat with previous line (between pilot and "x times")
end
end
-- Fuse fluff prefix and all the lines
local finInput = ''
for k,line in pairs(lines) do
finInput = finInput .. line .. '\n'
end
finInput = prefix .. '\n' .. finInput
Builder.ParseSquad.uk_PlainBrief(finInput)
end
Builder.ParseSquad.uk_PlainBrief = function(input)
-- Cut all the names/headers from input
local p_b,p_e = input:find('Pilots[%s]+%-%-%-%-%-%-')
local cutInput = input:sub(p_e+1, -1)
-- Strip parentheses with text inside
cutInput = cutInput:gsub('[%s]%([^%d][%w%s\']+%)', '')
-- Convert:
--> Pilot [upg1, upg2, ... , upgN] (cost)
-- into:
--> Pilot + upg1 + upg2 + ... + upgN (cost)
-- which is simply the co.uk browse format
cutInput = cutInput:gsub('%s%[', ' + ')
cutInput = cutInput:gsub(',%s', ' + ')
cutInput = cutInput:gsub('%]%s%(', ' (')
local finInput = ''
-- Replicate any lines that end in 'x [number]' (multiple of these pilots shortened)
for line in string.gmatch(cutInput,'[^\r\n]+') do
if (line:sub(-5,-1)):find('x[%s][%d]+') ~= nil then
local repNum = tonumber((line:sub(-5,-1)):match('x[%s]+([%d]+)'))
local trimmedLine = line:match('^[%s]*(.-)[%s]+x[%s]+[%d]+[%s]*$')
for k=1,repNum,1 do
finInput = finInput .. trimmedLine .. '\r\n'
end
else
finInput = finInput .. line .. '\r\n'
end
end
Builder.ParseSquad['uk_Browse'](finInput)
end
Builder.ParseSquad.geordanr_BB = function(input)
-- Cut all the footer stuff
local t_b,t_e = input:find('%[b%]%[i%]Total:')
local cutInput = input:sub(1, t_b-1)
-- Replace negative costs since they fuck it up later
cutInput = cutInput:gsub('%(%-', '(')
-- Strip parentheses with text inside
cutInput = cutInput:gsub('[%s]%([^%d][%w%s\']+%)', '')
-- Split input into lines marked by BB Code bold/italic symbols
for word in cutInput:gmatch('%[[bi]%][\'\"/%w%s%-%.]+[%s][%(][%d]+[%)]%[/[bi]%]') do
word = Builder.TrimWord(word)
-- Determine bold/italic symbol
local b_i = word:sub(2,2)
-- Strip BB Code and point cost
word = word:match('^%[[bi]%]([\'\"/%w%s%-%.]+)[%s][%(][%d]+[%)]%[/[bi]%]$')
word = Builder.TrimWord(word)
if b_i == 'b' then
-- Bold markes marks pilots
Builder.AddPilot(word)
elseif b_i == 'i' then
-- Italic markes marks upgrades
Builder.AddUpgrade(word)
end
end
end
-- Get a position in some object reference frame + height offset
-- Default object - self
-- Default height offset - 0.5
Builder.LocalPos = function(pos, ref, hOff)
local refOffset = nil
local refRot = nil
if ref == nil then ref = self end
if hOff == nil then hOff = 0.5 end
if type(ref) == 'table' then
refOffset = ref
refRot = ref.rotation
if refRot == nil then
refRot = 0
end
elseif type(ref) == 'userdata' then
refOffset = ref.getPosition()
refRot = math.rad(-1*ref.getRotation()[2])
else
Builder.Die('B27', 'Invalid LocalPos reftype')
return {0, 0, 0}
end
local posRot = {pos[3]*math.sin(refRot)-pos[1]*math.cos(refRot), pos[2], -1*pos[1]*math.sin(refRot) - pos[3]*math.cos(refRot)}
return {posRot[1]+refOffset[1], refOffset[2]+hOff, posRot[3]+refOffset[3]}
end
-- Check if pilot, ship and upgrades names can be found in Spawner
-- Return true if everything looks OK
-- Return first not found element if there was any
Builder.PrecheckPass = function()
for k, sInfo in pairs(Builder.pilots) do
if (not Spawner.Find('Ship: ' .. sInfo.name)) and (not Spawner.Find('Ship: ' .. sInfo.name .. ' v1')) then
return 'Ship:' .. sInfo.name
end
if (not Spawner.Find(sInfo.name)) and (not Spawner.Find(sInfo.name .. ' v1')) then
return sInfo.name
end
for k2, uInfo in pairs(sInfo.upgrades) do
if not Spawner.Find('Upgrade: ' .. uInfo.name) then
return uInfo.name
end
end
end
return true
end
-- Spawn core elements according to existing pilot/ship tables
-- Spawns ship models, pilot cards and upgrade cards
-- Creates choices when applicable
Builder.SpawnCore = function()
local shipPos = Builder.ShipInitPos(#Builder.pilots)
local pilotPos = nil
local upgPos = nil
local rot = self.getRotation()
-- For each pilot
for k, sInfo in pairs(Builder.pilots) do
pilotPos = Builder.PilotForShip(shipPos)
if Spawner.Find('Ship: ' .. sInfo.name) == true then
-- Spawn his ship model
sInfo.sRef = Spawner.Spawn('Ship: ' .. sInfo.name, Builder.LocalPos(shipPos), rot)
if Spawner.Find(sInfo.name) == true then
-- Spawn his pilot card
sInfo.pRef = Spawner.Spawn(sInfo.name, Builder.LocalPos(pilotPos), rot)
else
-- Error if pilot card not found when there is a ship
Builder.Log('Pilot \'' .. sInfo.name .. '\' not found... Stop')
Builder.DisplayLog()
return false
end
elseif Spawner.Find('Ship: ' .. sInfo.name .. ' v1') == true then
-- Create choice if ship choices exist
Builder.CreateChoice(sInfo.name, shipPos, k)
else
-- Error if neither regular or choices models exist
Builder.Log('Ship model \'' .. sInfo.name .. '\' not found... Stop')
Builder.DisplayLog()
return false
end
upgPos = Builder.UpgForPilotInit(pilotPos, #sInfo.upgrades)
-- For each upgrade for this pilot
for k2, uInfo in pairs(sInfo.upgrades) do
if Spawner.Find('Upgrade: ' .. uInfo.name) == true then
-- Spawn the upgrade if it exists
uInfo.ref = Spawner.Spawn('Upgrade: ' .. uInfo.name, Builder.LocalPos(upgPos), rot)
else
-- Error if upgrade doesn't exist
Builder.Log('Upgrade \'' .. uInfo.name .. '\' not found... Stop')
Builder.DisplayLog()
return false
end
upgPos = Builder.UpgStep(upgPos, k2, #sInfo.upgrades)
end
shipPos = Builder.ShipStep(shipPos)
end
-- If there were no choices created, advance spawn state
if Builder.AllResolved() == true then Builder.AdvanceState(Builder.states.CoreSpawned) end
end
-- Add numbers to ship models and pilot cards names if there are multiple of same name
-- Names in tables remain unchanged
Builder.MakeNamesUnique = function()
for pName,iTable in pairs(Builder.pilotCount) do
if #iTable > 1 then
for nr,index in pairs(iTable) do
local newName = Builder.pilots[index].name .. ' ' .. nr
Builder.pilots[index].sRef.setName(newName)
Builder.pilots[index].pRef.setName(newName)
end
end
end
end
-- Table with builder names corrections
-- VERSION PRINTED ON CARD IS ALWAYS THE CORRECT ONE
Builder.Errata = {}
Builder.Errata['IG88-A'] = 'IG-88A'
Builder.Errata['IG88-B'] = 'IG-88B'
Builder.Errata['IG88-C'] = 'IG-88C'
Builder.Errata['IG88-D'] = 'IG-88D'
Builder.Errata['Fire Control System'] = 'Fire-Control System'
Builder.Errata['Burnout Slam'] = 'Burnout SLAM'
Builder.Errata['StarViper Mk. II'] = 'StarViper Mk.II'
Builder.Errata['Starviper Mk.II'] = 'StarViper Mk.II'
Builder.Errata['Countermeasures'] = 'Counter-Measures'
Builder.Errata['Slave-1'] = 'Slave I'
-- Check if a name should be corrected
-- Return correct version (same if no correction entry)
Builder.ErrataPass = function(name)
local corr = Builder.Errata[name]
if corr ~= nil and type(corr) == 'string' then
return corr
end
return name
end
-- Fil ship types table
-- This needs to be done once they are spawned (also all choices resolved)
-- since we need physical models with mesh attribute there
-- DEPENDENCY ON THE MAIN TABLE DATABASE!!!
Builder.FillTypes = function()
for k,pTable in pairs(Builder.pilots) do
local shipType = Global.call('API_ModelDB_GetData', {pTable.sRef}).type
if not shipType then