- Support for pausing multiplayer games
authoralfadur
Fri, 22 Aug 2014 00:57:07 +0400 (2014-08-21)
changeset 10392 5012e1f9e893
parent 10391 ce3ccc45d790
child 10393 c3daae6fd723
- Support for pausing multiplayer games - Some code cleanup
QTfrontend/game.cpp
QTfrontend/game.h
QTfrontend/hwform.cpp
gameServer/CoreTypes.hs
gameServer/HWProtoInRoomState.hs
gameServer/Votes.hs
hedgewars/uChat.pas
hedgewars/uCommandHandlers.pas
hedgewars/uIO.pas
hedgewars/uInputHandler.pas
hedgewars/uWorld.pas
--- a/QTfrontend/game.cpp	Fri Aug 22 00:37:26 2014 +0400
+++ b/QTfrontend/game.cpp	Fri Aug 22 00:57:07 2014 +0400
@@ -312,6 +312,13 @@
             config->Form->ui.pageOptions->windowHeightEdit->setValue(wh[1].toInt());
             break;
         }
+        case '~':
+        {
+            int size = msg.size();
+            QString msgbody = QString::fromUtf8(msg.mid(2).left(size - 4));
+            emit SendConsoleCommand(msgbody);
+            break;
+        }
         default:
         {
             if (gameType == gtNet && !netSuspend)
--- a/QTfrontend/game.h	Fri Aug 22 00:37:26 2014 +0400
+++ b/QTfrontend/game.h	Fri Aug 22 00:57:07 2014 +0400
@@ -99,6 +99,7 @@
         void HaveRecord(RecordType type, const QByteArray & record);
         void ErrorMessage(const QString &);
         void CampStateChanged(int);
+        void SendConsoleCommand(const QString & command);
 
     public slots:
         void FromNet(const QByteArray & msg);
--- a/QTfrontend/hwform.cpp	Fri Aug 22 00:37:26 2014 +0400
+++ b/QTfrontend/hwform.cpp	Fri Aug 22 00:57:07 2014 +0400
@@ -1753,6 +1753,7 @@
 
     connect(game, SIGNAL(SendNet(const QByteArray &)), hwnet, SLOT(SendNet(const QByteArray &)));
     connect(game, SIGNAL(SendChat(const QString &)), hwnet, SLOT(chatLineToNet(const QString &)));
+    connect(game, SIGNAL(SendConsoleCommand(const QString&)), hwnet, SLOT(consoleCommand(const QString&)));
     connect(game, SIGNAL(SendTeamMessage(const QString &)), hwnet, SLOT(SendTeamMessage(const QString &)));
     connect(hwnet, SIGNAL(chatStringFromNet(const QString &)), game, SLOT(FromNetChat(const QString &)), Qt::QueuedConnection);
 
--- a/gameServer/CoreTypes.hs	Fri Aug 22 00:37:26 2014 +0400
+++ b/gameServer/CoreTypes.hs	Fri Aug 22 00:57:07 2014 +0400
@@ -171,7 +171,8 @@
         teamsInGameNumber :: Int,
         allPlayersHaveRegisteredAccounts :: !Bool,
         giMapParams :: Map.Map B.ByteString B.ByteString,
-        giParams :: Map.Map B.ByteString [B.ByteString]
+        giParams :: Map.Map B.ByteString [B.ByteString],
+        isPaused :: Bool
     } deriving (Show, Read)
 
 newGameInfo :: [TeamInfo]
@@ -179,6 +180,7 @@
                 -> Bool
                 -> Map.Map ByteString ByteString
                 -> Map.Map ByteString [ByteString]
+                -> Bool
                 -> GameInfo
 newGameInfo =
     GameInfo
@@ -298,6 +300,7 @@
 
 data VoteType = VoteKick B.ByteString
               | VoteMap B.ByteString
+              | VotePause
 
 
 newVoting :: VoteType -> Voting
--- a/gameServer/HWProtoInRoomState.hs	Fri Aug 22 00:37:26 2014 +0400
+++ b/gameServer/HWProtoInRoomState.hs	Fri Aug 22 00:57:07 2014 +0400
@@ -30,7 +30,7 @@
             return [
                 ModifyRoom
                     (\r -> r{
-                        gameInfo = Just $ newGameInfo (teams rm) (length $ teams rm) allPlayersRegistered (mapParams rm) (params rm)
+                        gameInfo = Just $ newGameInfo (teams rm) (length $ teams rm) allPlayersRegistered (mapParams rm) (params rm) False
                         }
                     )
                 , AnswerClients chans ["RUN_GAME"]
@@ -374,7 +374,7 @@
 
 handleCmd_inRoom ["CALLVOTE"] = do
     cl <- thisClient
-    return [AnswerClients [sendChan cl] ["CHAT", "[server]", "Available callvote commands: kick <nickname>, map <name>"]]
+    return [AnswerClients [sendChan cl] ["CHAT", "[server]", "Available callvote commands: kick <nickname>, map <name>, pause"]]
 
 handleCmd_inRoom ["CALLVOTE", "KICK"] = do
     cl <- thisClient
@@ -412,6 +412,14 @@
         else
         return [AnswerClients [sendChan cl] ["CHAT", "[server]", "callvote map: no such map"]]
 
+handleCmd_inRoom ["CALLVOTE", "PAUSE"] = do
+    cl <- thisClient
+    rm <- thisRoom
+
+    if isJust $ gameInfo rm then
+        startVote VotePause    
+        else 
+        return [AnswerClients [sendChan cl] ["CHAT", "[server]", "callvote pause: no game in progress"]]
 
 handleCmd_inRoom ["VOTE", m] = do
     cl <- thisClient
--- a/gameServer/Votes.hs	Fri Aug 22 00:37:26 2014 +0400
+++ b/gameServer/Votes.hs	Fri Aug 22 00:57:07 2014 +0400
@@ -12,6 +12,7 @@
 import Utils
 import CoreTypes
 import HandlerUtils
+import EngineInteraction
 
 
 voted :: Bool -> Reader (ClientIndex, IRnC) [Action]
@@ -20,23 +21,27 @@
     rm <- thisRoom
     uid <- liftM clUID thisClient
 
-    if isNothing $ voting rm then
-        return [AnswerClients [sendChan cl] ["CHAT", "[server]", loc "There's no voting going on"]]
-    else if uid `L.notElem` entitledToVote (fromJust $ voting rm) then
-        return []
-    else if uid `L.elem` map fst (votes . fromJust $ voting rm) then
-        return [AnswerClients [sendChan cl] ["CHAT", "[server]", loc "You already have voted"]]
-    else
-        actOnVoting . fromJust . liftM (\v -> v{votes = (uid, vote):votes v}) $ voting rm
+    case voting rm of
+        Nothing -> 
+            return [AnswerClients [sendChan cl] ["CHAT", "[server]", loc "There's no voting going on"]]
+        Just voting ->
+            if uid `L.notElem` entitledToVote voting then
+                return []
+            else if uid `L.elem` map fst (votes voting) then
+                return [AnswerClients [sendChan cl] ["CHAT", "[server]", loc "You already have voted"]]
+            else
+                actOnVoting $ voting{votes = (uid, vote):votes voting}
+      
     where
     actOnVoting :: Voting -> Reader (ClientIndex, IRnC) [Action]
     actOnVoting vt = do
         let (pro, contra) = L.partition snd $ votes vt
-        let v = (length $ entitledToVote vt) `div` 2 + 1
+        let totalV = length $ entitledToVote vt 
+        let successV = totalV `div` 2 + 1
 
-        if length contra >= v then
+        if length contra > totalV - successV then
             closeVoting
-        else if length pro >= v then do
+        else if length pro >= successV then do
             a <- act $ voteType vt
             c <- closeVoting
             return $ c ++ a
@@ -79,6 +84,13 @@
         where
             replaceChans chans (AnswerClients _ msg) = AnswerClients chans msg
             replaceChans _ a = a
+    act (VotePause) = do
+        rm <- thisRoom
+        chans <- roomClientsChans
+        let modifyGameInfo f room  = room{gameInfo = fmap f $ gameInfo room}
+        return [ModifyRoom (modifyGameInfo $ \g -> g{isPaused = not $ isPaused g}),
+                AnswerClients chans ["CHAT", "[server]", "Pause toggled"],
+                AnswerClients chans ["EM", toEngineMsg "I"]]
 
 
 startVote :: VoteType -> Reader (ClientIndex, IRnC) [Action]
@@ -123,3 +135,4 @@
 voteInfo :: VoteType -> B.ByteString
 voteInfo (VoteKick n) = B.concat [loc "kick", " ", n]
 voteInfo (VoteMap n) = B.concat [loc "map", " ", n]
+voteInfo (VotePause) = B.concat [loc "pause"]
--- a/hedgewars/uChat.pas	Fri Aug 22 00:37:26 2014 +0400
+++ b/hedgewars/uChat.pas	Fri Aug 22 00:57:07 2014 +0400
@@ -262,6 +262,12 @@
 ParseCommand('/hogsay '+s, true)
 end;
 
+procedure SendConsoleCommand(s: shortstring);
+begin
+    Delete(s, 1, 1);
+    SendIPC('~' + s)
+end;
+
 procedure AcceptChatString(s: shortstring);
 var i: TWave;
     j: TChatCmd;
@@ -383,7 +389,10 @@
                 ParseCommand(ChatCommandz[j].ProcedureCallChatCmd, true);
                 exit
                 end;
-        end
+        end;
+
+    if (gameType = gmtNet) then
+        SendConsoleCommand(s)
     end
 else
     begin
--- a/hedgewars/uCommandHandlers.pas	Fri Aug 22 00:37:26 2014 +0400
+++ b/hedgewars/uCommandHandlers.pas	Fri Aug 22 00:57:07 2014 +0400
@@ -26,7 +26,7 @@
 procedure freeModule;
 
 implementation
-uses uCommands, uTypes, uVariables, uIO, uDebug, uConsts, uScript, uUtils, SDLh, uRandom, uCaptions
+uses uCommands, uTypes, uVariables, uIO, uDebug, uConsts, uScript, uUtils, SDLh, uWorld, uRandom, uCaptions
      {$IFDEF USE_VIDEO_RECORDING}, uVideoRec {$ENDIF};
 
 var prevGState: TGameState = gsConfirm;
@@ -52,14 +52,12 @@
         begin
         prevGState:= GameState;
         GameState:= gsConfirm;
-        SDL_ShowCursor(1)
         end
     else
-        if GameState = gsConfirm then
-            begin
-            GameState:= prevGState;
-            SDL_ShowCursor(ord(isPaused))
-            end
+        if GameState = gsConfirm then            
+            GameState:= prevGState;            
+            
+    updateCursorVisibility;
 end;
 
 procedure chForceQuit(var s: shortstring);
@@ -176,7 +174,7 @@
 procedure chLeft_p(var s: shortstring);
 begin
 s:= s; // avoid compiler hint
-if CheckNoTeamOrHH or isPaused then
+if CheckNoTeamOrHH then
     exit;
 if not isExternalSource then
     SendIPC(_S'L');
@@ -201,7 +199,7 @@
 procedure chRight_p(var s: shortstring);
 begin
 s:= s; // avoid compiler hint
-if CheckNoTeamOrHH or isPaused then
+if CheckNoTeamOrHH then
     exit;
 if not isExternalSource then
     SendIPC(_S'R');
@@ -226,7 +224,7 @@
 procedure chUp_p(var s: shortstring);
 begin
 s:= s; // avoid compiler hint
-if CheckNoTeamOrHH or isPaused then
+if CheckNoTeamOrHH then
     exit;
 if not isExternalSource then
     SendIPC(_S'U');
@@ -251,7 +249,7 @@
 procedure chDown_p(var s: shortstring);
 begin
 s:= s; // avoid compiler hint
-if CheckNoTeamOrHH or isPaused then
+if CheckNoTeamOrHH then
     exit;
 if not isExternalSource then
     SendIPC(_S'D');
@@ -276,7 +274,7 @@
 procedure chPrecise_p(var s: shortstring);
 begin
 s:= s; // avoid compiler hint
-if CheckNoTeamOrHH or isPaused then
+if CheckNoTeamOrHH then
     exit;
 if not isExternalSource then
     SendIPC(_S'Z');
@@ -301,7 +299,7 @@
 procedure chLJump(var s: shortstring);
 begin
 s:= s; // avoid compiler hint
-if CheckNoTeamOrHH or isPaused then
+if CheckNoTeamOrHH then
     exit;
 if not isExternalSource then
     SendIPC(_S'j');
@@ -314,7 +312,7 @@
 procedure chHJump(var s: shortstring);
 begin
 s:= s; // avoid compiler hint
-if CheckNoTeamOrHH or isPaused then
+if CheckNoTeamOrHH then
     exit;
 if not isExternalSource then
     SendIPC(_S'J');
@@ -327,7 +325,7 @@
 procedure chAttack_p(var s: shortstring);
 begin
 s:= s; // avoid compiler hint
-if CheckNoTeamOrHH or isPaused then
+if CheckNoTeamOrHH then
     exit;
 bShowFinger:= false;
 with CurrentHedgehog^.Gear^ do
@@ -362,7 +360,7 @@
 procedure chSwitch(var s: shortstring);
 begin
 s:= s; // avoid compiler hint
-if CheckNoTeamOrHH or isPaused then
+if CheckNoTeamOrHH then
     exit;
 if not isExternalSource then
     SendIPC(_S'S');
@@ -576,7 +574,7 @@
 procedure chFindhh(var s: shortstring);
 begin
 s:= s; // avoid compiler hint
-if CheckNoTeamOrHH or isPaused then
+if CheckNoTeamOrHH then
     exit;
 
 if autoCameraOn then
@@ -597,8 +595,7 @@
 
 procedure chPause(var s: shortstring);
 begin
-s:= s; // avoid compiler hint
-if gameType <> gmtNet then
+if (gameType <> gmtNet) or (s = 'server') then
     isPaused:= not isPaused
     else
     if (CurrentTeam^.ExtDriven) or (CurrentHedgehog^.BotLevel > 0) then
@@ -606,10 +603,7 @@
     else
         isAFK:= false; // for real ninjas
 
-if isPaused or isAFK then
-    SDL_ShowCursor(1)
-    else
-    SDL_ShowCursor(ord(GameState = gsConfirm))
+updateCursorVisibility;
 end;
 
 procedure chRotateMask(var s: shortstring);
--- a/hedgewars/uIO.pas	Fri Aug 22 00:37:26 2014 +0400
+++ b/hedgewars/uIO.pas	Fri Aug 22 00:57:07 2014 +0400
@@ -118,9 +118,22 @@
     WriteLnToConsole(msgOK)
 end;
 
+procedure ParseChatCommand(command: shortstring; message: shortstring;
+                           messageStartIndex: Byte);
+var
+    text: shortstring;
+begin
+    text:= copy(message, messageStartIndex,
+                Length(message) - messageStartIndex + 1);
+    ParseCommand(command + text, true);
+    WriteLnToConsole(text)
+end;
+
 procedure ParseIPCCommand(s: shortstring);
 var loTicks: Word;
+    isProcessed: boolean;
 begin
+isProcessed := true;
 
 case s[1] of
      '!': begin AddFileLog('Ping? Pong!'); isPonged:= true; end;
@@ -140,12 +153,26 @@
      'V': begin
               if s[2] = '.' then
                   ParseCommand('campvar ' + copy(s, 3, length(s) - 2), true);
-          end
+          end;
+     'I': ParseCommand('pause server', true);
+     's': if gameType = gmtNet then
+             ParseChatCommand('chatmsg ', s, 2)
+          else 
+             isProcessed:= false;
+     'b': if gameType = gmtNet then
+             ParseChatCommand('chatmsg ' + #4, s, 2)
+          else 
+             isProcessed:= false;
      else
-     loTicks:= SDLNet_Read16(@s[byte(s[0]) - 1]);
-     AddCmd(loTicks, s);
-     AddFileLog('[IPC in] ' + sanitizeCharForLog(s[1]) + ' ticks ' + IntToStr(lastcmd^.loTime));
-     end
+        isProcessed:= false;
+     end;
+
+    if (not isProcessed) then
+    begin
+        loTicks:= SDLNet_Read16(@s[byte(s[0]) - 1]);
+        AddCmd(loTicks, s);
+        AddFileLog('[IPC in] ' + sanitizeCharForLog(s[1]) + ' ticks ' + IntToStr(lastcmd^.loTime));
+    end
 end;
 
 procedure IPCCheckSock;
@@ -357,16 +384,8 @@
             s:= copy(headcmd^.str, 2, Pred(headcmd^.len));
             ParseCommand('gencmd ' + s, true);
              end;
-        's': begin
-            s:= copy(headcmd^.str, 2, Pred(headcmd^.len));
-            ParseCommand('chatmsg ' + s, true);
-            WriteLnToConsole(s)
-             end;
-        'b': begin
-            s:= copy(headcmd^.str, 2, Pred(headcmd^.len));
-            ParseCommand('chatmsg ' + #4 + s, true);
-            WriteLnToConsole(s)
-             end;
+        's': ParseChatCommand('chatmsg ', headcmd^.str, 2);
+        'b': ParseChatCommand('chatmsg ' + #4, headcmd^.str, 2);
         'F': ParseCommand('teamgone u' + copy(headcmd^.str, 2, Pred(headcmd^.len)), true);
         'G': ParseCommand('teamback u' + copy(headcmd^.str, 2, Pred(headcmd^.len)), true);
         'f': ParseCommand('teamgone s' + copy(headcmd^.str, 2, Pred(headcmd^.len)), true);
--- a/hedgewars/uInputHandler.pas	Fri Aug 22 00:37:26 2014 +0400
+++ b/hedgewars/uInputHandler.pas	Fri Aug 22 00:57:07 2014 +0400
@@ -171,6 +171,8 @@
     if (code > 3) and KeyDown and (not ((CurrentBinds[code] = 'put')) or (CurrentBinds[code] = 'ammomenu') or (CurrentBinds[code] = '+cur_u') or (CurrentBinds[code] = '+cur_d') or (CurrentBinds[code] = '+cur_l') or (CurrentBinds[code] = '+cur_r')) and (CurrentTeam <> nil) and (not CurrentTeam^.ExtDriven) then bShowAmmoMenu:= false;
     if KeyDown then
         begin
+        Trusted:= Trusted and (not isPaused); //releasing keys during pause should be allowed on the other hand
+                              
         if CurrentBinds[code] = 'switch' then
             LocalMessage:= LocalMessage or gmSwitch
         else if CurrentBinds[code] = '+precise' then
--- a/hedgewars/uWorld.pas	Fri Aug 22 00:37:26 2014 +0400
+++ b/hedgewars/uWorld.pas	Fri Aug 22 00:57:07 2014 +0400
@@ -40,6 +40,7 @@
 procedure animateWidget(widget: POnScreenWidget; fade, showWidget: boolean);
 procedure MoveCamera;
 procedure onFocusStateChanged;
+procedure updateCursorVisibility;
 
 implementation
 uses
@@ -2054,6 +2055,14 @@
     end;
 end;
 
+procedure updateCursorVisibility;
+begin       
+    if isPaused or isAFK then
+        SDL_ShowCursor(1)
+    else
+        SDL_ShowCursor(ord(GameState = gsConfirm))
+end;
+
 procedure SetUtilityWidgetState(ammoType: TAmmoType);
 begin
 {$IFDEF USE_TOUCH_INTERFACE}