gameServer/Actions.hs
branchwebgl
changeset 8444 75db7bb8dce8
parent 8330 aaefa587e277
parent 8439 3850c4bfe6b5
child 8833 c13ebed437cb
equal deleted inserted replaced
8340:46a9fde631f4 8444:75db7bb8dce8
     1 {-# LANGUAGE CPP, OverloadedStrings #-}
     1 {-# LANGUAGE CPP, OverloadedStrings, ScopedTypeVariables #-}
     2 {-# OPTIONS_GHC -fno-warn-orphans #-}
     2 {-# OPTIONS_GHC -fno-warn-orphans #-}
     3 module Actions where
     3 module Actions where
     4 
     4 
     5 import Control.Concurrent
     5 import Control.Concurrent
     6 import qualified Data.Set as Set
     6 import qualified Data.Set as Set
     7 import qualified Data.Sequence as Seq
     7 import qualified Data.Map as Map
     8 import qualified Data.List as L
     8 import qualified Data.List as L
     9 import qualified Control.Exception as Exception
     9 import qualified Control.Exception as Exception
    10 import System.Log.Logger
    10 import System.Log.Logger
    11 import Control.Monad
    11 import Control.Monad
    12 import Data.Time
    12 import Data.Time
    54     | BanIP B.ByteString NominalDiffTime B.ByteString
    54     | BanIP B.ByteString NominalDiffTime B.ByteString
    55     | BanNick B.ByteString NominalDiffTime B.ByteString
    55     | BanNick B.ByteString NominalDiffTime B.ByteString
    56     | BanList
    56     | BanList
    57     | Unban B.ByteString
    57     | Unban B.ByteString
    58     | ChangeMaster (Maybe ClientIndex)
    58     | ChangeMaster (Maybe ClientIndex)
    59     | RemoveClientTeams ClientIndex
    59     | RemoveClientTeams
    60     | ModifyClient (ClientInfo -> ClientInfo)
    60     | ModifyClient (ClientInfo -> ClientInfo)
    61     | ModifyClient2 ClientIndex (ClientInfo -> ClientInfo)
    61     | ModifyClient2 ClientIndex (ClientInfo -> ClientInfo)
    62     | ModifyRoomClients (ClientInfo -> ClientInfo)
    62     | ModifyRoomClients (ClientInfo -> ClientInfo)
    63     | ModifyRoom (RoomInfo -> RoomInfo)
    63     | ModifyRoom (RoomInfo -> RoomInfo)
    64     | ModifyServerInfo (ServerInfo -> ServerInfo)
    64     | ModifyServerInfo (ServerInfo -> ServerInfo)
    74     | RestartServer
    74     | RestartServer
    75     | AddNick2Bans B.ByteString B.ByteString UTCTime
    75     | AddNick2Bans B.ByteString B.ByteString UTCTime
    76     | AddIP2Bans B.ByteString B.ByteString UTCTime
    76     | AddIP2Bans B.ByteString B.ByteString UTCTime
    77     | CheckBanned Bool
    77     | CheckBanned Bool
    78     | SaveReplay
    78     | SaveReplay
       
    79     | Stats
    79 
    80 
    80 
    81 
    81 type CmdHandler = [B.ByteString] -> Reader (ClientIndex, IRnC) [Action]
    82 type CmdHandler = [B.ByteString] -> Reader (ClientIndex, IRnC) [Action]
    82 
    83 
    83 instance NFData Action where
    84 instance NFData Action where
    84     rnf (AnswerClients chans msg) = chans `deepseq` msg `deepseq` ()
    85     rnf (AnswerClients chans msg) = chans `deepseq` msg `deepseq` ()
    85     rnf a = a `seq` ()
    86     rnf a = a `seq` ()
    86 
    87 
    87 instance NFData B.ByteString
    88 --instance NFData B.ByteString
    88 instance NFData (Chan a)
    89 instance NFData (Chan a)
    89 
    90 
    90 
    91 
    91 othersChans :: StateT ServerState IO [ClientChan]
    92 othersChans :: StateT ServerState IO [ClientChan]
    92 othersChans = do
    93 othersChans = do
   141     (Just ci) <- gets clientIndex
   142     (Just ci) <- gets clientIndex
   142     ri <- clientRoomA
   143     ri <- clientRoomA
   143 
   144 
   144     chan <- client's sendChan
   145     chan <- client's sendChan
   145     clNick <- client's nick
   146     clNick <- client's nick
   146     loggedIn <- client's logonPassed
   147     loggedIn <- client's isVisible
   147 
   148 
   148     when (ri /= lobbyId) $ do
   149     when (ri /= lobbyId) $ do
   149         processAction $ MoveToLobby ("quit: " `B.append` msg)
   150         processAction $ MoveToLobby ("quit: " `B.append` msg)
   150         return ()
   151         return ()
   151 
   152 
   152     clientsChans <- liftM (Prelude.map sendChan . Prelude.filter logonPassed) $! allClientsS
   153     clientsChans <- liftM (Prelude.map sendChan . Prelude.filter isVisible) $! allClientsS
   153     io $
   154     io $
   154         infoM "Clients" (show ci ++ " quits: " ++ B.unpack msg)
   155         infoM "Clients" (show ci ++ " quits: " ++ B.unpack msg)
   155 
   156 
   156     when loggedIn $ processAction $ AnswerClients clientsChans ["LOBBY:LEFT", clNick, msg]
   157     when loggedIn $ processAction $ AnswerClients clientsChans ["LOBBY:LEFT", clNick, msg]
   157 
   158 
   158     mapM_ processAction
   159     mapM_ processAction
   159         [
   160         [
   160         AnswerClients [chan] ["BYE", msg]
   161         AnswerClients [chan] ["BYE", msg]
   161         , ModifyClient (\c -> c{nick = "", logonPassed = False}) -- this will effectively hide client from others while he isn't deleted from list
   162         , ModifyClient (\c -> c{nick = "", isVisible = False}) -- this will effectively hide client from others while he isn't deleted from list
   162         ]
   163         ]
   163 
   164 
   164     s <- get
   165     s <- get
   165     put $! s{removedClients = ci `Set.insert` removedClients s}
   166     put $! s{removedClients = ci `Set.insert` removedClients s}
   166 
   167 
   233     clNick <- client's nick
   234     clNick <- client's nick
   234     chans <- othersChans
   235     chans <- othersChans
   235 
   236 
   236     if master then
   237     if master then
   237         if playersNum > 1 then
   238         if playersNum > 1 then
   238             mapM_ processAction [ChangeMaster Nothing, NoticeMessage AdminLeft, RemoveClientTeams ci, AnswerClients chans ["LEFT", clNick, msg]]
   239             mapM_ processAction [ChangeMaster Nothing, NoticeMessage AdminLeft, RemoveClientTeams, AnswerClients chans ["LEFT", clNick, msg]]
   239             else
   240             else
   240             processAction RemoveRoom
   241             processAction RemoveRoom
   241         else
   242         else
   242         mapM_ processAction [RemoveClientTeams ci, AnswerClients chans ["LEFT", clNick, msg]]
   243         mapM_ processAction [RemoveClientTeams, AnswerClients chans ["LEFT", clNick, msg]]
   243 
   244 
   244     -- when not removing room
   245     -- when not removing room
   245     ready <- client's isReady
   246     ready <- client's isReady
   246     when (not master || playersNum > 1) . io $ do
   247     when (not master || playersNum > 1) . io $ do
   247         modifyRoom rnc (\r -> r{
   248         modifyRoom rnc (\r -> r{
   372     mapM_ processAction [
   373     mapM_ processAction [
   373         AnswerClients chans ["EM", rmTeamMsg],
   374         AnswerClients chans ["EM", rmTeamMsg],
   374         ModifyRoom (\r -> r{
   375         ModifyRoom (\r -> r{
   375                 gameInfo = liftM (\g -> g{
   376                 gameInfo = liftM (\g -> g{
   376                     teamsInGameNumber = teamsInGameNumber g - 1
   377                     teamsInGameNumber = teamsInGameNumber g - 1
   377                     , roundMsgs = roundMsgs g Seq.|> rmTeamMsg
   378                     , roundMsgs = rmTeamMsg : roundMsgs g
   378                 }) $ gameInfo r
   379                 }) $ gameInfo r
   379             })
   380             })
   380         ]
   381         ]
   381 
   382 
   382     rnc <- gets roomsClients
   383     rnc <- gets roomsClients
   383     ri <- clientRoomA
   384     ri <- clientRoomA
   384     gi <- io $ room'sM rnc gameInfo ri
   385     gi <- io $ room'sM rnc gameInfo ri
   385     when (isJust gi && 0 == teamsInGameNumber (fromJust gi)) $
   386     when (0 == teamsInGameNumber (fromJust gi)) $
   386         processAction FinishGame
   387         processAction FinishGame
   387     where
   388     where
   388         rmTeamMsg = toEngineMsg $ 'F' `B.cons` teamName
   389         rmTeamMsg = toEngineMsg $ 'F' `B.cons` teamName
   389 
   390 
   390 
   391 
   391 processAction (RemoveTeam teamName) = do
   392 processAction (RemoveTeam teamName) = do
   392     rnc <- gets roomsClients
   393     (Just ci) <- gets clientIndex
   393     ri <- clientRoomA
   394     rnc <- gets roomsClients
   394     inGame <- io $ room'sM rnc (isJust . gameInfo) ri
   395     ri <- clientRoomA
       
   396     inGame <- io $ do
       
   397         r <- room'sM rnc (isJust . gameInfo) ri
       
   398         c <- client'sM rnc isInGame ci
       
   399         return $ r && c
   395     chans <- othersChans
   400     chans <- othersChans
   396     mapM_ processAction $
   401     mapM_ processAction $
   397         ModifyRoom (\r -> r{
   402         ModifyRoom (\r -> r{
   398             teams = Prelude.filter (\t -> teamName /= teamname t) $ teams r
   403             teams = Prelude.filter (\t -> teamName /= teamname t) $ teams r
   399             , gameInfo = liftM (\g -> g{leftTeams = teamName : leftTeams g}) $ gameInfo r
   404             , gameInfo = liftM (\g -> g{leftTeams = teamName : leftTeams g}) $ gameInfo r
   401         : SendUpdateOnThisRoom
   406         : SendUpdateOnThisRoom
   402         : AnswerClients chans ["REMOVE_TEAM", teamName]
   407         : AnswerClients chans ["REMOVE_TEAM", teamName]
   403         : [SendTeamRemovalMessage teamName | inGame]
   408         : [SendTeamRemovalMessage teamName | inGame]
   404 
   409 
   405 
   410 
   406 processAction (RemoveClientTeams clId) = do
   411 processAction RemoveClientTeams = do
       
   412     (Just ci) <- gets clientIndex
   407     rnc <- gets roomsClients
   413     rnc <- gets roomsClients
   408 
   414 
   409     removeTeamActions <- io $ do
   415     removeTeamActions <- io $ do
   410         clNick <- client'sM rnc nick clId
   416         rId <- clientRoomM rnc ci
   411         rId <- clientRoomM rnc clId
       
   412         roomTeams <- room'sM rnc teams rId
   417         roomTeams <- room'sM rnc teams rId
   413         return . Prelude.map (RemoveTeam . teamname) . Prelude.filter (\t -> teamowner t == clNick) $ roomTeams
   418         return . Prelude.map (RemoveTeam . teamname) . Prelude.filter (\t -> teamownerId t == ci) $ roomTeams
   414 
   419 
   415     mapM_ processAction removeTeamActions
   420     mapM_ processAction removeTeamActions
   416 
   421 
   417 
   422 
   418 
   423 
   419 processAction CheckRegistered = do
   424 processAction CheckRegistered = do
   420     (Just ci) <- gets clientIndex
   425     (Just ci) <- gets clientIndex
   421     n <- client's nick
   426     n <- client's nick
   422     h <- client's host
   427     h <- client's host
   423     p <- client's clientProto
   428     p <- client's clientProto
       
   429     checker <- client's isChecker
   424     uid <- client's clUID
   430     uid <- client's clUID
   425     haveSameNick <- liftM (not . null . tail . filter (\c -> caseInsensitiveCompare (nick c) n)) allClientsS
   431     -- allow multiple checker logins
   426     if haveSameNick then
   432     haveSameNick <- liftM (not . null . tail . filter (\c -> (not $ isChecker c) && caseInsensitiveCompare (nick c) n)) allClientsS
       
   433     if haveSameNick && (not checker) then
   427         if p < 38 then
   434         if p < 38 then
   428             processAction $ ByeClient "Nickname is already in use"
   435             processAction $ ByeClient $ loc "Nickname is already in use"
   429             else
   436             else
   430             processAction $ NoticeMessage NickAlreadyInUse
   437             processAction $ NoticeMessage NickAlreadyInUse
   431         else
   438         else
   432         do
   439         do
   433         db <- gets (dbQueries . serverInfo)
   440         db <- gets (dbQueries . serverInfo)
   442 
   449 
   443 processAction (ProcessAccountInfo info) = do
   450 processAction (ProcessAccountInfo info) = do
   444     case info of
   451     case info of
   445         HasAccount passwd isAdmin -> do
   452         HasAccount passwd isAdmin -> do
   446             b <- isBanned
   453             b <- isBanned
   447             when (not b) $ do
   454             c <- client's isChecker
   448                 chan <- client's sendChan
   455             when (not b) $ (if c then checkerLogin else playerLogin) passwd isAdmin
   449                 mapM_ processAction [AnswerClients [chan] ["ASKPASSWORD"], ModifyClient (\c -> c{webPassword = passwd, isAdministrator = isAdmin})]
       
   450         Guest -> do
   456         Guest -> do
   451             b <- isBanned
   457             b <- isBanned
   452             when (not b) $
   458             when (not b) $
   453                 processAction JoinLobby
   459                 processAction JoinLobby
   454         Admin -> do
   460         Admin -> do
   457             processAction $ AnswerClients [chan] ["ADMIN_ACCESS"]
   463             processAction $ AnswerClients [chan] ["ADMIN_ACCESS"]
   458     where
   464     where
   459     isBanned = do
   465     isBanned = do
   460         processAction $ CheckBanned False
   466         processAction $ CheckBanned False
   461         liftM B.null $ client's nick
   467         liftM B.null $ client's nick
   462 
   468     checkerLogin _ False = processAction $ ByeClient $ loc "No checker rights"
       
   469     checkerLogin p True = do
       
   470         wp <- client's webPassword
       
   471         processAction $
       
   472             if wp == p then ModifyClient $ \c -> c{logonPassed = True} else ByeClient $ loc "Authentication failed"
       
   473     playerLogin p a = do
       
   474         chan <- client's sendChan
       
   475         mapM_ processAction [AnswerClients [chan] ["ASKPASSWORD"], ModifyClient (\c -> c{webPassword = p, isAdministrator = a})]
   463 
   476 
   464 processAction JoinLobby = do
   477 processAction JoinLobby = do
   465     chan <- client's sendChan
   478     chan <- client's sendChan
   466     clientNick <- client's nick
   479     clientNick <- client's nick
   467     isAuthenticated <- liftM (not . B.null) $ client's webPassword
   480     isAuthenticated <- liftM (not . B.null) $ client's webPassword
   468     isAdmin <- client's isAdministrator
   481     isAdmin <- client's isAdministrator
   469     loggedInClients <- liftM (Prelude.filter logonPassed) $! allClientsS
   482     loggedInClients <- liftM (Prelude.filter isVisible) $! allClientsS
   470     let (lobbyNicks, clientsChans) = unzip . L.map (nick &&& sendChan) $ loggedInClients
   483     let (lobbyNicks, clientsChans) = unzip . L.map (nick &&& sendChan) $ loggedInClients
   471     let authenticatedNicks = L.map nick . L.filter (not . B.null . webPassword) $ loggedInClients
   484     let authenticatedNicks = L.map nick . L.filter (not . B.null . webPassword) $ loggedInClients
   472     let adminsNicks = L.map nick . L.filter isAdministrator $ loggedInClients
   485     let adminsNicks = L.map nick . L.filter isAdministrator $ loggedInClients
   473     let clFlags = B.concat . L.concat $ [["u" | isAuthenticated], ["a" | isAdmin]]
   486     let clFlags = B.concat . L.concat $ [["u" | isAuthenticated], ["a" | isAdmin]]
   474     mapM_ processAction . concat $ [
   487     mapM_ processAction . concat $ [
   475         [AnswerClients clientsChans ["LOBBY:JOINED", clientNick]]
   488         [AnswerClients clientsChans ["LOBBY:JOINED", clientNick]]
   476         , [AnswerClients [chan] ("LOBBY:JOINED" : clientNick : lobbyNicks)]
   489         , [AnswerClients [chan] ("LOBBY:JOINED" : clientNick : lobbyNicks)]
   477         , [AnswerClients [chan] ("CLIENT_FLAGS" : "+u" : authenticatedNicks) | not $ null authenticatedNicks]
   490         , [AnswerClients [chan] ("CLIENT_FLAGS" : "+u" : authenticatedNicks) | not $ null authenticatedNicks]
   478         , [AnswerClients [chan] ("CLIENT_FLAGS" : "+a" : adminsNicks) | not $ null adminsNicks]
   491         , [AnswerClients [chan] ("CLIENT_FLAGS" : "+a" : adminsNicks) | not $ null adminsNicks]
   479         , [AnswerClients (chan : clientsChans) ["CLIENT_FLAGS",  B.concat["+" , clFlags], clientNick] | not $ B.null clFlags]
   492         , [AnswerClients (chan : clientsChans) ["CLIENT_FLAGS",  B.concat["+" , clFlags], clientNick] | not $ B.null clFlags]
   480         , [ModifyClient (\cl -> cl{logonPassed = True})]
   493         , [ModifyClient (\cl -> cl{logonPassed = True, isVisible = True})]
   481         , [SendServerMessage]
   494         , [SendServerMessage]
   482         ]
   495         ]
   483 
   496 
   484 
   497 
   485 processAction (KickClient kickId) = do
   498 processAction (KickClient kickId) = do
   486     modify (\s -> s{clientIndex = Just kickId})
   499     modify (\s -> s{clientIndex = Just kickId})
   487     clHost <- client's host
   500     clHost <- client's host
   488     currentTime <- io getCurrentTime
   501     currentTime <- io getCurrentTime
   489     mapM_ processAction [
   502     mapM_ processAction [
   490         AddIP2Bans clHost "60 seconds cooldown after kick" (addUTCTime 60 currentTime)
   503         AddIP2Bans clHost (loc "60 seconds cooldown after kick") (addUTCTime 60 currentTime)
   491         , ModifyClient (\c -> c{isKickedFromServer = True})
   504         , ModifyClient (\c -> c{isKickedFromServer = True})
   492         , ByeClient "Kicked"
   505         , ByeClient "Kicked"
   493         ]
   506         ]
   494 
   507 
   495 
   508 
   541 
   554 
   542 
   555 
   543 processAction (KickRoomClient kickId) = do
   556 processAction (KickRoomClient kickId) = do
   544     modify (\s -> s{clientIndex = Just kickId})
   557     modify (\s -> s{clientIndex = Just kickId})
   545     ch <- client's sendChan
   558     ch <- client's sendChan
   546     mapM_ processAction [AnswerClients [ch] ["KICKED"], MoveToLobby "kicked"]
   559     mapM_ processAction [AnswerClients [ch] ["KICKED"], MoveToLobby $ loc "kicked"]
   547 
   560 
   548 
   561 
   549 processAction (AddClient cl) = do
   562 processAction (AddClient cl) = do
   550     rnc <- gets roomsClients
   563     rnc <- gets roomsClients
   551     si <- gets serverInfo
   564     si <- gets serverInfo
   604     where
   617     where
   605         kickTimeouted rnc ci = do
   618         kickTimeouted rnc ci = do
   606             pq <- io $ client'sM rnc pingsQueue ci
   619             pq <- io $ client'sM rnc pingsQueue ci
   607             when (pq > 0) $ do
   620             when (pq > 0) $ do
   608                 withStateT (\as -> as{clientIndex = Just ci}) $
   621                 withStateT (\as -> as{clientIndex = Just ci}) $
   609                     processAction (ByeClient "Ping timeout")
   622                     processAction (ByeClient $ loc "Ping timeout")
   610 --                when (pq > 1) $
   623 --                when (pq > 1) $
   611 --                    processAction $ DeleteClient ci -- smth went wrong with client io threads, issue DeleteClient here
   624 --                    processAction $ DeleteClient ci -- smth went wrong with client io threads, issue DeleteClient here
   612 
   625 
   613 
   626 
   614 processAction StatsAction = do
   627 processAction StatsAction = do
   631             noticeM "Core" "Spawning new server"
   644             noticeM "Core" "Spawning new server"
   632             _ <- createProcess (proc "./hedgewars-server" args)
   645             _ <- createProcess (proc "./hedgewars-server" args)
   633             return ()
   646             return ()
   634         processAction $ ModifyServerInfo (\s -> s{shutdownPending = True})
   647         processAction $ ModifyServerInfo (\s -> s{shutdownPending = True})
   635 
   648 
       
   649 processAction Stats = do
       
   650     cls <- allClientsS
       
   651     let stats = versions cls
       
   652     processAction $ Warning stats
       
   653     where
       
   654         versions = B.concat . ((:) "<table border=1>") . (flip (++) ["</table>"])
       
   655             . concatMap (\(p, n :: Int) -> ["<tr><td>", protoNumber2ver p, "</td><td>", showB n, "</td></tr>"])
       
   656             . Map.toList . Map.fromListWith (+) . map (\c -> (clientProto c, 1))
       
   657 
   636 #if defined(OFFICIAL_SERVER)
   658 #if defined(OFFICIAL_SERVER)
   637 processAction SaveReplay = do
   659 processAction SaveReplay = do
   638     ri <- clientRoomA
   660     ri <- clientRoomA
   639     rnc <- gets roomsClients
   661     rnc <- gets roomsClients
       
   662 
   640     io $ do
   663     io $ do
   641         r <- room'sM rnc id ri
   664         r <- room'sM rnc id ri
   642         saveReplay r
   665         saveReplay r
   643 #else
   666 #else
   644 processAction SaveReplay = return ()
   667 processAction SaveReplay = return ()