<commit_before>{-# LANGUAGE TupleSections #-}
-- | Sending atomic commands to clients and executing them on the server.
--
-- See
-- <https://github.com/LambdaHack/LambdaHack/wiki/Client-server-architecture>.
module Game.LambdaHack.Server.BroadcastAtomic
  ( handleAndBroadcast, sendPer, handleCmdAtomicServer
#ifdef EXPOSE_INTERNAL
    -- * Internal operations
  , cmdItemsFromIids, hearUpdAtomic, hearSfxAtomic, filterHear, atomicForget
  , atomicRemember
#endif
  ) where

import Prelude ()

import Game.LambdaHack.Core.Prelude

import qualified Data.EnumMap.Strict as EM
import qualified Data.EnumSet as ES

import           Game.LambdaHack.Atomic
import           Game.LambdaHack.Common.Actor
import           Game.LambdaHack.Common.ActorState
import           Game.LambdaHack.Common.Faction
import qualified Game.LambdaHack.Common.ItemAspect as IA
import           Game.LambdaHack.Common.Kind
import           Game.LambdaHack.Common.Level
import           Game.LambdaHack.Common.Misc
import           Game.LambdaHack.Common.MonadStateRead
import           Game.LambdaHack.Common.Perception
import           Game.LambdaHack.Common.Point
import           Game.LambdaHack.Common.State
import qualified Game.LambdaHack.Common.Tile as Tile
import           Game.LambdaHack.Common.Types
import qualified Game.LambdaHack.Content.ItemKind as IK
import           Game.LambdaHack.Content.TileKind (isUknownSpace)
import qualified Game.LambdaHack.Core.Dice as Dice
import qualified Game.LambdaHack.Definition.Ability as Ability
import           Game.LambdaHack.Definition.Defs
import           Game.LambdaHack.Server.MonadServer
import           Game.LambdaHack.Server.ProtocolM
import           Game.LambdaHack.Server.ServerOptions
import           Game.LambdaHack.Server.State

--storeUndo :: MonadServer m => CmdAtomic -> m ()
--storeUndo _atomic =
--  maybe skip (\a -> modifyServer $ \ser -> ser {sundo = a : sundo ser})
--    $ Nothing   -- undoCmdAtomic atomic

handleCmdAtomicServer :: MonadServerAtomic m
                      => UpdAtomic -> m (PosAtomic, [UpdAtomic], Bool)
handleCmdAtomicServer cmd = do
  ps <- posUpdAtomic cmd
  atomicBroken <- breakUpdAtomic cmd
    -- needs to be done before the states are changed and may make no sense
  executedOnServer <- if seenAtomicSer ps
                      then execUpdAtomicSer cmd
                      else return False
  return (ps, atomicBroken, executedOnServer)

-- | Send an atomic action to all clients that can see it.
handleAndBroadcast :: (MonadServerAtomic m, MonadServerComm m)
                   => PosAtomic -> [UpdAtomic] -> CmdAtomic -> m ()
handleAndBroadcast ps atomicBroken atomic = do
  -- This is calculated in the server State before action (simulating
  -- current client State, because action has not been applied
  -- on the client yet).
  -- E.g., actor's position in @breakUpdAtomic@ is assumed to be pre-action.
  -- To get rid of breakUpdAtomic we'd need to send only Spot and Lose
  -- commands instead of Move and Displace (plus Sfx for Displace).
  -- So this only makes sense when we switch to sending state diffs.
  knowEvents <- getsServer $ sknowEvents . soptions
  sperFidOld <- getsServer sperFid
  -- Send some actions to the clients, one faction at a time.
  let sendAtomic fid (UpdAtomic cmd) = do
        let iids = iidUpdAtomic cmd
        s <- getState
        sClient <- getsServer $ (EM.! fid) . sclientStates
        mapM_ (sendUpdateCheck fid) $ cmdItemsFromIids iids sClient s
        sendUpdate fid cmd
      sendAtomic fid (SfxAtomic sfx) = do
        let iids = iidSfxAtomic sfx
        s <- getState
        sClient <- getsServer $ (EM.! fid) . sclientStates
        mapM_ (sendUpdateCheck fid) $ cmdItemsFromIids iids sClient s
        sendSfx fid sfx
      breakSend fid perFid = case lidOfPos ps of
        Nothing -> return ()
        Just lidOriginal -> do
          psBroken <- mapM posUpdAtomic atomicBroken
          case psBroken of
            _ : _ -> do
              let send2 (cmd2, ps2) =
                    when (seenAtomicCli knowEvents fid perFid ps2) $
                      sendAtomic fid (UpdAtomic cmd2)
              mapM_ send2 $ zip atomicBroken psBroken
            [] -> do  -- hear only here; broken commands are never loud
              -- At most @minusM@ applied total over a single actor move,
              -- to avoid distress as if wounded (which is measured via deltas).
              -- So, if faction hits an enemy and it yells, hearnig yell will
              -- not decrease calm over the decrease from hearing strike.
              -- This may accumulate over time, though, to eventually wake up
              -- sleeping actors.
              let drainCalmOnce aid = do
                    b <- getsState $ getActorBody aid
                    when (deltaBenign $ bcalmDelta b) $
                      execUpdAtomic $ UpdRefillCalm aid minusM
              -- Projectiles never hear, for speed and simplicity,
              -- even though they sometimes see. There are flying cameras,
              -- but no microphones --- drones make too much noise themselves.
              as <- getsState $ fidActorRegularAssocs fid lidOriginal
              case atomic of
                UpdAtomic cmd -> do
                  (profound, mpos) <- hearUpdAtomic cmd
                  case mpos of
                    Nothing | profound ->
                      sendUpdate fid $ UpdHearFid fid Nothing
                                     $ HearUpd cmd
                    Nothing -> return ()
                    Just pos -> do
                      let distance = Nothing
                      aids <- filterHear pos as
                      if null aids && not profound
                      then return ()
                      else do
                        sendUpdate fid $ UpdHearFid fid distance
                                       $ HearUpd cmd
                        mapM_ drainCalmOnce aids
                SfxAtomic cmd -> do
                  mhear <- hearSfxAtomic cmd
                  case mhear of
                    Nothing -> return ()
                    Just (hearMsg, profound, pos) -> do
                      let distance = Nothing
                      aids <- filterHear pos as
                      if null aids && not profound
                      then return ()
                      else do
                        sendUpdate fid $ UpdHearFid fid distance hearMsg
                        mapM_ drainCalmOnce aids
      -- We assume players perceive perception change before the action,
      -- so the action is perceived in the new perception,
      -- even though the new perception depends on the action's outcome
      -- (e.g., new actor created).
      send fid = do
        let perFid = sperFidOld EM.! fid<commit_msg>Send noise distance information<commit_after>{-# LANGUAGE TupleSections #-}
-- | Sending atomic commands to clients and executing them on the server.
--
-- See
-- <https://github.com/LambdaHack/LambdaHack/wiki/Client-server-architecture>.
module Game.LambdaHack.Server.BroadcastAtomic
  ( handleAndBroadcast, sendPer, handleCmdAtomicServer
#ifdef EXPOSE_INTERNAL
    -- * Internal operations
  , cmdItemsFromIids, hearUpdAtomic, hearSfxAtomic, filterHear, atomicForget
  , atomicRemember
#endif
  ) where

import Prelude ()

import Game.LambdaHack.Core.Prelude

import qualified Data.EnumMap.Strict as EM
import qualified Data.EnumSet as ES

import           Game.LambdaHack.Atomic
import           Game.LambdaHack.Common.Actor
import           Game.LambdaHack.Common.ActorState
import           Game.LambdaHack.Common.Faction
import qualified Game.LambdaHack.Common.ItemAspect as IA
import           Game.LambdaHack.Common.Kind
import           Game.LambdaHack.Common.Level
import           Game.LambdaHack.Common.Misc
import           Game.LambdaHack.Common.MonadStateRead
import           Game.LambdaHack.Common.Perception
import           Game.LambdaHack.Common.Point
import           Game.LambdaHack.Common.State
import qualified Game.LambdaHack.Common.Tile as Tile
import           Game.LambdaHack.Common.Types
import qualified Game.LambdaHack.Content.ItemKind as IK
import           Game.LambdaHack.Content.TileKind (isUknownSpace)
import qualified Game.LambdaHack.Core.Dice as Dice
import qualified Game.LambdaHack.Definition.Ability as Ability
import           Game.LambdaHack.Definition.Defs
import           Game.LambdaHack.Server.MonadServer
import           Game.LambdaHack.Server.ProtocolM
import           Game.LambdaHack.Server.ServerOptions
import           Game.LambdaHack.Server.State

--storeUndo :: MonadServer m => CmdAtomic -> m ()
--storeUndo _atomic =
--  maybe skip (\a -> modifyServer $ \ser -> ser {sundo = a : sundo ser})
--    $ Nothing   -- undoCmdAtomic atomic

handleCmdAtomicServer :: MonadServerAtomic m
                      => UpdAtomic -> m (PosAtomic, [UpdAtomic], Bool)
handleCmdAtomicServer cmd = do
  ps <- posUpdAtomic cmd
  atomicBroken <- breakUpdAtomic cmd
    -- needs to be done before the states are changed and may make no sense
  executedOnServer <- if seenAtomicSer ps
                      then execUpdAtomicSer cmd
                      else return False
  return (ps, atomicBroken, executedOnServer)

-- | Send an atomic action to all clients that can see it.
handleAndBroadcast :: (MonadServerAtomic m, MonadServerComm m)
                   => PosAtomic -> [UpdAtomic] -> CmdAtomic -> m ()
handleAndBroadcast ps atomicBroken atomic = do
  -- This is calculated in the server State before action (simulating
  -- current client State, because action has not been applied
  -- on the client yet).
  -- E.g., actor's position in @breakUpdAtomic@ is assumed to be pre-action.
  -- To get rid of breakUpdAtomic we'd need to send only Spot and Lose
  -- commands instead of Move and Displace (plus Sfx for Displace).
  -- So this only makes sense when we switch to sending state diffs.
  knowEvents <- getsServer $ sknowEvents . soptions
  sperFidOld <- getsServer sperFid
  -- Send some actions to the clients, one faction at a time.
  let sendAtomic fid (UpdAtomic cmd) = do
        let iids = iidUpdAtomic cmd
        s <- getState
        sClient <- getsServer $ (EM.! fid) . sclientStates
        mapM_ (sendUpdateCheck fid) $ cmdItemsFromIids iids sClient s
        sendUpdate fid cmd
      sendAtomic fid (SfxAtomic sfx) = do
        let iids = iidSfxAtomic sfx
        s <- getState
        sClient <- getsServer $ (EM.! fid) . sclientStates
        mapM_ (sendUpdateCheck fid) $ cmdItemsFromIids iids sClient s
        sendSfx fid sfx
      breakSend fid perFid = case lidOfPos ps of
        Nothing -> return ()
        Just lidOriginal -> do
          psBroken <- mapM posUpdAtomic atomicBroken
          case psBroken of
            _ : _ -> do
              let send2 (cmd2, ps2) =
                    when (seenAtomicCli knowEvents fid perFid ps2) $
                      sendAtomic fid (UpdAtomic cmd2)
              mapM_ send2 $ zip atomicBroken psBroken
            [] -> do  -- hear only here; broken commands are never loud
              -- At most @minusM@ applied total over a single actor move,
              -- to avoid distress as if wounded (which is measured via deltas).
              -- So, if faction hits an enemy and it yells, hearnig yell will
              -- not decrease calm over the decrease from hearing strike.
              -- This may accumulate over time, though, to eventually wake up
              -- sleeping actors.
              let drainCalmOnce aid = do
                    b <- getsState $ getActorBody aid
                    when (deltaBenign $ bcalmDelta b) $
                      execUpdAtomic $ UpdRefillCalm aid minusM
                  leaderDistance pos = do
                    mleader <- getsState $ gleader . (EM.! fid) . sfactionD
                    case mleader of
                      Nothing -> return Nothing
                      Just leader -> do
                        b <- getsState $ getActorBody leader
                        -- Leader's hearing irrelevant, which prevents
                        -- changing leader just to get hearing intel.
                        -- However, leader's position affects accuracy.
                        return $ Just $ min 5 $ chessDist pos (bpos b) `div` 10
              -- Projectiles never hear, for speed and simplicity,
              -- even though they sometimes see. There are flying cameras,
              -- but no microphones --- drones make too much noise themselves.
              as <- getsState $ fidActorRegularAssocs fid lidOriginal
              case atomic of
                UpdAtomic cmd -> do
                  (profound, mpos) <- hearUpdAtomic cmd
                  case mpos of
                    Nothing | profound ->
                      sendUpdate fid $ UpdHearFid fid Nothing
                                     $ HearUpd cmd
                    Nothing -> return ()
                    Just pos -> do
                      distance <- leaderDistance pos
                      aids <- filterHear pos as
                      if null aids && not profound
                      then return ()
                      else do
                        sendUpdate fid $ UpdHearFid fid distance
                                       $ HearUpd cmd
                        mapM_ drainCalmOnce aids
                SfxAtomic cmd -> do
                  mhear <- hearSfxAtomic cmd
                  case mhear of
                    Nothing -> return ()
                    Just (hearMsg, profound, pos) -> do
                      distance <- leaderDistance pos
                      aids <- filterHear pos as
                      if null aids && not profound
                      then return ()
                      else do
                        sendUpdate fid $ UpdHearFid fid distance hearMsg
                        mapM_ drainCalmOnce aids
      -- We assume players perceive perception change before the action,
      -- so the action is perceived in the new perception,
      -- even though the new perception depends on the action's outcome
      -- (e.g., new actor created).
      send fid = do
        let perFid = sperFidOld EM.! fid
{'fraction_non_alphanumeric': 0.06335159432504565, 'fraction_numerical': 0.001053518752633797, 'mean_word_length': 1.781597968353194, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 49, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5320224', 'n_tokens_mistral': 3796, 'n_tokens_neox': 3654, 'n_words': 1291}