~ubuntu-branches/ubuntu/wily/hedgewars/wily

« back to all changes in this revision

Viewing changes to gameServer/Actions.hs

  • Committer: Package Import Robot
  • Author(s): Dmitry E. Oboukhov
  • Date: 2011-09-23 10:16:55 UTC
  • mfrom: (1.2.11 upstream)
  • Revision ID: package-import@ubuntu.com-20110923101655-3977th2gc5n0a3pv
Tags: 0.9.16-1
* New upstream version.
 + Downloadable content! Simply click to install any content.
   New voices, hats, maps, themes, translations, music, scripts...
   Hedgewars is now more customisable than ever before! As time goes
   by we will be soliciting community content to feature on this page,
   so remember to check it from time to time. If you decide you want
   to go back to standard Hedgewars, just remove the Data directory
   from your Hedgewars config directory.
 + 3-D rendering! Diorama-like rendering of the game in a variety
   of 3D modes. Let us know which ones work best for you, we didn't
   really have the equipment to test them all.
 + Resizable game window.
 + New utilities! The Time Box will remove one of your hedgehogs
   from the game for a while, protecting from attack until it returns,
   somewhere else on the map. Land spray will allow you to build bridges,
   seal up holes, or just make life unpleasant for your enemies.
 + New single player: Bamboo Thicket, That Sinking Feeling, Newton and
   the Tree and multi-player: The Specialists, Space Invaders,
   Racer - scripts! And a ton more script hooks for scripters
 + New twists on old weapons. Drill strike, seduction and fire have
   been adjusted. Defective mines have been added, rope can attach to
   hogs/crates/barrels again, grenades now have variable bounce (use
   precise key + 1-5). Portal gun is now more usable in flight and
   all game actions are a lot faster.
 + New theme - Golf, dozens of new community hats and a new
   localised Default voice, Ukranian.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
{-# LANGUAGE OverloadedStrings #-}
1
2
module Actions where
2
3
 
3
 
import Control.Concurrent.STM
4
 
import Control.Concurrent.Chan
5
 
import Data.IntMap
6
 
import qualified Data.IntSet as IntSet
 
4
import Control.Concurrent
 
5
import qualified Data.Set as Set
7
6
import qualified Data.Sequence as Seq
 
7
import qualified Data.List as L
 
8
import qualified Control.Exception as Exception
8
9
import System.Log.Logger
9
10
import Control.Monad
10
11
import Data.Time
11
12
import Data.Maybe
 
13
import Control.Monad.Reader
 
14
import Control.Monad.State.Strict
 
15
import qualified Data.ByteString.Char8 as B
 
16
import Control.DeepSeq
 
17
import Data.Unique
 
18
import Control.Arrow
 
19
import Control.Exception
 
20
import OfficialServer.GameReplayStore
 
21
import System.Process
 
22
import Network.Socket
12
23
-----------------------------
13
24
import CoreTypes
14
25
import Utils
 
26
import ClientIO
 
27
import ServerState
 
28
import Consts
 
29
import ConfigFile
15
30
 
16
31
data Action =
17
 
    AnswerThisClient [String]
18
 
    | AnswerAll [String]
19
 
    | AnswerAllOthers [String]
20
 
    | AnswerThisRoom [String]
21
 
    | AnswerOthersInRoom [String]
22
 
    | AnswerSameClan [String]
23
 
    | AnswerLobby [String]
 
32
    AnswerClients ![ClientChan] ![B.ByteString]
24
33
    | SendServerMessage
25
34
    | SendServerVars
26
 
    | RoomAddThisClient Int -- roomID
27
 
    | RoomRemoveThisClient String
28
 
    | RemoveTeam String
 
35
    | MoveToRoom RoomIndex
 
36
    | MoveToLobby B.ByteString
 
37
    | RemoveTeam B.ByteString
29
38
    | RemoveRoom
30
39
    | UnreadyRoomClients
31
 
    | MoveToLobby
32
 
    | ProtocolError String
33
 
    | Warning String
34
 
    | ByeClient String
35
 
    | KickClient Int -- clID
36
 
    | KickRoomClient Int -- clID
37
 
    | BanClient String -- nick
38
 
    | RemoveClientTeams Int -- clID
 
40
    | JoinLobby
 
41
    | ProtocolError B.ByteString
 
42
    | Warning B.ByteString
 
43
    | NoticeMessage Notice
 
44
    | ByeClient B.ByteString
 
45
    | KickClient ClientIndex
 
46
    | KickRoomClient ClientIndex
 
47
    | BanClient NominalDiffTime B.ByteString ClientIndex
 
48
    | BanIP B.ByteString NominalDiffTime B.ByteString
 
49
    | BanList
 
50
    | ChangeMaster
 
51
    | RemoveClientTeams ClientIndex
39
52
    | ModifyClient (ClientInfo -> ClientInfo)
40
 
    | ModifyClient2 Int (ClientInfo -> ClientInfo)
 
53
    | ModifyClient2 ClientIndex (ClientInfo -> ClientInfo)
41
54
    | ModifyRoom (RoomInfo -> RoomInfo)
42
55
    | ModifyServerInfo (ServerInfo -> ServerInfo)
43
 
    | AddRoom String String
 
56
    | AddRoom B.ByteString B.ByteString
44
57
    | CheckRegistered
45
58
    | ClearAccountsCache
46
59
    | ProcessAccountInfo AccountInfo
47
 
    | Dump
48
60
    | AddClient ClientInfo
 
61
    | DeleteClient ClientIndex
49
62
    | PingAll
50
63
    | StatsAction
51
 
 
52
 
type CmdHandler = Int -> Clients -> Rooms -> [String] -> [Action]
53
 
 
54
 
replaceID a (b, c, d, e) = (a, c, d, e)
55
 
 
56
 
processAction :: (Int, ServerInfo, Clients, Rooms) -> Action -> IO (Int, ServerInfo, Clients, Rooms)
57
 
 
58
 
 
59
 
processAction (clID, serverInfo, clients, rooms) (AnswerThisClient msg) = do
60
 
    writeChan (sendChan $ clients ! clID) msg
61
 
    return (clID, serverInfo, clients, rooms)
62
 
 
63
 
 
64
 
processAction (clID, serverInfo, clients, rooms) (AnswerAll msg) = do
65
 
    mapM_ (\cl -> writeChan (sendChan cl) msg) (elems clients)
66
 
    return (clID, serverInfo, clients, rooms)
67
 
 
68
 
 
69
 
processAction (clID, serverInfo, clients, rooms) (AnswerAllOthers msg) = do
70
 
    mapM_ (\id' -> writeChan (sendChan $ clients ! id') msg) $
71
 
        Prelude.filter (\id' -> (id' /= clID) && logonPassed (clients ! id')) (keys clients)
72
 
    return (clID, serverInfo, clients, rooms)
73
 
 
74
 
 
75
 
processAction (clID, serverInfo, clients, rooms) (AnswerThisRoom msg) = do
76
 
    mapM_ (\id' -> writeChan (sendChan $ clients ! id') msg) roomClients
77
 
    return (clID, serverInfo, clients, rooms)
78
 
    where
79
 
        roomClients = IntSet.elems $ playersIDs room
80
 
        room = rooms ! rID
81
 
        rID = roomID client
82
 
        client = clients ! clID
83
 
 
84
 
 
85
 
processAction (clID, serverInfo, clients, rooms) (AnswerOthersInRoom msg) = do
86
 
    mapM_ (\id' -> writeChan (sendChan $ clients ! id') msg) $ Prelude.filter (/= clID) roomClients
87
 
    return (clID, serverInfo, clients, rooms)
88
 
    where
89
 
        roomClients = IntSet.elems $ playersIDs room
90
 
        room = rooms ! rID
91
 
        rID = roomID client
92
 
        client = clients ! clID
93
 
 
94
 
 
95
 
processAction (clID, serverInfo, clients, rooms) (AnswerLobby msg) = do
96
 
    mapM_ (\id' -> writeChan (sendChan $ clients ! id') msg) roomClients
97
 
    return (clID, serverInfo, clients, rooms)
98
 
    where
99
 
        roomClients = IntSet.elems $ playersIDs room
100
 
        room = rooms ! 0
101
 
 
102
 
 
103
 
processAction (clID, serverInfo, clients, rooms) (AnswerSameClan msg) = do
104
 
    mapM_ (\cl -> writeChan (sendChan cl) msg) sameClanOrSpec
105
 
    return (clID, serverInfo, clients, rooms)
106
 
    where
107
 
        otherRoomClients = Prelude.map ((!) clients) $ IntSet.elems $ clID `IntSet.delete` (playersIDs room)
108
 
        sameClanOrSpec = if teamsInGame client > 0 then sameClanClients else spectators
109
 
        spectators = Prelude.filter (\cl -> teamsInGame cl == 0) otherRoomClients
110
 
        sameClanClients = Prelude.filter (\cl -> teamsInGame cl > 0 && clientClan cl == thisClan) otherRoomClients
111
 
        thisClan = clientClan client
112
 
        room = rooms ! rID
113
 
        rID = roomID client
114
 
        client = clients ! clID
115
 
 
116
 
 
117
 
processAction (clID, serverInfo, clients, rooms) SendServerMessage = do
118
 
    writeChan (sendChan $ clients ! clID) ["SERVER_MESSAGE", message serverInfo]
119
 
    return (clID, serverInfo, clients, rooms)
120
 
    where
121
 
        client = clients ! clID
122
 
        message si = if clientProto client < latestReleaseVersion si then
 
64
    | RestartServer
 
65
    | AddNick2Bans B.ByteString B.ByteString UTCTime
 
66
    | AddIP2Bans B.ByteString B.ByteString UTCTime
 
67
    | CheckBanned
 
68
    | SaveReplay
 
69
 
 
70
 
 
71
type CmdHandler = [B.ByteString] -> Reader (ClientIndex, IRnC) [Action]
 
72
 
 
73
instance NFData Action where
 
74
    rnf (AnswerClients chans msg) = chans `deepseq` msg `deepseq` ()
 
75
    rnf a = a `seq` ()
 
76
 
 
77
instance NFData B.ByteString
 
78
instance NFData (Chan a)
 
79
 
 
80
 
 
81
othersChans :: StateT ServerState IO [ClientChan]
 
82
othersChans = do
 
83
    cl <- client's id
 
84
    ri <- clientRoomA
 
85
    liftM (map sendChan . filter (/= cl)) $ roomClientsS ri
 
86
 
 
87
processAction :: Action -> StateT ServerState IO ()
 
88
 
 
89
 
 
90
processAction (AnswerClients chans msg) =
 
91
    io $ mapM_ (`writeChan` (msg `deepseq` msg)) (chans `deepseq` chans)
 
92
 
 
93
 
 
94
processAction SendServerMessage = do
 
95
    chan <- client's sendChan
 
96
    protonum <- client's clientProto
 
97
    si <- liftM serverInfo get
 
98
    let message = if protonum < latestReleaseVersion si then
123
99
            serverMessageForOldVersions si
124
100
            else
125
101
            serverMessage si
126
 
 
127
 
processAction (clID, serverInfo, clients, rooms) SendServerVars = do
128
 
    writeChan (sendChan $ clients ! clID) ("SERVER_VARS" : vars)
129
 
    return (clID, serverInfo, clients, rooms)
 
102
    processAction $ AnswerClients [chan] ["SERVER_MESSAGE", message]
 
103
 
 
104
 
 
105
processAction SendServerVars = do
 
106
    chan <- client's sendChan
 
107
    si <- gets serverInfo
 
108
    io $ writeChan chan ("SERVER_VARS" : vars si)
130
109
    where
131
 
        client = clients ! clID
132
 
        vars = [
133
 
            "MOTD_NEW", serverMessage serverInfo, 
134
 
            "MOTD_OLD", serverMessageForOldVersions serverInfo, 
135
 
            "LATEST_PROTO", show $ latestReleaseVersion serverInfo
 
110
        vars si = [
 
111
            "MOTD_NEW", serverMessage si,
 
112
            "MOTD_OLD", serverMessageForOldVersions si,
 
113
            "LATEST_PROTO", showB $ latestReleaseVersion si
136
114
            ]
137
115
 
138
116
 
139
 
processAction (clID, serverInfo, clients, rooms) (ProtocolError msg) = do
140
 
    writeChan (sendChan $ clients ! clID) ["ERROR", msg]
141
 
    return (clID, serverInfo, clients, rooms)
142
 
 
143
 
 
144
 
processAction (clID, serverInfo, clients, rooms) (Warning msg) = do
145
 
    writeChan (sendChan $ clients ! clID) ["WARNING", msg]
146
 
    return (clID, serverInfo, clients, rooms)
147
 
 
148
 
 
149
 
processAction (clID, serverInfo, clients, rooms) (ByeClient msg) = do
150
 
    infoM "Clients" (show (clientUID client) ++ " quits: " ++ msg)
151
 
    (_, _, newClients, newRooms) <-
152
 
            if roomID client /= 0 then
153
 
                processAction  (clID, serverInfo, clients, rooms) $ RoomRemoveThisClient "quit"
154
 
                else
155
 
                    return (clID, serverInfo, clients, rooms)
156
 
 
157
 
    mapM_ (processAction (clID, serverInfo, newClients, newRooms)) $ answerOthersQuit ++ answerInformRoom
158
 
    writeChan (sendChan $ clients ! clID) ["BYE", msg]
159
 
    return (
160
 
            0,
161
 
            serverInfo,
162
 
            delete clID newClients,
163
 
            adjust (\r -> r{
164
 
                    playersIDs = IntSet.delete clID (playersIDs r),
165
 
                    playersIn = (playersIn r) - 1,
166
 
                    readyPlayers = if isReady client then readyPlayers r - 1 else readyPlayers r
167
 
                    }) (roomID $ newClients ! clID) newRooms
168
 
            )
169
 
    where
170
 
        client = clients ! clID
171
 
        clientNick = nick client
172
 
        answerInformRoom =
173
 
            if roomID client /= 0 then
174
 
                if not $ Prelude.null msg then
175
 
                    [AnswerThisRoom ["LEFT", clientNick, msg]]
176
 
                else
177
 
                    [AnswerThisRoom ["LEFT", clientNick]]
178
 
            else
179
 
                []
180
 
        answerOthersQuit =
181
 
            if logonPassed client then
182
 
                if not $ Prelude.null msg then
183
 
                    [AnswerAll ["LOBBY:LEFT", clientNick, msg]]
184
 
                else
185
 
                    [AnswerAll ["LOBBY:LEFT", clientNick]]
186
 
            else
187
 
                []
188
 
 
189
 
 
190
 
processAction (clID, serverInfo, clients, rooms) (ModifyClient func) =
191
 
    return (clID, serverInfo, adjust func clID clients, rooms)
192
 
 
193
 
 
194
 
processAction (clID, serverInfo, clients, rooms) (ModifyClient2 cl2ID func) =
195
 
    return (clID, serverInfo, adjust func cl2ID clients, rooms)
196
 
 
197
 
 
198
 
processAction (clID, serverInfo, clients, rooms) (ModifyRoom func) =
199
 
    return (clID, serverInfo, clients, adjust func rID rooms)
200
 
    where
201
 
        rID = roomID $ clients ! clID
202
 
 
203
 
 
204
 
processAction (clID, serverInfo, clients, rooms) (ModifyServerInfo func) =
205
 
    return (clID, func serverInfo, clients, rooms)
206
 
 
207
 
 
208
 
processAction (clID, serverInfo, clients, rooms) (RoomAddThisClient rID) =
209
 
    processAction (
210
 
        clID,
211
 
        serverInfo,
212
 
        adjust (\cl -> cl{roomID = rID, teamsInGame = if rID == 0 then teamsInGame cl else 0}) clID clients,
213
 
        adjust (\r -> r{playersIDs = IntSet.insert clID (playersIDs r), playersIn = (playersIn r) + 1}) rID $
214
 
            adjust (\r -> r{playersIDs = IntSet.delete clID (playersIDs r)}) 0 rooms
215
 
        ) joinMsg
216
 
    where
217
 
        client = clients ! clID
218
 
        joinMsg = if rID == 0 then
219
 
                AnswerAllOthers ["LOBBY:JOINED", nick client]
220
 
            else
221
 
                AnswerThisRoom ["JOINED", nick client]
222
 
 
223
 
 
224
 
processAction (clID, serverInfo, clients, rooms) (RoomRemoveThisClient msg) = do
225
 
    (_, _, newClients, newRooms) <-
226
 
        if roomID client /= 0 then
227
 
            if isMaster client then
228
 
                if (gameinprogress room) && (playersIn room > 1) then
229
 
                    (changeMaster >>= (\state -> foldM processAction state
230
 
                        [AnswerOthersInRoom ["LEFT", nick client, msg],
231
 
                        AnswerOthersInRoom ["WARNING", "Admin left the room"],
232
 
                        RemoveClientTeams clID]))
233
 
                else -- not in game
234
 
                    processAction (clID, serverInfo, clients, rooms) RemoveRoom
235
 
            else -- not master
236
 
                foldM
237
 
                    processAction
238
 
                        (clID, serverInfo, clients, rooms)
239
 
                        [AnswerOthersInRoom ["LEFT", nick client, msg],
240
 
                        RemoveClientTeams clID]
241
 
        else -- in lobby
242
 
            return (clID, serverInfo, clients, rooms)
 
117
processAction (ProtocolError msg) = do
 
118
    chan <- client's sendChan
 
119
    processAction $ AnswerClients [chan] ["ERROR", msg]
 
120
 
 
121
 
 
122
processAction (Warning msg) = do
 
123
    chan <- client's sendChan
 
124
    processAction $ AnswerClients [chan] ["WARNING", msg]
 
125
 
 
126
processAction (NoticeMessage n) = do
 
127
    chan <- client's sendChan
 
128
    processAction $ AnswerClients [chan] ["NOTICE", showB . fromEnum $ n]
 
129
 
 
130
processAction (ByeClient msg) = do
 
131
    (Just ci) <- gets clientIndex
 
132
    ri <- clientRoomA
 
133
 
 
134
    chan <- client's sendChan
 
135
    clNick <- client's nick
 
136
    loggedIn <- client's logonPassed
 
137
 
 
138
    when (ri /= lobbyId) $ do
 
139
        processAction $ MoveToLobby ("quit: " `B.append` msg)
 
140
        return ()
 
141
 
 
142
    clientsChans <- liftM (Prelude.map sendChan . Prelude.filter logonPassed) $! allClientsS
 
143
    io $
 
144
        infoM "Clients" (show ci ++ " quits: " ++ B.unpack msg)
 
145
 
 
146
    processAction $ AnswerClients [chan] ["BYE", msg]
 
147
    when loggedIn $ processAction $ AnswerClients clientsChans ["LOBBY:LEFT", clNick, msg]
 
148
 
 
149
    s <- get
 
150
    put $! s{removedClients = ci `Set.insert` removedClients s}
 
151
 
 
152
processAction (DeleteClient ci) = do
 
153
    io $ debugM "Clients"  $ "DeleteClient: " ++ show ci
 
154
 
 
155
    rnc <- gets roomsClients
 
156
    io $ removeClient rnc ci
 
157
 
 
158
    s <- get
 
159
    put $! s{removedClients = ci `Set.delete` removedClients s}
243
160
    
244
 
    return (
245
 
        clID,
246
 
        serverInfo,
247
 
        adjust resetClientFlags clID newClients,
248
 
        adjust removeClientFromRoom rID $ adjust insertClientToRoom 0 newRooms
249
 
        )
250
 
    where
251
 
        rID = roomID client
252
 
        client = clients ! clID
253
 
        room = rooms ! rID
254
 
        resetClientFlags cl = cl{roomID = 0, isMaster = False, isReady = False, teamsInGame = undefined}
255
 
        removeClientFromRoom r = r{
256
 
                playersIDs = otherPlayersSet,
257
 
                playersIn = (playersIn r) - 1,
258
 
                readyPlayers = if isReady client then (readyPlayers r) - 1 else readyPlayers r
259
 
                }
260
 
        insertClientToRoom r = r{playersIDs = IntSet.insert clID (playersIDs r)}
261
 
        changeMaster = do
262
 
            processAction (newMasterId, serverInfo, clients, rooms) $ AnswerThisClient ["ROOM_CONTROL_ACCESS", "1"]
263
 
            return (
264
 
                clID,
265
 
                serverInfo,
266
 
                adjust (\cl -> cl{isMaster = True}) newMasterId clients,
267
 
                adjust (\r -> r{masterID = newMasterId, name = newRoomName}) rID rooms
268
 
                )
269
 
        newRoomName = nick newMasterClient
270
 
        otherPlayersSet = IntSet.delete clID (playersIDs room)
271
 
        newMasterId = IntSet.findMin otherPlayersSet
272
 
        newMasterClient = clients ! newMasterId
273
 
 
274
 
 
275
 
processAction (clID, serverInfo, clients, rooms) (AddRoom roomName roomPassword) = do
276
 
    let newServerInfo = serverInfo {nextRoomID = newID}
277
 
    let room = newRoom{
278
 
            roomUID = newID,
279
 
            masterID = clID,
 
161
    sp <- gets (shutdownPending . serverInfo)
 
162
    cls <- allClientsS
 
163
    io $ when (sp && null cls) $ throwIO ShutdownException
 
164
 
 
165
processAction (ModifyClient f) = do
 
166
    (Just ci) <- gets clientIndex
 
167
    rnc <- gets roomsClients
 
168
    io $ modifyClient rnc f ci
 
169
    return ()
 
170
 
 
171
processAction (ModifyClient2 ci f) = do
 
172
    rnc <- gets roomsClients
 
173
    io $ modifyClient rnc f ci
 
174
    return ()
 
175
 
 
176
 
 
177
processAction (ModifyRoom f) = do
 
178
    rnc <- gets roomsClients
 
179
    ri <- clientRoomA
 
180
    io $ modifyRoom rnc f ri
 
181
    return ()
 
182
 
 
183
 
 
184
processAction (ModifyServerInfo f) = do
 
185
    modify (\s -> s{serverInfo = f $ serverInfo s})
 
186
    si <- gets serverInfo
 
187
    io $ writeServerConfig si
 
188
 
 
189
 
 
190
processAction (MoveToRoom ri) = do
 
191
    (Just ci) <- gets clientIndex
 
192
    rnc <- gets roomsClients
 
193
 
 
194
    io $ do
 
195
        modifyClient rnc (\cl -> cl{teamsInGame = 0, isReady = False, isMaster = False}) ci
 
196
        modifyRoom rnc (\r -> r{playersIn = playersIn r + 1}) ri
 
197
        moveClientToRoom rnc ri ci
 
198
 
 
199
    chans <- liftM (map sendChan) $ roomClientsS ri
 
200
    clNick <- client's nick
 
201
 
 
202
    processAction $ AnswerClients chans ["JOINED", clNick]
 
203
 
 
204
 
 
205
processAction (MoveToLobby msg) = do
 
206
    (Just ci) <- gets clientIndex
 
207
    ri <- clientRoomA
 
208
    rnc <- gets roomsClients
 
209
    (gameProgress, playersNum) <- io $ room'sM rnc (gameinprogress &&& playersIn) ri
 
210
    ready <- client's isReady
 
211
    master <- client's isMaster
 
212
--    client <- client's id
 
213
    clNick <- client's nick
 
214
    chans <- othersChans
 
215
 
 
216
    if master then
 
217
        if gameProgress && playersNum > 1 then
 
218
            mapM_ processAction [ChangeMaster, AnswerClients chans ["LEFT", clNick, msg], NoticeMessage AdminLeft, RemoveClientTeams ci]
 
219
            else
 
220
            processAction RemoveRoom
 
221
        else
 
222
        mapM_ processAction [AnswerClients chans ["LEFT", clNick, msg], RemoveClientTeams ci]
 
223
 
 
224
    -- when not removing room
 
225
    when (not master || (gameProgress && playersNum > 1)) . io $ do
 
226
        modifyRoom rnc (\r -> r{
 
227
                playersIn = playersIn r - 1,
 
228
                readyPlayers = if ready then readyPlayers r - 1 else readyPlayers r
 
229
                }) ri
 
230
        moveClientToLobby rnc ci
 
231
 
 
232
processAction ChangeMaster = do
 
233
    (Just ci) <- gets clientIndex
 
234
    ri <- clientRoomA
 
235
    rnc <- gets roomsClients
 
236
    newMasterId <- liftM (head . filter (/= ci)) . io $ roomClientsIndicesM rnc ri
 
237
    newMaster <- io $ client'sM rnc id newMasterId
 
238
    let newRoomName = nick newMaster
 
239
    mapM_ processAction [
 
240
        ModifyRoom (\r -> r{masterID = newMasterId, name = newRoomName}),
 
241
        ModifyClient2 newMasterId (\c -> c{isMaster = True}),
 
242
        AnswerClients [sendChan newMaster] ["ROOM_CONTROL_ACCESS", "1"]
 
243
        ]
 
244
 
 
245
processAction (AddRoom roomName roomPassword) = do
 
246
    Just clId <- gets clientIndex
 
247
    rnc <- gets roomsClients
 
248
    proto <- io $ client'sM rnc clientProto clId
 
249
 
 
250
    let rm = newRoom{
 
251
            masterID = clId,
280
252
            name = roomName,
281
253
            password = roomPassword,
282
 
            roomProto = (clientProto client)
 
254
            roomProto = proto
283
255
            }
284
256
 
285
 
    processAction (clID, serverInfo, clients, rooms) $ AnswerLobby ["ROOM", "ADD", roomName]
286
 
 
287
 
    processAction (
288
 
        clID,
289
 
        newServerInfo,
290
 
        adjust (\cl -> cl{isMaster = True}) clID clients,
291
 
        insert newID room rooms
292
 
        ) $ RoomAddThisClient newID
293
 
    where
294
 
        newID = (nextRoomID serverInfo) - 1
295
 
        client = clients ! clID
296
 
 
297
 
 
298
 
processAction (clID, serverInfo, clients, rooms) (RemoveRoom) = do
299
 
    processAction (clID, serverInfo, clients, rooms) $ AnswerLobby ["ROOM", "DEL", name room]
300
 
    processAction (clID, serverInfo, clients, rooms) $ AnswerOthersInRoom ["ROOMABANDONED", name room]
301
 
    return (clID,
302
 
        serverInfo,
303
 
        Data.IntMap.map (\cl -> if roomID cl == rID then cl{roomID = 0, isMaster = False, isReady = False, teamsInGame = undefined} else cl) clients,
304
 
        delete rID $ adjust (\r -> r{playersIDs = IntSet.union (playersIDs room) (playersIDs r)}) 0 rooms
305
 
        )
306
 
    where
307
 
        room = rooms ! rID
308
 
        rID = roomID client
309
 
        client = clients ! clID
310
 
 
311
 
 
312
 
processAction (clID, serverInfo, clients, rooms) (UnreadyRoomClients) = do
313
 
    processAction (clID, serverInfo, clients, rooms) $ AnswerThisRoom ("NOT_READY" : roomPlayers)
314
 
    return (clID,
315
 
        serverInfo,
316
 
        Data.IntMap.map (\cl -> if roomID cl == rID then cl{isReady = False} else cl) clients,
317
 
        adjust (\r -> r{readyPlayers = 0}) rID rooms)
318
 
    where
319
 
        room = rooms ! rID
320
 
        rID = roomID client
321
 
        client = clients ! clID
322
 
        roomPlayers = Prelude.map (nick . (clients !)) roomPlayersIDs
323
 
        roomPlayersIDs = IntSet.elems $ playersIDs room
324
 
 
325
 
 
326
 
processAction (clID, serverInfo, clients, rooms) (RemoveTeam teamName) = do
327
 
    newRooms <- if not $ gameinprogress room then
328
 
            do
329
 
            processAction (clID, serverInfo, clients, rooms) $ AnswerOthersInRoom ["REMOVE_TEAM", teamName]
330
 
            return $
331
 
                adjust (\r -> r{teams = Prelude.filter (\t -> teamName /= teamname t) $ teams r}) rID rooms
332
 
        else
333
 
            do
334
 
            processAction (clID, serverInfo, clients, rooms) $ AnswerOthersInRoom ["EM", rmTeamMsg]
335
 
            return $
336
 
                adjust (\r -> r{
337
 
                teams = Prelude.filter (\t -> teamName /= teamname t) $ teams r,
338
 
                leftTeams = teamName : leftTeams r,
339
 
                roundMsgs = roundMsgs r Seq.|> rmTeamMsg
340
 
                }) rID rooms
341
 
    return (clID, serverInfo, clients, newRooms)
342
 
    where
343
 
        room = rooms ! rID
344
 
        rID = roomID client
345
 
        client = clients ! clID
346
 
        rmTeamMsg = toEngineMsg $ 'F' : teamName
347
 
 
348
 
 
349
 
processAction (clID, serverInfo, clients, rooms) (CheckRegistered) = do
350
 
    writeChan (dbQueries serverInfo) $ CheckAccount (clientUID client) (nick client) (host client)
351
 
    return (clID, serverInfo, clients, rooms)
352
 
    where
353
 
        client = clients ! clID
354
 
 
355
 
 
356
 
processAction (clID, serverInfo, clients, rooms) (ClearAccountsCache) = do
357
 
    writeChan (dbQueries serverInfo) ClearCache
358
 
    return (clID, serverInfo, clients, rooms)
359
 
    where
360
 
        client = clients ! clID
361
 
 
362
 
 
363
 
processAction (clID, serverInfo, clients, rooms) (Dump) = do
364
 
    writeChan (sendChan $ clients ! clID) ["DUMP", show serverInfo, showTree clients, showTree rooms]
365
 
    return (clID, serverInfo, clients, rooms)
366
 
 
367
 
 
368
 
processAction (clID, serverInfo, clients, rooms) (ProcessAccountInfo info) =
 
257
    rId <- io $ addRoom rnc rm
 
258
 
 
259
    processAction $ MoveToRoom rId
 
260
 
 
261
    chans <- liftM (map sendChan) $! roomClientsS lobbyId
 
262
 
 
263
    mapM_ processAction [
 
264
        AnswerClients chans ["ROOM", "ADD", roomName]
 
265
        , ModifyClient (\cl -> cl{isMaster = True})
 
266
        ]
 
267
 
 
268
 
 
269
processAction RemoveRoom = do
 
270
    Just clId <- gets clientIndex
 
271
    rnc <- gets roomsClients
 
272
    ri <- io $ clientRoomM rnc clId
 
273
    roomName <- io $ room'sM rnc name ri
 
274
    others <- othersChans
 
275
    lobbyChans <- liftM (map sendChan) $! roomClientsS lobbyId
 
276
 
 
277
    mapM_ processAction [
 
278
            AnswerClients lobbyChans ["ROOM", "DEL", roomName],
 
279
            AnswerClients others ["ROOMABANDONED", roomName]
 
280
        ]
 
281
 
 
282
    io $ removeRoom rnc ri
 
283
 
 
284
 
 
285
processAction (UnreadyRoomClients) = do
 
286
    rnc <- gets roomsClients
 
287
    ri <- clientRoomA
 
288
    roomPlayers <- roomClientsS ri
 
289
    roomClIDs <- io $ roomClientsIndicesM rnc ri
 
290
    pr <- client's clientProto
 
291
    processAction $ AnswerClients (map sendChan roomPlayers) $ notReadyMessage pr (map nick roomPlayers)
 
292
    io $ mapM_ (modifyClient rnc (\cl -> cl{isReady = False})) roomClIDs
 
293
    processAction $ ModifyRoom (\r -> r{readyPlayers = 0})
 
294
    where
 
295
        notReadyMessage p nicks = if p < 38 then "NOT_READY" : nicks else "CLIENT_FLAGS" : "-r" : nicks
 
296
 
 
297
 
 
298
processAction (RemoveTeam teamName) = do
 
299
    rnc <- gets roomsClients
 
300
    ri <- clientRoomA
 
301
    inGame <- io $ room'sM rnc gameinprogress ri
 
302
    chans <- othersChans
 
303
    if not $ inGame then
 
304
            mapM_ processAction [
 
305
                AnswerClients chans ["REMOVE_TEAM", teamName],
 
306
                ModifyRoom (\r -> r{teams = Prelude.filter (\t -> teamName /= teamname t) $ teams r})
 
307
                ]
 
308
        else
 
309
            mapM_ processAction [
 
310
                AnswerClients chans ["EM", rmTeamMsg],
 
311
                ModifyRoom (\r -> r{
 
312
                    teams = Prelude.filter (\t -> teamName /= teamname t) $ teams r,
 
313
                    leftTeams = teamName : leftTeams r,
 
314
                    roundMsgs = roundMsgs r Seq.|> rmTeamMsg
 
315
                    })
 
316
                ]
 
317
    where
 
318
        rmTeamMsg = toEngineMsg $ 'F' `B.cons` teamName
 
319
 
 
320
 
 
321
processAction (RemoveClientTeams clId) = do
 
322
    rnc <- gets roomsClients
 
323
 
 
324
    removeTeamActions <- io $ do
 
325
        clNick <- client'sM rnc nick clId
 
326
        rId <- clientRoomM rnc clId
 
327
        roomTeams <- room'sM rnc teams rId
 
328
        return . Prelude.map (RemoveTeam . teamname) . Prelude.filter (\t -> teamowner t == clNick) $ roomTeams
 
329
 
 
330
    mapM_ processAction removeTeamActions
 
331
 
 
332
 
 
333
 
 
334
processAction CheckRegistered = do
 
335
    (Just ci) <- gets clientIndex
 
336
    n <- client's nick
 
337
    h <- client's host
 
338
    p <- client's clientProto
 
339
    uid <- client's clUID
 
340
    haveSameNick <- liftM (not . null . tail . filter (\c -> nick c == n)) allClientsS
 
341
    if haveSameNick then
 
342
        if p < 38 then
 
343
            mapM_ processAction [ByeClient "Nickname is already in use", removeNick]
 
344
            else
 
345
            mapM_ processAction [NoticeMessage NickAlreadyInUse, removeNick]
 
346
        else
 
347
        do
 
348
        db <- gets (dbQueries . serverInfo)
 
349
        io $ writeChan db $ CheckAccount ci (hashUnique uid) n h
 
350
        return ()
 
351
   where
 
352
       removeNick = ModifyClient (\c -> c{nick = ""})
 
353
 
 
354
 
 
355
processAction ClearAccountsCache = do
 
356
    dbq <- gets (dbQueries . serverInfo)
 
357
    io $ writeChan dbq ClearCache
 
358
    return ()
 
359
 
 
360
 
 
361
processAction (ProcessAccountInfo info) =
369
362
    case info of
370
363
        HasAccount passwd isAdmin -> do
371
 
            infoM "Clients" $ show clID ++ " has account"
372
 
            writeChan (sendChan $ clients ! clID) ["ASKPASSWORD"]
373
 
            return (clID, serverInfo, adjust (\cl -> cl{webPassword = passwd, isAdministrator = isAdmin}) clID clients, rooms)
374
 
        Guest -> do
375
 
            infoM "Clients" $ show clID ++ " is guest"
376
 
            processAction (clID, serverInfo, adjust (\cl -> cl{logonPassed = True}) clID clients, rooms) MoveToLobby
 
364
            chan <- client's sendChan
 
365
            mapM_ processAction [AnswerClients [chan] ["ASKPASSWORD"], ModifyClient (\c -> c{webPassword = passwd, isAdministrator = isAdmin})]
 
366
        Guest ->
 
367
            processAction JoinLobby
377
368
        Admin -> do
378
 
            infoM "Clients" $ show clID ++ " is admin"
379
 
            foldM processAction (clID, serverInfo, adjust (\cl -> cl{logonPassed = True, isAdministrator = True}) clID clients, rooms) [MoveToLobby, AnswerThisClient ["ADMIN_ACCESS"]]
380
 
 
381
 
 
382
 
processAction (clID, serverInfo, clients, rooms) (MoveToLobby) =
383
 
    foldM processAction (clID, serverInfo, clients, rooms) $
384
 
        (RoomAddThisClient 0)
385
 
        : answerLobbyNicks
386
 
        ++ [SendServerMessage]
387
 
 
388
 
        -- ++ (answerServerMessage client clients)
389
 
    where
390
 
        lobbyNicks = Prelude.map nick $ Prelude.filter logonPassed $ elems clients
391
 
        answerLobbyNicks = [AnswerThisClient ("LOBBY:JOINED": lobbyNicks) | not $ Prelude.null lobbyNicks]
392
 
 
393
 
 
394
 
processAction (clID, serverInfo, clients, rooms) (KickClient kickID) =
395
 
    liftM2 replaceID (return clID) (processAction (kickID, serverInfo, clients, rooms) $ ByeClient "Kicked")
396
 
 
397
 
 
398
 
processAction (clID, serverInfo, clients, rooms) (BanClient banNick) =
399
 
    return (clID, serverInfo, clients, rooms)
400
 
 
401
 
 
402
 
processAction (clID, serverInfo, clients, rooms) (KickRoomClient kickID) = do
403
 
    writeChan (sendChan $ clients ! kickID) ["KICKED"]
404
 
    liftM2 replaceID (return clID) (processAction (kickID, serverInfo, clients, rooms) $ RoomRemoveThisClient "kicked")
405
 
 
406
 
 
407
 
processAction (clID, serverInfo, clients, rooms) (RemoveClientTeams teamsClID) =
408
 
    liftM2 replaceID (return clID) $
409
 
        foldM processAction (teamsClID, serverInfo, clients, rooms) removeTeamsActions
410
 
    where
411
 
        client = clients ! teamsClID
412
 
        room = rooms ! (roomID client)
413
 
        teamsToRemove = Prelude.filter (\t -> teamowner t == nick client) $ teams room
414
 
        removeTeamsActions = Prelude.map (RemoveTeam . teamname) teamsToRemove
415
 
 
416
 
 
417
 
processAction (clID, serverInfo, clients, rooms) (AddClient client) = do
418
 
    let updatedClients = insert (clientUID client) client clients
419
 
    infoM "Clients" (show (clientUID client) ++ ": New client. Time: " ++ show (connectTime client))
420
 
    writeChan (sendChan client) ["CONNECTED", "Hedgewars server http://www.hedgewars.org/"]
421
 
 
422
 
    let newLogins = takeWhile (\(_ , time) -> (connectTime client) `diffUTCTime` time <= 11) $ lastLogins serverInfo
423
 
 
424
 
    if isJust $ host client `Prelude.lookup` newLogins then
425
 
        processAction (clID, serverInfo{lastLogins = newLogins}, updatedClients, rooms) $ ByeClient "Reconnected too fast"
426
 
        else
427
 
        return (clID, serverInfo{lastLogins = (host client, connectTime client) : newLogins}, updatedClients, rooms)
428
 
 
429
 
 
430
 
processAction (clID, serverInfo, clients, rooms) PingAll = do
431
 
    (_, _, newClients, newRooms) <- foldM kickTimeouted (clID, serverInfo, clients, rooms) $ elems clients
432
 
    processAction (clID,
433
 
        serverInfo,
434
 
        Data.IntMap.map (\cl -> cl{pingsQueue = pingsQueue cl + 1}) newClients,
435
 
        newRooms) $ AnswerAll ["PING"]
436
 
    where
437
 
        kickTimeouted (clID, serverInfo, clients, rooms) client =
438
 
            if pingsQueue client > 0 then
439
 
                processAction (clientUID client, serverInfo, clients, rooms) $ ByeClient "Ping timeout"
440
 
                else
441
 
                return (clID, serverInfo, clients, rooms)
442
 
 
443
 
 
444
 
processAction (clID, serverInfo, clients, rooms) (StatsAction) = do
445
 
    writeChan (dbQueries serverInfo) $ SendStats (size clients) (size rooms - 1)
446
 
    return (clID, serverInfo, clients, rooms)
 
369
            mapM_ processAction [ModifyClient (\cl -> cl{isAdministrator = True}), JoinLobby]
 
370
            chan <- client's sendChan
 
371
            processAction $ AnswerClients [chan] ["ADMIN_ACCESS"]
 
372
 
 
373
 
 
374
processAction JoinLobby = do
 
375
    chan <- client's sendChan
 
376
    clientNick <- client's nick
 
377
    (lobbyNicks, clientsChans) <- liftM (unzip . Prelude.map (nick &&& sendChan) . Prelude.filter logonPassed) $! allClientsS
 
378
    mapM_ processAction $
 
379
        AnswerClients clientsChans ["LOBBY:JOINED", clientNick]
 
380
        : AnswerClients [chan] ("LOBBY:JOINED" : clientNick : lobbyNicks)
 
381
        : [ModifyClient (\cl -> cl{logonPassed = True}), SendServerMessage]
 
382
 
 
383
 
 
384
processAction (KickClient kickId) = do
 
385
    modify (\s -> s{clientIndex = Just kickId})
 
386
    clHost <- client's host
 
387
    currentTime <- io getCurrentTime
 
388
    mapM_ processAction [
 
389
        AddIP2Bans clHost "60 seconds cooldown after kick" (addUTCTime 60 currentTime),
 
390
        ByeClient "Kicked"
 
391
        ]
 
392
 
 
393
 
 
394
processAction (BanClient seconds reason banId) = do
 
395
    modify (\s -> s{clientIndex = Just banId})
 
396
    clHost <- client's host
 
397
    currentTime <- io getCurrentTime
 
398
    let msg = B.concat ["Ban for ", B.pack . show $ seconds, " (", reason, ")"]
 
399
    mapM_ processAction [
 
400
        AddIP2Bans clHost msg (addUTCTime seconds currentTime)
 
401
        , KickClient banId
 
402
        ]
 
403
 
 
404
processAction (BanIP ip seconds reason) = do
 
405
    currentTime <- io getCurrentTime
 
406
    let msg = B.concat ["Ban for ", B.pack . show $ seconds, " (", reason, ")"]
 
407
    processAction $
 
408
        AddIP2Bans ip msg (addUTCTime seconds currentTime)
 
409
 
 
410
processAction BanList = do
 
411
    ch <- client's sendChan
 
412
    bans <- gets (bans . serverInfo)
 
413
    processAction $
 
414
        AnswerClients [ch] ["BANLIST", B.pack $ show bans]
 
415
    
 
416
 
 
417
 
 
418
processAction (KickRoomClient kickId) = do
 
419
    modify (\s -> s{clientIndex = Just kickId})
 
420
    ch <- client's sendChan
 
421
    mapM_ processAction [AnswerClients [ch] ["KICKED"], MoveToLobby "kicked"]
 
422
 
 
423
 
 
424
processAction (AddClient cl) = do
 
425
    rnc <- gets roomsClients
 
426
    si <- gets serverInfo
 
427
    newClId <- io $ do
 
428
        ci <- addClient rnc cl
 
429
        _ <- Exception.mask (forkIO . clientRecvLoop (clientSocket cl) (coreChan si) (sendChan cl) ci)
 
430
 
 
431
        infoM "Clients" (show ci ++ ": New client. Time: " ++ show (connectTime cl))
 
432
 
 
433
        return ci
 
434
 
 
435
    modify (\s -> s{clientIndex = Just newClId})
 
436
    mapM_ processAction
 
437
        [
 
438
            AnswerClients [sendChan cl] ["CONNECTED", "Hedgewars server http://www.hedgewars.org/", serverVersion]
 
439
            , CheckBanned
 
440
            , AddIP2Bans (host cl) "Reconnected too fast" (addUTCTime 10 $ connectTime cl)
 
441
        ]
 
442
 
 
443
 
 
444
processAction (AddNick2Bans n reason expiring) = do
 
445
    processAction $ ModifyServerInfo (\s -> s{bans = BanByNick n reason expiring : bans s})
 
446
 
 
447
processAction (AddIP2Bans ip reason expiring) = do
 
448
    (Just ci) <- gets clientIndex
 
449
    rc <- gets removedClients
 
450
    when (not $ ci `Set.member` rc)
 
451
        $ processAction $ ModifyServerInfo (\s -> s{bans = BanByIP ip reason expiring : bans s})
 
452
 
 
453
processAction CheckBanned = do
 
454
    clTime <- client's connectTime
 
455
    clNick <- client's nick
 
456
    clHost <- client's host
 
457
    si <- gets serverInfo
 
458
    let validBans = filter (checkNotExpired clTime) $ bans si
 
459
    let ban = L.find (checkBan clHost clNick) $ validBans
 
460
    mapM_ processAction $
 
461
        ModifyServerInfo (\s -> s{bans = validBans})
 
462
        : [ByeClient (getBanReason $ fromJust ban) | isJust ban]
 
463
    where
 
464
        checkNotExpired testTime (BanByIP _ _ time) = testTime `diffUTCTime` time <= 0
 
465
        checkNotExpired testTime (BanByNick _ _ time) = testTime `diffUTCTime` time <= 0
 
466
        checkBan ip _ (BanByIP bip _ _) = bip `B.isPrefixOf` ip
 
467
        checkBan _ n (BanByNick bn _ _) = bn == n
 
468
        getBanReason (BanByIP _ msg _) = msg
 
469
        getBanReason (BanByNick _ msg _) = msg
 
470
 
 
471
processAction PingAll = do
 
472
    rnc <- gets roomsClients
 
473
    io (allClientsM rnc) >>= mapM_ (kickTimeouted rnc)
 
474
    cis <- io $ allClientsM rnc
 
475
    chans <- io $ mapM (client'sM rnc sendChan) cis
 
476
    io $ mapM_ (modifyClient rnc (\cl -> cl{pingsQueue = pingsQueue cl + 1})) cis
 
477
    processAction $ AnswerClients chans ["PING"]
 
478
    where
 
479
        kickTimeouted rnc ci = do
 
480
            pq <- io $ client'sM rnc pingsQueue ci
 
481
            when (pq > 0) $
 
482
                withStateT (\as -> as{clientIndex = Just ci}) $
 
483
                    processAction (ByeClient "Ping timeout")
 
484
 
 
485
 
 
486
processAction StatsAction = do
 
487
    si <- gets serverInfo
 
488
    when (not $ shutdownPending si) $ do
 
489
        rnc <- gets roomsClients
 
490
        (roomsNum, clientsNum) <- io $ withRoomsAndClients rnc st
 
491
        io $ writeChan (dbQueries si) $ SendStats clientsNum (roomsNum - 1)
 
492
    where
 
493
          st irnc = (length $ allRooms irnc, length $ allClients irnc)
 
494
 
 
495
processAction RestartServer = do 
 
496
    sp <- gets (shutdownPending . serverInfo)
 
497
    when (not sp) $ do
 
498
        sock <- gets (fromJust . serverSocket . serverInfo)
 
499
        args <- gets (runArgs . serverInfo)
 
500
        io $ do
 
501
            noticeM "Core" "Closing listening socket"
 
502
            sClose sock
 
503
            noticeM "Core" "Spawning new server"
 
504
            _ <- createProcess (proc "./hedgewars-server" args)
 
505
            return ()
 
506
        processAction $ ModifyServerInfo (\s -> s{shutdownPending = True})
 
507
 
 
508
processAction SaveReplay = do
 
509
    ri <- clientRoomA
 
510
    rnc <- gets roomsClients
 
511
    io $ do
 
512
        r <- room'sM rnc id ri
 
513
        saveReplay r