-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathGlobal.-1.lua
More file actions
1791 lines (1649 loc) · 71.4 KB
/
Global.-1.lua
File metadata and controls
1791 lines (1649 loc) · 71.4 KB
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
--[[ Lua code. See documentation: https://api.tabletopsimulator.com/ --]]
--TableTop Simulator UNO Scripted
--Steam Workshop ID : NA
--Last UpdatedB By: ITzMeek
--Date Last Updated: 5-18-2021
--TTS Version Created On: v13.1.1
local debug_mode = false
--[[ Zone References --]]
local PlayZoneTrigger --Scripting Zone Object that defines where the play card pile is
local DrawZoneTrigger --Scripting Zone Object that defines where the draw card pile is
--[[Static Object GUIDs --]]
local PlayZoneMattGUID = 'f82f1f' --GUID of the 'Play Zone Matt' game object
local DrawZoneMattGUID = '2c2e1c' --GUID of the 'Draw Zone Matt' game object
local DrawDeckGUID = nil --GUID of the Draw card deck
local PlayDeckGUID = nil --GUID of the Play card deck
--[[ Static Objects --]]
local PlayZoneMattObject = nil --Object reference of the Play Zone Matt game object
local DrawZoneMattObject = nil --Object reference of the Draw Zone Matt game object
local DrawDeckObject = nil --Object reference of the Draw Card Deck game object
local PlayDeckObject = nil --Object reference to the Play Card Deck game object
local PlayDeckGUID = nil --Object reference of the Play Card Deck game object
local CurrentPlayerToken = nil --Object reference of the current player token game object
--[[String Variables That Are Used For Different Messages In The UI]]--
Waiting_For_Game_Start_Message="Waiting For The Host (%s) To Start The Game"
Waiting_For_Stacking_Message="Waiting For %s To Stack A Card, Or Draw Cards"
Waiting_For_Wild_Card_Message="Waiting For %s To Pick A Color"
Waiting_For_Trade_Message="Waiting For %s To Trade Their Cards With Another Player"
--Static references of Vector3 Locations to use for the CurrentPlayer token
SEATLOCATIONS = {
["GREEN"] = { 0, 1, 10},
["BLUE"] = { 7, 1, 7},
["PURPLE"] = { 10, 1, 0},
["PINK"] = { 7, 1, -7},
["WHITE"] = { 0, 1, -10},
["RED"] = {-7, 1, -7},
["ORANGE"] = {-10, 1, 0},
["YELLOW"] = {-7, 1, 7}}
--Static reference of Vector3 Locations to use for CPU tokens
CPULOCATIONS = {
["GREEN"] = { 0, 1, 13},
["BLUE"] = { 10, 1, 10},
["PURPLE"] = { 13, 1, 0},
["PINK"] = { 10, 1, -10},
["WHITE"] = { 0, 1, -13},
["RED"] = {-10, 1, -10},
["ORANGE"] = {-13, 1, 0},
["YELLOW"] = {-10, 1, 10}}
--[[Static reference of transform locations for the Current Player label to move aruond the table
SEATLOCATIONS = {
["GREEN"] = { 0, 1.5, 9},
["BLUE"] = { 6.36, 1.5, 6.36},
["PURPLE"] = { 9, 1.5, 0},
["PINK"] = { 6.36, 1.5,-6.36},
["WHITE"] = { 0, 1.5, -9},
["RED"] = {-6.36, 1.5,-6.36},
["ORANGE"] = { -9, 1.5, 0},
["YELLOW"] = {-6.36, 1.5, 6.36}}]]
--Static reference of rotations to keep the player label token facing outward for each given color
SEATROTATIONS = {
["GREEN"] = { 0, 360, 0},
["BLUE"] = { 0, 45, 0},
["PURPLE"] = { 0, 90, 0},
["PINK"] = { 0, 135, 0},
["WHITE"] = { 0, 180, 0},
["RED"] = { 0, 225, 0},
["ORANGE"] = { 0, 270, 0},
["YELLOW"] = { 0, 315, 0}}
--Quick reference table for the static Player.<color> objects
PLAYERS_REF = {
Player.Green,
Player.Blue,
Player.Purple,
Player.Pink,
Player.White,
Player.Red,
Player.Orange,
Player.Yellow}
--Quick table reference to give a Player object by color name
local COLORTOPLAYER = {
["Green"] = Player.Green,
["Blue"] = Player.Blue,
["Purple"] = Player.Purple,
["Pink"] = Player.Pink,
["White"] = Player.White,
["Red"] = Player.Red,
["Orange"] = Player.Orange,
["Yellow"] = Player.Yellow}
--List of Computer Controlled Players
local COMPUTERPLAYERS = {}
--Table reference used for CPU picking a wild card color
local WILDCOLORS = {
"WildButtonRed",
"WildButtonBlue",
"WildButtonGreen",
"WildButtonYellow"}
-- 'ENUM' table for turn states. Used to dictate the game's main state machine loop
local TURN_STATE = {
["Play"] = "Play A Card", --Default State, the player can play a card as normal
["Respond"] = "Resopnd To The Last Card", --The current player must choose something in response to the last card played
["Decide"] = "Make A Decision", --The current player must choose something in response to a card that they have played
["End"] = "End Their Turn"} --The current player's turn is over
-- 'ENUM' table for decision states. Used to track certain game states
local DESCISION_STATE ={
["Wild"] = "Choose A Color",
["SevenZero"] = "Choose Who To Trade With"}
--[[State Machine trackers]]
local PlayerTurnState = nil; --Tracks the game's current Game State
local DescisionState = nil --Tracks the game's current Descision State (if there is one)
local clockwise = true --Determines the direction of turn rotation
local stacking = false --Tracks if the game state is in Draw Card Stacking
local cardsToDraw = 0 --Tracks the amount of cards that a player will be dealt at the end of their turn
local cardDrawn = false --Tracks if a player has draw a card from the Draw Pile this turn
--[[List of players currently setaed (including CPU players)]]
local CurrentPlayerList = {}
local lastCard = { --Table tracker for details of the last card that was played
["GUID"] = nil,
["Name"] = nil,
["Description"] = nil}
local HouseRules = { --Table of references to the current set of game rules
["Multi_Draw"] = false,
["Pass_Turn"] = false,
["Stack_Plus4"] = false,
["Stack_Plus2"] = false,
["Stack_All"] = false,
["Call_Uno"] = true,
["Seven_Zero"] = false}
local playerOneIndex = 0 --Index in the currentPlayerList of who player one is
local currentPlayer = nil --Tracks who the current player is
local currentPlayerIndex = nil --Tracks the index of the current player from 'CurrentPlayerList'
local unoPlayer = nil --Tracker for any player that has UNO!
local cardPlayed = nil --Tracks if a card has been played this turn
local StartingHandAmount = 7 --Amount of cards that players start with at the beginning of the game
local decksFlipped = false --Tracker for if the play and draw decks have been re-combined
local winCondition = false --Tracker for player reaching a win condition : having 0 cards in their hand
--[[===================EVENT RELATED FUNCITONS======================]]
--[[The onLoad event is called after the game save finishes loading.]]
function onLoad()
InitGame()
end
--[[The onUpdate event is called once per frame.]]
function onUpdate()
end
--[[Called when a player changes color or selects it for the first time. It also returns "Grey" if they disconnect.]]
function onPlayerChangeColor(player_color)
UpdateCurrentPlayers()
--[[Iterate through all hands at the table to look for cards owned by the same player, and move them to their new color hand]]
if player_color ~= 'Grey'
then
UpdatePlayerOne(nil,0,nil)
debug('New Player Seated ')
--Clear out any cards that exist in the new color's hand
ClearPlayerHand(COLORTOPLAYER[player_color])
for i=1, #PLAYERS_REF--iterate through all players
do
if #PLAYERS_REF[i].getHandObjects() > 0 --if there are object in this player's hand zone
then
for j=1,#PLAYERS_REF[i].getHandObjects()--iterate through the objects in this hand zone
do
local temp = PLAYERS_REF[i].getHandObjects()[j]
if temp.getVar('Owner') == Player[player_color].steam_name --if the 'Owner' variable is the same as the player that has changed their color, move the cards to that player
then
MoveCardToPlayer(temp,Player[player_color])
end
end
end
end
end
end
function onPlayerDisconnect(player_id)
--collect cards from the disconnected player's hand and return them to the deck
debug(player_id)
UpdateCurrentPlayers()
ClearPlayerHand(player_id)
end
--[[This function is primarily used to make sure players are not throwing cards out of their hands]]
function onObjectDrop(player_color,dropped_object)
local cardDropped = true
--First, we only care if the object a player is dropping is a card
if dropped_object.tag == 'Card'
then
--We check the "Owner" of the dropped card, and compare it to the Player that dropped it
if dropped_object.getVar("Owner") == Player[player_color].steam_name
then
--Using Wait.time() and an in-line function, we can execute the following code after a certain amount of time
Wait.time(
function ()
--First we get a reference to if the card being dropped is being played
local inPlay = dropped_object.getVar("Card_Played")
--We also get a reference to the player color for simplicity
local _player = Player[player_color]
--We check if the card being dropped is NOT being played
if inPlay == false
then
--What we do here is loop through all of the objects that are now in the players hands, to see if the card being dropped
--is being dropped into their hand, in which case no further action is needed
for i = 1 , #Player[player_color].getHandObjects(), 1
do--Loop through all the objects the player has picked up
if Player[player_color].getHandObjects()[i] == dropped_object
then
cardDropped = false
end
end
--At this point, if the card being dropped is NOT being played, and is NOT being dropped into their hand, we will assume
--The card is being dropped out in the open, and we should return the card to their hand
if cardDropped == true
then
dropped_object.setPosition({_player.getPlayerHand()['pos_x'],_player.getPlayerHand()['pos_y'],_player.getPlayerHand()['pos_z']})
dropped_object.setRotation({_player.getPlayerHand()['rot_x'],_player.getPlayerHand()['rot_y']+180,_player.getPlayerHand()['rot_z']})
broadcastToColor("Please only leave your cards in the play zone", player_color, getColorValueFromPlayer(player_color))
end
end
end,--End In-Line Function
0.2)--This function will execute 0.2 seconds after a card is dropped
end
end
end--Function onObjectDrop END
--[[This is mostly used to stop players from trying to play more than one card at a time]]
function onObjectPickUp(player_color, picked_up_object)
--If the card being picked up does not belong to the player picking it up, or if the player is NOT the Game Master (Black seat) we need to force them to drop it
if picked_up_object.getVar("Owner") ~= Player[player_color].steam_name and player_color ~= "Black" and debug_mode == false
then
picked_up_object.drop()
broadcastToColor("Do Not Take Other Players Cards", player_color, getColorValueFromPlayer(player_color))
end
end--Function onObjectPickUp END
--[[the onObjectEnterScriptingZone event is called when a game object enters a scripting zone]]
function onObjectEnterScriptingZone(zone, enter_object)
if zone == PlayZoneTrigger
then
if enter_object.tag == 'Card'
then
if enter_object.held_by_color ~=nil
then
if CheckPlayedCard(enter_object)
then--The card being played is allowed
PlayCard(enter_object)
else--The card being played is not allowed
RejectCard(enter_object,"You Cannot Play A Card")
end
end
end
end
end
--[[===================END EVENT RELATED FUNCITONS======================]]
--[[===================GAMEPLAY RELATED FUNCITONS======================]]
--[[Function sets all necessary variables to set up the game before starting a round of UNO]]
function InitGame()
--Hide other UI elements
UI.setAttribute('PassTurnButton', 'active', 'false')
UI.setAttribute('UNOButton', 'active', 'false')
--Get reference of the PlayZone and DrawZone matt objects
PlayZoneMattObject = getObjectFromGUID(PlayZoneMattGUID)
DrawZoneMattObject = getObjectFromGUID(DrawZoneMattGUID)
DrawZoneMattObject.UI.hide("DrawButton1")
DrawZoneMattObject.UI.hide("DrawButton2")
--Set a few local variables to be used to create our scripting zones
local TriggerHeight = 20
local MattPos = PlayZoneMattObject.getPosition()
local MattScale = PlayZoneMattObject.getBounds().size
--Spawn a ScriptingZone over the play zone
PlayZoneTrigger = spawnObject({
type = 'ScriptingTrigger',
position = {MattPos.x,TriggerHeight/2,MattPos.z},
scale = {MattScale.x,TriggerHeight,MattScale.z}
})
--Spawn a ScriptingZone over the draw zone
MattPos = DrawZoneMattObject.getPosition()
MattScale = DrawZoneMattObject.getBounds().size
DrawZoneTrigger = spawnObject({
type = 'ScriptingTrigger',
position = {MattPos.x,TriggerHeight/2,MattPos.z},
scale = {MattScale.x,TriggerHeight,MattScale.z},
--Create a callback function that will grab a reference to our Draw Card deck object once the scritpting zone has been created
callback_function = function(obj)
local DrawZoneObjects = DrawZoneTrigger.getObjects()
for i=1,#DrawZoneObjects do
if DrawZoneObjects[i].getGUID() ~= DrawZoneMattGUID
then
DrawDeckObjectGUID = DrawZoneObjects[i].getGUID()
DrawDeckObject = getObjectFromGUID(DrawDeckObjectGUID)
end
end
end
})
Wait.frames(function() UpdateDeckObjects() end, 5)
--Update the scripts reference to the currently seated players
UpdateCurrentPlayers()
UpdatePlayerOne(nil,0,nil)
end
--[[Function is called when the game is ready to start, and handles all of the necessary legwork to start a round of UNO]]
function GameStart()
--enable the draw draw card buttons
DrawZoneMattObject.UI.show("DrawButton1")
DrawZoneMattObject.UI.show("DrawButton2")
--Hide the main menu
UI.setAttribute("MainMenuContainer", "active", "false")
--Mark any computer players that are turned on
MarkComputerPlayers()
--Give each player their starting hand
for i=1, #CurrentPlayerList do
GiveCardsToPlayer(StartingHandAmount, CurrentPlayerList[i])
end
--set currentPlayer to first player in Player List
-- //TODO : Implement ability to determine player 1
currentPlayerIndex = playerOneIndex
currentPlayer = CurrentPlayerList[playerOneIndex]
--Set our PlayerTurnState to 'Play'
PlayerTurnState = TURN_STATE.Play
--Shuffle the draw deck pile
DrawDeckObject.shuffle()
--Enter the game's state machine loop
PlayerTurnLoop()
end
--This function gives me nightmares
function Reset_Game()
debug("Game Ending!")
broadcastToAll(("%s has won this round!"):format(currentPlayer.steam_name), getColorValueFromPlayer(currentPlayer.color))
--First we hide and reset any UI elements that are used during gameplay
UI.hide("StackingCardPanel")
UI.hide("WildCardPanel")
UI.hide("PassButtonPanel")
UI.hide("UNO_Button")
UI.setAttribute("PlayerLabel", "active", "false")
UI.setAttribute("playerOneButton", "Text", currentPlayer.steam_name)
UI.setAttribute("playerOneButton", "Color", currentPlayer.color)
UpdateCurrentPlayers()--Update our reference to the seated players
HideDrawButtons()--Hide our draw card buttons
Wait.time(
function()
ClearHands(false)--clear out the cards out of players' hands, ignoring if a player is seated there
end,
1)
--Reset our UNO tracking variables
unoPlayer = nil
ToggleUnoButton(false)
--This function will reset the play and draw decks after 2 seconds
Wait.time(
function ()
UpdateDeckObjects()
PlayDeckObject.interactable = true
PlayDeckObject.setRotation({180,180,0})
PlayDeckObject.setPosition({DrawZoneTrigger.getPosition()['x'] , 3 , DrawZoneTrigger.getPosition()['z']})
--playDeckObject = nil
Wait.time(function () DrawDeckObject.shuffle() end, 1)
end,
2)
--This function will reset any key variables after 3 seconds
Wait.time(
function ()
winCondition = false
playerOne_Index = currentPlayer_Index
lastCard.GUID = nil
lastCard.Name = nil
lastCard.Description = nil
stacking = false
end,
3)
--This function will show the Show the Start Game UI elements after 4 seconds
Wait.time(
function ()
UI.setAttribute("MainMenuContainer", "active", "true")
UI.setAttribute("HideMenuButton", "visibility", "Host")
UI.setAttribute("StartGameMenu", "visibility", "Host")
end,
4)
--Reset the color tint of our Play Mat and Rotation Mat
PlayZoneMattObject.setColorTint({1,1,1})
end--Function Reset_Game END
--[[Main gameplay loop / state machine. Controls the flow of logic based on conditions of the game]]
function PlayerTurnLoop()
debug('Enter Turn Loop: '..PlayerTurnState)
UpdateDeckObjects()
--Turn State Machine
--=========================================================================================================================
if PlayerTurnState == TURN_STATE.Decide--For now, the only cards that trigger the 'Decide' turn state is a wild card - so we can assume that's the state the game is in
then
HideDrawButtons() --Hide the draw card buttons
if DescisionState == DESCISION_STATE.Wild--If the player is deciding what color to make a wild card
then
HideDrawButtons()
if not isComputerPlayer(currentPlayer)
then
UI.setAttribute("StackingCardPanel", "active", "false")
--Show the UI panel to pick a wild card color
UI.setAttribute("WildCardPanel", "active", "true")
UI.setAttribute("WildCardPanel", "visibility", currentPlayer.color)
else
--For now, CPU players will choose a wild card color at random.
-- //TODO : Allow CPU players to make an informed wild card color decision based on the cards they are holding
WildPanelButtons(nil,nil,WILDCOLORS[math.random(4)])
end
end
elseif PlayerTurnState == TURN_STATE.Respond--For now, the only event that triggers the 'Respond' turn state is a draw card when the Stacking rule is enabled - so we can assume that's the state the game is in at this point
then
HideDrawButtons()
UpdateCurrentPlayerToken() --Update the Current Player Token
if not isComputerPlayer(currentPlayer)
then
--Show the Stacking Card UI Panel to the appropriate player
UI.setAttribute("StackingCardPanel", "active", "true")
UI.show("StackingCardPanel")
UI.setAttribute("StackingCardPanel", "visibility", currentPlayer.color)
UI.setAttribute("StackingCardPanelText02", "Text", cardsToDraw)
--Set the Stacking Panel UI Message depending on the type of draw card stacking that is allowed
local temp = ""
if HouseRules.Stack_Plus4 == true and HouseRules.Stack_Plus2 == true and HouseRules.Stack_All == false
then
if lastCard.Name == "+2"
then
temp = "Stack On Another +2"
elseif lastCard.Name == "+4"
then
temp = "Stack On Another +4"
end
elseif HouseRules.Stack_Plus4 == true and HouseRules.Stack_Plus2 == false and HouseRules.Stack_All == false
then
temp = "Stack On Another +4"
elseif HouseRules.Stack_Plus2 == false and HouseRules.Stack_Plus2 == true and HouseRules.Stack_All == false
then
temp = "Stack On Another +2"
elseif HouseRules.Stack_All == true
then
temp = "Stack On +2 or a +4"
else
temp = "Stack On Antoher Draw Card"
end
UI.setAttribute("StackingCardPanelText01", "Text", temp)
else
UI.setAttribute("StackingCardPanel", "active", "false")
Wait.time(function()
DoComputerPlayerTurn(currentPlayer,false)
end,
2)
end
--=========================================================================================================================
elseif PlayerTurnState == TURN_STATE.Play
then
if lastCard.Description ~= nil --Update the Play Zone matt color tint to match that of the last card played
then
PlayZoneMattObject.setColorTint(getColorValueFromCard(lastCard.Description))
end
ShowDrawButtons() --Show the 'Draw Card' buttons
UpdateCurrentPlayerToken() --Update the Current Player Token
if isComputerPlayer(currentPlayer) --If the current player is CPU controlled, do CPU turn
then
debug('Current Player is a CPU')
Wait.time(function()
DoComputerPlayerTurn(currentPlayer,false)
end,
2)
else
debug('Current Player is Human')
end
--=========================================================================================================================
elseif PlayerTurnState == TURN_STATE.End
then
HideDrawButtons() --Hide the draw card buttons
--Update the play deck's color
if lastCard.guid ~= nil
then
PlayZoneMattObject.setColorTint(getColorValueFromCard(lastCard.Description))
end
if not stacking --If the current player is NOT stacking on another draw card / normal flow of play
then
--Deal total number of cards to the player
GiveCardsToPlayer(cardsToDraw,currentPlayer)
--During normal flow of play, when the stacking rule is not enabled - stacking will always be false, but the amount of cards to draw will be 0 unless from a Draw Card being played
else
debug("Stacking Amount:"..cardsToDraw)
end
--UNO check for non computer Players
if not isComputerPlayer(currentPlayer)
then
if #currentPlayer.getHandObjects() == 1 and cardPlayed == true--If the current player only has 1 card left, set the unoPlayer tracker to them
then
debug(currentPlayer.color .. ' has UNO!')
unoPlayer = currentPlayer
broadcastToAll( ("%s has UNO!"):format(unoPlayer.steam_name) , getColorValueFromPlayer(unoPlayer.color) )
ToggleUnoButton(true)
elseif #currentPlayer.getHandObjects() == 0--If the current player has 0 cards left
then
debug('Current Player is out of cards')
winCondition = true
else--else if the current player has not reach an UNO or WIN condition, clear the unoPlayer tracker
if cardPlayed == true or cardDrawn == true--Check that a card was drawn or played this turn.
then
unoPlayer = nil
end
ToggleUnoButton(false)
end
end
debug('Ending Player\'s Turn')
Wait.time(function() UpdateDeckObjects() end, 2.0)
EndPlayerTurn()
end
end
--[[Function called by PlayZoneTrigger: checks if the object entering the zone is a card is a card that is allowed to be played]]
function CheckPlayedCard(card_played)
if card_played.held_by_color == currentPlayer.color or card_played.getVar("CopmuterPlayerCard") == true --Check that the card being played is held by the current player
then
--If the card being played is held by a computer player, we allow it - assuming that the logic controlling computer players is working correctly
if lastCard.GUID == nil--Assume that this is the first card being played, if lastCard Object is nil
then
return true--The card is allowed to be played
else
if stacking -- If the game is checking for stacking cards
then
if card_played.getName() == "+2" -- check if the card being played is a +2
then
if lastCard.Name == "+2" or HouseRules.Stack_All -- check that the last card played was also a +2, or the ALL stacking rule is enabled
then
return true -- return true : the card being played is allowed
end
elseif card_played.getName() == "+4" -- check if the card being played is a +4
then
if lastCard.Name == "+4" or HouseRules.Stack_All -- check that the last card played was also a +4, or the ALL stacking rule is enabled
then
return true -- return true : the card being played is allowed
end
else
return false -- return false : the game is waiting for a stacking card, but an eligible card is not being played
end
else -- If the game is NOT checking for a stacking card
if card_played.getDescription() == lastCard.Description --Card being played does matches face / color / wild
then
return true --The card is allowed to be played
elseif card_played.getName() == lastCard.Name --Card being played does matches face / color / wild
then
return true--The card is allowed to be played
elseif card_played.getDescription() == 'WILD' --Card being played does matches face / color / wild
then
return true--The card is allowed to be played
else--Card being played does NOT match face / color / wild
return false--The card is NOT allowed to be played
end
end
end
end
end
--[[Function accepts card from player and adds it to the Draw Deck]]
function PlayCard(card)
--update cardPlayed tracker
cardPlayed = true
--Hide the Turn passing button once a card has been played
UI.setAttribute('PassTurnButton', 'active', 'false')
--Update reference to lastCard
lastCard.GUID = card.getGUID()
lastCard.Description = card.getDescription()
lastCard.Name = card.getName()
--Update individual card variables
card.setVar('Owner', nil)
card.setVar('Card_Played', true)
--Add card to the play deck
if(card.getVar("CopmuterPlayerCard") == true)
then
card.setRotation({0,180,0}, false, true)
local newPos = PlayZoneTrigger.getPosition()
card.setPosition({newPos.x,3,newPos.z}, false, true)
else
card.setRotationSmooth({0,180,0}, false, true)
local newPos = PlayZoneTrigger.getPosition()
card.setPositionSmooth({newPos.x,3,newPos.z}, false, true)
end
--By default we will set the turn state to 'End'
PlayerTurnState = TURN_STATE.End
--But there are some exceptions that will change the turn state
if lastCard.Description == "WILD" -- if the card played is a wild card
then
--chand the turn state to decidce : the current player will have to decide what color to call
PlayerTurnState = TURN_STATE.Decide
DescisionState = DESCISION_STATE.Wild
end
--If the card played was a reverse, change the direction of play
if lastCard.Name == "reverse"
then
clockwise = not clockwise
end
if lastCard.Name == "7" and HouseRules.Seven_Zero then
--change cards with one person
UI.setAttribute("SwitchPanel", "visibility", currentPlayer.color)
UI.setAttribute("SwitchPanel", "active", "true")
--activate the necessary buttons
for i=1, #CurrentPlayerList do
if CurrentPlayerList[i].color~=currentPlayer.color then
colorName=CurrentPlayerList[i].color
UI.setAttribute(colorName.."SwitchCell", "visibility", currentPlayer.color)
UI.setAttribute(colorName.."SwitchCell", "active", "true")
UI.setAttribute(colorName.."Switch", "visibility",currentPlayer.color)
UI.setAttribute(colorName.."Switch", "active", "true")
UI.setAttribute(colorName.."Switch", "Text", CurrentPlayerList[i].steam_name)
end
end
--prevent PlayerTurnLoop(), is later triggered by SwitchPanel()
return
end
if lastCard.Name == "0" and HouseRules.Seven_Zero then
local move = {}
--calculate card positions in current order
if clockwise then
for i = 1, #CurrentPlayerList do
cards = CurrentPlayerList[i].getHandObjects()
local nextPlayer
if i == #CurrentPlayerList then
nextPlayer = CurrentPlayerList[1]
else
nextPlayer = CurrentPlayerList[i+1]
end
for k, v in pairs(cards) do
move[v.getGUID()] = nextPlayer
end
end
else
for i = 1, #CurrentPlayerList do
cards = CurrentPlayerList[i].getHandObjects()
local nextPlayer
if i == 1 then
nextPlayer = CurrentPlayerList[#CurrentPlayerList]
else
nextPlayer = CurrentPlayerList[i-1]
end
for k, v in pairs(cards) do
move[v.getGUID()] = nextPlayer
end
end
end
--execute precomputed movement for all cards
for guid, player in pairs(move) do
card = getObjectFromGUID(guid)
card.setPosition(player.getHandTransform().position)
card.setRotationSmooth(player.getHandTransform().rotation, false, true)
card.setVar('Owner', player.steam_name)
end
end
--Return to the turn loop
PlayerTurnLoop()
end
function SwitchPanel(a,b,ID)
color = string.sub(ID, 0, string.find(ID, "Switch")-1)
if color~="Abort" then
chosenPlayer = COLORTOPLAYER[color]
local move = {}
cards = currentPlayer.getHandObjects()
for k, v in pairs(cards) do
move[v.getGUID()] = chosenPlayer
end
cards = chosenPlayer.getHandObjects()
for k, v in pairs(cards) do
move[v.getGUID()] = currentPlayer
end
for guid, player in pairs(move) do
card = getObjectFromGUID(guid)
card.setPosition(player.getHandTransform().position)
card.setRotationSmooth(player.getHandTransform().rotation, false, true)
card.setVar('Owner', player.steam_name)
end
end
--hide UI
UI.setAttribute("SwitchPanel", "visibility", "")
UI.setAttribute("SwitchPanel", "active", "false")
for i=1, #CurrentPlayerList do
colorName=CurrentPlayerList[i].color
UI.setAttribute(colorName.."SwitchCell", "visibility", "")
UI.setAttribute(colorName.."SwitchCell", "active", "false")
UI.setAttribute(colorName.."Switch", "visibility","")
UI.setAttribute(colorName.."Switch", "active", "false")
end
--Return to the turn loop
PlayerTurnLoop()
end
--[[Function rejects card from player and sends it back to their hand]]
function RejectCard(card,message)
--Grab a function local reference to the player holding the card
local _player = Player[card.held_by_color]
--return the card back to the player's hand zone
card.setPositionSmooth(_player.getHandTransform().position, false, true)
--send a message to the player that the card cannot be played
broadcastToColor(message,_player.color,getColorValueFromPlayer(_player.color))
end
--[[Called byu the 'Draw Card' buttons attatched to the drawing deck matt. Deals a card to the player - abiding to related house rules]]
function DrawCardButton(_player)
if _player.color == currentPlayer.color --Check that the player clicking the button is the current player
then
if not HouseRules.Multi_Draw and not cardDrawn --If multiple cards are NOT allowed to be drawn per turn, and a card has not yet been drawn this turn
then
--set card drawn variable to true
cardDrawn = true
--hide the draw card buttons
HideDrawButtons()
--give a single card to the player
GiveCardsToPlayer(1, _player)
Wait.frames(--wait for 5 frames before running this next bit of logic
function()
if not checkForPlayableCard(_player)--If the player does NOT have a card that is able to be played
then
debug("Player has drawn their card, and has no playable cards")
--Set the turn state to end
PlayerTurnState = TURN_STATE.End
--return to the turn loop
PlayerTurnLoop()
else--If the player DOES have a card that is able to be played
if HouseRules.Pass_Turn and UI.getAttribute('PassTurnButton', 'active') == 'false' --If the player does have a card that can be played, but the 'Turn Passing' house rule is enabled, show the pass turn button
then
--active the pass turn button, and set it's visibility to the current player
UI.setAttribute('PassTurnButton', 'active', 'true')
UI.setAttribute('PassTurnButton', 'visibility', currentPlayer.color)
end
end
end
, 5)
elseif HouseRules.Multi_Draw -- else, if multiple cards are allowed to be drawn per turn
then
-- set card drawn to true
cardDrawn = true
-- Give a single card to the player
GiveCardsToPlayer(1, _player)
if HouseRules.Pass_Turn and UI.getAttribute('PassTurnButton', 'active') == 'false' --If the player does have a card that can be played, but the 'Turn Passing' house rule is enabled, show the pass turn button
then
Wait.frames(--wait for 5 frames before running this next bit of logic
function()
--active the pass turn button, and set it's visibility to the current player
UI.setAttribute('PassTurnButton', 'active', 'true')
UI.setAttribute('PassTurnButton', 'visibility', currentPlayer.color)
end
, 5)
end
end
else
broadcastToColor("You can only draw cards on your turn", _player.color,getColorValueFromPlayer(_player.color))
end
end
--[[Our own function to deal cards to a player, so that we can perform any special actions on cards we need to]]
function GiveCardsToPlayer(NumberOfCards,PlayerToDeal)
debug('Dealing '.. NumberOfCards ..' to '.. PlayerToDeal.color)
for i=1, NumberOfCards do--loop for the given number of cards to deal
local cardDealt--create a local object reference of the top card in the draw deck
cardDealt = DrawDeckObject.takeObject({
position = PlayerToDeal.getHandTransform().position,
rotation = {PlayerToDeal.getHandTransform().rotation.x,PlayerToDeal.getHandTransform().rotation.y+180,0},
index = 1,
smooth = false})
cardDealt.setVar('Card_Played', false)--reset 'Card_Played' varianble
if not isComputerPlayer(PlayerToDeal)
then
--if the player being dealt to is NOT a CPU - set a 'Owner' variable to the player's steam name
cardDealt.setVar('Owner', PlayerToDeal.steam_name)
end
end
--reset the number of cards to be drawn
cardsToDraw = 0
end
--[[Function to move a given card object to a player[object] hand]]
function MoveCardToPlayer(card,receiving_player)
card.setPosition(receiving_player.getHandTransform().position)
card.setRotation({receiving_player.getHandTransform().rotation.x,receiving_player.getHandTransform().rotation.y+180,0})
end
--[[Clear hands of cards and return them to the draw deck. Conditional to only clear empty seats]]
function ClearHands(only_empty)
for i=1,#PLAYERS_REF -- loop through all player references
do
if PLAYERS_REF[i].seated or isComputerPlayer(PLAYERS_REF[i]) -- if the current player in the loop is a seated player or CPU controlled
then
if only_empty == false --check if we are only clearing hand zones that are not being controlled ('only_empty')
then
ClearPlayerHand(PLAYERS_REF[i])
end
else--else clear the heand zone regaurdless of if it is player/CPU controlled or not
ClearPlayerHand(PLAYERS_REF[i])
end
end
end
--[[Clear the hand for a specific player and returning them to the draw deck]]
function ClearPlayerHand(player_to_clear)
--This color does not have a player or CPU
if #player_to_clear.getHandObjects() > 0
then
--create easy references to the position and rotation of the draw deck pile for use later
deckPosition = { DrawDeckObject.getPosition()['x'] , DrawDeckObject.getPosition()['y'] + 0.5 , DrawDeckObject.getPosition()['z'] }
deckRotation = DrawDeckObject.getRotation()
for i, card in ipairs(player_to_clear.getHandObjects()) --loop through all objects in the hand zone
do
--Reset individual card variables
card.setVar('Owner', nil)
card.setVar('Card_Played', false)
--move the card back to the draw deck pile
card.setRotation(deckRotation)
card.setPosition(deckPosition)
end
--shuffle the draw deck afterwards
DrawDeckObject.shuffle()
end
end
--[[Handles any game logic that needs to occur at the end of a players turn, and movers the current player to the next player]]
function EndPlayerTurn()
if winCondition == false
then
if unoPlayer == nil --If there is not a player with uno, make sure to hide the call UNO buttons
then
ToggleUnoButton(false)
end
--Determine the next player in turn
DetermineNextPlayer()
--reset the state machine to default - 'play'
PlayerTurnState = TURN_STATE.Play
if cardPlayed == true
then--if a card was played this round, check for special cases that would effect the player turn state machine
if lastCard.Name == "+2"
then
if HouseRules.Stack_Plus2 or HouseRules.Stack_ALL
then--If stacking rules apply, the turn state will be 'Respond'
PlayerTurnState = TURN_STATE.Respond
stacking = true
else -- otherwise, the player's turn will end
PlayerTurnState = TURN_STATE.End
end
--Add 2 to the number of cards to be drawn
cardsToDraw = cardsToDraw + 2
elseif lastCard.Name == "+4"
then
if HouseRules.Stack_Plus4 or HouseRules.Stack_ALL
then--If stacking rules apply, the turn state will be 'Respond'
PlayerTurnState = TURN_STATE.Respond
stacking = true
else--otherwise the player's turn will end
PlayerTurnState = TURN_STATE.End
end
--Add 4 to the number of cards to be drawn
cardsToDraw = cardsToDraw + 4
elseif lastCard.Name == "skip"
then--check if the last card played was a skip card
debug('Last Card skip. Ending next players turn')
PlayerTurnState = TURN_STATE.End
elseif lastCard.Name == "reverse"
then
if #CurrentPlayerList == 2
then
PlayerTurnState = TURN_STATE.End
end
end
end
cardPlayed = false --reset the cardPlayed variable
cardDrawn = false --reset the cardDrawn variable
debug('\n')
--Return to the game state loop
PlayerTurnLoop()
else
Reset_Game()
end
end
--[[Updates the currentPlayer & currentPlayerIndex variables to the next player in the turn order]]
function DetermineNextPlayer()
--Change currentPlayerIndex to the next player, based on the rotation of play
if clockwise == true
then
--If statements for index wrap-around conditions
if currentPlayerIndex + 1 > #CurrentPlayerList
then
currentPlayerIndex = 1
else
currentPlayerIndex = currentPlayerIndex + 1
end
else
--If statements for index wrap-around conditions
if currentPlayerIndex - 1 < 1
then
currentPlayerIndex = #CurrentPlayerList
else
currentPlayerIndex = currentPlayerIndex - 1
end
end
--set the currentPlayer to the new currentPlayerIndex
currentPlayer = CurrentPlayerList[currentPlayerIndex]
debug('Current Player is now ' .. currentPlayer.color)
end
--[[Grab players that are currently seated and populate 'CurrentPlayerList']]
function UpdateCurrentPlayers()
local counter = 1
--Empty our reference tables
CurrentPlayerList = {}
for i = 1, #PLAYERS_REF
do--Go through our static PLAYERS_REF table
local _player = PLAYERS_REF[i]
if _player.admin
then--Check if the player is the host or a promoted player
playerOneIndex = counter --set the default Player One to the game host
end
if _player.seated
then
CurrentPlayerList[counter] = _player--If a given player is seated add that player to our reference table
counter = counter + 1
else
if isComputerPlayer(_player)--if the given player is CPU controlled, we also add it to our reference table
then
debug(_player.color .. ' is a CPU')
CurrentPlayerList[counter] = _player
counter = counter + 1
end
end
end
if currentPlayer ~= nil
then
if #CurrentPlayerList > 0
then
if currentPlayer.seated == false and not isComputerPlayer(currentPlayer)
then
debug('Current Player is not seated!')
EndPlayerTurn()