{-# OPTIONS_GHC -fno-warn-orphans #-}
module Game(
mainWire
, Player(..)
, Camera(..)
, Game(..)
, AppMonad
) where
import Control.Wire
import Control.Wire.Unsafe.Event (event)
import Data.Text (pack)
import Prelude hiding (id, (.))
import qualified Data.HashMap.Strict as H
import qualified Data.Sequence as S
import Game.GoreAndAsh
import Game.GoreAndAsh.Actor
import Game.GoreAndAsh.Logging
import Game.GoreAndAsh.Network
import Game.GoreAndAsh.SDL
import Game.GoreAndAsh.Sync
import Consts
import Game.Bullet
import Game.Camera
import Game.Core
import Game.Data
import Game.Player
import Game.Shared
-- | Entry point of the game, controls global game stages
mainWire :: AppWire a (Maybe Game)
mainWire = waitConnection
where
-- | Game stage before connection established
waitConnection = dSwitch $ proc _ -> do
e <- mapE seqLeftHead . peersConnected -< ()
traceEvent (const "Connected to server") -< e
returnA -< (Nothing, waitPlayerId <$> e)
-- | Helper to get left most element from sequence or die
seqLeftHead s = case S.viewl s of
S.EmptyL -> error "seqLeftHead: empty sequence"
(h S.:< _) -> h
-- | After connection succeded, get important data to begin game
waitPlayerId peer = switch $ proc _ -> do
-- Send request for player id
emsg <- now -< PlayerRequestId
peerSendIndexed peer (ChannelID 0) globalGameId ReliableMessage -< emsg
traceEvent (const "Waiting for player id") -< emsg
-- Waiting for respond with player id
e <- mapE seqLeftHead . filterMsgs isPlayerResponseId . peerIndexedMessages peer (ChannelID 0) globalGameId -< ()
traceEvent (\(PlayerResponseId i _ _) -> "Got player id: " <> pack (show i)) -< e
-- When recieved switch to next stage
let nextWire = (\(PlayerResponseId i bulletsColId playersColId) -> untilDisconnected peer (PlayerId i) (fromCounter bulletsColId) (fromCounter playersColId)) <$> e
returnA -< (Nothing, nextWire)
-- | Main game stage, actual playing
untilDisconnected peer pid bulletsColId playersColId = switch $ proc _ -> do
e <- peerDisconnected peer -< ()
traceEvent (const "Disconnected from server") -< e
g <- runActor' (playGame pid peer bulletsColId playersColId) -< ()
returnA -< (g, const disconnected <$> e)
-- | Final stage, we are disconnected
disconnected = pure Nothing
-- | Controller of main game stage when actual playing is happen
playGame :: PlayerId -> Peer -> RemActorCollId -> RemActorCollId -> AppActor GameId a (Maybe Game)
playGame pid peer bulletsColId playersColId = makeFixedActor globalGameId $ stateWire Nothing $ proc (_, mg) -> do
c <- runActor' $ cameraWire initialCamera -< ()
exitRes <- exitCheck -< ()
(ps, bs) <- case mg of
Nothing -> returnA -< (H.empty, H.empty)
Just g -> do
ps <- processPlayers playersColId -< g
bs <- processBullets bulletsColId -< g
returnA -< (ps, bs)
forceNF -< Just $! case mg of
Nothing -> Game {
gameId = globalGameId
, gamePlayer = H.lookup pid ps
, gameCamera = c
, gamePlayers = ps
, gameBullets = bs
, gameExit = exitRes
}
Just g -> g {
gamePlayer = H.lookup pid ps
, gameCamera = c
, gamePlayers = ps
, gameBullets = bs
, gameExit = exitRes
}
where
-- | Check if user or system wants us to die
exitCheck = proc _ -> do
e <- windowClosed mainWindowName -< ()
q <- liftGameMonad sdlQuitEventM -< ()
returnA -< event False (const True) e || q
-- | Maker of startup camera
initialCamera i = Camera i 0 0.1
-- | Handles spawing/despawing of other players
processPlayers :: RemActorCollId -> AppWire Game PlayerMap
processPlayers cid = proc g -> do
ps <- runActor' $ remoteActorCollectionClient cid peer makePlayerActor -< gameCamera g
returnA -< mapFromSeq $ fmap playerId ps `S.zip` ps
where
makePlayerActor i = if i == pid
then playerActor peer pid
else remotePlayerActor peer i
-- | Handles spawing/despawing of bullets
processBullets :: RemActorCollId -> AppWire Game BulletMap
processBullets cid = proc g -> do
bs <- runActor' $ remoteActorCollectionClient cid peer (bulletActor peer) -< g
returnA -< mapFromSeq $ fmap bulletId bs `S.zip` bs