diff --git a/Output/PartyModes/RuleRoulette.xml b/Output/PartyModes/RuleRoulette.xml
new file mode 100644
index 00000000..13491669
--- /dev/null
+++ b/Output/PartyModes/RuleRoulette.xml
@@ -0,0 +1,21 @@
+
+
+ 2
+
+ TR_PARTYMODE_NAME
+ McSchneidar
+ TR_PARTYMODE_DESCRIPTION
+ RuleRoulette
+ CPartyModeRuleRoulette
+ 1
+ 0
+ TR_PARTYMODE_AUDIENCE
+
+
+ CPartyScreenRuleRouletteMain
+ CPartyScreenRuleRouletteConfig
+ CPartyScreenRuleRouletteNames
+ CPartyScreenRuleRouletteSongs
+ CPartyScreenRuleRouletteRules
+
+
diff --git a/Output/PartyModes/RuleRoulette/Languages/de.xml b/Output/PartyModes/RuleRoulette/Languages/de.xml
new file mode 100644
index 00000000..eb544b18
--- /dev/null
+++ b/Output/PartyModes/RuleRoulette/Languages/de.xml
@@ -0,0 +1,84 @@
+
+
+ Deutsch
+ Rule Roulette
+ Singt Duelle, bei denen ein Rad die Spielregel jeder Runde bestimmt.
+ Für 2 bis 12 Spieler, die Überraschungen mögen, 2 Mikrofone nötig.
+ Weiter
+ Zurück
+ Start
+ Beenden
+ Ja
+ Nein
+ Rad drehen
+ Rad drehen
+ Duell starten
+ Songs
+ Rule Roulette: Konfiguration
+ Anzahl der Spieler
+ Songs pro Spieler
+ Joker pro Spieler
+ Regeln
+ Zufällig
+ Manuell
+ Regeln auf dem Rad
+ Rule Roulette: Regeln
+ Immer dabei
+ Zufällig
+ Nie
+ Hoch/Runter = Regel wählen · Links/Rechts oder Klick = Ändern · Enter = Weiter · ESC = Zurück
+ Rule Roulette: Spieler auswählen
+ Zufällig wählen
+ Rule Roulette
+ Willst du das Spiel wirklich beenden?
+ Pos
+ Name
+ Punkte
+ Duell
+ gegen
+ Zielzahl:
+ %s wählt den Song
+ Joker:
+ Letztes Duell:
+ Sieger
+ Kurzversion
+ Es wird die Kurzversion gesungen.
+ Medley
+ Ein Medley aus 3 Songs, ohne Joker.
+ Doppelpack
+ Zwei Songs hintereinander, die Summe zählt.
+ Duett-Pflicht
+ Ein Duett-Song, jeder singt eine Stimme.
+ Freie Songwahl
+ Ihr sucht den Song selbst aus.
+ Wahl des Schwächeren
+ Wer in der Tabelle weiter hinten liegt, wählt den Song.
+ Goldrausch
+ Nur Punkte aus goldenen Noten zählen. Ohne Joker.
+ Perfekte Zeilen
+ Nur der Zeilenbonus zählt.
+ Zielpunkte
+ Wer näher an der Zielzahl liegt, gewinnt.
+ Verkehrte Welt
+ Wer weniger Punkte hat, gewinnt.
+ Teamwork
+ Zusammen über 12.000 Punkte, dann bekommen beide 2 Punkte.
+ Doppelsieg
+ Schaffen beide über 7.000, gewinnen beide.
+ Doppelte Punkte
+ Der Sieger bekommt 4 statt 2 Punkte.
+ Blitzrunde
+ Kurzversion, und der Sieg zählt doppelt.
+ Alles oder nichts
+ Der Sieger bekommt 3 Punkte, der Verlierer verliert 1.
+ Punkteklau
+ Der Sieger nimmt dem Verlierer 1 Punkt weg.
+ Vorsprung zählt
+ Je größer der Vorsprung, desto mehr Punkte (1 bis 4).
+ Underdog
+ Gewinnt der in der Tabelle Schwächere, bekommt er 3 Punkte.
+ Joker-Regen
+ Der Sieger bekommt 1 Joker dazu.
+ Ohne Netz
+ In diesem Duell sind keine Joker erlaubt.
+
diff --git a/Output/PartyModes/RuleRoulette/Languages/en.xml b/Output/PartyModes/RuleRoulette/Languages/en.xml
new file mode 100644
index 00000000..630bf07e
--- /dev/null
+++ b/Output/PartyModes/RuleRoulette/Languages/en.xml
@@ -0,0 +1,84 @@
+
+
+ English
+ Rule Roulette
+ Sing duels where a wheel decides the rule of every round.
+ For 2 to 12 players who like surprises, 2 microphones needed.
+ Next
+ Back
+ Start
+ Exit
+ Yes
+ No
+ Spin wheel
+ Spin wheel
+ Start duel
+ Songs
+ Rule Roulette: Configuration
+ Number of players
+ Songs per player
+ Jokers per player
+ Rules
+ Random
+ Manual
+ Rules on the wheel
+ Rule Roulette: Rules
+ Always
+ Random
+ Never
+ Up/Down = Select rule · Left/Right or click = Change · Enter = Next · ESC = Back
+ Rule Roulette: Choose Players
+ Select random
+ Rule Roulette
+ Do you really want to exit the game?
+ Pos
+ Name
+ Points
+ Duel
+ vs.
+ Target score:
+ %s chooses the song
+ Jokers:
+ Last duel:
+ Winner
+ Short Song
+ The short version is sung.
+ Medley
+ A medley of 3 songs, without jokers.
+ Double Pack
+ Two songs in a row, the total counts.
+ Duet Time
+ A duet song, each singer takes one voice.
+ Free Choice
+ You choose the song yourselves.
+ Underdog Picks
+ The player further behind chooses the song.
+ Gold Rush
+ Only points from golden notes count. No jokers.
+ Perfect Lines
+ Only the line bonus counts.
+ Target Score
+ Whoever gets closer to the target score wins.
+ Upside Down
+ Whoever has fewer points wins.
+ Teamwork
+ More than 12,000 points together and both get 2 points.
+ Double Win
+ If both score more than 7,000, both win.
+ Double Points
+ The winner gets 4 points instead of 2.
+ Blitz
+ Short version, and the win counts double.
+ All or Nothing
+ The winner gets 3 points, the loser loses 1.
+ Point Steal
+ The winner takes 1 point from the loser.
+ Lead Counts
+ The bigger the lead, the more points (1 to 4).
+ Underdog
+ If the player further behind wins, they get 3 points.
+ Joker Rain
+ The winner gets 1 extra joker.
+ No Safety Net
+ No jokers allowed in this duel.
+
diff --git a/Output/PartyModes/RuleRoulette/Themes/Default.xml b/Output/PartyModes/RuleRoulette/Themes/Default.xml
new file mode 100644
index 00000000..97392ded
--- /dev/null
+++ b/Output/PartyModes/RuleRoulette/Themes/Default.xml
@@ -0,0 +1,10 @@
+
+
+ 9
+
+ Default
+ McSchneidar
+ 0
+ 1
+
+
diff --git a/Output/PartyModes/RuleRoulette/Themes/Default/Default.xml b/Output/PartyModes/RuleRoulette/Themes/Default/Default.xml
new file mode 100644
index 00000000..d88d34b4
--- /dev/null
+++ b/Output/PartyModes/RuleRoulette/Themes/Default/Default.xml
@@ -0,0 +1,15 @@
+
+
+ 4
+
+ Default
+ McSchneidar
+ 0
+ 1
+
+
+
+ PartyModeIcon.png
+
+
+
diff --git a/Output/PartyModes/RuleRoulette/Themes/Default/PartyModeIcon.png b/Output/PartyModes/RuleRoulette/Themes/Default/PartyModeIcon.png
new file mode 100644
index 00000000..b61c247d
Binary files /dev/null and b/Output/PartyModes/RuleRoulette/Themes/Default/PartyModeIcon.png differ
diff --git a/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteConfig.xml b/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteConfig.xml
new file mode 100644
index 00000000..409603fa
--- /dev/null
+++ b/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteConfig.xml
@@ -0,0 +1,407 @@
+
+
+
+ PartyScreenRuleRouletteConfig
+ 1
+
+
+
+ Video
+
+
+ BG_Default
+
+
+
+
+
+
+
+
+ 0
+ 60
+ 1920
+ 67.5
+ 1
+
+
+
+
+
+ 960
+ 75
+ 0
+ 40.5
+ 0
+
+
+ Center
+ Center
+
+ Outline
+ TR_SCREENCONFIG_TITLE
+
+
+ 112.5
+ 157.5
+ 0
+ 40.5
+ 0
+
+
+ Left
+ Center
+
+ Outline
+ TR_SCREENCONFIG_NUMPLAYERS
+
+
+ 112.5
+ 247.5
+ 0
+ 40.5
+ 0
+
+
+ Left
+ Center
+
+ Outline
+ TR_SCREENCONFIG_SONGSPERPLAYER
+
+
+ 112.5
+ 337.5
+ 0
+ 40.5
+ 0
+
+
+ Left
+ Center
+
+ Outline
+ TR_SCREENCONFIG_NUMJOKERS
+
+
+ 112.5
+ 427.5
+ 0
+ 40.5
+ 0
+
+
+ Left
+ Center
+
+ Outline
+ TR_SCREENCONFIG_RULES
+
+
+ 112.5
+ 517.5
+ 0
+ 40.5
+ 0
+
+
+ Left
+ Center
+
+ Outline
+ TR_SCREENCONFIG_NUMRULES
+
+
+
+
+
+
+
+
+
+
+
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+ SelectSlide
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+
+ 750
+ 150
+ 1050
+ 75
+ 0
+
+
+ 757.5
+ 157.5
+ 60
+ 60
+ 0
+
+
+ 1732.5
+ 157.5
+ 60
+ 60
+ 0
+
+
+
+
+
+
+
+ 40.5
+ 75
+ 0
+ 300
+ Outline
+ Bold
+ 9
+
+
+
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+ SelectSlide
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+
+ 750
+ 240
+ 1050
+ 75
+ 0
+
+
+ 757.5
+ 247.5
+ 60
+ 60
+ 0
+
+
+ 1732.5
+ 247.5
+ 60
+ 60
+ 0
+
+
+
+
+
+
+
+ 40.5
+ 75
+ 0
+ 300
+ Outline
+ Bold
+ 9
+
+
+
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+ SelectSlide
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+
+ 750
+ 330
+ 1050
+ 75
+ 0
+
+
+ 757.5
+ 337.5
+ 60
+ 60
+ 0
+
+
+ 1732.5
+ 337.5
+ 60
+ 60
+ 0
+
+
+
+
+
+
+
+ 40.5
+ 75
+ 0
+ 300
+ Outline
+ Bold
+ 9
+
+
+
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+ SelectSlide
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+
+ 750
+ 420
+ 1050
+ 75
+ 0
+
+
+ 757.5
+ 427.5
+ 60
+ 60
+ 0
+
+
+ 1732.5
+ 427.5
+ 60
+ 60
+ 0
+
+
+
+
+
+
+
+ 40.5
+ 75
+ 0
+ 300
+ Outline
+ Bold
+ 9
+
+
+
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+ SelectSlide
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+
+ 750
+ 510
+ 1050
+ 75
+ 0
+
+
+ 757.5
+ 517.5
+ 60
+ 60
+ 0
+
+
+ 1732.5
+ 517.5
+ 60
+ 60
+ 0
+
+
+
+
+
+
+
+ 40.5
+ 75
+ 0
+ 300
+ Outline
+ Bold
+ 9
+
+
+
+
+
+
+
+
+
diff --git a/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteMain.xml b/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteMain.xml
new file mode 100644
index 00000000..adf78962
--- /dev/null
+++ b/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteMain.xml
@@ -0,0 +1,675 @@
+
+
+
+ PartyScreenRuleRouletteMain
+ 1
+
+
+
+ Video
+
+
+ BG_Default
+
+
+
+
+
+
+
+
+ 0
+ 60
+ 1920
+ 67.5
+ 1
+
+
+
+ PopupBG
+
+
+ 0
+ 0
+ 1920
+ 1080
+ -1
+
+
+
+
+
+ 0.1
+ 0.09
+ 0.25
+ 0.75
+
+
+ 30
+ 190
+ 520
+ 720
+ 0.8
+
+
+
+
+
+ 1
+ 0.8
+ 0
+ 0.18
+
+
+ 40
+ 0
+ 500
+ 46
+ 0.6
+
+
+
+
+
+
+ 98
+ 0
+ 40
+ 40
+ 0.4
+
+
+
+
+
+ 0.1
+ 0.09
+ 0.25
+ 0.92
+
+
+ 0
+ 0
+ 200
+ 60
+ 0.5
+
+
+
+
+
+ 1
+ 0.8
+ 0
+ 1
+
+
+ 0
+ 0
+ 208
+ 68
+ 0.6
+
+
+
+
+
+
+ 1070
+ 320
+ 140
+ 140
+ 0.4
+
+
+
+
+
+
+ 1270
+ 320
+ 140
+ 140
+ 0.4
+
+
+
+
+
+ 960
+ 75
+ 0
+ 40.5
+ 0
+
+
+ Center
+ Center
+
+ Outline
+ TR_SCREENMAIN_TITLE
+
+
+ 960
+ 405
+ -11
+ 40.5
+ 570
+
+
+ Center
+ Center
+
+ Outline
+ TR_SCREENMAIN_POPUP_REALLYEXIT
+
+
+ 85
+ 205
+ 0
+ 32
+ 0
+
+ 0.75
+ 0.78
+ 0.85
+ 1
+
+
+ Right
+ Center
+
+ Outline
+ TR_SCREENMAIN_HEADER_POS
+
+
+ 148
+ 205
+ 0
+ 32
+ 0
+
+ 0.75
+ 0.78
+ 0.85
+ 1
+
+
+ Left
+ Center
+
+ Outline
+ TR_SCREENMAIN_HEADER_NAME
+
+
+ 525
+ 205
+ 0
+ 32
+ 0
+
+ 0.75
+ 0.78
+ 0.85
+ 1
+
+
+ Right
+ Center
+
+ Outline
+ TR_SCREENMAIN_HEADER_POINTS
+
+
+ 85
+ 0
+ 0
+ 34
+ 0
+
+
+ Right
+ Center
+
+ Outline
+
+
+
+ 148
+ 0
+ 0
+ 34
+ 290
+
+
+ Left
+ Center
+
+ Outline
+
+
+
+ 525
+ 0
+ 0
+ 34
+ 0
+
+
+ Right
+ Center
+
+ Outline
+
+
+
+ 0
+ 0
+ 0
+ 24
+ 186
+
+
+ Center
+ Center
+
+ Outline
+
+
+
+ 0
+ 0
+ 0
+ 24
+ 186
+
+ 1
+ 0.8
+ 0
+ 1
+
+
+ Center
+ Center
+
+ Outline
+
+
+
+ 1240
+ 275
+ 0
+ 30
+ 500
+
+ 0.75
+ 0.78
+ 0.85
+ 1
+
+
+ Center
+ Center
+
+ Outline
+
+
+
+ 1140
+ 470
+ 0
+ 32
+ 190
+
+
+ Center
+ Center
+
+ Outline
+
+
+
+ 1340
+ 470
+ 0
+ 32
+ 190
+
+
+ Center
+ Center
+
+ Outline
+
+
+
+ 1240
+ 372
+ 0
+ 32
+ 0
+
+ 1
+ 0.8
+ 0
+ 1
+
+
+ Center
+ Center
+
+ Outline
+ TR_SCREENMAIN_VERSUS
+
+
+ 1240
+ 528
+ 0
+ 44
+ 640
+
+ 1
+ 0.8
+ 0
+ 1
+
+
+ Center
+ Center
+
+ Outline
+
+
+
+ 1240
+ 584
+ 0
+ 28
+ 720
+
+
+ Center
+ Center
+
+ Outline
+
+
+
+ 1240
+ 624
+ 0
+ 28
+ 720
+
+ 0.31
+ 0.82
+ 0.91
+ 1
+
+
+ Center
+ Center
+
+ Outline
+
+
+
+ 1240
+ 560
+ 0
+ 28
+ 740
+
+
+ Center
+ Center
+
+ Outline
+
+
+
+ 1240
+ 230
+ 0
+ 36
+ 0
+
+ 1
+ 0.8
+ 0
+ 1
+
+
+ Center
+ Center
+
+ Outline
+ TR_SCREENMAIN_CHAMPION
+
+
+ 1240
+ 272
+ 0
+ 64
+ 640
+
+ 1
+ 0.8
+ 0
+ 1
+
+
+ Center
+ Center
+
+ Outline
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Star
+
+ 940
+ 220
+ 600
+ 340
+ -1
+
+
+ 1
+ 0.8
+ 0
+ 0.6
+
+ Star
+ 18
+ 40
+
+
+
+
diff --git a/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteNames.xml b/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteNames.xml
new file mode 100644
index 00000000..1ccd61ed
--- /dev/null
+++ b/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteNames.xml
@@ -0,0 +1,382 @@
+
+
+
+ PartyScreenRuleRouletteNames
+ 1
+
+
+
+ Video
+
+
+ BG_Default
+
+
+
+
+
+
+
+
+ 0
+ 60
+ 1920
+ 67.5
+ 1
+
+
+
+
+
+ 960
+ 75
+ 0
+ 40.5
+ 0
+
+
+ Center
+ Center
+
+ Outline
+ TR_SCREENNAMES_TITLE
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+ SelectSlide
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+
+ 60
+ 150
+ 1800
+ 75
+ 0
+
+
+ 67.5
+ 157.5
+ 60
+ 60
+ 0
+
+
+ 1792.5
+ 157.5
+ 60
+ 60
+ 0
+
+
+
+
+
+
+
+ 40.5
+ 75
+ 0
+ 300
+ Outline
+ Bold
+ 2
+
+
+
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+ SelectSlidePlayer
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+
+ 60
+ 240
+ 1800
+ 225
+ 0
+
+
+ 67.5
+ 315
+ 60
+ 60
+ 0
+
+
+ 1792.5
+ 315
+ 60
+ 60
+ 0
+
+
+
+
+
+
+
+ 40.5
+ 75
+ 5
+ 300
+ Outline
+ Bold
+ 8
+
+
+
+
+
+
+ 142.5
+ 525
+ 1800
+ 589.5
+ 0
+
+ CoverTile
+
+ PlayerSelector
+
+ 150
+ 150
+ 10
+ 2
+ 15
+ 15
+
+ -20
+ 25.5
+ Outline
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteRules.xml b/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteRules.xml
new file mode 100644
index 00000000..e52e0beb
--- /dev/null
+++ b/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteRules.xml
@@ -0,0 +1,282 @@
+
+
+
+ PartyScreenRuleRouletteRules
+ 1
+
+
+
+ Video
+
+
+ BG_Default
+
+
+
+
+
+
+
+
+ 0
+ 60
+ 1920
+ 67.5
+ 1
+
+
+
+
+
+ 1
+ 0.8
+ 0
+ 0.18
+
+
+ 0
+ 0
+ 890
+ 62
+ 0.6
+
+
+
+ SelectSlideArrowLeft
+
+
+ 0
+ 0
+ 34
+ 34
+ 0.4
+
+
+
+ SelectSlideArrowRight
+
+
+ 0
+ 0
+ 34
+ 34
+ 0.4
+
+
+
+
+
+ 960
+ 75
+ 0
+ 40.5
+ 0
+
+
+ Center
+ Center
+
+ Outline
+ TR_SCREENRULES_TITLE
+
+
+ 0
+ 0
+ 0
+ 30
+ 575
+
+
+ Left
+ Center
+
+ Outline
+
+
+
+ 0
+ 0
+ 0
+ 22
+ 575
+
+ 0.75
+ 0.78
+ 0.85
+ 1
+
+
+ Left
+ Center
+
+ Outline
+
+
+
+ 0
+ 0
+ 0
+ 28
+ 190
+
+
+ Center
+ Center
+
+ Outline
+
+
+
+ 0
+ 0
+ 0
+ 28
+ 0
+
+ 0.31
+ 0.82
+ 0.91
+ 1
+
+
+ Center
+ Center
+
+ Outline
+ TR_SCREENRULES_ALWAYS
+
+
+ 0
+ 0
+ 0
+ 28
+ 0
+
+
+ Center
+ Center
+
+ Outline
+ TR_SCREENRULES_RANDOM
+
+
+ 0
+ 0
+ 0
+ 28
+ 0
+
+ 0.6
+ 0.62
+ 0.68
+ 1
+
+
+ Center
+ Center
+
+ Outline
+ TR_SCREENRULES_NEVER
+
+
+ 960
+ 880
+ 0
+ 26
+ 1100
+
+ 0.75
+ 0.78
+ 0.85
+ 1
+
+
+ Center
+ Center
+
+ Outline
+ TR_SCREENRULES_HINT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteSongs.xml b/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteSongs.xml
new file mode 100644
index 00000000..c647f8ee
--- /dev/null
+++ b/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteSongs.xml
@@ -0,0 +1,407 @@
+
+
+
+ PartyScreenRuleRouletteSongs
+ 1
+
+
+
+ Video
+
+
+ BG_Default
+
+
+
+
+
+
+
+
+ 0
+ 60
+ 1920
+ 67.5
+ 1
+
+
+
+
+
+ 960
+ 75
+ 0
+ 40.5
+ 0
+
+
+ Center
+ Center
+
+ Outline
+ TR_SCREENCONFIG_TITLE
+
+
+ 112.5
+ 157.5
+ 0
+ 40.5
+ 0
+
+
+ Left
+ Center
+
+ Outline
+ TR_SONGSOURCE
+
+
+ 112.5
+ 435
+ 0
+ 40.5
+ 0
+
+
+ Left
+ Center
+
+ Outline
+ TR_GAMEMODE
+
+
+
+
+
+
+
+
+
+
+
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+ SelectSlide
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+
+ 750
+ 150
+ 1050
+ 75
+ 0
+
+
+ 757.5
+ 157.5
+ 60
+ 60
+ 0
+
+
+ 1732.5
+ 157.5
+ 60
+ 60
+ 0
+
+
+
+
+
+
+
+ 40.5
+ 75
+ 0
+ 308
+ Outline
+ Bold
+ 3
+
+
+
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+ SelectSlide
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+
+ 750
+ 240
+ 1050
+ 75
+ 0
+
+
+ 757.5
+ 247.5
+ 60
+ 60
+ 0
+
+
+ 1732.5
+ 247.5
+ 60
+ 60
+ 0
+
+
+
+
+
+
+
+ 40.5
+ 75
+ 0
+ 900
+ Outline
+ Bold
+ 3
+
+
+
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+ SelectSlide
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+
+ 750
+ 240
+ 1050
+ 75
+ 0
+
+
+ 757.5
+ 247.5
+ 60
+ 60
+ 0
+
+
+ 1732.5
+ 247.5
+ 60
+ 60
+ 0
+
+
+
+
+
+
+
+ 40.5
+ 75
+ 0
+ 900
+ Outline
+ Bold
+ 3
+
+
+
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+ SelectSlide
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+
+ 750
+ 330
+ 1050
+ 75
+ 0
+
+
+ 757.5
+ 337.5
+ 60
+ 60
+ 0
+
+
+ 1732.5
+ 337.5
+ 60
+ 60
+ 0
+
+
+
+
+
+
+
+ 40.5
+ 75
+ 0
+ 900
+ Outline
+ Bold
+ 1
+
+
+
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+ SelectSlide
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+
+ 750
+ 435
+ 1050
+ 75
+ 0
+
+
+ 757.5
+ 442.5
+ 60
+ 60
+ 0
+
+
+ 1732.5
+ 442.5
+ 60
+ 60
+ 0
+
+
+
+
+
+
+
+ 40.5
+ 75
+ 0
+ 308
+ Outline
+ Bold
+ 2
+
+
+
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+ SelectSlide
+ SelectSlideArrowLeft
+ SelectSlideArrowRight
+
+ 750
+ 540
+ 1050
+ 75
+ 0
+
+
+ 757.5
+ 547.5
+ 60
+ 60
+ 0
+
+
+ 1732.5
+ 547.5
+ 60
+ 60
+ 0
+
+
+
+
+
+
+
+ 40.5
+ 75
+ 0
+ 308
+ Outline
+ Bold
+ 5
+
+
+
+
+
+
+
+
+
diff --git a/PartyModes/PartyModeRuleRoulette/CPartyModeRuleRoulette.cs b/PartyModes/PartyModeRuleRoulette/CPartyModeRuleRoulette.cs
new file mode 100644
index 00000000..2f96653c
--- /dev/null
+++ b/PartyModes/PartyModeRuleRoulette/CPartyModeRuleRoulette.cs
@@ -0,0 +1,984 @@
+#region license
+// This file is part of Vocaluxe.
+//
+// Vocaluxe is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Vocaluxe is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Vocaluxe. If not, see .
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using VocaluxeLib.Menu;
+using VocaluxeLib.Songs;
+using VocaluxeLib.Utils;
+
+[assembly: ComVisible(false)]
+
+namespace VocaluxeLib.PartyModes.RuleRoulette
+{
+ public enum ERule
+ {
+ ShortSong,
+ Medley,
+ DoublePack,
+ DuetOnly,
+ FreeChoice,
+ UnderdogChoice,
+ GoldRush,
+ PerfectLines,
+ TargetScore,
+ Reverse,
+ Teamwork,
+ DoubleWin,
+ DoublePoints,
+ Blitz,
+ AllOrNothing,
+ PointSteal,
+ LeadCounts,
+ Underdog,
+ JokerRain,
+ NoNet
+ }
+
+ public enum ERuleState
+ {
+ Random,
+ Always,
+ Never
+ }
+
+ public class CRoulettePlayer
+ {
+ public Guid ProfileId;
+ public int TablePoints;
+ public int SingPoints;
+ public int Duels;
+ public int Wins;
+ public int Jokers;
+ }
+
+ public class CRouletteDuel
+ {
+ public CRoulettePlayer[] Players = new CRoulettePlayer[2];
+ public ERule Rule;
+ public int TargetScore;
+ public CRoulettePlayer Chooser;
+ public int[] Scores = new int[2];
+ public int[] Awarded = new int[2];
+ public bool Decided;
+ }
+
+ public abstract class CPartyScreenRuleRoulette : CMenuParty
+ {
+ protected new CPartyModeRuleRoulette _PartyMode;
+
+ public override void Init()
+ {
+ base.Init();
+ _PartyMode = (CPartyModeRuleRoulette)base._PartyMode;
+ }
+ }
+
+ public sealed class CPartyModeRuleRoulette : CPartyMode
+ {
+ public const int PlayersPerDuel = 2;
+ public const int MaxJokers = 3;
+ public const int MinRulesOnWheel = 6;
+ public const int MaxRulesOnWheel = 12;
+ public const int MaxSongsPerPlayer = 10;
+ public const int MinSongsForRule = 5;
+ public const int MedleySongs = 3;
+ public const int TeamworkScore = 12000;
+ public const int DoubleWinScore = 7000;
+
+ public override int MinMics
+ {
+ get { return PlayersPerDuel; }
+ }
+ public override int MaxMics
+ {
+ get { return CBase.Config.GetMaxNumMics(); }
+ }
+ public override int MinPlayers
+ {
+ get { return 2; }
+ }
+ public override int MaxPlayers
+ {
+ get { return 12; }
+ }
+ public override int MinTeams
+ {
+ get { return 1; }
+ }
+ public override int MaxTeams
+ {
+ get { return 1; }
+ }
+ public override int MinPlayersPerTeam
+ {
+ get { return MinPlayers; }
+ }
+ public override int MaxPlayersPerTeam
+ {
+ get { return MaxPlayers; }
+ }
+
+ private enum EStage
+ {
+ Config,
+ Rules,
+ Songs,
+ Names,
+ Main,
+ SongSelection,
+ Singing,
+ DirectSinging
+ }
+
+ public struct SData
+ {
+ public int NumPlayer;
+ public int SongsPerPlayer;
+ public int NumJokers;
+ public bool ManualRules;
+ public int NumRulesOnWheel;
+ public Dictionary RuleStates;
+ public List ProfileIds;
+
+ public ESongSource SongSource;
+ public ESongSorting Sorting;
+ public int CategoryIndex;
+ public int PlaylistId;
+ public EGameMode GameMode;
+ public int NumMedleySongs;
+
+ public List Players;
+ public List Duels;
+ public int DuelIndex;
+ public List Wheel;
+ public List UsedInCycle;
+ public bool RuleChosen;
+ public CRouletteDuel LastDuel;
+ public int NumDuelsPlayed;
+ }
+
+ public SData GameData;
+ private EStage _Stage;
+ private List _SourceSongs = new List();
+ private readonly HashSet _SungSongs = new HashSet();
+
+ public CPartyModeRuleRoulette(int id) : base(id)
+ {
+ _ScreenSongOptions.Selection.RandomOnly = false;
+ _ScreenSongOptions.Selection.PartyMode = true;
+ _ScreenSongOptions.Selection.CategoryChangeAllowed = true;
+ _ScreenSongOptions.Selection.NumJokers = new int[] { 1, 1 };
+ _ScreenSongOptions.Selection.TeamNames = new string[] { "foo", "bar" };
+
+ _ScreenSongOptions.Sorting.SearchString = string.Empty;
+ _ScreenSongOptions.Sorting.SearchActive = false;
+ _ScreenSongOptions.Sorting.DuetOptions = EDuetOptions.All;
+ _ScreenSongOptions.Sorting.FilterPlaylistId = -1;
+
+ var states = new Dictionary();
+ foreach (ERule rule in Enum.GetValues(typeof(ERule)))
+ {
+ states[rule] = ERuleState.Random;
+ }
+
+ GameData = new SData
+ {
+ NumPlayer = 4,
+ SongsPerPlayer = 3,
+ NumJokers = 1,
+ ManualRules = false,
+ NumRulesOnWheel = 8,
+ RuleStates = states,
+ ProfileIds = new List(),
+ Sorting = CBase.Config.GetSongSorting(),
+ SongSource = ESongSource.TR_SONGSOURCE_ALLSONGS,
+ PlaylistId = 0,
+ CategoryIndex = 0,
+ GameMode = EGameMode.TR_GAMEMODE_NORMAL,
+ NumMedleySongs = MedleySongs,
+ Players = new List(),
+ Duels = new List(),
+ Wheel = new List(),
+ UsedInCycle = new List()
+ };
+ }
+
+ public override void SetDefaults()
+ {
+ _Stage = EStage.Config;
+
+ _ScreenSongOptions.Sorting.IgnoreArticles = CBase.Config.GetIgnoreArticles();
+ _ScreenSongOptions.Sorting.SongSorting = CBase.Config.GetSongSorting();
+ _ScreenSongOptions.Sorting.Tabs = EOffOn.TR_CONFIG_OFF;
+ _ScreenSongOptions.Selection.SongIndex = -1;
+ _ScreenSongOptions.Selection.CategoryIndex = -1;
+
+ if (CBase.Config.GetTabs() == EOffOn.TR_CONFIG_ON && _ScreenSongOptions.Sorting.SongSorting != ESongSorting.TR_CONFIG_NONE)
+ {
+ _ScreenSongOptions.Sorting.Tabs = EOffOn.TR_CONFIG_ON;
+ }
+ }
+
+ public override bool Init()
+ {
+ if (!base.Init())
+ {
+ return false;
+ }
+
+ SetDefaults();
+ return true;
+ }
+
+ public override void UpdateGame() { }
+
+ public bool IsFinished
+ {
+ get { return GameData.Duels.Count > 0 && GameData.DuelIndex >= GameData.Duels.Count; }
+ }
+
+ public CRouletteDuel CurrentDuel
+ {
+ get { return IsFinished || GameData.Duels.Count == 0 ? null : GameData.Duels[GameData.DuelIndex]; }
+ }
+
+ public List GetRanking()
+ {
+ return GameData.Players.OrderByDescending(player => player.TablePoints).ThenByDescending(player => player.SingPoints).ToList();
+ }
+
+ public static string GetRuleKey(ERule rule)
+ {
+ return "TR_RULE_" + rule.ToString().ToUpperInvariant();
+ }
+
+ private IMenu _GetNextScreen()
+ {
+ switch (_Stage)
+ {
+ case EStage.Config:
+ return _Screens["CPartyScreenRuleRouletteConfig"];
+ case EStage.Rules:
+ return _Screens["CPartyScreenRuleRouletteRules"];
+ case EStage.Songs:
+ return _Screens["CPartyScreenRuleRouletteSongs"];
+ case EStage.Names:
+ return _Screens["CPartyScreenRuleRouletteNames"];
+ case EStage.Main:
+ return _Screens["CPartyScreenRuleRouletteMain"];
+ case EStage.SongSelection:
+ return CBase.Graphics.GetScreen(EScreen.Song);
+ case EStage.Singing:
+ case EStage.DirectSinging:
+ return CBase.Graphics.GetScreen(EScreen.Sing);
+ default:
+ throw new ArgumentException("Invalid stage: " + _Stage);
+ }
+ }
+
+ private void _FadeToScreen()
+ {
+ CBase.Graphics.FadeTo(_GetNextScreen());
+ }
+
+ public void Next()
+ {
+ switch (_Stage)
+ {
+ case EStage.Config:
+ _Stage = GameData.ManualRules ? EStage.Rules : EStage.Songs;
+ break;
+ case EStage.Rules:
+ _Stage = EStage.Songs;
+ break;
+ case EStage.Songs:
+ _Stage = EStage.Names;
+ break;
+ case EStage.Names:
+ _Stage = EStage.Main;
+ _StartGame();
+ break;
+ case EStage.Main:
+ if (CurrentDuel == null || !GameData.RuleChosen)
+ {
+ return;
+ }
+
+ if (_IsDirectRule(CurrentDuel.Rule))
+ {
+ _Stage = EStage.DirectSinging;
+ _PrepareRound(_PickDirectSongs(CurrentDuel.Rule));
+ }
+ else
+ {
+ _Stage = EStage.SongSelection;
+ _PrepareSongSelection();
+ }
+
+ break;
+ case EStage.SongSelection:
+ _Stage = EStage.Singing;
+ break;
+ case EStage.Singing:
+ case EStage.DirectSinging:
+ _Stage = EStage.Main;
+ _UpdateScores();
+ break;
+ default:
+ throw new ArgumentException("Invalid stage: " + _Stage);
+ }
+
+ _FadeToScreen();
+ }
+
+ public void Back()
+ {
+ switch (_Stage)
+ {
+ case EStage.Config:
+ CBase.Graphics.FadeTo(EScreen.Party);
+ return;
+ case EStage.Rules:
+ _Stage = EStage.Config;
+ break;
+ case EStage.Songs:
+ _Stage = GameData.ManualRules ? EStage.Rules : EStage.Config;
+ break;
+ case EStage.Names:
+ _Stage = EStage.Songs;
+ break;
+ case EStage.Main:
+ _Stage = EStage.Names;
+ break;
+ default:
+ throw new ArgumentException("Invalid stage: " + _Stage);
+ }
+
+ _FadeToScreen();
+ }
+
+ public override IMenu GetStartScreen()
+ {
+ return _Screens["CPartyScreenRuleRouletteConfig"];
+ }
+
+ public override SScreenSongOptions GetScreenSongOptions()
+ {
+ return _ScreenSongOptions;
+ }
+
+ public override void OnSongChange(int songIndex, ref SScreenSongOptions screenSongOptions)
+ {
+ if (_ScreenSongOptions.Selection.SelectNextRandomSong && songIndex != -1)
+ {
+ _ScreenSongOptions.Selection.SelectNextRandomSong = false;
+ }
+
+ _ScreenSongOptions.Selection.SongIndex = songIndex;
+
+ screenSongOptions = _ScreenSongOptions;
+ }
+
+ public override void OnCategoryChange(int categoryIndex, ref SScreenSongOptions screenSongOptions)
+ {
+ var freeChoice = CurrentDuel != null && _IsFreeChoiceRule(CurrentDuel.Rule);
+ if (!freeChoice && (categoryIndex != -1 || CBase.Config.GetTabs() == EOffOn.TR_CONFIG_OFF))
+ {
+ _ScreenSongOptions.Selection.SelectNextRandomSong = true;
+ _ScreenSongOptions.Selection.RandomOnly = true;
+ }
+ else
+ {
+ _ScreenSongOptions.Selection.SongIndex = -1;
+ _ScreenSongOptions.Selection.RandomOnly = false;
+ }
+
+ _ScreenSongOptions.Selection.CategoryIndex = categoryIndex;
+
+ screenSongOptions = _ScreenSongOptions;
+ }
+
+ public override void SetSearchString(string searchString, bool visible)
+ {
+ _ScreenSongOptions.Sorting.SearchString = searchString;
+ _ScreenSongOptions.Sorting.SearchActive = visible;
+ }
+
+ public override void JokerUsed(int teamNr)
+ {
+ if (_ScreenSongOptions.Selection.NumJokers == null || teamNr >= _ScreenSongOptions.Selection.NumJokers.Length)
+ {
+ return;
+ }
+
+ var duel = CurrentDuel;
+ if (duel != null && duel.Players[teamNr].Jokers > 0)
+ {
+ duel.Players[teamNr].Jokers--;
+ }
+
+ _ScreenSongOptions.Selection.NumJokers[teamNr]--;
+ _ScreenSongOptions.Selection.RandomOnly = true;
+ _ScreenSongOptions.Selection.CategoryChangeAllowed = false;
+ }
+
+ public override void SongSelected(int songId)
+ {
+ var songIds = new List { songId };
+ if (CurrentDuel != null && CurrentDuel.Rule == ERule.DoublePack)
+ {
+ _SungSongs.Add(songId);
+ var extra = _PickSongs(song => !song.IsDuet, 1);
+ songIds.AddRange(extra);
+ }
+
+ _PrepareRound(songIds.ToArray());
+
+ Next();
+ }
+
+ public override void LeavingHighscore()
+ {
+ for (var i = 0; i < CBase.Game.GetNumSongs(); i++)
+ {
+ CBase.Songs.AddPartySongSung(CBase.Game.GetSong(i).Id);
+ _SungSongs.Add(CBase.Game.GetSong(i).Id);
+ }
+
+ Next();
+ }
+
+ public void SpinWheel(out int resultIndex)
+ {
+ resultIndex = -1;
+ var duel = CurrentDuel;
+ if (duel == null || GameData.Wheel.Count == 0)
+ {
+ return;
+ }
+
+ var open = GameData.Wheel.Where(wheelRule => !GameData.UsedInCycle.Contains(wheelRule)).ToList();
+ if (open.Count == 0)
+ {
+ GameData.UsedInCycle.Clear();
+ open = new List(GameData.Wheel);
+ }
+
+ var rule = open[CBase.Game.GetRandom(open.Count)];
+ GameData.UsedInCycle.Add(rule);
+ resultIndex = GameData.Wheel.IndexOf(rule);
+
+ duel.Rule = rule;
+ duel.Chooser = null;
+ duel.TargetScore = 0;
+ if (rule == ERule.TargetScore)
+ {
+ duel.TargetScore = 4000 + CBase.Game.GetRandom(41) * 100;
+ }
+ else if (rule == ERule.UnderdogChoice)
+ {
+ duel.Chooser = _GetWeaker(duel);
+ }
+
+ GameData.RuleChosen = true;
+ }
+
+ private CRoulettePlayer _GetWeaker(CRouletteDuel duel)
+ {
+ var a = duel.Players[0];
+ var b = duel.Players[1];
+ if (a.TablePoints != b.TablePoints)
+ {
+ return a.TablePoints < b.TablePoints ? a : b;
+ }
+
+ if (a.SingPoints != b.SingPoints)
+ {
+ return a.SingPoints < b.SingPoints ? a : b;
+ }
+
+ return duel.Players[CBase.Game.GetRandom(2)];
+ }
+
+ private static bool _IsDirectRule(ERule rule)
+ {
+ return rule == ERule.Medley || rule == ERule.GoldRush;
+ }
+
+ private static bool _IsFreeChoiceRule(ERule rule)
+ {
+ return rule == ERule.FreeChoice || rule == ERule.UnderdogChoice;
+ }
+
+ private static bool _HasGoldenNotes(CSong song)
+ {
+ return song.Notes.Voices.Any(voice => voice.Lines.Any(line => line.Notes.Any(note => note.IsGoldenNote)));
+ }
+
+ private void _StartGame()
+ {
+ CBase.Songs.ResetSongSung();
+ _SungSongs.Clear();
+ _SourceSongs = _GetSourceSongs();
+
+ GameData.Players = new List();
+ foreach (var profileId in GameData.ProfileIds)
+ {
+ GameData.Players.Add(new CRoulettePlayer { ProfileId = profileId, Jokers = GameData.NumJokers });
+ }
+
+ _BuildDuels();
+ _BuildWheel();
+
+ GameData.DuelIndex = 0;
+ GameData.UsedInCycle = new List();
+ GameData.RuleChosen = false;
+ GameData.LastDuel = null;
+ GameData.NumDuelsPlayed = 0;
+ }
+
+ private void _BuildDuels()
+ {
+ var numDuels = Math.Max(1, (GameData.Players.Count * GameData.SongsPerPlayer + 1) / PlayersPerDuel);
+ var counts = GameData.Players.ToDictionary(player => player, player => 0);
+ var met = new Dictionary();
+ GameData.Duels = new List();
+ CRouletteDuel last = null;
+
+ for (var d = 0; d < numDuels; d++)
+ {
+ CRoulettePlayer bestA = null;
+ CRoulettePlayer bestB = null;
+ var bestValue = int.MaxValue;
+ var players = new List(GameData.Players);
+ players.Shuffle();
+
+ foreach (var a in players)
+ {
+ foreach (var b in players)
+ {
+ if (a == b)
+ {
+ continue;
+ }
+
+ int times;
+ met.TryGetValue(_PairKey(a, b), out times);
+ var repeat = last != null && (last.Players.Contains(a) || last.Players.Contains(b)) ? 1 : 0;
+ var value = (counts[a] + counts[b]) * 100 + times * 10 + repeat;
+ if (value < bestValue)
+ {
+ bestValue = value;
+ bestA = a;
+ bestB = b;
+ }
+ }
+ }
+
+ var duel = new CRouletteDuel();
+ duel.Players[0] = bestA;
+ duel.Players[1] = bestB;
+ GameData.Duels.Add(duel);
+ counts[bestA]++;
+ counts[bestB]++;
+ var key = _PairKey(bestA, bestB);
+ int count;
+ met.TryGetValue(key, out count);
+ met[key] = count + 1;
+ last = duel;
+ }
+ }
+
+ private string _PairKey(CRoulettePlayer a, CRoulettePlayer b)
+ {
+ var ia = GameData.Players.IndexOf(a);
+ var ib = GameData.Players.IndexOf(b);
+ return Math.Min(ia, ib) + "-" + Math.Max(ia, ib);
+ }
+
+ public bool IsRuleAvailable(ERule rule)
+ {
+ switch (rule)
+ {
+ case ERule.ShortSong:
+ case ERule.Blitz:
+ return _SourceSongs.Count(song => song.IsGameModeAvailable(EGameMode.TR_GAMEMODE_SHORTSONG)) >= MinSongsForRule;
+ case ERule.Medley:
+ return _SourceSongs.Count(song => !song.IsDuet && song.IsGameModeAvailable(EGameMode.TR_GAMEMODE_MEDLEY)) >= MinSongsForRule;
+ case ERule.DuetOnly:
+ return _SourceSongs.Count(song => song.IsDuet) >= 1;
+ case ERule.GoldRush:
+ return _SourceSongs.Count(song => !song.IsDuet && _HasGoldenNotes(song)) >= MinSongsForRule;
+ default:
+ return true;
+ }
+ }
+
+ private void _BuildWheel()
+ {
+ var available = Enum.GetValues(typeof(ERule)).Cast().Where(IsRuleAvailable).ToList();
+ var always = new List();
+ var random = new List();
+ foreach (var rule in available)
+ {
+ var state = GameData.ManualRules ? GameData.RuleStates[rule] : ERuleState.Random;
+ if (state == ERuleState.Always)
+ {
+ always.Add(rule);
+ }
+ else if (state == ERuleState.Random)
+ {
+ random.Add(rule);
+ }
+ }
+
+ always.Shuffle();
+ random.Shuffle();
+ var size = Math.Max(Math.Min(always.Count, MaxRulesOnWheel), GameData.NumRulesOnWheel);
+ var wheel = always.Take(size).ToList();
+ wheel.AddRange(random.Take(size - wheel.Count));
+ if (wheel.Count == 0)
+ {
+ available.Shuffle();
+ wheel = available.Take(size).ToList();
+ }
+
+ wheel.Shuffle();
+ GameData.Wheel = wheel;
+ }
+
+ private List _GetSourceSongs()
+ {
+ var songs = new List();
+ switch (GameData.SongSource)
+ {
+ case ESongSource.TR_SONGSOURCE_PLAYLIST:
+ for (var i = 0; i < CBase.Playlist.GetSongCount(GameData.PlaylistId); i++)
+ {
+ var song = CBase.Songs.GetSongById(CBase.Playlist.GetSong(GameData.PlaylistId, i).SongId);
+ if (song != null)
+ {
+ songs.Add(song);
+ }
+ }
+
+ break;
+
+ case ESongSource.TR_SONGSOURCE_ALLSONGS:
+ songs.AddRange(CBase.Songs.GetSongs());
+ break;
+
+ case ESongSource.TR_SONGSOURCE_CATEGORY:
+ var oldSorting = CBase.Config.GetSongSorting();
+ CBase.Songs.SortSongs(GameData.Sorting, EOffOn.TR_CONFIG_ON, CBase.Config.GetIgnoreArticles(), string.Empty, EDuetOptions.All, -1);
+ CBase.Songs.SetCategory(GameData.CategoryIndex);
+ songs.AddRange(CBase.Songs.GetVisibleSongs());
+ CBase.Songs.SetCategory(-1);
+ CBase.Songs.SortSongs(oldSorting, CBase.Config.GetTabs(), CBase.Config.GetIgnoreArticles(), string.Empty, EDuetOptions.All, -1);
+ break;
+ }
+
+ return songs;
+ }
+
+ private List _PickSongs(Func filter, int count)
+ {
+ var candidates = _SourceSongs.Where(song => filter(song) && !_SungSongs.Contains(song.Id)).ToList();
+ if (candidates.Count < count)
+ {
+ candidates = _SourceSongs.Where(filter).ToList();
+ }
+
+ candidates.Shuffle();
+ return candidates.Take(count).Select(song => song.Id).ToList();
+ }
+
+ private int[] _PickDirectSongs(ERule rule)
+ {
+ if (rule == ERule.Medley)
+ {
+ return _PickSongs(song => !song.IsDuet && song.IsGameModeAvailable(EGameMode.TR_GAMEMODE_MEDLEY), MedleySongs).ToArray();
+ }
+
+ return _PickSongs(song => !song.IsDuet && _HasGoldenNotes(song), 1).ToArray();
+ }
+
+ private void _PrepareSongSelection()
+ {
+ var duel = CurrentDuel;
+ var freeChoice = _IsFreeChoiceRule(duel.Rule);
+
+ _ScreenSongOptions.Selection.RandomOnly = !freeChoice;
+ _ScreenSongOptions.Selection.SelectNextRandomSong = !freeChoice;
+ _ScreenSongOptions.Sorting.IgnoreArticles = CBase.Config.GetIgnoreArticles();
+ _ScreenSongOptions.Sorting.DuetOptions = duel.Rule == ERule.DuetOnly ? EDuetOptions.Duets : EDuetOptions.All;
+
+ switch (GameData.SongSource)
+ {
+ case ESongSource.TR_SONGSOURCE_ALLSONGS:
+ _ScreenSongOptions.Sorting.SongSorting = CBase.Config.GetSongSorting();
+ _ScreenSongOptions.Sorting.Tabs = CBase.Config.GetTabs();
+ _ScreenSongOptions.Sorting.FilterPlaylistId = -1;
+
+ _ScreenSongOptions.Selection.SongIndex = -1;
+ _ScreenSongOptions.Selection.CategoryIndex = -1;
+ _ScreenSongOptions.Selection.CategoryChangeAllowed = true;
+ break;
+
+ case ESongSource.TR_SONGSOURCE_CATEGORY:
+ _ScreenSongOptions.Sorting.SongSorting = GameData.Sorting;
+ _ScreenSongOptions.Sorting.Tabs = EOffOn.TR_CONFIG_ON;
+ _ScreenSongOptions.Sorting.FilterPlaylistId = -1;
+
+ _ScreenSongOptions.Selection.CategoryIndex = GameData.CategoryIndex;
+ _ScreenSongOptions.Selection.CategoryChangeAllowed = false;
+ break;
+
+ case ESongSource.TR_SONGSOURCE_PLAYLIST:
+ _ScreenSongOptions.Sorting.SongSorting = CBase.Config.GetSongSorting();
+ _ScreenSongOptions.Sorting.Tabs = EOffOn.TR_CONFIG_OFF;
+ _ScreenSongOptions.Sorting.FilterPlaylistId = GameData.PlaylistId;
+
+ _ScreenSongOptions.Selection.CategoryChangeAllowed = false;
+ break;
+ }
+
+ var noJokers = duel.Rule == ERule.NoNet || freeChoice;
+ _ScreenSongOptions.Selection.NumJokers = new int[] { noJokers ? 0 : duel.Players[0].Jokers, noJokers ? 0 : duel.Players[1].Jokers };
+ _ScreenSongOptions.Selection.TeamNames = new string[]
+ {
+ CBase.Profiles.GetPlayerName(duel.Players[0].ProfileId),
+ CBase.Profiles.GetPlayerName(duel.Players[1].ProfileId)
+ };
+ }
+
+ private bool _PrepareRound(int[] songIds)
+ {
+ CBase.Game.Reset();
+ CBase.Game.ClearSongs();
+
+ CBase.Game.SetNumPlayer(PlayersPerDuel);
+ var players = CBase.Game.GetPlayers();
+ var duel = CurrentDuel;
+ if (players == null || players.Length < PlayersPerDuel || duel == null)
+ {
+ return false;
+ }
+
+ for (var p = 0; p < PlayersPerDuel; p++)
+ {
+ players[p].ProfileId = duel.Players[p].ProfileId;
+ players[p].VoiceNr = p;
+ }
+
+ var medley = duel.Rule == ERule.Medley;
+ var shortSong = duel.Rule == ERule.ShortSong || duel.Rule == ERule.Blitz;
+ foreach (var songId in songIds)
+ {
+ var song = CBase.Songs.GetSongById(songId);
+ EGameMode gameMode;
+ if (medley)
+ {
+ gameMode = EGameMode.TR_GAMEMODE_MEDLEY;
+ }
+ else if (song.IsDuet)
+ {
+ gameMode = EGameMode.TR_GAMEMODE_DUET;
+ }
+ else if (shortSong && song.IsGameModeAvailable(EGameMode.TR_GAMEMODE_SHORTSONG))
+ {
+ gameMode = EGameMode.TR_GAMEMODE_SHORTSONG;
+ }
+ else if (song.IsGameModeAvailable(GameData.GameMode))
+ {
+ gameMode = GameData.GameMode;
+ }
+ else
+ {
+ gameMode = EGameMode.TR_GAMEMODE_NORMAL;
+ }
+
+ CBase.Game.AddSong(songId, gameMode);
+ }
+
+ return true;
+ }
+
+ private static int _LeadPoints(int lead)
+ {
+ if (lead <= 1000)
+ {
+ return 1;
+ }
+
+ if (lead <= 4000)
+ {
+ return 2;
+ }
+
+ return lead <= 6500 ? 3 : 4;
+ }
+
+ private void _UpdateScores()
+ {
+ var duel = CurrentDuel;
+ if (duel == null)
+ {
+ return;
+ }
+
+ var points = CBase.Game.GetPoints();
+ if (points == null || points.NumRounds == 0)
+ {
+ return;
+ }
+
+ var total = new double[PlayersPerDuel];
+ var golden = new double[PlayersPerDuel];
+ var lines = new double[PlayersPerDuel];
+ for (var round = 0; round < points.NumRounds; round++)
+ {
+ var res = points.GetPlayer(round, PlayersPerDuel);
+ if (res == null || res.Length < PlayersPerDuel)
+ {
+ return;
+ }
+
+ for (var p = 0; p < PlayersPerDuel; p++)
+ {
+ total[p] += res[p].Points;
+ golden[p] += res[p].PointsGoldenNotes;
+ lines[p] += res[p].PointsLineBonus;
+ }
+ }
+
+ var divisor = duel.Rule == ERule.DoublePack ? 1 : points.NumRounds;
+ var metric = new int[PlayersPerDuel];
+ for (var p = 0; p < PlayersPerDuel; p++)
+ {
+ duel.Scores[p] = (int)Math.Round(total[p] / divisor);
+ switch (duel.Rule)
+ {
+ case ERule.GoldRush:
+ metric[p] = (int)Math.Round(golden[p] / divisor);
+ break;
+ case ERule.PerfectLines:
+ metric[p] = (int)Math.Round(lines[p] / divisor);
+ break;
+ case ERule.TargetScore:
+ metric[p] = -Math.Abs(duel.Scores[p] - duel.TargetScore);
+ break;
+ case ERule.Reverse:
+ metric[p] = -duel.Scores[p];
+ break;
+ default:
+ metric[p] = duel.Scores[p];
+ break;
+ }
+ }
+
+ var weaker = duel.Rule == ERule.Underdog ? _GetWeaker(duel) : null;
+ _Award(duel, metric, weaker);
+
+ for (var p = 0; p < PlayersPerDuel; p++)
+ {
+ var player = duel.Players[p];
+ player.TablePoints = Math.Max(0, player.TablePoints + duel.Awarded[p]);
+ player.SingPoints += duel.Scores[p];
+ player.Duels++;
+ }
+
+ duel.Decided = true;
+ GameData.LastDuel = duel;
+ GameData.NumDuelsPlayed++;
+ GameData.DuelIndex++;
+ GameData.RuleChosen = false;
+ }
+
+ private void _Award(CRouletteDuel duel, int[] metric, CRoulettePlayer weaker)
+ {
+ duel.Awarded[0] = 0;
+ duel.Awarded[1] = 0;
+
+ if (duel.Rule == ERule.Teamwork)
+ {
+ var together = duel.Scores[0] + duel.Scores[1] > TeamworkScore ? 2 : 0;
+ duel.Awarded[0] = together;
+ duel.Awarded[1] = together;
+ return;
+ }
+
+ if (duel.Rule == ERule.DoubleWin && duel.Scores[0] > DoubleWinScore && duel.Scores[1] > DoubleWinScore)
+ {
+ duel.Awarded[0] = 2;
+ duel.Awarded[1] = 2;
+ duel.Players[0].Wins++;
+ duel.Players[1].Wins++;
+ return;
+ }
+
+ if (metric[0] == metric[1])
+ {
+ duel.Awarded[0] = 1;
+ duel.Awarded[1] = 1;
+ if (duel.Rule == ERule.JokerRain)
+ {
+ duel.Players[0].Jokers++;
+ duel.Players[1].Jokers++;
+ }
+
+ return;
+ }
+
+ var winner = metric[0] > metric[1] ? 0 : 1;
+ var loser = 1 - winner;
+ duel.Players[winner].Wins++;
+ if (duel.Rule == ERule.JokerRain)
+ {
+ duel.Players[winner].Jokers++;
+ }
+
+ switch (duel.Rule)
+ {
+ case ERule.DoublePoints:
+ case ERule.Blitz:
+ duel.Awarded[winner] = 4;
+ break;
+ case ERule.AllOrNothing:
+ duel.Awarded[winner] = 3;
+ duel.Awarded[loser] = -1;
+ break;
+ case ERule.PointSteal:
+ duel.Awarded[winner] = 2 + (duel.Players[loser].TablePoints > 0 ? 1 : 0);
+ duel.Awarded[loser] = duel.Players[loser].TablePoints > 0 ? -1 : 0;
+ break;
+ case ERule.LeadCounts:
+ duel.Awarded[winner] = _LeadPoints(Math.Abs(duel.Scores[0] - duel.Scores[1]));
+ break;
+ case ERule.Underdog:
+ duel.Awarded[winner] = duel.Players[winner] == weaker ? 3 : 2;
+ break;
+ default:
+ duel.Awarded[winner] = 2;
+ break;
+ }
+ }
+ }
+}
diff --git a/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteConfig.cs b/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteConfig.cs
new file mode 100644
index 00000000..05a0f2c2
--- /dev/null
+++ b/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteConfig.cs
@@ -0,0 +1,165 @@
+#region license
+// This file is part of Vocaluxe.
+//
+// Vocaluxe is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Vocaluxe is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Vocaluxe. If not, see .
+#endregion
+
+using System.Windows.Forms;
+
+namespace VocaluxeLib.PartyModes.RuleRoulette
+{
+ public class CPartyScreenRuleRouletteConfig : CPartyScreenRuleRoulette
+ {
+ protected override int _ScreenVersion
+ {
+ get { return 1; }
+ }
+
+ private const string _SelectSlideNumPlayers = "SelectSlideNumPlayers";
+ private const string _SelectSlideSongsPerPlayer = "SelectSlideSongsPerPlayer";
+ private const string _SelectSlideNumJokers = "SelectSlideNumJokers";
+ private const string _SelectSlideRuleSelection = "SelectSlideRuleSelection";
+ private const string _SelectSlideNumRules = "SelectSlideNumRules";
+ private const string _ButtonNext = "ButtonNext";
+ private const string _ButtonBack = "ButtonBack";
+
+ public override void Init()
+ {
+ base.Init();
+
+ _ThemeSelectSlides = new string[] { _SelectSlideNumPlayers, _SelectSlideSongsPerPlayer, _SelectSlideNumJokers, _SelectSlideRuleSelection, _SelectSlideNumRules };
+ _ThemeButtons = new string[] { _ButtonNext, _ButtonBack };
+ }
+
+ public override bool HandleInput(SKeyEvent keyEvent)
+ {
+ base.HandleInput(keyEvent);
+
+ if (!keyEvent.KeyPressed)
+ {
+ switch (keyEvent.Key)
+ {
+ case Keys.Back:
+ case Keys.Escape:
+ _PartyMode.Back();
+ break;
+
+ case Keys.Enter:
+ _UpdateSlides();
+
+ if (_Buttons[_ButtonBack].Selected)
+ {
+ _PartyMode.Back();
+ }
+
+ if (_Buttons[_ButtonNext].Selected)
+ {
+ _PartyMode.Next();
+ }
+
+ break;
+
+ case Keys.Left:
+ case Keys.Right:
+ _UpdateSlides();
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ public override bool HandleMouse(SMouseEvent mouseEvent)
+ {
+ base.HandleMouse(mouseEvent);
+
+ if (mouseEvent.LB && _IsMouseOverCurSelection(mouseEvent))
+ {
+ _UpdateSlides();
+ if (_Buttons[_ButtonBack].Selected)
+ {
+ _PartyMode.Back();
+ }
+
+ if (_Buttons[_ButtonNext].Selected)
+ {
+ _PartyMode.Next();
+ }
+ }
+
+ if (mouseEvent.RB)
+ {
+ _PartyMode.Back();
+ }
+
+ return true;
+ }
+
+ public override void OnShow()
+ {
+ base.OnShow();
+
+ _SelectSlides[_SelectSlideNumPlayers].Clear();
+ for (var i = _PartyMode.MinPlayers; i <= _PartyMode.MaxPlayers; i++)
+ {
+ _SelectSlides[_SelectSlideNumPlayers].AddValue(i.ToString());
+ }
+
+ _SelectSlides[_SelectSlideNumPlayers].SelectedValue = _PartyMode.GameData.NumPlayer.ToString();
+
+ _SelectSlides[_SelectSlideSongsPerPlayer].Clear();
+ for (var i = 1; i <= CPartyModeRuleRoulette.MaxSongsPerPlayer; i++)
+ {
+ _SelectSlides[_SelectSlideSongsPerPlayer].AddValue(i.ToString());
+ }
+
+ _SelectSlides[_SelectSlideSongsPerPlayer].SelectedValue = _PartyMode.GameData.SongsPerPlayer.ToString();
+
+ _SelectSlides[_SelectSlideRuleSelection].Clear();
+ _SelectSlides[_SelectSlideRuleSelection].AddValue(CBase.Language.Translate("TR_SCREENCONFIG_RULES_RANDOM", PartyModeId));
+ _SelectSlides[_SelectSlideRuleSelection].AddValue(CBase.Language.Translate("TR_SCREENCONFIG_RULES_MANUAL", PartyModeId));
+ _SelectSlides[_SelectSlideRuleSelection].Selection = _PartyMode.GameData.ManualRules ? 1 : 0;
+
+ _SelectSlides[_SelectSlideNumRules].Clear();
+ for (var i = CPartyModeRuleRoulette.MinRulesOnWheel; i <= CPartyModeRuleRoulette.MaxRulesOnWheel; i++)
+ {
+ _SelectSlides[_SelectSlideNumRules].AddValue(i.ToString());
+ }
+
+ _SelectSlides[_SelectSlideNumRules].SelectedValue = _PartyMode.GameData.NumRulesOnWheel.ToString();
+
+ _SelectSlides[_SelectSlideNumJokers].Clear();
+ for (var i = 0; i <= CPartyModeRuleRoulette.MaxJokers; i++)
+ {
+ _SelectSlides[_SelectSlideNumJokers].AddValue(i.ToString());
+ }
+
+ _SelectSlides[_SelectSlideNumJokers].SelectedValue = _PartyMode.GameData.NumJokers.ToString();
+ }
+
+ public override bool UpdateGame()
+ {
+ return true;
+ }
+
+ private void _UpdateSlides()
+ {
+ _PartyMode.GameData.NumPlayer = _SelectSlides[_SelectSlideNumPlayers].Selection + _PartyMode.MinPlayers;
+ _PartyMode.GameData.NumJokers = _SelectSlides[_SelectSlideNumJokers].Selection;
+ _PartyMode.GameData.SongsPerPlayer = _SelectSlides[_SelectSlideSongsPerPlayer].Selection + 1;
+ _PartyMode.GameData.ManualRules = _SelectSlides[_SelectSlideRuleSelection].Selection == 1;
+ _PartyMode.GameData.NumRulesOnWheel = _SelectSlides[_SelectSlideNumRules].Selection + CPartyModeRuleRoulette.MinRulesOnWheel;
+ }
+ }
+}
diff --git a/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteMain.cs b/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteMain.cs
new file mode 100644
index 00000000..3c006652
--- /dev/null
+++ b/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteMain.cs
@@ -0,0 +1,622 @@
+#region license
+// This file is part of Vocaluxe.
+//
+// Vocaluxe is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Vocaluxe is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Vocaluxe. If not, see .
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Windows.Forms;
+using VocaluxeLib.Menu;
+
+namespace VocaluxeLib.PartyModes.RuleRoulette
+{
+ class CWheelCard
+ {
+ public CStatic Active;
+ public CStatic Box;
+ public CText Name;
+ }
+
+ class CTableRow
+ {
+ public CStatic Highlight;
+ public CStatic Avatar;
+ public CText Pos;
+ public CText Name;
+ public CText Points;
+ }
+
+ public class CPartyScreenRuleRouletteMain : CPartyScreenRuleRoulette
+ {
+ protected override int _ScreenVersion
+ {
+ get { return 1; }
+ }
+
+ private const string _TextPopupReallyExit = "TextPopupReallyExit";
+ private const string _TextDuel = "TextDuel";
+ private const string _TextRowPos = "TextRowPos";
+ private const string _TextRowName = "TextRowName";
+ private const string _TextRowPoints = "TextRowPoints";
+ private const string _TextCardName = "TextCardName";
+ private const string _TextCardNameResult = "TextCardNameResult";
+ private const string _TextName1 = "TextName1";
+ private const string _TextName2 = "TextName2";
+ private const string _TextVersus = "TextVersus";
+ private const string _TextRuleTitle = "TextRuleTitle";
+ private const string _TextRuleDescription = "TextRuleDescription";
+ private const string _TextRuleInfo = "TextRuleInfo";
+ private const string _TextLastResult = "TextLastResult";
+ private const string _TextChampionLabel = "TextChampionLabel";
+ private const string _TextChampion = "TextChampion";
+
+ private const string _ButtonNextRound = "ButtonNextRound";
+ private const string _ButtonBack = "ButtonBack";
+ private const string _ButtonExit = "ButtonExit";
+ private const string _ButtonPopupYes = "ButtonPopupYes";
+ private const string _ButtonPopupNo = "ButtonPopupNo";
+
+ private const string _StaticPopupBG = "StaticPopupBG";
+ private const string _StaticRowHighlight = "StaticRowHighlight";
+ private const string _StaticRowAvatar = "StaticRowAvatar";
+ private const string _StaticCardBox = "StaticCardBox";
+ private const string _StaticCardActive = "StaticCardActive";
+ private const string _StaticAvatar1 = "StaticAvatar1";
+ private const string _StaticAvatar2 = "StaticAvatar2";
+
+ private const string _ParticleEffectChampion = "ParticleEffectChampion";
+
+ private const float _WheelCenterX = 1240f;
+ private const float _WheelCenterY = 530f;
+ private const float _WheelRadiusX = 545f;
+ private const float _WheelRadiusY = 320f;
+ private const float _ActiveBorder = 4f;
+ private const float _UsedAlpha = 0.35f;
+ private const float _FirstRowY = 255f;
+ private const float _RowStep = 52f;
+ private const float _PopDuration = 700f;
+ private const int _SpinRounds = 3;
+ private const float _SpinMinStep = 50f;
+ private const float _SpinMaxStep = 420f;
+
+ private readonly string[] _RankColors = new string[] { "Top1Color", "Top2Color", "Top3Color" };
+
+ private bool _ExitPopupVisible;
+ private List _Cards;
+ private List _Rows;
+ private readonly Stopwatch _AnimTimer = new Stopwatch();
+ private readonly Stopwatch _SpinTimer = new Stopwatch();
+ private List _SpinSteps;
+ private int _SpinTarget = -1;
+ private bool _Spinning;
+ private int _LastSeenDuels = -1;
+ private bool _ShowNewResult;
+ private float _LastResultHeight;
+ private float _ChampionHeight;
+ private SRectF _Avatar1Rect;
+
+ public override void Init()
+ {
+ base.Init();
+
+ _ThemeTexts = new string[]
+ {
+ _TextPopupReallyExit, _TextDuel, _TextRowPos, _TextRowName, _TextRowPoints, _TextCardName, _TextCardNameResult, _TextName1, _TextName2, _TextVersus,
+ _TextRuleTitle, _TextRuleDescription, _TextRuleInfo, _TextLastResult, _TextChampionLabel, _TextChampion
+ };
+ _ThemeButtons = new string[] { _ButtonNextRound, _ButtonBack, _ButtonExit, _ButtonPopupYes, _ButtonPopupNo };
+ _ThemeStatics = new string[] { _StaticPopupBG, _StaticRowHighlight, _StaticRowAvatar, _StaticCardBox, _StaticCardActive, _StaticAvatar1, _StaticAvatar2 };
+ _ThemeParticleEffects = new string[] { _ParticleEffectChampion };
+ }
+
+ public override void LoadTheme(string xmlPath)
+ {
+ base.LoadTheme(xmlPath);
+
+ _Cards = new List();
+ for (var i = 0; i < CPartyModeRuleRoulette.MaxRulesOnWheel; i++)
+ {
+ var card = new CWheelCard
+ {
+ Active = GetNewStatic(_Statics[_StaticCardActive]),
+ Box = GetNewStatic(_Statics[_StaticCardBox]),
+ Name = GetNewText(_Texts[_TextCardName])
+ };
+ _AddStatic(card.Active);
+ _AddStatic(card.Box);
+ _AddText(card.Name);
+ _Cards.Add(card);
+ }
+
+ _Rows = new List();
+ for (var i = 0; i < _PartyMode.MaxPlayers; i++)
+ {
+ var row = new CTableRow
+ {
+ Highlight = GetNewStatic(_Statics[_StaticRowHighlight]),
+ Avatar = GetNewStatic(_Statics[_StaticRowAvatar]),
+ Pos = GetNewText(_Texts[_TextRowPos]),
+ Name = GetNewText(_Texts[_TextRowName]),
+ Points = GetNewText(_Texts[_TextRowPoints])
+ };
+ row.Avatar.Aspect = EAspect.Crop;
+ var y = _FirstRowY + i * _RowStep;
+ row.Highlight.Y = y + (row.Name.H - row.Highlight.H) / 2;
+ row.Avatar.Y = y + (row.Name.H - row.Avatar.H) / 2;
+ row.Pos.Y = y;
+ row.Name.Y = y;
+ row.Points.Y = y;
+ _AddStatic(row.Highlight);
+ _AddStatic(row.Avatar);
+ _AddText(row.Pos);
+ _AddText(row.Name);
+ _AddText(row.Points);
+ _Rows.Add(row);
+ }
+
+ _Statics[_StaticCardBox].Visible = false;
+ _Statics[_StaticCardActive].Visible = false;
+ _Statics[_StaticRowHighlight].Visible = false;
+ _Statics[_StaticRowAvatar].Visible = false;
+ _Texts[_TextCardName].Visible = false;
+ _Texts[_TextCardNameResult].Visible = false;
+ _Texts[_TextRowPos].Visible = false;
+ _Texts[_TextRowName].Visible = false;
+ _Texts[_TextRowPoints].Visible = false;
+ _Statics[_StaticAvatar1].Aspect = EAspect.Crop;
+ _Statics[_StaticAvatar2].Aspect = EAspect.Crop;
+ _Avatar1Rect = _Statics[_StaticAvatar1].Rect;
+
+ _LastResultHeight = _Texts[_TextLastResult].Font.Height;
+ _ChampionHeight = _Texts[_TextChampion].Font.Height;
+ }
+
+ public override bool HandleInput(SKeyEvent keyEvent)
+ {
+ base.HandleInput(keyEvent);
+
+ if (!keyEvent.KeyPressed)
+ {
+ switch (keyEvent.Key)
+ {
+ case Keys.Back:
+ case Keys.Escape:
+ _OnBack();
+ break;
+
+ case Keys.Enter:
+ _OnSelect();
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ public override bool HandleMouse(SMouseEvent mouseEvent)
+ {
+ base.HandleMouse(mouseEvent);
+
+ if (mouseEvent.LB && _IsMouseOverCurSelection(mouseEvent))
+ {
+ _OnSelect();
+ }
+
+ if (mouseEvent.RB)
+ {
+ _OnBack();
+ }
+
+ return true;
+ }
+
+ public override void OnShow()
+ {
+ base.OnShow();
+
+ _ShowNewResult = _PartyMode.GameData.LastDuel != null && _PartyMode.GameData.NumDuelsPlayed != _LastSeenDuels;
+ _LastSeenDuels = _PartyMode.GameData.NumDuelsPlayed;
+ _Spinning = false;
+ _SpinTarget = _PartyMode.GameData.RuleChosen && _PartyMode.CurrentDuel != null ? _PartyMode.GameData.Wheel.IndexOf(_PartyMode.CurrentDuel.Rule) : -1;
+
+ _LayoutCards();
+ _UpdateTable();
+ _UpdateCenter();
+ _UpdateCards(_SpinTarget);
+ _UpdateButtons();
+
+ _AnimTimer.Reset();
+ _AnimTimer.Start();
+ _UpdateAnimations();
+
+ _ShowPopup(false);
+ }
+
+ public override bool UpdateGame()
+ {
+ if (_Spinning)
+ {
+ _UpdateSpin();
+ }
+
+ _UpdateAnimations();
+ return true;
+ }
+
+ private void _OnBack()
+ {
+ if (_Spinning)
+ {
+ return;
+ }
+
+ if (_ExitPopupVisible)
+ {
+ _ShowPopup(false);
+ }
+ else if (_PartyMode.GameData.NumDuelsPlayed == 0 && !_PartyMode.GameData.RuleChosen)
+ {
+ _PartyMode.Back();
+ }
+ else if (_PartyMode.IsFinished)
+ {
+ _EndParty();
+ }
+ else
+ {
+ _ShowPopup(true);
+ }
+ }
+
+ private void _OnSelect()
+ {
+ if (_Spinning)
+ {
+ return;
+ }
+
+ if (_ExitPopupVisible)
+ {
+ if (_Buttons[_ButtonPopupYes].Selected)
+ {
+ _EndParty();
+ }
+
+ if (_Buttons[_ButtonPopupNo].Selected)
+ {
+ _ShowPopup(false);
+ }
+
+ return;
+ }
+
+ if (_Buttons[_ButtonNextRound].Selected && _Buttons[_ButtonNextRound].Visible)
+ {
+ if (_PartyMode.GameData.RuleChosen)
+ {
+ _PartyMode.Next();
+ }
+ else
+ {
+ _StartSpin();
+ }
+ }
+
+ if (_Buttons[_ButtonBack].Selected && _Buttons[_ButtonBack].Visible)
+ {
+ _PartyMode.Back();
+ }
+
+ if (_Buttons[_ButtonExit].Selected && _Buttons[_ButtonExit].Visible)
+ {
+ if (_PartyMode.IsFinished)
+ {
+ _EndParty();
+ }
+ else
+ {
+ _ShowPopup(true);
+ }
+ }
+ }
+
+ private void _EndParty()
+ {
+ CBase.Graphics.FadeTo(EScreen.Party);
+ }
+
+ private void _ShowPopup(bool visible)
+ {
+ _ExitPopupVisible = visible;
+
+ _Statics[_StaticPopupBG].Visible = _ExitPopupVisible;
+ _Texts[_TextPopupReallyExit].Visible = _ExitPopupVisible;
+ _Buttons[_ButtonPopupYes].Visible = _ExitPopupVisible;
+ _Buttons[_ButtonPopupNo].Visible = _ExitPopupVisible;
+
+ if (_ExitPopupVisible)
+ {
+ _SelectElement(_Buttons[_ButtonPopupNo]);
+ }
+ }
+
+ private void _UpdateButtons()
+ {
+ var started = _PartyMode.GameData.NumDuelsPlayed > 0 || _PartyMode.GameData.RuleChosen;
+ _Buttons[_ButtonBack].Visible = !started;
+ _Buttons[_ButtonExit].Visible = started;
+ _Buttons[_ButtonNextRound].Visible = !_PartyMode.IsFinished;
+ _Buttons[_ButtonNextRound].Text.Text = _PartyMode.GameData.RuleChosen ? "TR_BUTTON_START_DUEL" : "TR_BUTTON_SPIN";
+
+ if (_PartyMode.IsFinished)
+ {
+ _SelectElement(_Buttons[_ButtonExit]);
+ }
+ else
+ {
+ _SelectElement(_Buttons[_ButtonNextRound]);
+ }
+ }
+
+ private static float _EaseOutBack(float t)
+ {
+ const float c1 = 1.70158f;
+ const float c3 = c1 + 1f;
+ return 1f + c3 * (float)Math.Pow(t - 1f, 3) + c1 * (float)Math.Pow(t - 1f, 2);
+ }
+
+ private string _GetName(CRoulettePlayer player)
+ {
+ return CBase.Profiles.GetPlayerName(player.ProfileId);
+ }
+
+ private string _Translate(string key)
+ {
+ return CBase.Language.Translate(key, PartyModeId);
+ }
+
+ private void _LayoutCards()
+ {
+ var wheel = _PartyMode.GameData.Wheel;
+ var box = _Statics[_StaticCardBox].Rect;
+ for (var i = 0; i < _Cards.Count; i++)
+ {
+ var card = _Cards[i];
+ var visible = !_PartyMode.IsFinished && i < wheel.Count;
+ card.Box.Visible = visible;
+ card.Name.Visible = visible;
+ card.Active.Visible = false;
+ if (!visible)
+ {
+ continue;
+ }
+
+ var angle = 2 * Math.PI * i / wheel.Count - Math.PI / 2;
+ var centerX = _WheelCenterX + _WheelRadiusX * (float)Math.Cos(angle);
+ var centerY = _WheelCenterY + _WheelRadiusY * (float)Math.Sin(angle);
+ card.Box.MaxRect = new SRectF(centerX - box.W / 2, centerY - box.H / 2, box.W, box.H, box.Z);
+ card.Active.MaxRect = new SRectF(centerX - box.W / 2 - _ActiveBorder, centerY - box.H / 2 - _ActiveBorder, box.W + 2 * _ActiveBorder,
+ box.H + 2 * _ActiveBorder, _Statics[_StaticCardActive].Rect.Z);
+ card.Name.X = centerX;
+ card.Name.Y = centerY - card.Name.H / 2;
+ card.Name.Text = CPartyModeRuleRoulette.GetRuleKey(wheel[i]);
+ }
+ }
+
+ private void _UpdateCards(int highlight)
+ {
+ var wheel = _PartyMode.GameData.Wheel;
+ for (var i = 0; i < _Cards.Count && i < wheel.Count; i++)
+ {
+ var card = _Cards[i];
+ if (!card.Box.Visible)
+ {
+ continue;
+ }
+
+ var used = _PartyMode.GameData.UsedInCycle.Contains(wheel[i]) && i != _SpinTarget;
+ var isResult = !_Spinning && i == _SpinTarget;
+ var alpha = used && i != highlight ? _UsedAlpha : 1f;
+ var boxColor = _Statics[_StaticCardBox].Color;
+ card.Box.Color = new SColorF(boxColor.R, boxColor.G, boxColor.B, boxColor.A * alpha);
+ var nameColor = isResult ? _Texts[_TextCardNameResult].Color : _Texts[_TextCardName].Color;
+ card.Name.Color = new SColorF(nameColor.R, nameColor.G, nameColor.B, nameColor.A * alpha);
+ card.Active.Visible = i == highlight;
+ }
+ }
+
+ private void _StartSpin()
+ {
+ int target;
+ _PartyMode.SpinWheel(out target);
+ if (target < 0)
+ {
+ return;
+ }
+
+ _SpinTarget = target;
+ var count = _PartyMode.GameData.Wheel.Count;
+ var steps = _SpinRounds * count + target;
+ _SpinSteps = new List();
+ var time = 0f;
+ for (var i = 0; i < steps; i++)
+ {
+ var progress = (float)i / steps;
+ time += _SpinMinStep + (_SpinMaxStep - _SpinMinStep) * progress * progress;
+ _SpinSteps.Add(time);
+ }
+
+ _Spinning = true;
+ _SpinTimer.Reset();
+ _SpinTimer.Start();
+ _UpdateCenter();
+ _Buttons[_ButtonNextRound].Visible = false;
+ _Buttons[_ButtonBack].Visible = false;
+ _Buttons[_ButtonExit].Visible = true;
+ }
+
+ private void _UpdateSpin()
+ {
+ var elapsed = _SpinTimer.ElapsedMilliseconds;
+ var step = 0;
+ while (step < _SpinSteps.Count && _SpinSteps[step] <= elapsed)
+ {
+ step++;
+ }
+
+ var count = _PartyMode.GameData.Wheel.Count;
+ if (step >= _SpinSteps.Count && elapsed >= _SpinSteps[_SpinSteps.Count - 1] + 300f)
+ {
+ _Spinning = false;
+ _UpdateCards(_SpinTarget);
+ _UpdateCenter();
+ _UpdateButtons();
+ _AnimTimer.Reset();
+ _AnimTimer.Start();
+ return;
+ }
+
+ _UpdateCards(step % count);
+ }
+
+ private void _UpdateTable()
+ {
+ var ranking = _PartyMode.GetRanking();
+ for (var i = 0; i < _Rows.Count; i++)
+ {
+ var row = _Rows[i];
+ var visible = i < ranking.Count;
+ row.Avatar.Visible = visible;
+ row.Pos.Visible = visible;
+ row.Name.Visible = visible;
+ row.Points.Visible = visible;
+ row.Highlight.Visible = visible && i == 0 && ranking[0].TablePoints > 0;
+ if (!visible)
+ {
+ continue;
+ }
+
+ var player = ranking[i];
+ row.Avatar.Texture = CBase.Profiles.GetAvatar(player.ProfileId);
+ row.Pos.Text = (i + 1) + ".";
+ row.Name.Text = _GetName(player);
+ row.Points.Text = player.TablePoints.ToString();
+
+ SColorF rankColor;
+ var color = _Texts[_TextRowName].Color;
+ if (_PartyMode.GameData.NumDuelsPlayed > 0 && i < _RankColors.Length && CBase.Themes.GetColor(_RankColors[i], PartyModeId, out rankColor))
+ {
+ color = rankColor;
+ }
+
+ row.Pos.Color = color;
+ row.Name.Color = color;
+ }
+ }
+
+ private void _UpdateCenter()
+ {
+ var finished = _PartyMode.IsFinished;
+ var duel = _PartyMode.CurrentDuel;
+ var showDuel = !finished && duel != null;
+ var showRule = showDuel && _PartyMode.GameData.RuleChosen && !_Spinning;
+
+ _Statics[_StaticAvatar2].Visible = showDuel;
+ _Texts[_TextName1].Visible = showDuel;
+ _Texts[_TextName2].Visible = showDuel;
+ _Texts[_TextVersus].Visible = showDuel;
+ _Texts[_TextDuel].Visible = showDuel;
+ _Texts[_TextRuleTitle].Visible = showRule;
+ _Texts[_TextRuleDescription].Visible = showRule;
+ _Texts[_TextRuleInfo].Visible = false;
+ _Texts[_TextLastResult].Visible = !finished && _PartyMode.GameData.LastDuel != null && !showRule && !_Spinning;
+
+ _Statics[_StaticAvatar1].MaxRect = _Avatar1Rect;
+ _Statics[_StaticAvatar1].Visible = showDuel || finished;
+
+ if (showDuel)
+ {
+ _Statics[_StaticAvatar1].Texture = CBase.Profiles.GetAvatar(duel.Players[0].ProfileId);
+ _Statics[_StaticAvatar2].Texture = CBase.Profiles.GetAvatar(duel.Players[1].ProfileId);
+ _Texts[_TextName1].Text = _GetName(duel.Players[0]);
+ _Texts[_TextName2].Text = _GetName(duel.Players[1]);
+ _Texts[_TextDuel].Text = _Translate("TR_SCREENMAIN_DUEL") + " " + (_PartyMode.GameData.DuelIndex + 1) + " / " + _PartyMode.GameData.Duels.Count;
+ }
+
+ if (showRule)
+ {
+ var key = CPartyModeRuleRoulette.GetRuleKey(duel.Rule);
+ _Texts[_TextRuleTitle].Text = key;
+ _Texts[_TextRuleDescription].Text = key + "_DESC";
+ if (duel.Rule == ERule.TargetScore)
+ {
+ _Texts[_TextRuleInfo].Visible = true;
+ _Texts[_TextRuleInfo].Text = _Translate("TR_SCREENMAIN_TARGET") + " " + duel.TargetScore.ToString("N0");
+ }
+ else if (duel.Rule == ERule.UnderdogChoice && duel.Chooser != null)
+ {
+ _Texts[_TextRuleInfo].Visible = true;
+ _Texts[_TextRuleInfo].Text = _Translate("TR_SCREENMAIN_CHOOSER").Replace("%s", _GetName(duel.Chooser));
+ }
+ else if (duel.Rule == ERule.JokerRain)
+ {
+ _Texts[_TextRuleInfo].Visible = true;
+ _Texts[_TextRuleInfo].Text = _Translate("TR_SCREENMAIN_JOKERS") + " " + _GetName(duel.Players[0]) + " " + duel.Players[0].Jokers + " · " +
+ _GetName(duel.Players[1]) + " " + duel.Players[1].Jokers;
+ }
+ }
+
+ var last = _PartyMode.GameData.LastDuel;
+ if (last != null)
+ {
+ _Texts[_TextLastResult].Text = _Translate("TR_SCREENMAIN_LAST_DUEL") + " " + _GetName(last.Players[0]) + " " + last.Scores[0].ToString("N0") + " (" +
+ _FormatAward(last.Awarded[0]) + ") · " + _GetName(last.Players[1]) + " " + last.Scores[1].ToString("N0") + " (" +
+ _FormatAward(last.Awarded[1]) + ")";
+ }
+
+ _Texts[_TextChampionLabel].Visible = finished;
+ _Texts[_TextChampion].Visible = finished;
+ _ParticleEffects[_ParticleEffectChampion].Visible = finished;
+ if (finished)
+ {
+ var champion = _PartyMode.GetRanking()[0];
+ _Texts[_TextChampion].Text = _GetName(champion);
+ _Statics[_StaticAvatar1].Texture = CBase.Profiles.GetAvatar(champion.ProfileId);
+ _Statics[_StaticAvatar1].X = _WheelCenterX - _Avatar1Rect.W / 2;
+ _Statics[_StaticAvatar1].Y = _Avatar1Rect.Y + 80f;
+ }
+ }
+
+ private static string _FormatAward(int award)
+ {
+ return award > 0 ? "+" + award : award.ToString();
+ }
+
+ private void _UpdateAnimations()
+ {
+ var time = _AnimTimer.ElapsedMilliseconds;
+ var pop = time < _PopDuration ? 0.3f + 0.7f * _EaseOutBack(time / _PopDuration) : 1f;
+
+ _Texts[_TextLastResult].Font.Height = _LastResultHeight * (_ShowNewResult ? pop : 1f);
+
+ if (_PartyMode.IsFinished)
+ {
+ var scale = time < _PopDuration ? pop : 1f + 0.03f * (float)Math.Sin((time - _PopDuration) / 1000.0 * Math.PI * 1.4);
+ _Texts[_TextChampion].Font.Height = _ChampionHeight * scale;
+ }
+ }
+ }
+}
diff --git a/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteNames.cs b/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteNames.cs
new file mode 100644
index 00000000..c040f8b4
--- /dev/null
+++ b/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteNames.cs
@@ -0,0 +1,74 @@
+#region license
+// This file is part of Vocaluxe.
+//
+// Vocaluxe is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Vocaluxe is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Vocaluxe. If not, see .
+#endregion
+
+using System;
+using System.Collections.Generic;
+using VocaluxeLib.Menu;
+
+namespace VocaluxeLib.PartyModes.RuleRoulette
+{
+ public class CPartyScreenRuleRouletteNames : CMenuPartyNameSelection
+ {
+ protected override int _ScreenVersion
+ {
+ get { return 1; }
+ }
+
+ private new CPartyModeRuleRoulette _PartyMode;
+
+ public override void Init()
+ {
+ base.Init();
+ _PartyMode = (CPartyModeRuleRoulette)base._PartyMode;
+ _AllowChangePlayerNum = false;
+ _Teams = false;
+ }
+
+ public override void OnShow()
+ {
+ base.OnShow();
+ SetPartyModeData(_PartyMode.GameData.NumPlayer);
+ while (_PartyMode.GameData.ProfileIds.Count > _NumPlayer)
+ {
+ _PartyMode.GameData.ProfileIds.RemoveAt(_PartyMode.GameData.ProfileIds.Count - 1);
+ }
+
+ var ids = new List[] { _PartyMode.GameData.ProfileIds };
+ SetPartyModeProfiles(ids);
+ }
+
+ public override void Back()
+ {
+ if (_TeamList.Length == 1)
+ {
+ _PartyMode.GameData.ProfileIds = _TeamList[0];
+ }
+
+ _PartyMode.Back();
+ }
+
+ public override void Next()
+ {
+ if (_TeamList.Length == 1)
+ {
+ _PartyMode.GameData.ProfileIds = _TeamList[0];
+ }
+
+ _PartyMode.Next();
+ }
+ }
+}
\ No newline at end of file
diff --git a/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteRules.cs b/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteRules.cs
new file mode 100644
index 00000000..324d37d7
--- /dev/null
+++ b/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteRules.cs
@@ -0,0 +1,361 @@
+#region license
+// This file is part of Vocaluxe.
+//
+// Vocaluxe is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Vocaluxe is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Vocaluxe. If not, see .
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Forms;
+using VocaluxeLib.Menu;
+
+namespace VocaluxeLib.PartyModes.RuleRoulette
+{
+ class CRuleRow
+ {
+ public ERule Rule;
+ public CStatic Highlight;
+ public CText Name;
+ public CText Description;
+ public CText State;
+ public CStatic ArrowLeft;
+ public CStatic ArrowRight;
+ }
+
+ public class CPartyScreenRuleRouletteRules : CPartyScreenRuleRoulette
+ {
+ protected override int _ScreenVersion
+ {
+ get { return 1; }
+ }
+
+ private const string _ButtonNext = "ButtonNext";
+ private const string _ButtonBack = "ButtonBack";
+
+ private const string _StaticRowHighlight = "StaticRowHighlight";
+ private const string _StaticArrowLeft = "StaticArrowLeft";
+ private const string _StaticArrowRight = "StaticArrowRight";
+ private const string _TextRuleName = "TextRuleName";
+ private const string _TextRuleDescription = "TextRuleDescription";
+ private const string _TextRuleState = "TextRuleState";
+ private const string _TextStateAlways = "TextStateAlways";
+ private const string _TextStateRandom = "TextStateRandom";
+ private const string _TextStateNever = "TextStateNever";
+
+ private const float _FirstRowY = 190f;
+ private const float _RowStep = 68f;
+ private const float _DescriptionOffset = 33f;
+ private const int _RowsPerColumn = 10;
+ private const float _StateWidth = 190f;
+ private const float _ArrowSpace = 6f;
+
+ private readonly float[] _ColumnX = new float[] { 50f, 980f };
+
+ private List _Rows;
+ private int _SelectedRow;
+ private bool _OnButtons;
+
+ public override void Init()
+ {
+ base.Init();
+
+ _ThemeButtons = new string[] { _ButtonNext, _ButtonBack };
+ _ThemeStatics = new string[] { _StaticRowHighlight, _StaticArrowLeft, _StaticArrowRight };
+ _ThemeTexts = new string[] { _TextRuleName, _TextRuleDescription, _TextRuleState, _TextStateAlways, _TextStateRandom, _TextStateNever };
+ }
+
+ public override void LoadTheme(string xmlPath)
+ {
+ base.LoadTheme(xmlPath);
+
+ _Rows = new List();
+ var rules = Enum.GetValues(typeof(ERule)).Cast().ToList();
+ for (var i = 0; i < rules.Count; i++)
+ {
+ var x = _ColumnX[i / _RowsPerColumn];
+ var y = _FirstRowY + (i % _RowsPerColumn) * _RowStep;
+ var row = new CRuleRow
+ {
+ Rule = rules[i],
+ Highlight = GetNewStatic(_Statics[_StaticRowHighlight]),
+ Name = GetNewText(_Texts[_TextRuleName]),
+ Description = GetNewText(_Texts[_TextRuleDescription]),
+ State = GetNewText(_Texts[_TextRuleState]),
+ ArrowLeft = GetNewStatic(_Statics[_StaticArrowLeft]),
+ ArrowRight = GetNewStatic(_Statics[_StaticArrowRight])
+ };
+
+ var highlight = _Statics[_StaticRowHighlight].Rect;
+ row.Highlight.MaxRect = new SRectF(x, y, highlight.W, highlight.H, highlight.Z);
+ row.Name.X = x + 16f;
+ row.Name.Y = y + 3f;
+ row.Description.X = x + 16f;
+ row.Description.Y = y + _DescriptionOffset;
+ var arrow = _Statics[_StaticArrowRight].Rect;
+ var arrowY = y + (highlight.H - arrow.H) / 2;
+ var rightX = x + highlight.W - 12f - arrow.W;
+ var leftX = rightX - _ArrowSpace - _StateWidth - _ArrowSpace - arrow.W;
+ row.ArrowRight.MaxRect = new SRectF(rightX, arrowY, arrow.W, arrow.H, arrow.Z);
+ row.ArrowLeft.MaxRect = new SRectF(leftX, arrowY, arrow.W, arrow.H, arrow.Z);
+ row.State.X = leftX + arrow.W + _ArrowSpace + _StateWidth / 2;
+ row.State.Y = y + (highlight.H - row.State.H) / 2;
+ row.Name.Text = CPartyModeRuleRoulette.GetRuleKey(row.Rule);
+ row.Description.Text = CPartyModeRuleRoulette.GetRuleKey(row.Rule) + "_DESC";
+
+ _AddStatic(row.Highlight);
+ _AddText(row.Name);
+ _AddText(row.Description);
+ _AddText(row.State);
+ _AddStatic(row.ArrowLeft);
+ _AddStatic(row.ArrowRight);
+ _Rows.Add(row);
+ }
+
+ _Statics[_StaticRowHighlight].Visible = false;
+ _Statics[_StaticArrowLeft].Visible = false;
+ _Statics[_StaticArrowRight].Visible = false;
+ _Texts[_TextRuleName].Visible = false;
+ _Texts[_TextRuleDescription].Visible = false;
+ _Texts[_TextRuleState].Visible = false;
+ _Texts[_TextStateAlways].Visible = false;
+ _Texts[_TextStateRandom].Visible = false;
+ _Texts[_TextStateNever].Visible = false;
+ }
+
+ public override void OnShow()
+ {
+ base.OnShow();
+
+ _SelectedRow = 0;
+ _UpdateRows();
+ _FocusRows();
+ }
+
+ public override bool UpdateGame()
+ {
+ return true;
+ }
+
+ public override bool HandleInput(SKeyEvent keyEvent)
+ {
+ if (keyEvent.KeyPressed)
+ {
+ return true;
+ }
+
+ switch (keyEvent.Key)
+ {
+ case Keys.Back:
+ case Keys.Escape:
+ _PartyMode.Back();
+ break;
+
+ case Keys.Enter:
+ if (_OnButtons && _Buttons[_ButtonBack].Selected)
+ {
+ _PartyMode.Back();
+ }
+ else
+ {
+ _PartyMode.Next();
+ }
+
+ break;
+
+ case Keys.Up:
+ if (_OnButtons)
+ {
+ _FocusRows();
+ }
+ else
+ {
+ _SelectRow(_SelectedRow - 1);
+ }
+
+ break;
+
+ case Keys.Down:
+ if (_OnButtons)
+ {
+ break;
+ }
+
+ if (_SelectedRow + 1 < _Rows.Count)
+ {
+ _SelectRow(_SelectedRow + 1);
+ }
+ else
+ {
+ _FocusButtons();
+ }
+
+ break;
+
+ case Keys.Left:
+ case Keys.Right:
+ if (_OnButtons)
+ {
+ _SwitchButton();
+ }
+ else
+ {
+ _ChangeState(_SelectedRow, keyEvent.Key == Keys.Left ? -1 : 1);
+ }
+
+ break;
+
+ case Keys.Tab:
+ if (_OnButtons)
+ {
+ _SwitchButton();
+ }
+ else
+ {
+ _FocusButtons();
+ }
+
+ break;
+ }
+
+ return true;
+ }
+
+ public override bool HandleMouse(SMouseEvent mouseEvent)
+ {
+ base.HandleMouse(mouseEvent);
+ if (_Buttons[_ButtonBack].Selected || _Buttons[_ButtonNext].Selected)
+ {
+ _OnButtons = true;
+ }
+
+ if (mouseEvent.LB)
+ {
+ for (var r = 0; r < _Rows.Count; r++)
+ {
+ if (CHelper.IsInBounds(_Rows[r].ArrowLeft.Rect, mouseEvent))
+ {
+ _SelectRow(r);
+ _ChangeState(r, -1);
+ return true;
+ }
+
+ if (CHelper.IsInBounds(_Rows[r].Highlight.Rect, mouseEvent))
+ {
+ _SelectRow(r);
+ _ChangeState(r, 1);
+ return true;
+ }
+ }
+
+ if (_IsMouseOverCurSelection(mouseEvent))
+ {
+ if (_Buttons[_ButtonBack].Selected)
+ {
+ _PartyMode.Back();
+ }
+
+ if (_Buttons[_ButtonNext].Selected)
+ {
+ _PartyMode.Next();
+ }
+ }
+ }
+
+ if (mouseEvent.RB)
+ {
+ _PartyMode.Back();
+ }
+
+ return true;
+ }
+
+ private void _FocusRows()
+ {
+ _OnButtons = false;
+ _Buttons[_ButtonBack].Selected = false;
+ _Buttons[_ButtonNext].Selected = false;
+ _UpdateRows();
+ }
+
+ private void _FocusButtons()
+ {
+ _OnButtons = true;
+ _SelectElement(_Buttons[_ButtonNext]);
+ _UpdateRows();
+ }
+
+ private void _SwitchButton()
+ {
+ _SelectElement(_Buttons[_ButtonNext].Selected ? _Buttons[_ButtonBack] : _Buttons[_ButtonNext]);
+ }
+
+ private void _SelectRow(int row)
+ {
+ _OnButtons = false;
+ _Buttons[_ButtonBack].Selected = false;
+ _Buttons[_ButtonNext].Selected = false;
+ _SelectedRow = Math.Max(0, Math.Min(_Rows.Count - 1, row));
+ _UpdateRows();
+ }
+
+ private void _ChangeState(int row, int direction)
+ {
+ if (row < 0 || row >= _Rows.Count)
+ {
+ return;
+ }
+
+ var rule = _Rows[row].Rule;
+ var order = new ERuleState[] { ERuleState.Never, ERuleState.Random, ERuleState.Always };
+ var index = Array.IndexOf(order, _PartyMode.GameData.RuleStates[rule]);
+ index = (index + direction + order.Length) % order.Length;
+ _PartyMode.GameData.RuleStates[rule] = order[index];
+ _UpdateRows();
+ }
+
+ private void _UpdateRows()
+ {
+ for (var r = 0; r < _Rows.Count; r++)
+ {
+ var row = _Rows[r];
+ row.Highlight.Visible = r == _SelectedRow && !_OnButtons;
+ var arrowColor = r == _SelectedRow && !_OnButtons ? _Statics[_StaticArrowRight].Color : _Statics[_StaticArrowLeft].Color;
+ row.ArrowLeft.Color = arrowColor;
+ row.ArrowRight.Color = arrowColor;
+
+ CText template;
+ switch (_PartyMode.GameData.RuleStates[row.Rule])
+ {
+ case ERuleState.Always:
+ template = _Texts[_TextStateAlways];
+ break;
+ case ERuleState.Never:
+ template = _Texts[_TextStateNever];
+ break;
+ default:
+ template = _Texts[_TextStateRandom];
+ break;
+ }
+
+ row.State.Text = template.Text;
+ row.State.Color = template.Color;
+ var nameColor = _Texts[_TextRuleName].Color;
+ row.Name.Color = _PartyMode.GameData.RuleStates[row.Rule] == ERuleState.Never
+ ? new SColorF(nameColor.R, nameColor.G, nameColor.B, nameColor.A * 0.45f)
+ : nameColor;
+ }
+ }
+ }
+}
diff --git a/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteSongs.cs b/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteSongs.cs
new file mode 100644
index 00000000..934af99a
--- /dev/null
+++ b/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteSongs.cs
@@ -0,0 +1,66 @@
+using VocaluxeLib.Menu;
+
+namespace VocaluxeLib.PartyModes.RuleRoulette
+{
+ class CPartyScreenRuleRouletteSongs : CMenuPartySongSelection
+ {
+ protected override int _ScreenVersion
+ {
+ get { return 1; }
+ }
+
+ private new CPartyModeRuleRoulette _PartyMode;
+
+ public override void Init()
+ {
+ base.Init();
+ _PartyMode = (CPartyModeRuleRoulette)base._PartyMode;
+ }
+
+ public override void OnShow()
+ {
+ Source = _PartyMode.GameData.SongSource;
+ Sorting = _PartyMode.GameData.Sorting;
+ Category = _PartyMode.GameData.CategoryIndex;
+ Playlist = _PartyMode.GameData.PlaylistId;
+ SongMode = _PartyMode.GameData.GameMode;
+ NumMedleySongs = _PartyMode.GameData.NumMedleySongs;
+
+ base.OnShow();
+ }
+
+ public override void Back()
+ {
+ _SaveConfig();
+ _PartyMode.Back();
+ }
+
+ public override void Next()
+ {
+ _SaveConfig();
+ _PartyMode.Next();
+ }
+
+ public override bool UpdateGame()
+ {
+ return true;
+ }
+
+ protected override void _SetAllowedOptions()
+ {
+ base._SetAllowedOptions();
+
+ AllowedSongModes = new EGameMode[] { EGameMode.TR_GAMEMODE_NORMAL, EGameMode.TR_GAMEMODE_SHORTSONG };
+ }
+
+ private void _SaveConfig()
+ {
+ _PartyMode.GameData.SongSource = Source;
+ _PartyMode.GameData.Sorting = Sorting;
+ _PartyMode.GameData.CategoryIndex = Category;
+ _PartyMode.GameData.PlaylistId = Playlist;
+ _PartyMode.GameData.GameMode = SongMode;
+ _PartyMode.GameData.NumMedleySongs = NumMedleySongs;
+ }
+ }
+}
\ No newline at end of file
diff --git a/PartyModes/PartyModeRuleRoulette/GlobalSuppressions.cs b/PartyModes/PartyModeRuleRoulette/GlobalSuppressions.cs
new file mode 100644
index 00000000..e661ff03
Binary files /dev/null and b/PartyModes/PartyModeRuleRoulette/GlobalSuppressions.cs differ
diff --git a/PartyModes/PartyModeRuleRoulette/PartyModeRuleRoulette.csproj b/PartyModes/PartyModeRuleRoulette/PartyModeRuleRoulette.csproj
new file mode 100644
index 00000000..efadd403
--- /dev/null
+++ b/PartyModes/PartyModeRuleRoulette/PartyModeRuleRoulette.csproj
@@ -0,0 +1,119 @@
+
+
+
+ DebugWin
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}
+ Library
+ Properties
+ VocaluxeLib.PartyModes.RuleRoulette
+ RuleRoulette
+ v4.8.1
+
+
+ 512
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin
+ TRACE;DEBUG;WIN
+ prompt
+ 4
+ Off
+ ..\..\CheckingRules.ruleset
+ false
+ 9.0
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin
+ prompt
+ 4
+ false
+ WIN
+ Off
+ false
+ true
+ 9.0
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin
+ TRACE;DEBUG;LINUX
+ prompt
+ 4
+ Off
+ false
+ 9.0
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin
+ prompt
+ 4
+ false
+ LINUX
+ Off
+ false
+ true
+ 9.0
+
+
+ bin
+ WIN
+ true
+ AnyCPU
+ false
+ Off
+ prompt
+ ManagedMinimumRules.ruleset
+ false
+ pdbonly
+ true
+ 9.0
+
+
+
+
+
+
+
+
+
+
+
+
+ {D2B6DDEF-E0E1-4DDF-A73A-E6ACEF5C95EC}
+ VocaluxeLib
+ False
+
+
+
+
+
+
+
+
+
+ if exist "$(SolutionDir)Output\PartyModes\$(TargetName)\Code" rd "$(SolutionDir)Output\PartyModes\$(TargetName)\Code" /S /Q
+cd $(SolutionDir)Partymodes\
+xcopy "$(ProjectDir)*.cs" "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\" /EXCLUDE:Excludes.txt /I /Y
+ rm -rf "$(SolutionDir)Output\PartyModes\$(TargetName)\Code"
+mkdir -p "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\"
+cp -R $(ProjectDir)*.cs "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\"
+rm -f "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\GlobalSuppressions.cs"
+
+
+
\ No newline at end of file
diff --git a/PartyModes/PartyModeRuleRoulette/Settings.StyleCop b/PartyModes/PartyModeRuleRoulette/Settings.StyleCop
new file mode 100644
index 00000000..f4a4ea2a
--- /dev/null
+++ b/PartyModes/PartyModeRuleRoulette/Settings.StyleCop
@@ -0,0 +1,809 @@
+
+
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Vocaluxe.sln b/Vocaluxe.sln
index ad929fc1..21d9d0f9 100644
--- a/Vocaluxe.sln
+++ b/Vocaluxe.sln
@@ -11,6 +11,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vocaluxe", "Vocaluxe\Vocalu
{D2B6DDEF-E0E1-4DDF-A73A-E6ACEF5C95EC} = {D2B6DDEF-E0E1-4DDF-A73A-E6ACEF5C95EC}
{5A4053F1-5AD5-441F-B254-D97A088127B6} = {5A4053F1-5AD5-441F-B254-D97A088127B6}
{54A48AF7-515C-4EDD-B395-2BFFA096050A} = {54A48AF7-515C-4EDD-B395-2BFFA096050A}
+ {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F} = {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VocaluxeLib", "VocaluxeLib\VocaluxeLib.csproj", "{D2B6DDEF-E0E1-4DDF-A73A-E6ACEF5C95EC}"
@@ -23,6 +24,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PartyModeChallenge", "Party
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebserverInitalConfig", "WebserverInitalConfig\WebserverInitalConfig.csproj", "{55F89808-4E79-4FFE-BC13-48258913F5C9}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PartyModeRuleRoulette", "PartyModes\PartyModeRuleRoulette\PartyModeRuleRoulette.csproj", "{2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}"
+EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PitchTracker", "PitchTracker\PitchTracker.vcxproj", "{54A48AF7-515C-4EDD-B395-2BFFA096050A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{8EDF4429-251A-416D-BB68-93F227191BCF}"
@@ -159,6 +162,26 @@ Global
{55F89808-4E79-4FFE-BC13-48258913F5C9}.ReleaseWin|x64.Build.0 = Release|Any CPU
{55F89808-4E79-4FFE-BC13-48258913F5C9}.ReleaseWin|x86.ActiveCfg = Release|Any CPU
{55F89808-4E79-4FFE-BC13-48258913F5C9}.ReleaseWin|x86.Build.0 = Release|Any CPU
+ {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}.DebugLinux|x64.ActiveCfg = DebugLinux|Any CPU
+ {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}.DebugLinux|x64.Build.0 = DebugLinux|Any CPU
+ {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}.DebugLinux|x86.ActiveCfg = DebugLinux|Any CPU
+ {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}.DebugLinux|x86.Build.0 = DebugLinux|Any CPU
+ {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}.DebugWin|x64.ActiveCfg = DebugWin|Any CPU
+ {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}.DebugWin|x64.Build.0 = DebugWin|Any CPU
+ {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}.DebugWin|x86.ActiveCfg = DebugWin|Any CPU
+ {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}.DebugWin|x86.Build.0 = DebugWin|Any CPU
+ {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}.InstallerWin|x64.ActiveCfg = InstallerWin|Any CPU
+ {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}.InstallerWin|x64.Build.0 = InstallerWin|Any CPU
+ {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}.InstallerWin|x86.ActiveCfg = InstallerWin|Any CPU
+ {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}.InstallerWin|x86.Build.0 = InstallerWin|Any CPU
+ {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}.ReleaseLinux|x64.ActiveCfg = ReleaseLinux|Any CPU
+ {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}.ReleaseLinux|x64.Build.0 = ReleaseLinux|Any CPU
+ {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}.ReleaseLinux|x86.ActiveCfg = ReleaseLinux|Any CPU
+ {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}.ReleaseLinux|x86.Build.0 = ReleaseLinux|Any CPU
+ {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}.ReleaseWin|x64.ActiveCfg = ReleaseWin|Any CPU
+ {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}.ReleaseWin|x64.Build.0 = ReleaseWin|Any CPU
+ {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}.ReleaseWin|x86.ActiveCfg = ReleaseWin|Any CPU
+ {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}.ReleaseWin|x86.Build.0 = ReleaseWin|Any CPU
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.DebugLinux|x64.ActiveCfg = Debug|x64
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.DebugLinux|x64.Build.0 = Debug|x64
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.DebugLinux|x86.ActiveCfg = Debug|Win32