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) |
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 |
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 () |