File: HWProtoInRoomState.hs

package info (click to toggle)
hedgewars 1.0.2-6
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 218,948 kB
  • sloc: pascal: 54,829; cpp: 27,221; ansic: 22,649; java: 8,210; haskell: 6,788; xml: 3,076; sh: 580; objc: 113; python: 105; makefile: 33
file content (573 lines) | stat: -rw-r--r-- 21,498 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
{-
 * Hedgewars, a free turn based strategy game
 * Copyright (c) 2004-2015 Andrey Korotaev <unC0Rr@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 \-}

{-# LANGUAGE OverloadedStrings #-}
module HWProtoInRoomState where

import qualified Data.Map as Map
import Data.List as L
import Data.Maybe
import qualified Data.ByteString.Char8 as B
import Control.Monad
import Control.Monad.Reader
--------------------------------------
import CoreTypes
import Consts
import Utils
import HandlerUtils
import RoomsAndClients
import EngineInteraction
import Votes
import CommandHelp

startGame :: Reader (ClientIndex, IRnC) [Action]
startGame = do
    (ci, rnc) <- ask
    cl <- thisClient
    rm <- thisRoom
    chans <- roomClientsChans

    let nicks = map (nick . client rnc) . roomClients rnc $ clientRoom rnc ci
    let allPlayersRegistered = all isOwnerRegistered $ teams rm

    if (playersIn rm == readyPlayers rm || clientProto cl > 43) && not (isJust $ gameInfo rm) then
        if enoughClans rm then
            return [
                ModifyRoom
                    (\r -> r{
                        gameInfo = Just $ newGameInfo (teams rm) (length $ teams rm) allPlayersRegistered (mapParams rm) (params rm) False
                        }
                    )
                , AnswerClients chans ["RUN_GAME"]
                , SendUpdateOnThisRoom
                , AnswerClients chans $ "CLIENT_FLAGS" : "+g" : nicks
                , ModifyRoomClients (\c -> c{isInGame = True, teamIndexes = map snd . filter (\(t, _) -> teamowner t == nick c) $ zip (teams rm) [0..]})
                ]
            else
            return [Warning $ loc "The game can't be started with less than two clans!"]
        else
        return []
    where
        enoughClans = not . null . drop 1 . group . map teamcolor . teams



handleCmd_inRoom :: CmdHandler

handleCmd_inRoom ["CHAT", msg] = do
    n <- clientNick
    s <- roomOthersChans
    return [AnswerClients s ["CHAT", n, msg]]

-- Leave room normally
handleCmd_inRoom ["PART"] = return [MoveToLobby ""]
handleCmd_inRoom ["PART", _] = return [MoveToLobby ""]


handleCmd_inRoom ("CFG" : paramName : paramStrs)
    | null paramStrs = return [ProtocolError $ loc "Empty config entry."]
    | otherwise = do
        chans <- roomOthersChans
        cl <- thisClient
        rm <- thisRoom

        if isSpecial rm then
            return [Warning $ loc "Access denied."]
        else if isMaster cl then
           return [
                ModifyRoom $ f (clientProto cl),
                AnswerClients chans ("CFG" : paramName : paramStrs)]
            else
            return [ProtocolError $ loc "You're not the room master!"]
    where
        f clproto r = if paramName `Map.member` (mapParams r) then
                r{mapParams = Map.insert paramName (head paramStrs) (mapParams r)}
                else
                r{params = Map.insert paramName (fixedParamStr clproto) (params r)}
        fixedParamStr clproto
            | clproto /= 49 = paramStrs
            | paramName /= "SCHEME" = paramStrs
            | otherwise = L.init paramStrs ++ [B.replicate 50 'X' `B.append` L.last paramStrs]


handleCmd_inRoom ("ADD_TEAM" : tName : color : grave : fort : voicepack : flag : difStr : hhsInfo)
    | length hhsInfo /= 16 = return [ProtocolError $ loc "Corrupted hedgehogs info!"]
    | otherwise = do
        rm <- thisRoom
        cl <- thisClient
        clNick <- clientNick
        clChan <- thisClientChans
        othChans <- roomOthersChans
        roomChans <- roomClientsChans
        teamColor <-
            if clientProto cl < 42 then
                return color
                else
                liftM (head . (L.\\) (map B.singleton ['0'..]) . map teamcolor . teams) thisRoom
        let roomTeams = teams rm
        let hhNum = newTeamHHNum roomTeams $
                if not $ null roomTeams then
                    minimum [hhnum $ head roomTeams, canAddNumber roomTeams]
                else
                    defaultHedgehogsNumber rm
        let newTeam = clNick `seq` TeamInfo clNick tName teamColor grave fort voicepack flag (isRegistered cl) dif hhNum (hhsList hhsInfo)
        return $
            if not . null . drop (teamsNumberLimit rm - 1) $ roomTeams then
                [Warning $ loc "Too many teams!"]
            else if canAddNumber roomTeams <= 0 then
                [Warning $ loc "Too many hedgehogs!"]
            else if isJust $ findTeam rm then
                [Warning $ loc "There's already a team with same name in the list."]
            else if isJust $ gameInfo rm then
                [Warning $ loc "Joining not possible: Round is in progress."]
            else if isRestrictedTeams rm then
                [Warning $ loc "This room currently does not allow adding new teams."]
            else
                [ModifyRoom (\r -> r{teams = teams r ++ [newTeam]}),
                SendUpdateOnThisRoom,
                ModifyClient (\c -> c{teamsInGame = teamsInGame c + 1, clientClan = Just teamColor}),
                AnswerClients clChan ["TEAM_ACCEPTED", tName],
                AnswerClients othChans $ teamToNet $ newTeam,
                AnswerClients roomChans ["TEAM_COLOR", tName, teamColor],
                AnswerClients roomChans ["HH_NUM", tName, showB $ hhnum newTeam]
                ]
        where
        canAddNumber rt = (cMaxHHs) - (sum $ map hhnum rt)
        findTeam = find (\t -> tName == teamname t) . teams
        dif = readInt_ difStr
        hhsList [] = []
        hhsList [_] = error "Hedgehogs list with odd elements number"
        hhsList (n:h:hhs) = HedgehogInfo n h : hhsList hhs
        newTeamHHNum rt p = min p (canAddNumber rt)
        maxTeams r
            | roomProto r < 38 = 6
            | otherwise = cMaxTeams


handleCmd_inRoom ["REMOVE_TEAM", tName] = do
        (ci, _) <- ask
        r <- thisRoom
        clNick <- clientNick

        let maybeTeam = findTeam r
        let team = fromJust maybeTeam

        return $
            if isNothing $ maybeTeam then
                [Warning $ loc "Error: The team you tried to remove does not exist."]
            else if clNick /= teamowner team then
                [ProtocolError $ loc "You can't remove a team you don't own."]
            else
                [RemoveTeam tName,
                ModifyClient
                    (\c -> c{
                        teamsInGame = teamsInGame c - 1,
                        clientClan = if teamsInGame c == 1 then Nothing else anotherTeamClan clNick team r
                    })
                ]
    where
        anotherTeamClan clNick team = liftM teamcolor . find (\t -> (teamowner t == clNick) && (t /= team)) . teams
        findTeam = find (\t -> tName == teamname t) . teams


handleCmd_inRoom ["HH_NUM", teamName, numberStr] = do
    cl <- thisClient
    r <- thisRoom
    clChan <- thisClientChans
    others <- roomOthersChans

    let maybeTeam = findTeam r
    let team = fromJust maybeTeam

    return $
        if not $ isMaster cl then
            [ProtocolError $ loc "You're not the room master!"]
        else if isNothing maybeTeam then
            []
        else if hhNumber < 1 || hhNumber > cHogsPerTeam || hhNumber > canAddNumber r + hhnum team then
            [AnswerClients clChan ["HH_NUM", teamName, showB $ hhnum team]]
        else
            [ModifyRoom $ modifyTeam team{hhnum = hhNumber},
            AnswerClients others ["HH_NUM", teamName, showB hhNumber]]
    where
        hhNumber = readInt_ numberStr
        findTeam = find (\t -> teamName == teamname t) . teams
        canAddNumber = (-) cMaxHHs . sum . map hhnum . teams



handleCmd_inRoom ["TEAM_COLOR", teamName, newColor] = do
    cl <- thisClient
    others <- roomOthersChans
    r <- thisRoom

    let maybeTeam = findTeam r
    let team = fromJust maybeTeam
    maybeClientId <- clientByNick $ teamowner team
    let teamOwnerId = fromJust maybeClientId

    return $
        if not $ isMaster cl then
            [ProtocolError $ loc "You're not the room master!"]
        else if isNothing maybeTeam || isNothing maybeClientId then
            []
        else
            [ModifyRoom $ modifyTeam team{teamcolor = newColor},
            AnswerClients others ["TEAM_COLOR", teamName, newColor],
            ModifyClient2 teamOwnerId (\c -> c{clientClan = Just newColor})]
    where
        findTeam = find (\t -> teamName == teamname t) . teams


handleCmd_inRoom ["TOGGLE_READY"] = do
    cl <- thisClient
    rm <- thisRoom
    chans <- roomClientsChans

    (ci, rnc) <- ask
    let ri = clientRoom rnc ci
    let unreadyClients = filter (not . isReady) . map (client rnc) $ roomClients rnc ri

    gs <- if (not $ isReady cl) && (isSpecial rm) && (unreadyClients == [cl]) then startGame else return []

    return $
        ModifyRoom (\r -> r{readyPlayers = readyPlayers r + (if isReady cl then -1 else 1)})
        : ModifyClient (\c -> c{isReady = not $ isReady cl})
        : (AnswerClients chans $ if clientProto cl < 38 then
                [if isReady cl then "NOT_READY" else "READY", nick cl]
                else
                ["CLIENT_FLAGS", if isReady cl then "-r" else "+r", nick cl])
        : gs


handleCmd_inRoom ["START_GAME"] = roomAdminOnly startGame

handleCmd_inRoom ["EM", msg] = do
    cl <- thisClient
    rm <- thisRoom
    chans <- roomOthersChans

    let (legalMsgs, nonEmptyMsgs, lastFTMsg) = checkNetCmd (teamIndexes cl) msg

    if teamsInGame cl > 0 && (isJust $ gameInfo rm) && (not $ B.null legalMsgs) then
        return $ AnswerClients chans ["EM", legalMsgs]
            : [ModifyRoom (\r -> r{gameInfo = liftM
                (\g -> g{
                    roundMsgs = if B.null nonEmptyMsgs then roundMsgs g else nonEmptyMsgs : roundMsgs g
                    , lastFilteredTimedMsg = fromMaybe (lastFilteredTimedMsg g) lastFTMsg})
                $ gameInfo r}), RegisterEvent EngineMessage]
        else
        return []


handleCmd_inRoom ["ROUNDFINISHED", _] = do
    cl <- thisClient
    rm <- thisRoom
    chans <- roomClientsChans

    let clTeams = map teamname . filter (\t -> teamowner t == nick cl) . teams $ rm
    let unsetInGameState = [AnswerClients chans ["CLIENT_FLAGS", "-g", nick cl], ModifyClient (\c -> c{isInGame = False})]

    if isInGame cl then
        if isJust $ gameInfo rm then
            return $ unsetInGameState ++ map SendTeamRemovalMessage clTeams
            else
            return unsetInGameState
        else
        return [] -- don't accept this message twice
    where
--        isCorrect = correctly == "1"

-- compatibility with clients with protocol < 38
handleCmd_inRoom ["ROUNDFINISHED"] =
    handleCmd_inRoom ["ROUNDFINISHED", "1"]

handleCmd_inRoom ["TOGGLE_RESTRICT_JOINS"] = roomAdminOnly $
    return [ModifyRoom (\r -> r{isRestrictedJoins = not $ isRestrictedJoins r}), SendUpdateOnThisRoom]


handleCmd_inRoom ["TOGGLE_RESTRICT_TEAMS"] = roomAdminOnly $
    return [ModifyRoom (\r -> r{isRestrictedTeams = not $ isRestrictedTeams r})]


handleCmd_inRoom ["TOGGLE_REGISTERED_ONLY"] = roomAdminOnly $
    return [ModifyRoom (\r -> r{isRegisteredOnly = not $ isRegisteredOnly r}), SendUpdateOnThisRoom]


handleCmd_inRoom ["ROOM_NAME", newName] = roomAdminOnly $ do
    cl <- thisClient
    rs <- allRoomInfos
    rm <- thisRoom
    chans <- sameProtoChans

    return $
        if illegalName newName then
            [Warning $ loc "Illegal room name! The room name must be between 1-40 characters long, must not have a trailing or leading space and must not have any of these characters: $()*+?[]^{|}"]
        else
        if isSpecial rm then
            [Warning $ loc "Access denied."]
        else
        if isJust $ find (\r -> newName == name r) rs then
            [Warning $ loc "A room with the same name already exists."]
        else
            [ModifyRoom roomUpdate,
            AnswerClients chans ("ROOM" : "UPD" : name rm : roomInfo (clientProto cl) (nick cl) (roomUpdate rm)),
            RegisterEvent RoomNameUpdate]
    where
        roomUpdate r = r{name = newName}


handleCmd_inRoom ["KICK", kickNick] = roomAdminOnly $ do
    (thisClientId, rnc) <- ask
    maybeKickId <- clientByNick kickNick
    rm <- thisRoom
    let kickId = fromJust maybeKickId
    let kickCl = rnc `client` kickId
    let sameRoom = clientRoom rnc thisClientId == clientRoom rnc kickId
    let notOnly2Players = (length . group . sort . map teamowner . teams $ rm) > 2
    return $
        -- Catch some error conditions
        if (isNothing maybeKickId) then
            [Warning $ loc "Player is not online."]
        else if (kickId == thisClientId) then
            [Warning $ loc "You can't kick yourself!"]
        else if (not ((isNothing $ gameInfo rm) || notOnly2Players || teamsInGame kickCl == 0)) then
            [Warning $ loc "You can't kick the only other player!"]
        else if (not sameRoom) then
            [Warning $ loc "The player is not in your room."]
        else if (hasSuperPower kickCl) then
            [Warning $ loc "This player is protected from being kicked."]
        else
            -- Kick!
            [KickRoomClient kickId]

handleCmd_inRoom ["DELEGATE", newAdmin] = do
    (thisClientId, rnc) <- ask
    maybeClientId <- clientByNick newAdmin
    master <- liftM isMaster thisClient
    serverAdmin <- liftM isAdministrator thisClient
    thisRoomMasterId <- liftM masterID thisRoom
    let newAdminId = fromJust maybeClientId
    let sameRoom = clientRoom rnc thisClientId == clientRoom rnc newAdminId
    return $
        if (not (master || serverAdmin)) then
            [Warning $ loc "You're not the room master or a server admin!"]
        else if (isNothing maybeClientId) then
            [Warning $ loc "Player is not online."]
        else if (Just newAdminId == thisRoomMasterId) then
            [Warning $ loc "You're already the room master."]
        else if (not sameRoom) then
            [Warning $ loc "The player is not in your room."]
        else
            [ChangeMaster (Just newAdminId)]

handleCmd_inRoom ["TEAMCHAT", msg] = do
    cl <- thisClient
    chans <- roomSameClanChans
    return [AnswerClients chans ["EM", engineMsg cl]]
    where
        -- This is formatted in a way so it can parsed by engine to make it translatable
        -- Format: b<PLAYER NAME>]<MESSAGE>
        engineMsg cl = toEngineMsg $ B.concat ["b", nick cl, "]", msg, "\x20\x20"]


handleCmd_inRoom ["BAN", banNick] = do
    (thisClientId, rnc) <- ask
    maybeClientId <- clientByNick banNick
    master <- liftM isMaster thisClient
    let banId = fromJust maybeClientId
    let sameRoom = clientRoom rnc thisClientId == clientRoom rnc banId
    if master && isJust maybeClientId && (banId /= thisClientId) && sameRoom then
        return [
--                ModifyRoom (\r -> r{roomBansList = let h = host $ rnc `client` banId in h `deepseq` h : roomBansList r})
                KickRoomClient banId
            ]
        else
        return []

handleCmd_inRoom ("RND":rs) = do
    n <- clientNick
    s <- roomClientsChans
    return [AnswerClients s ["CHAT", n, B.unwords $ "/rnd" : rs], Random s rs]


handleCmd_inRoom ["MAXTEAMS", n] = do
    cl <- thisClient
    let m = readInt_ n
    if not $ isMaster cl then
        return [Warning $ loc "You're not the room master!"]
    else if m < 2 || m > cMaxTeams then
        return [Warning $ loc "/maxteams: specify number from 2 to 8"]
    else
        return [ModifyRoom (\r -> r{teamsNumberLimit = m})]

handleCmd_inRoom ["MAXTEAMS"] = handleCmd_inRoom ["MAXTEAMS", ""]

handleCmd_inRoom ["FIX"] = serverAdminOnly $
    return [ModifyRoom (\r -> r{isSpecial = True})]

handleCmd_inRoom ["UNFIX"] = serverAdminOnly $ do
    cl <- thisClient
    return $ if not $ isMaster cl then
                 [Warning $ loc "You're not the room master!"]
             else
                 [ModifyRoom (\r -> r{isSpecial = False})]

handleCmd_inRoom ["HELP"] = do
    cl <- thisClient
    if isAdministrator cl then
        return (cmdHelpActionList [sendChan cl] cmdHelpRoomAdmin)
    else
        return (cmdHelpActionList [sendChan cl] cmdHelpRoomPlayer)

handleCmd_inRoom ["GREETING", msg] = do
    cl <- thisClient
    rm <- thisRoom
    return $ if (not (isAdministrator cl || (isMaster cl && (not $ isSpecial rm)))) then
                 [Warning $ loc "You're not the room master or a server admin!"]
             else
                 [ModifyRoom (\r -> r{greeting = msg}),
                  AnswerClients [sendChan cl]
                      ["CHAT", nickServer,
                          if B.null msg then
                              loc "Greeting message cleared."
                          else
                              loc "Greeting message set."
                      ]]

handleCmd_inRoom ["CALLVOTE"] = do
    cl <- thisClient
    return [AnswerClients [sendChan cl]
        ["CHAT", nickServer, loc "Available callvote commands: hedgehogs <number>, pause, newseed, map <name>, kick <player>"]
        ]

handleCmd_inRoom ["CALLVOTE", "KICK"] = do
    cl <- thisClient
    rm <- thisRoom
    return
        [Warning $
        if isJust $ masterID rm then
            loc "/callvote kick: This is only allowed in rooms without a room master."
        else
            loc "/callvote kick: You need to specify a nickname."
        ]

handleCmd_inRoom ["CALLVOTE", "KICK", nickname] = do
    (thisClientId, rnc) <- ask
    cl <- thisClient
    rm <- thisRoom
    maybeClientId <- clientByNick nickname
    let kickId = fromJust maybeClientId
    let sameRoom = clientRoom rnc thisClientId == clientRoom rnc kickId

    if isJust $ masterID rm then
        return [Warning $ loc "/callvote kick: This is only allowed in rooms without a room master."]
        else
        if isJust maybeClientId && sameRoom then
            startVote $ VoteKick nickname
            else
            return [Warning $ loc "/callvote kick: No such user!"]


handleCmd_inRoom ["CALLVOTE", "MAP"] = do
    -- Display list of available maps for voting
    cl <- thisClient
    s <- liftM (Map.keys . roomSaves) thisRoom
    return [AnswerClients [sendChan cl]
        ["CHAT", nickServer,
            if (not $ null s) then
                (B.concat ["/callvote map: ", B.intercalate ", " s])
            else
                loc "/callvote map: No maps available."
        ]]


handleCmd_inRoom ["CALLVOTE", "MAP", roomSave] = do
    cl <- thisClient
    rm <- thisRoom

    if Map.member roomSave $ roomSaves rm then
        startVote $ VoteMap roomSave
        else
        return [Warning $ loc "/callvote map: No such map!"]


handleCmd_inRoom ["CALLVOTE", "PAUSE"] = do
    cl <- thisClient
    rm <- thisRoom

    if isJust $ gameInfo rm then
        startVote VotePause
        else 
        return [Warning $ loc "/callvote pause: No game in progress!"]

handleCmd_inRoom ["CALLVOTE", "PAUSE", _] = handleCmd_inRoom ["CALLVOTE", "PAUSE"]

handleCmd_inRoom ["CALLVOTE", "NEWSEED"] = do
    startVote VoteNewSeed

handleCmd_inRoom ["CALLVOTE", "NEWSEED", _] = handleCmd_inRoom ["CALLVOTE", "NEWSEED"]

handleCmd_inRoom ["CALLVOTE", "HEDGEHOGS"] = do
    cl <- thisClient
    return [Warning $ loc "/callvote hedgehogs: Specify number from 1 to 8."]


handleCmd_inRoom ["CALLVOTE", "HEDGEHOGS", hhs] = do
    cl <- thisClient
    let h = readInt_ hhs

    if h > 0 && h <= cHogsPerTeam then
        startVote $ VoteHedgehogsPerTeam h
        else
        return [Warning $ loc "/callvote hedgehogs: Specify number from 1 to 8."]

handleCmd_inRoom ["CALLVOTE", _] = handleCmd_inRoom ["CALLVOTE"]
handleCmd_inRoom ["CALLVOTE", _, _] = handleCmd_inRoom ["CALLVOTE"]

handleCmd_inRoom ("VOTE" : m : p) = do
    cl <- thisClient
    let b = if m == "YES" then Just True else if m == "NO" then Just False else Nothing
    if isJust b then
        voted (p == ["FORCE"]) (fromJust b)
    else
        return [Warning $
            if (p == ["FORCE"]) then
                loc "/force: Please use 'yes' or 'no'."
            else
                loc "/vote: Please use 'yes' or 'no'."
        ]


handleCmd_inRoom ["SAVE", stateName, location] = serverAdminOnly $ do
    return [ModifyRoom $ \r -> r{roomSaves = Map.insert stateName (location, mapParams r, params r) (roomSaves r)}]

handleCmd_inRoom ["DELETE", stateName] = serverAdminOnly $ do
    return [ModifyRoom $ \r -> r{roomSaves = Map.delete stateName (roomSaves r)}]

handleCmd_inRoom ["SAVEROOM", fileName] = serverAdminOnly $ do
    return [SaveRoom fileName]

handleCmd_inRoom ["LOADROOM", fileName] = serverAdminOnly $ do
    return [LoadRoom fileName]

handleCmd_inRoom ["LIST"] = return [] -- for old clients (<= 0.9.17)

handleCmd_inRoom (s:_) = return [ProtocolError $ "Incorrect command '" `B.append` s `B.append` "' (state: in room)"]

handleCmd_inRoom [] = return [ProtocolError "Empty command (state: in room)"]