diff --git a/Output/PartyModes/PartnerSwap.xml b/Output/PartyModes/PartnerSwap.xml
new file mode 100644
index 00000000..2b7446ac
--- /dev/null
+++ b/Output/PartyModes/PartnerSwap.xml
@@ -0,0 +1,20 @@
+
+
+ 2
+
+ TR_PARTYMODE_NAME
+ McSchneidar
+ TR_PARTYMODE_DESCRIPTION
+ PartnerSwap
+ CPartyModePartnerSwap
+ 1
+ 0
+ TR_PARTYMODE_AUDIENCE
+
+
+ CPartyScreenPartnerSwapMain
+ CPartyScreenPartnerSwapConfig
+ CPartyScreenPartnerSwapNames
+ CPartyScreenPartnerSwapSongs
+
+
diff --git a/Output/PartyModes/PartnerSwap/Languages/de.xml b/Output/PartyModes/PartnerSwap/Languages/de.xml
new file mode 100644
index 00000000..520024ee
--- /dev/null
+++ b/Output/PartyModes/PartnerSwap/Languages/de.xml
@@ -0,0 +1,42 @@
+
+
+ Deutsch
+ Partnertausch
+ Singt zu zweit mit ständig wechselnden Partnern. Beide Sänger bekommen die Punkte des Paares, der beste Durchschnitt gewinnt.
+ Für 3 bis 12 Spieler, die gern gemeinsam singen, 2 Mikrofone nötig.
+ Weiter
+ Zurück
+ Start
+ Beenden
+ Ja
+ Nein
+ Song starten
+ Songs
+ Partnertausch: Konfiguration
+ Anzahl der Spieler
+ Durchgänge
+ Gleichklang-Bonus
+ Duett-Songs
+ Gemischt
+ Nur Duette
+ Keine Duette
+ Joker pro Spieler
+ Partnertausch: Spieler auswählen
+ Zufällig wählen
+ Partnertausch
+ Willst du das Spiel wirklich beenden?
+ Pos
+ Name
+ Songs
+ Ø
+ Durchgang
+ Song
+ Pause:
+ singen zum ersten Mal zusammen
+ singen zum %n. Mal zusammen
+ Letzter Song:
+ Gleichklang-Bonus
+ Bestes Paar bisher
+ Bestes Paar des Abends
+ Sieger
+
diff --git a/Output/PartyModes/PartnerSwap/Languages/en.xml b/Output/PartyModes/PartnerSwap/Languages/en.xml
new file mode 100644
index 00000000..b904cc81
--- /dev/null
+++ b/Output/PartyModes/PartnerSwap/Languages/en.xml
@@ -0,0 +1,42 @@
+
+
+ English
+ Partner Swap
+ Sing in pairs with ever-changing partners. Both singers of a pair get the pair score, the best average wins.
+ For 3 to 12 players who like to sing together, 2 microphones needed.
+ Next
+ Back
+ Start
+ Exit
+ Yes
+ No
+ Start song
+ Songs
+ Partner Swap: Configuration
+ Number of players
+ Rounds
+ Harmony bonus
+ Duet songs
+ Mixed
+ Duets only
+ No duets
+ Jokers per player
+ Partner Swap: Choose Players
+ Select random
+ Partner Swap
+ Do you really want to exit the game?
+ Pos
+ Name
+ Songs
+ Ø
+ Round
+ Song
+ Taking a break:
+ singing together for the first time
+ singing together for the %n. time
+ Last song:
+ Harmony bonus
+ Best pair so far
+ Best pair of the night
+ Winner
+
diff --git a/Output/PartyModes/PartnerSwap/Themes/Default.xml b/Output/PartyModes/PartnerSwap/Themes/Default.xml
new file mode 100644
index 00000000..97392ded
--- /dev/null
+++ b/Output/PartyModes/PartnerSwap/Themes/Default.xml
@@ -0,0 +1,10 @@
+
+
+ 9
+
+ Default
+ McSchneidar
+ 0
+ 1
+
+
diff --git a/Output/PartyModes/PartnerSwap/Themes/Default/Default.xml b/Output/PartyModes/PartnerSwap/Themes/Default/Default.xml
new file mode 100644
index 00000000..d88d34b4
--- /dev/null
+++ b/Output/PartyModes/PartnerSwap/Themes/Default/Default.xml
@@ -0,0 +1,15 @@
+
+
+ 4
+
+ Default
+ McSchneidar
+ 0
+ 1
+
+
+
+ PartyModeIcon.png
+
+
+
diff --git a/Output/PartyModes/PartnerSwap/Themes/Default/PartyModeIcon.png b/Output/PartyModes/PartnerSwap/Themes/Default/PartyModeIcon.png
new file mode 100644
index 00000000..d19b6431
Binary files /dev/null and b/Output/PartyModes/PartnerSwap/Themes/Default/PartyModeIcon.png differ
diff --git a/Output/PartyModes/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapConfig.xml b/Output/PartyModes/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapConfig.xml
new file mode 100644
index 00000000..bf22385a
--- /dev/null
+++ b/Output/PartyModes/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapConfig.xml
@@ -0,0 +1,407 @@
+
+
+
+ PartyScreenPartnerSwapConfig
+ 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_NUMPASSES
+
+
+ 112.5
+ 337.5
+ 0
+ 40.5
+ 0
+
+
+ Left
+ Center
+
+ Outline
+ TR_SCREENCONFIG_HARMONYBONUS
+
+
+ 112.5
+ 427.5
+ 0
+ 40.5
+ 0
+
+
+ Left
+ Center
+
+ Outline
+ TR_SCREENCONFIG_DUETS
+
+
+ 112.5
+ 517.5
+ 0
+ 40.5
+ 0
+
+
+ Left
+ Center
+
+ Outline
+ TR_SCREENCONFIG_NUMJOKERS
+
+
+
+
+
+
+
+
+
+
+
+ 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/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapMain.xml b/Output/PartyModes/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapMain.xml
new file mode 100644
index 00000000..26070008
--- /dev/null
+++ b/Output/PartyModes/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapMain.xml
@@ -0,0 +1,738 @@
+
+
+
+ PartyScreenPartnerSwapMain
+ 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
+ 760
+ 720
+ 0.8
+
+
+
+
+
+ 0.1
+ 0.09
+ 0.25
+ 0.75
+
+
+ 1470
+ 190
+ 420
+ 292
+ 0.8
+
+
+
+
+
+ 1
+ 0.8
+ 0
+ 0.18
+
+
+ 40
+ 0
+ 740
+ 48
+ 0.6
+
+
+
+
+
+
+ 125
+ 0
+ 42
+ 42
+ 0.4
+
+
+
+
+
+
+ 925
+ 290
+ 170
+ 170
+ 0.4
+
+
+
+
+
+
+ 1165
+ 290
+ 170
+ 170
+ 0.4
+
+
+
+
+
+
+ 1585
+ 260
+ 90
+ 90
+ 0.4
+
+
+
+
+
+
+ 1685
+ 260
+ 90
+ 90
+ 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
+
+
+ 110
+ 205
+ 0
+ 34
+ 0
+
+ 0.75
+ 0.78
+ 0.85
+ 1
+
+
+ Right
+ Center
+
+ Outline
+ TR_SCREENMAIN_HEADER_POS
+
+
+ 180
+ 205
+ 0
+ 34
+ 0
+
+ 0.75
+ 0.78
+ 0.85
+ 1
+
+
+ Left
+ Center
+
+ Outline
+ TR_SCREENMAIN_HEADER_NAME
+
+
+ 560
+ 205
+ 0
+ 34
+ 0
+
+ 0.75
+ 0.78
+ 0.85
+ 1
+
+
+ Right
+ Center
+
+ Outline
+ TR_SCREENMAIN_HEADER_SONGS
+
+
+ 765
+ 205
+ 0
+ 34
+ 0
+
+ 0.75
+ 0.78
+ 0.85
+ 1
+
+
+ Right
+ Center
+
+ Outline
+ TR_SCREENMAIN_HEADER_AVERAGE
+
+
+ 110
+ 0
+ 0
+ 36
+ 0
+
+
+ Right
+ Center
+
+ Outline
+
+
+
+ 180
+ 0
+ 0
+ 36
+ 300
+
+
+ Left
+ Center
+
+ Outline
+
+
+
+ 560
+ 0
+ 0
+ 36
+ 0
+
+
+ Right
+ Center
+
+ Outline
+
+
+
+ 765
+ 0
+ 0
+ 36
+ 0
+
+
+ Right
+ Center
+
+ Outline
+
+
+
+ 1130
+ 205
+ 0
+ 32
+ 620
+
+ 0.75
+ 0.78
+ 0.85
+ 1
+
+
+ Center
+ Center
+
+ Outline
+
+
+
+ 1010
+ 478
+ 0
+ 38
+ 220
+
+
+ Center
+ Center
+
+ Outline
+
+
+
+ 1250
+ 478
+ 0
+ 38
+ 220
+
+
+ Center
+ Center
+
+ Outline
+
+
+
+ 1130
+ 345
+ 0
+ 56
+ 0
+
+ 1
+ 0.8
+ 0
+ 1
+
+
+ Center
+ Center
+
+ Outline
+ &
+
+
+ 1130
+ 535
+ 0
+ 30
+ 620
+
+ 0.75
+ 0.78
+ 0.85
+ 1
+
+
+ Center
+ Center
+
+ Outline
+
+
+
+ 1130
+ 575
+ 0
+ 30
+ 620
+
+ 0.75
+ 0.78
+ 0.85
+ 1
+
+
+ Center
+ Center
+
+ Outline
+
+
+
+ 1130
+ 660
+ 0
+ 32
+ 640
+
+
+ Center
+ Center
+
+ Outline
+
+
+
+ 1130
+ 705
+ 0
+ 32
+ 640
+
+ 1
+ 0.8
+ 0
+ 1
+
+
+ Center
+ Center
+
+ Outline
+
+
+
+ 1680
+ 210
+ 0
+ 32
+ 400
+
+ 1
+ 0.8
+ 0
+ 1
+
+
+ Center
+ Center
+
+ Outline
+ TR_SCREENMAIN_BEST_PAIR
+
+
+ 1680
+ 365
+ 0
+ 32
+ 400
+
+
+ Center
+ Center
+
+ Outline
+
+
+
+ 1680
+ 410
+ 0
+ 48
+ 0
+
+ 1
+ 0.8
+ 0
+ 1
+
+
+ Center
+ Center
+
+ Outline
+
+
+
+ 1130
+ 215
+ 0
+ 36
+ 0
+
+ 1
+ 0.8
+ 0
+ 1
+
+
+ Center
+ Center
+
+ Outline
+ TR_SCREENMAIN_CHAMPION
+
+
+ 1130
+ 262
+ 0
+ 64
+ 600
+
+ 1
+ 0.8
+ 0
+ 1
+
+
+ Center
+ Center
+
+ Outline
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Star
+
+ 850
+ 200
+ 560
+ 380
+ -1
+
+
+ 1
+ 0.8
+ 0
+ 0.6
+
+ Star
+ 18
+ 40
+
+
+
+
diff --git a/Output/PartyModes/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapNames.xml b/Output/PartyModes/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapNames.xml
new file mode 100644
index 00000000..3d8c2be7
--- /dev/null
+++ b/Output/PartyModes/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapNames.xml
@@ -0,0 +1,382 @@
+
+
+
+ PartyScreenPartnerSwapNames
+ 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/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapSongs.xml b/Output/PartyModes/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapSongs.xml
new file mode 100644
index 00000000..d3129f36
--- /dev/null
+++ b/Output/PartyModes/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapSongs.xml
@@ -0,0 +1,407 @@
+
+
+
+ PartyScreenPartnerSwapSongs
+ 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/PartyModePartnerSwap/CPartyModePartnerSwap.cs b/PartyModes/PartyModePartnerSwap/CPartyModePartnerSwap.cs
new file mode 100644
index 00000000..1fe85f75
--- /dev/null
+++ b/PartyModes/PartyModePartnerSwap/CPartyModePartnerSwap.cs
@@ -0,0 +1,734 @@
+#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.Utils;
+
+[assembly: ComVisible(false)]
+
+namespace VocaluxeLib.PartyModes.PartnerSwap
+{
+ public enum EDuetSongs
+ {
+ Mixed,
+ DuetsOnly,
+ NoDuets
+ }
+
+ public class CSwapPlayer
+ {
+ public Guid ProfileId;
+ public int TotalPoints;
+ public int NumSongs;
+ public int Jokers;
+
+ public int Average
+ {
+ get { return NumSongs == 0 ? 0 : (int)Math.Round((double)TotalPoints / NumSongs); }
+ }
+ }
+
+ public class CSwapPair
+ {
+ public CSwapPlayer[] Players = new CSwapPlayer[2];
+ public int[] Scores = new int[2];
+ public int Bonus;
+ public bool Sung;
+
+ public int PairScore
+ {
+ get { return Scores[0] + Scores[1] + Bonus; }
+ }
+ }
+
+ public abstract class CPartyScreenPartnerSwap : CMenuParty
+ {
+ protected new CPartyModePartnerSwap _PartyMode;
+
+ public override void Init()
+ {
+ base.Init();
+ _PartyMode = (CPartyModePartnerSwap)base._PartyMode;
+ }
+ }
+
+ public sealed class CPartyModePartnerSwap : CPartyMode
+ {
+ public const int PlayersPerSong = 2;
+ public const int MaxJokers = 3;
+ public const int HarmonyRange = 500;
+ public const int HarmonyBonusPoints = 500;
+
+ public override int MinMics
+ {
+ get { return PlayersPerSong; }
+ }
+ public override int MaxMics
+ {
+ get { return CBase.Config.GetMaxNumMics(); }
+ }
+ public override int MinPlayers
+ {
+ get { return 3; }
+ }
+ 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,
+ Songs,
+ Names,
+ Main,
+ SongSelection,
+ Singing,
+ MedleySinging
+ }
+
+ public struct SData
+ {
+ public int NumPlayer;
+ public int NumPasses;
+ public bool HarmonyBonus;
+ public EDuetSongs DuetSongs;
+ public int NumJokers;
+ 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> Passes;
+ public List PassBreaks;
+ public int PassIndex;
+ public int PairIndex;
+ public int NumSongsSung;
+ public CSwapPair BestPair;
+ public CSwapPair LastPair;
+ public List PreviousRanking;
+
+ public List Songs;
+ }
+
+ public SData GameData;
+ private EStage _Stage;
+
+ public CPartyModePartnerSwap(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;
+
+ GameData = new SData
+ {
+ NumPlayer = 6,
+ NumPasses = 3,
+ HarmonyBonus = true,
+ DuetSongs = EDuetSongs.Mixed,
+ NumJokers = 1,
+ ProfileIds = new List(),
+ Sorting = CBase.Config.GetSongSorting(),
+ SongSource = ESongSource.TR_SONGSOURCE_ALLSONGS,
+ PlaylistId = 0,
+ CategoryIndex = 0,
+ GameMode = EGameMode.TR_GAMEMODE_NORMAL,
+ NumMedleySongs = 5,
+ Players = new List(),
+ Passes = new List>(),
+ PassBreaks = new List(),
+ PreviousRanking = new List(),
+ Songs = 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 static int GetMaxPasses(int numPlayer)
+ {
+ return numPlayer % 2 == 0 ? numPlayer - 1 : numPlayer;
+ }
+
+ public bool IsFinished
+ {
+ get { return GameData.Passes.Count > 0 && GameData.PassIndex >= GameData.Passes.Count; }
+ }
+
+ public CSwapPair CurrentPair
+ {
+ get
+ {
+ if (IsFinished || GameData.Passes.Count == 0)
+ {
+ return null;
+ }
+
+ return GameData.Passes[GameData.PassIndex][GameData.PairIndex];
+ }
+ }
+
+ public CSwapPlayer CurrentBreak
+ {
+ get { return IsFinished || GameData.PassIndex >= GameData.PassBreaks.Count ? null : GameData.PassBreaks[GameData.PassIndex]; }
+ }
+
+ public List GetRanking()
+ {
+ return GameData.Players.OrderByDescending(player => player.Average).ThenByDescending(player => player.TotalPoints).ToList();
+ }
+
+ public int GetTimesTogether(CSwapPlayer player1, CSwapPlayer player2)
+ {
+ var count = 0;
+ foreach (var pass in GameData.Passes)
+ {
+ foreach (var pair in pass)
+ {
+ if (pair.Sung && pair.Players.Contains(player1) && pair.Players.Contains(player2))
+ {
+ count++;
+ }
+ }
+ }
+
+ return count;
+ }
+
+ private IMenu _GetNextScreen()
+ {
+ switch (_Stage)
+ {
+ case EStage.Config:
+ return _Screens["CPartyScreenPartnerSwapConfig"];
+ case EStage.Songs:
+ return _Screens["CPartyScreenPartnerSwapSongs"];
+ case EStage.Names:
+ return _Screens["CPartyScreenPartnerSwapNames"];
+ case EStage.Main:
+ return _Screens["CPartyScreenPartnerSwapMain"];
+ case EStage.SongSelection:
+ return CBase.Graphics.GetScreen(EScreen.Song);
+ case EStage.Singing:
+ case EStage.MedleySinging:
+ 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 = EStage.Songs;
+ break;
+ case EStage.Songs:
+ _Stage = EStage.Names;
+ break;
+ case EStage.Names:
+ _Stage = EStage.Main;
+ _StartGame();
+ break;
+ case EStage.Main:
+ if (CurrentPair == null)
+ {
+ return;
+ }
+
+ if (GameData.GameMode == EGameMode.TR_GAMEMODE_MEDLEY)
+ {
+ _Stage = EStage.MedleySinging;
+ _PrepareMedleyRound();
+ }
+ else
+ {
+ _Stage = EStage.SongSelection;
+ _PrepareSongSelection();
+ }
+
+ break;
+ case EStage.SongSelection:
+ _Stage = EStage.Singing;
+ break;
+ case EStage.MedleySinging:
+ case EStage.Singing:
+ _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.Songs:
+ _Stage = 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["CPartyScreenPartnerSwapConfig"];
+ }
+
+ 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)
+ {
+ if (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 pair = CurrentPair;
+ if (pair != null && pair.Players[teamNr].Jokers > 0)
+ {
+ pair.Players[teamNr].Jokers--;
+ }
+
+ _ScreenSongOptions.Selection.NumJokers[teamNr]--;
+ _ScreenSongOptions.Selection.RandomOnly = true;
+ _ScreenSongOptions.Selection.CategoryChangeAllowed = false;
+ }
+
+ public override void SongSelected(int songId)
+ {
+ _PrepareRound(new int[] { songId });
+
+ Next();
+ }
+
+ public override void LeavingHighscore()
+ {
+ for (var i = 0; i < CBase.Game.GetNumSongs(); i++)
+ {
+ CBase.Songs.AddPartySongSung(CBase.Game.GetSong(i).Id);
+ }
+
+ Next();
+ }
+
+ private void _StartGame()
+ {
+ CBase.Songs.ResetSongSung();
+
+ GameData.Players = new List();
+ foreach (var profileId in GameData.ProfileIds)
+ {
+ GameData.Players.Add(new CSwapPlayer { ProfileId = profileId, Jokers = GameData.NumJokers });
+ }
+
+ var slots = new List(GameData.Players);
+ slots.Shuffle();
+ if (slots.Count % 2 == 1)
+ {
+ slots.Add(null);
+ }
+
+ GameData.Passes = new List>();
+ GameData.PassBreaks = new List();
+ var numPasses = Math.Max(1, Math.Min(GameData.NumPasses, GetMaxPasses(GameData.Players.Count)));
+ for (var pass = 0; pass < numPasses; pass++)
+ {
+ var pairs = new List();
+ CSwapPlayer pause = null;
+ for (var i = 0; i < slots.Count / 2; i++)
+ {
+ var player1 = slots[i];
+ var player2 = slots[slots.Count - 1 - i];
+ if (player1 == null || player2 == null)
+ {
+ pause = player1 ?? player2;
+ continue;
+ }
+
+ var pair = new CSwapPair();
+ pair.Players[0] = player1;
+ pair.Players[1] = player2;
+ pairs.Add(pair);
+ }
+
+ pairs.Shuffle();
+ GameData.Passes.Add(pairs);
+ GameData.PassBreaks.Add(pause);
+
+ var last = slots[slots.Count - 1];
+ slots.RemoveAt(slots.Count - 1);
+ slots.Insert(1, last);
+ }
+
+ GameData.PassIndex = 0;
+ GameData.PairIndex = 0;
+ GameData.NumSongsSung = 0;
+ GameData.BestPair = null;
+ GameData.LastPair = null;
+ GameData.PreviousRanking = GetRanking();
+ GameData.Songs = new List();
+ }
+
+ private EDuetOptions _GetDuetOptions()
+ {
+ switch (GameData.DuetSongs)
+ {
+ case EDuetSongs.NoDuets:
+ return EDuetOptions.NoDuets;
+ case EDuetSongs.DuetsOnly:
+ return CBase.Songs.GetSongs().Any(song => song.IsDuet) ? EDuetOptions.Duets : EDuetOptions.All;
+ default:
+ return EDuetOptions.All;
+ }
+ }
+
+ private void _PrepareMedleyRound()
+ {
+ var songIds = new List();
+ for (var i = 0; i < GameData.NumMedleySongs; i++)
+ {
+ if (GameData.Songs.Count == 0)
+ {
+ _UpdateSongList();
+ }
+
+ if (GameData.Songs.Count == 0)
+ {
+ break;
+ }
+
+ songIds.Add(GameData.Songs[0]);
+ GameData.Songs.RemoveAt(0);
+ }
+
+ _PrepareRound(songIds.ToArray());
+ }
+
+ private void _PrepareSongSelection()
+ {
+ _ScreenSongOptions.Selection.RandomOnly = true;
+ _ScreenSongOptions.Selection.SelectNextRandomSong = true;
+
+ _ScreenSongOptions.Sorting.IgnoreArticles = CBase.Config.GetIgnoreArticles();
+ _ScreenSongOptions.Sorting.DuetOptions = _GetDuetOptions();
+
+ 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 pair = CurrentPair;
+ _ScreenSongOptions.Selection.NumJokers = new int[] { pair.Players[0].Jokers, pair.Players[1].Jokers };
+ _ScreenSongOptions.Selection.TeamNames = new string[]
+ {
+ CBase.Profiles.GetPlayerName(pair.Players[0].ProfileId),
+ CBase.Profiles.GetPlayerName(pair.Players[1].ProfileId)
+ };
+ }
+
+ private bool _PrepareRound(int[] songIds)
+ {
+ CBase.Game.Reset();
+ CBase.Game.ClearSongs();
+
+ CBase.Game.SetNumPlayer(PlayersPerSong);
+ var players = CBase.Game.GetPlayers();
+ var pair = CurrentPair;
+ if (players == null || players.Length < PlayersPerSong || pair == null)
+ {
+ return false;
+ }
+
+ for (var p = 0; p < PlayersPerSong; p++)
+ {
+ players[p].ProfileId = pair.Players[p].ProfileId;
+ players[p].VoiceNr = p;
+ }
+
+ foreach (var songId in songIds)
+ {
+ var song = CBase.Songs.GetSongById(songId);
+ var gameMode = GameData.GameMode;
+ if (gameMode != EGameMode.TR_GAMEMODE_MEDLEY)
+ {
+ if (song.IsDuet)
+ {
+ gameMode = EGameMode.TR_GAMEMODE_DUET;
+ }
+ else if (!song.IsGameModeAvailable(gameMode))
+ {
+ gameMode = EGameMode.TR_GAMEMODE_NORMAL;
+ }
+ }
+
+ CBase.Game.AddSong(songId, gameMode);
+ }
+
+ return true;
+ }
+
+ private void _UpdateSongList()
+ {
+ if (GameData.Songs.Count > 0)
+ {
+ return;
+ }
+
+ switch (GameData.SongSource)
+ {
+ case ESongSource.TR_SONGSOURCE_PLAYLIST:
+ for (var i = 0; i < CBase.Playlist.GetSongCount(GameData.PlaylistId); i++)
+ {
+ var id = CBase.Playlist.GetSong(GameData.PlaylistId, i).SongId;
+ var song = CBase.Songs.GetSongById(id);
+ if (song.IsGameModeAvailable(EGameMode.TR_GAMEMODE_MEDLEY) && !song.IsDuet)
+ {
+ GameData.Songs.Add(id);
+ }
+ }
+
+ break;
+
+ case ESongSource.TR_SONGSOURCE_ALLSONGS:
+ var avSongs = CBase.Songs.GetSongs();
+ GameData.Songs.AddRange(avSongs.Where(song => song.IsGameModeAvailable(EGameMode.TR_GAMEMODE_MEDLEY) && !song.IsDuet).Select(song => song.Id));
+ 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.NoDuets, -1);
+
+ CBase.Songs.SetCategory(GameData.CategoryIndex);
+ avSongs = CBase.Songs.GetVisibleSongs();
+ GameData.Songs.AddRange(avSongs.Where(song => song.IsGameModeAvailable(EGameMode.TR_GAMEMODE_MEDLEY) && !song.IsDuet).Select(song => song.Id));
+
+ CBase.Songs.SetCategory(-1);
+ CBase.Songs.SortSongs(oldSorting, CBase.Config.GetTabs(), CBase.Config.GetIgnoreArticles(), string.Empty, EDuetOptions.NoDuets, -1);
+ break;
+ }
+
+ GameData.Songs.Shuffle();
+ }
+
+ private void _UpdateScores()
+ {
+ var pair = CurrentPair;
+ if (pair == null)
+ {
+ return;
+ }
+
+ var points = CBase.Game.GetPoints();
+ if (points == null || points.NumRounds == 0)
+ {
+ return;
+ }
+
+ var scores = new double[PlayersPerSong];
+ for (var round = 0; round < points.NumRounds; round++)
+ {
+ var res = points.GetPlayer(round, PlayersPerSong);
+ if (res == null || res.Length < PlayersPerSong)
+ {
+ return;
+ }
+
+ for (var p = 0; p < PlayersPerSong; p++)
+ {
+ scores[p] += res[p].Points;
+ }
+ }
+
+ GameData.PreviousRanking = GetRanking();
+
+ for (var p = 0; p < PlayersPerSong; p++)
+ {
+ pair.Scores[p] = (int)Math.Round(scores[p] / points.NumRounds);
+ }
+
+ pair.Bonus = GameData.HarmonyBonus && Math.Abs(pair.Scores[0] - pair.Scores[1]) <= HarmonyRange ? HarmonyBonusPoints : 0;
+ pair.Sung = true;
+
+ foreach (var player in pair.Players)
+ {
+ player.TotalPoints += pair.PairScore;
+ player.NumSongs++;
+ }
+
+ if (GameData.BestPair == null || pair.PairScore > GameData.BestPair.PairScore)
+ {
+ GameData.BestPair = pair;
+ }
+
+ GameData.LastPair = pair;
+ GameData.NumSongsSung++;
+
+ GameData.PairIndex++;
+ if (GameData.PairIndex >= GameData.Passes[GameData.PassIndex].Count)
+ {
+ GameData.PairIndex = 0;
+ GameData.PassIndex++;
+ }
+ }
+ }
+}
diff --git a/PartyModes/PartyModePartnerSwap/CPartyScreenPartnerSwapConfig.cs b/PartyModes/PartyModePartnerSwap/CPartyScreenPartnerSwapConfig.cs
new file mode 100644
index 00000000..f087a5a6
--- /dev/null
+++ b/PartyModes/PartyModePartnerSwap/CPartyScreenPartnerSwapConfig.cs
@@ -0,0 +1,173 @@
+#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.Windows.Forms;
+
+namespace VocaluxeLib.PartyModes.PartnerSwap
+{
+ public class CPartyScreenPartnerSwapConfig : CPartyScreenPartnerSwap
+ {
+ protected override int _ScreenVersion
+ {
+ get { return 1; }
+ }
+
+ private const string _SelectSlideNumPlayers = "SelectSlideNumPlayers";
+ private const string _SelectSlideNumPasses = "SelectSlideNumPasses";
+ private const string _SelectSlideHarmonyBonus = "SelectSlideHarmonyBonus";
+ private const string _SelectSlideDuetSongs = "SelectSlideDuetSongs";
+ private const string _SelectSlideNumJokers = "SelectSlideNumJokers";
+ private const string _ButtonNext = "ButtonNext";
+ private const string _ButtonBack = "ButtonBack";
+
+ public override void Init()
+ {
+ base.Init();
+
+ _ThemeSelectSlides = new string[] { _SelectSlideNumPlayers, _SelectSlideNumPasses, _SelectSlideHarmonyBonus, _SelectSlideDuetSongs, _SelectSlideNumJokers };
+ _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();
+
+ _UpdatePassesSlide();
+
+ _SelectSlides[_SelectSlideHarmonyBonus].Clear();
+ _SelectSlides[_SelectSlideHarmonyBonus].AddValue(CBase.Language.Translate("TR_BUTTON_NO", PartyModeId));
+ _SelectSlides[_SelectSlideHarmonyBonus].AddValue(CBase.Language.Translate("TR_BUTTON_YES", PartyModeId));
+ _SelectSlides[_SelectSlideHarmonyBonus].Selection = _PartyMode.GameData.HarmonyBonus ? 1 : 0;
+
+ _SelectSlides[_SelectSlideDuetSongs].Clear();
+ _SelectSlides[_SelectSlideDuetSongs].AddValue(CBase.Language.Translate("TR_SCREENCONFIG_DUETS_MIXED", PartyModeId));
+ _SelectSlides[_SelectSlideDuetSongs].AddValue(CBase.Language.Translate("TR_SCREENCONFIG_DUETS_ONLY", PartyModeId));
+ _SelectSlides[_SelectSlideDuetSongs].AddValue(CBase.Language.Translate("TR_SCREENCONFIG_DUETS_NONE", PartyModeId));
+ _SelectSlides[_SelectSlideDuetSongs].Selection = (int)_PartyMode.GameData.DuetSongs;
+
+ _SelectSlides[_SelectSlideNumJokers].Clear();
+ for (var i = 0; i <= CPartyModePartnerSwap.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.NumPasses = _SelectSlides[_SelectSlideNumPasses].Selection + 1;
+ _PartyMode.GameData.HarmonyBonus = _SelectSlides[_SelectSlideHarmonyBonus].Selection == 1;
+ _PartyMode.GameData.DuetSongs = (EDuetSongs)_SelectSlides[_SelectSlideDuetSongs].Selection;
+ _PartyMode.GameData.NumJokers = _SelectSlides[_SelectSlideNumJokers].Selection;
+ _UpdatePassesSlide();
+ }
+
+ private void _UpdatePassesSlide()
+ {
+ var maxPasses = CPartyModePartnerSwap.GetMaxPasses(_PartyMode.GameData.NumPlayer);
+ _PartyMode.GameData.NumPasses = Math.Max(1, Math.Min(maxPasses, _PartyMode.GameData.NumPasses));
+
+ _SelectSlides[_SelectSlideNumPasses].Clear();
+ for (var i = 1; i <= maxPasses; i++)
+ {
+ _SelectSlides[_SelectSlideNumPasses].AddValue(i.ToString());
+ }
+
+ _SelectSlides[_SelectSlideNumPasses].Selection = _PartyMode.GameData.NumPasses - 1;
+ }
+ }
+}
diff --git a/PartyModes/PartyModePartnerSwap/CPartyScreenPartnerSwapMain.cs b/PartyModes/PartyModePartnerSwap/CPartyScreenPartnerSwapMain.cs
new file mode 100644
index 00000000..f5a1bfd9
--- /dev/null
+++ b/PartyModes/PartyModePartnerSwap/CPartyScreenPartnerSwapMain.cs
@@ -0,0 +1,527 @@
+#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.PartnerSwap
+{
+ class CRankRow
+ {
+ public CSwapPlayer Player;
+ public CStatic Highlight;
+ public CStatic Avatar;
+ public CText Pos;
+ public CText Name;
+ public CText Songs;
+ public CText Average;
+ public int OldRank;
+ public int NewRank;
+ }
+
+ public class CPartyScreenPartnerSwapMain : CPartyScreenPartnerSwap
+ {
+ protected override int _ScreenVersion
+ {
+ get { return 1; }
+ }
+
+ private const string _TextPopupReallyExit = "TextPopupReallyExit";
+ private const string _TextPass = "TextPass";
+ private const string _TextRowPos = "TextRowPos";
+ private const string _TextRowName = "TextRowName";
+ private const string _TextRowSongs = "TextRowSongs";
+ private const string _TextRowAverage = "TextRowAverage";
+ private const string _TextPairName1 = "TextPairName1";
+ private const string _TextPairName2 = "TextPairName2";
+ private const string _TextAmp = "TextAmp";
+ private const string _TextTogether = "TextTogether";
+ private const string _TextPause = "TextPause";
+ private const string _TextLastResult = "TextLastResult";
+ private const string _TextHarmony = "TextHarmony";
+ private const string _TextBestLabel = "TextBestLabel";
+ private const string _TextBestNames = "TextBestNames";
+ private const string _TextBestScore = "TextBestScore";
+ 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 _StaticPairAvatar1 = "StaticPairAvatar1";
+ private const string _StaticPairAvatar2 = "StaticPairAvatar2";
+ private const string _StaticBestAvatar1 = "StaticBestAvatar1";
+ private const string _StaticBestAvatar2 = "StaticBestAvatar2";
+
+ private const string _ParticleEffectChampion = "ParticleEffectChampion";
+
+ private const float _FirstRowY = 260f;
+ private const float _RowStep = 54f;
+ private const float _RankDelay = 300f;
+ private const float _RankDuration = 700f;
+ private const float _PopDuration = 700f;
+
+ private readonly string[] _RankColors = new string[] { "Top1Color", "Top2Color", "Top3Color" };
+
+ private bool _ExitPopupVisible;
+ private List _Rows;
+ private readonly Stopwatch _AnimTimer = new Stopwatch();
+ private bool _ShowNewResult;
+ private int _LastSeenSongs = -1;
+ private float _LastResultHeight;
+ private float _HarmonyHeight;
+ private float _ChampionHeight;
+ private SRectF _PairAvatar1Rect;
+
+ public override void Init()
+ {
+ base.Init();
+
+ _ThemeTexts = new string[]
+ {
+ _TextPopupReallyExit, _TextPass, _TextRowPos, _TextRowName, _TextRowSongs, _TextRowAverage, _TextPairName1, _TextPairName2, _TextAmp, _TextTogether,
+ _TextPause, _TextLastResult, _TextHarmony, _TextBestLabel, _TextBestNames, _TextBestScore, _TextChampionLabel, _TextChampion
+ };
+ _ThemeButtons = new string[] { _ButtonNextRound, _ButtonBack, _ButtonExit, _ButtonPopupYes, _ButtonPopupNo };
+ _ThemeStatics = new string[]
+ {
+ _StaticPopupBG, _StaticRowHighlight, _StaticRowAvatar, _StaticPairAvatar1, _StaticPairAvatar2, _StaticBestAvatar1,
+ _StaticBestAvatar2
+ };
+ _ThemeParticleEffects = new string[] { _ParticleEffectChampion };
+ }
+
+ public override void LoadTheme(string xmlPath)
+ {
+ base.LoadTheme(xmlPath);
+
+ _Rows = new List();
+ for (var i = 0; i < _PartyMode.MaxPlayers; i++)
+ {
+ var row = new CRankRow
+ {
+ Highlight = GetNewStatic(_Statics[_StaticRowHighlight]),
+ Avatar = GetNewStatic(_Statics[_StaticRowAvatar]),
+ Pos = GetNewText(_Texts[_TextRowPos]),
+ Name = GetNewText(_Texts[_TextRowName]),
+ Songs = GetNewText(_Texts[_TextRowSongs]),
+ Average = GetNewText(_Texts[_TextRowAverage])
+ };
+ row.Avatar.Aspect = EAspect.Crop;
+ _AddStatic(row.Highlight);
+ _AddStatic(row.Avatar);
+ _AddText(row.Pos);
+ _AddText(row.Name);
+ _AddText(row.Songs);
+ _AddText(row.Average);
+ _Rows.Add(row);
+ }
+
+ _Statics[_StaticRowHighlight].Visible = false;
+ _Statics[_StaticRowAvatar].Visible = false;
+ _Texts[_TextRowPos].Visible = false;
+ _Texts[_TextRowName].Visible = false;
+ _Texts[_TextRowSongs].Visible = false;
+ _Texts[_TextRowAverage].Visible = false;
+
+ _Statics[_StaticPairAvatar1].Aspect = EAspect.Crop;
+ _Statics[_StaticPairAvatar2].Aspect = EAspect.Crop;
+ _Statics[_StaticBestAvatar1].Aspect = EAspect.Crop;
+ _Statics[_StaticBestAvatar2].Aspect = EAspect.Crop;
+ _PairAvatar1Rect = _Statics[_StaticPairAvatar1].Rect;
+
+ _LastResultHeight = _Texts[_TextLastResult].Font.Height;
+ _HarmonyHeight = _Texts[_TextHarmony].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.LastPair != null && _PartyMode.GameData.NumSongsSung != _LastSeenSongs;
+ _LastSeenSongs = _PartyMode.GameData.NumSongsSung;
+
+ _UpdateRanking();
+ _UpdatePair();
+ _UpdateBestPair();
+ _UpdateLastResult();
+
+ _AnimTimer.Reset();
+ _AnimTimer.Start();
+ _UpdateAnimations();
+
+ var started = _PartyMode.GameData.NumSongsSung > 0;
+ _Buttons[_ButtonBack].Visible = !started;
+ _Buttons[_ButtonExit].Visible = started;
+ _Buttons[_ButtonNextRound].Visible = !_PartyMode.IsFinished;
+
+ _ShowPopup(false);
+
+ if (_PartyMode.IsFinished)
+ {
+ _SelectElement(_Buttons[_ButtonExit]);
+ }
+ else
+ {
+ _SelectElement(_Buttons[_ButtonNextRound]);
+ }
+ }
+
+ public override bool UpdateGame()
+ {
+ _UpdateAnimations();
+ return true;
+ }
+
+ private void _OnBack()
+ {
+ if (_ExitPopupVisible)
+ {
+ _ShowPopup(false);
+ }
+ else if (_PartyMode.GameData.NumSongsSung == 0)
+ {
+ _PartyMode.Back();
+ }
+ else if (_PartyMode.IsFinished)
+ {
+ _EndParty();
+ }
+ else
+ {
+ _ShowPopup(true);
+ }
+ }
+
+ private void _OnSelect()
+ {
+ if (_ExitPopupVisible)
+ {
+ if (_Buttons[_ButtonPopupYes].Selected)
+ {
+ _EndParty();
+ }
+
+ if (_Buttons[_ButtonPopupNo].Selected)
+ {
+ _ShowPopup(false);
+ }
+
+ return;
+ }
+
+ if (_Buttons[_ButtonNextRound].Selected && _Buttons[_ButtonNextRound].Visible)
+ {
+ _PartyMode.Next();
+ }
+
+ 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 static float _EaseInOut(float t)
+ {
+ return t < 0.5f ? 4f * t * t * t : 1f - (float)Math.Pow(-2f * t + 2f, 3) / 2f;
+ }
+
+ 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(CSwapPlayer player)
+ {
+ return CBase.Profiles.GetPlayerName(player.ProfileId);
+ }
+
+ private string _GetPairNames(CSwapPair pair)
+ {
+ return _GetName(pair.Players[0]) + " & " + _GetName(pair.Players[1]);
+ }
+
+ private void _UpdateRanking()
+ {
+ var ranking = _PartyMode.GetRanking();
+ var previous = _ShowNewResult ? _PartyMode.GameData.PreviousRanking : ranking;
+
+ for (var i = 0; i < _Rows.Count; i++)
+ {
+ var row = _Rows[i];
+ var visible = i < ranking.Count;
+ row.Player = visible ? _PartyMode.GameData.Players[i] : null;
+ row.Avatar.Visible = visible;
+ row.Pos.Visible = visible;
+ row.Name.Visible = visible;
+ row.Songs.Visible = visible;
+ row.Average.Visible = visible;
+ row.Highlight.Visible = false;
+ if (!visible)
+ {
+ continue;
+ }
+
+ row.NewRank = ranking.IndexOf(row.Player);
+ row.OldRank = previous.IndexOf(row.Player);
+ if (row.OldRank < 0)
+ {
+ row.OldRank = row.NewRank;
+ }
+
+ row.Avatar.Texture = CBase.Profiles.GetAvatar(row.Player.ProfileId);
+ row.Name.Text = _GetName(row.Player);
+ row.Songs.Text = row.Player.NumSongs.ToString();
+ row.Average.Text = row.Player.Average.ToString("N0");
+ row.Pos.Text = (row.NewRank + 1) + ".";
+ row.Highlight.Visible = row.NewRank == 0 && row.Player.NumSongs > 0;
+
+ SColorF rankColor;
+ var color = _Texts[_TextRowName].Color;
+ if (row.Player.NumSongs > 0 && row.NewRank < _RankColors.Length && CBase.Themes.GetColor(_RankColors[row.NewRank], PartyModeId, out rankColor))
+ {
+ color = rankColor;
+ }
+
+ row.Pos.Color = color;
+ row.Name.Color = color;
+ }
+ }
+
+ private void _PlaceRow(CRankRow row, float y)
+ {
+ row.Highlight.Y = y + (_Texts[_TextRowName].H - row.Highlight.H) / 2;
+ row.Avatar.Y = y + (_Texts[_TextRowName].H - row.Avatar.H) / 2;
+ row.Pos.Y = y;
+ row.Name.Y = y;
+ row.Songs.Y = y;
+ row.Average.Y = y;
+ }
+
+ private void _UpdatePair()
+ {
+ var finished = _PartyMode.IsFinished;
+ var pair = _PartyMode.CurrentPair;
+ var showPair = !finished && pair != null;
+
+ _Statics[_StaticPairAvatar2].Visible = showPair;
+ _Texts[_TextPairName1].Visible = showPair;
+ _Texts[_TextPairName2].Visible = showPair;
+ _Texts[_TextAmp].Visible = showPair;
+ _Texts[_TextTogether].Visible = showPair;
+ _Texts[_TextPass].Visible = showPair;
+
+ var pause = _PartyMode.CurrentBreak;
+ _Texts[_TextPause].Visible = showPair && pause != null;
+ if (pause != null)
+ {
+ _Texts[_TextPause].Text = CBase.Language.Translate("TR_SCREENMAIN_PAUSE", PartyModeId) + " " + _GetName(pause);
+ }
+
+ _Statics[_StaticPairAvatar1].MaxRect = _PairAvatar1Rect;
+ _Statics[_StaticPairAvatar1].Visible = showPair || finished;
+
+ if (showPair)
+ {
+ _Statics[_StaticPairAvatar1].Texture = CBase.Profiles.GetAvatar(pair.Players[0].ProfileId);
+ _Statics[_StaticPairAvatar2].Texture = CBase.Profiles.GetAvatar(pair.Players[1].ProfileId);
+ _Texts[_TextPairName1].Text = _GetName(pair.Players[0]);
+ _Texts[_TextPairName2].Text = _GetName(pair.Players[1]);
+
+ var together = _PartyMode.GetTimesTogether(pair.Players[0], pair.Players[1]);
+ _Texts[_TextTogether].Text = together == 0
+ ? CBase.Language.Translate("TR_SCREENMAIN_FIRST_TIME", PartyModeId)
+ : CBase.Language.Translate("TR_SCREENMAIN_TIMES_TOGETHER", PartyModeId).Replace("%n", (together + 1).ToString());
+
+ _Texts[_TextPass].Text = CBase.Language.Translate("TR_SCREENMAIN_PASS", PartyModeId) + " " + (_PartyMode.GameData.PassIndex + 1) + " / " +
+ _PartyMode.GameData.Passes.Count + " · " + CBase.Language.Translate("TR_SCREENMAIN_SONG", PartyModeId) + " " +
+ (_PartyMode.GameData.PairIndex + 1) + " / " + _PartyMode.GameData.Passes[_PartyMode.GameData.PassIndex].Count;
+ }
+
+ _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[_StaticPairAvatar1].Texture = CBase.Profiles.GetAvatar(champion.ProfileId);
+ var centerX = (_PairAvatar1Rect.X + _Statics[_StaticPairAvatar2].Rect.X + _Statics[_StaticPairAvatar2].Rect.W) / 2;
+ _Statics[_StaticPairAvatar1].X = centerX - _PairAvatar1Rect.W / 2;
+ _Statics[_StaticPairAvatar1].Y = _PairAvatar1Rect.Y + 60f;
+ }
+ }
+
+ private void _UpdateBestPair()
+ {
+ var best = _PartyMode.GameData.BestPair;
+ var visible = best != null;
+ _Statics[_StaticBestAvatar1].Visible = visible;
+ _Statics[_StaticBestAvatar2].Visible = visible;
+ _Texts[_TextBestNames].Visible = visible;
+ _Texts[_TextBestScore].Visible = visible;
+ _Texts[_TextBestLabel].Text = _PartyMode.IsFinished ? "TR_SCREENMAIN_BEST_PAIR_FINAL" : "TR_SCREENMAIN_BEST_PAIR";
+
+ if (!visible)
+ {
+ return;
+ }
+
+ _Statics[_StaticBestAvatar1].Texture = CBase.Profiles.GetAvatar(best.Players[0].ProfileId);
+ _Statics[_StaticBestAvatar2].Texture = CBase.Profiles.GetAvatar(best.Players[1].ProfileId);
+ _Texts[_TextBestNames].Text = _GetPairNames(best);
+ _Texts[_TextBestScore].Text = best.PairScore.ToString("N0");
+ }
+
+ private void _UpdateLastResult()
+ {
+ var last = _PartyMode.GameData.LastPair;
+ _Texts[_TextLastResult].Visible = last != null && !_PartyMode.IsFinished;
+ _Texts[_TextHarmony].Visible = last != null && last.Bonus > 0 && !_PartyMode.IsFinished;
+
+ if (last == null)
+ {
+ return;
+ }
+
+ _Texts[_TextLastResult].Text = CBase.Language.Translate("TR_SCREENMAIN_LAST_SONG", PartyModeId) + " " + _GetPairNames(last) + " · " +
+ last.PairScore.ToString("N0");
+ _Texts[_TextHarmony].Text = CBase.Language.Translate("TR_SCREENMAIN_HARMONY_BONUS", PartyModeId) + " +" + last.Bonus;
+ }
+
+ private void _UpdateAnimations()
+ {
+ var time = _AnimTimer.ElapsedMilliseconds;
+
+ var rankProgress = _ShowNewResult ? _EaseInOut(Math.Max(0f, Math.Min(1f, (time - _RankDelay) / _RankDuration))) : 1f;
+ foreach (var row in _Rows)
+ {
+ if (row.Player == null)
+ {
+ continue;
+ }
+
+ var oldY = _FirstRowY + row.OldRank * _RowStep;
+ var newY = _FirstRowY + row.NewRank * _RowStep;
+ _PlaceRow(row, oldY + (newY - oldY) * rankProgress);
+ }
+
+ var popScale = _ShowNewResult && time < _PopDuration ? 0.3f + 0.7f * _EaseOutBack(time / _PopDuration) : 1f;
+ _Texts[_TextLastResult].Font.Height = _LastResultHeight * popScale;
+ _Texts[_TextHarmony].Font.Height = _HarmonyHeight * popScale;
+
+ if (_PartyMode.IsFinished)
+ {
+ float scale;
+ if (time < _PopDuration)
+ {
+ scale = 0.3f + 0.7f * _EaseOutBack(time / _PopDuration);
+ }
+ else
+ {
+ scale = 1f + 0.03f * (float)Math.Sin((time - _PopDuration) / 1000.0 * Math.PI * 1.4);
+ }
+
+ _Texts[_TextChampion].Font.Height = _ChampionHeight * scale;
+ }
+ }
+ }
+}
diff --git a/PartyModes/PartyModePartnerSwap/CPartyScreenPartnerSwapNames.cs b/PartyModes/PartyModePartnerSwap/CPartyScreenPartnerSwapNames.cs
new file mode 100644
index 00000000..f28695e4
--- /dev/null
+++ b/PartyModes/PartyModePartnerSwap/CPartyScreenPartnerSwapNames.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.PartnerSwap
+{
+ public class CPartyScreenPartnerSwapNames : CMenuPartyNameSelection
+ {
+ protected override int _ScreenVersion
+ {
+ get { return 1; }
+ }
+
+ private new CPartyModePartnerSwap _PartyMode;
+
+ public override void Init()
+ {
+ base.Init();
+ _PartyMode = (CPartyModePartnerSwap)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/PartyModePartnerSwap/CPartyScreenPartnerSwapSongs.cs b/PartyModes/PartyModePartnerSwap/CPartyScreenPartnerSwapSongs.cs
new file mode 100644
index 00000000..0fa5df20
--- /dev/null
+++ b/PartyModes/PartyModePartnerSwap/CPartyScreenPartnerSwapSongs.cs
@@ -0,0 +1,66 @@
+using VocaluxeLib.Menu;
+
+namespace VocaluxeLib.PartyModes.PartnerSwap
+{
+ class CPartyScreenPartnerSwapSongs : CMenuPartySongSelection
+ {
+ protected override int _ScreenVersion
+ {
+ get { return 1; }
+ }
+
+ private new CPartyModePartnerSwap _PartyMode;
+
+ public override void Init()
+ {
+ base.Init();
+ _PartyMode = (CPartyModePartnerSwap)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, EGameMode.TR_GAMEMODE_MEDLEY };
+ }
+
+ 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/PartyModePartnerSwap/GlobalSuppressions.cs b/PartyModes/PartyModePartnerSwap/GlobalSuppressions.cs
new file mode 100644
index 00000000..e661ff03
Binary files /dev/null and b/PartyModes/PartyModePartnerSwap/GlobalSuppressions.cs differ
diff --git a/PartyModes/PartyModePartnerSwap/PartyModePartnerSwap.csproj b/PartyModes/PartyModePartnerSwap/PartyModePartnerSwap.csproj
new file mode 100644
index 00000000..f015ffb3
--- /dev/null
+++ b/PartyModes/PartyModePartnerSwap/PartyModePartnerSwap.csproj
@@ -0,0 +1,118 @@
+
+
+
+ DebugWin
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}
+ Library
+ Properties
+ VocaluxeLib.PartyModes.PartnerSwap
+ PartnerSwap
+ 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/PartyModePartnerSwap/Settings.StyleCop b/PartyModes/PartyModePartnerSwap/Settings.StyleCop
new file mode 100644
index 00000000..f4a4ea2a
--- /dev/null
+++ b/PartyModes/PartyModePartnerSwap/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 2681e92a..ad929fc1 100644
--- a/Vocaluxe.sln
+++ b/Vocaluxe.sln
@@ -7,6 +7,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vocaluxe", "Vocaluxe\Vocalu
{7B2E4A19-5C3D-4E8F-A16B-2D9C0F3E8B41} = {7B2E4A19-5C3D-4E8F-A16B-2D9C0F3E8B41}
{55F89808-4E79-4FFE-BC13-48258913F5C9} = {55F89808-4E79-4FFE-BC13-48258913F5C9}
{6F3E1874-F28A-40E9-AFBD-31934D593679} = {6F3E1874-F28A-40E9-AFBD-31934D593679}
+ {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53} = {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}
{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}
@@ -14,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vocaluxe", "Vocaluxe\Vocalu
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VocaluxeLib", "VocaluxeLib\VocaluxeLib.csproj", "{D2B6DDEF-E0E1-4DDF-A73A-E6ACEF5C95EC}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PartyModePartnerSwap", "PartyModes\PartyModePartnerSwap\PartyModePartnerSwap.csproj", "{9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PartyModeTicTacToe", "PartyModes\PartyModeTicTacToe\PartyModeTicTacToe.csproj", "{6F3E1874-F28A-40E9-AFBD-31934D593679}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PartyModeChallenge", "PartyModes\PartyModeChallenge\PartyModeChallenge.csproj", "{5A4053F1-5AD5-441F-B254-D97A088127B6}"
@@ -80,6 +83,26 @@ Global
{D2B6DDEF-E0E1-4DDF-A73A-E6ACEF5C95EC}.ReleaseWin|x64.Build.0 = ReleaseWin|Any CPU
{D2B6DDEF-E0E1-4DDF-A73A-E6ACEF5C95EC}.ReleaseWin|x86.ActiveCfg = ReleaseWin|Any CPU
{D2B6DDEF-E0E1-4DDF-A73A-E6ACEF5C95EC}.ReleaseWin|x86.Build.0 = ReleaseWin|Any CPU
+ {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}.DebugLinux|x64.ActiveCfg = DebugLinux|Any CPU
+ {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}.DebugLinux|x64.Build.0 = DebugLinux|Any CPU
+ {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}.DebugLinux|x86.ActiveCfg = DebugLinux|Any CPU
+ {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}.DebugLinux|x86.Build.0 = DebugLinux|Any CPU
+ {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}.DebugWin|x64.ActiveCfg = DebugWin|Any CPU
+ {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}.DebugWin|x64.Build.0 = DebugWin|Any CPU
+ {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}.DebugWin|x86.ActiveCfg = DebugWin|Any CPU
+ {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}.DebugWin|x86.Build.0 = DebugWin|Any CPU
+ {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}.InstallerWin|x64.ActiveCfg = InstallerWin|Any CPU
+ {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}.InstallerWin|x64.Build.0 = InstallerWin|Any CPU
+ {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}.InstallerWin|x86.ActiveCfg = InstallerWin|Any CPU
+ {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}.InstallerWin|x86.Build.0 = InstallerWin|Any CPU
+ {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}.ReleaseLinux|x64.ActiveCfg = ReleaseLinux|Any CPU
+ {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}.ReleaseLinux|x64.Build.0 = ReleaseLinux|Any CPU
+ {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}.ReleaseLinux|x86.ActiveCfg = ReleaseLinux|Any CPU
+ {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}.ReleaseLinux|x86.Build.0 = ReleaseLinux|Any CPU
+ {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}.ReleaseWin|x64.ActiveCfg = ReleaseWin|Any CPU
+ {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}.ReleaseWin|x64.Build.0 = ReleaseWin|Any CPU
+ {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}.ReleaseWin|x86.ActiveCfg = ReleaseWin|Any CPU
+ {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}.ReleaseWin|x86.Build.0 = ReleaseWin|Any CPU
{6F3E1874-F28A-40E9-AFBD-31934D593679}.DebugLinux|x64.ActiveCfg = DebugLinux|Any CPU
{6F3E1874-F28A-40E9-AFBD-31934D593679}.DebugLinux|x64.Build.0 = DebugLinux|Any CPU
{6F3E1874-F28A-40E9-AFBD-31934D593679}.DebugLinux|x86.ActiveCfg = DebugLinux|Any CPU