-- | Semantics of requests
-- .
-- A couple of them do not take time, the rest does.
-- Note that since the results are atomic commands, which are executed
-- only later (on the server and some of the clients), all condition
-- are checkd by the semantic functions in the context of the state
-- before the server command. Even if one or more atomic actions
-- are already issued by the point an expression is evaluated, they do not
-- influence the outcome of the evaluation.
module Game.LambdaHack.Server.HandleRequestM
  ( handleRequestAI, handleRequestUI, handleRequestTimed, switchLeader
  , reqMoveGeneric, reqDisplaceGeneric, reqAlterFail
  , reqGameDropAndExit, reqGameSaveAndExit
#ifdef EXPOSE_INTERNAL
    -- * Internal operations
  , execFailure, checkWaiting, processWatchfulness, managePerRequest
  , handleRequestTimedCases, affectSmell, reqMove, reqMelee, reqMeleeChecked
  , reqDisplace, reqAlter, reqWait, reqWait10, reqYell, reqMoveItems
  , reqMoveItem, reqProject, reqApply
  , reqGameRestart, reqGameSave, reqTactic, reqAutomate
#endif
  ) where

import Prelude ()

import Game.LambdaHack.Core.Prelude

import qualified Data.EnumMap.Strict as EM
import qualified Data.Ord as Ord
import qualified Data.Text as T
import qualified Text.Show.Pretty as Show.Pretty

import           Game.LambdaHack.Atomic
import           Game.LambdaHack.Client (ReqAI (..), ReqUI (..),
                                         RequestTimed (..))
import           Game.LambdaHack.Client.UI.ItemDescription
import           Game.LambdaHack.Common.Actor
import           Game.LambdaHack.Common.ActorState
import           Game.LambdaHack.Common.Analytics
import           Game.LambdaHack.Common.Faction
import           Game.LambdaHack.Common.Item
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.Point
import           Game.LambdaHack.Common.ReqFailure
import           Game.LambdaHack.Common.State
import qualified Game.LambdaHack.Common.Tile as Tile
import           Game.LambdaHack.Common.Time
import           Game.LambdaHack.Common.Types
import           Game.LambdaHack.Common.Vector
import qualified Game.LambdaHack.Content.ItemKind as IK
import           Game.LambdaHack.Content.ModeKind
import qualified Game.LambdaHack.Content.TileKind as TK
import qualified Game.LambdaHack.Definition.Ability as Ability
import           Game.LambdaHack.Definition.Defs
import           Game.LambdaHack.Server.CommonM
import           Game.LambdaHack.Server.HandleEffectM
import           Game.LambdaHack.Server.ItemM
import           Game.LambdaHack.Server.MonadServer
import           Game.LambdaHack.Server.PeriodicM
import           Game.LambdaHack.Server.ServerOptions
import           Game.LambdaHack.Server.State

execFailure :: MonadServerAtomic m
            => ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure :: ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure aid :: ActorId
aid req :: RequestTimed
req failureSer :: ReqFailure
failureSer = do
  -- Clients should rarely do that (only in case of invisible actors)
  -- so we report it to the client, but do not crash
  -- (server should work OK with stupid clients, too).
  Actor
body <- (State -> Actor) -> m Actor
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
aid
  let fid :: FactionId
fid = Actor -> FactionId
bfid Actor
body
      msg :: Text
msg = ReqFailure -> Text
showReqFailure ReqFailure
failureSer
      impossible :: Bool
impossible = ReqFailure -> Bool
impossibleReqFailure ReqFailure
failureSer
      debugShow :: Show a => a -> Text
      debugShow :: a -> Text
debugShow = String -> Text
T.pack (String -> Text) -> (a -> String) -> a -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> String
forall a. Show a => a -> String
Show.Pretty.ppShow
      possiblyAlarm :: Text -> m ()
possiblyAlarm = if Bool
impossible
                      then Text -> m ()
forall (m :: * -> *). MonadServer m => Text -> m ()
debugPossiblyPrintAndExit
                      else Text -> m ()
forall (m :: * -> *). MonadServer m => Text -> m ()
debugPossiblyPrint
  Text -> m ()
possiblyAlarm (Text -> m ()) -> Text -> m ()
forall a b. (a -> b) -> a -> b
$
    "Server: execFailure:" Text -> Text -> Text
<+> Text
msg Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> "\n"
    Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Actor -> Text
forall a. Show a => a -> Text
debugShow Actor
body Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> "\n" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> RequestTimed -> Text
forall a. Show a => a -> Text
debugShow RequestTimed
req Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> "\n" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> ReqFailure -> Text
forall a. Show a => a -> Text
debugShow ReqFailure
failureSer
  SfxAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => SfxAtomic -> m ()
execSfxAtomic (SfxAtomic -> m ()) -> SfxAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ FactionId -> SfxMsg -> SfxAtomic
SfxMsgFid FactionId
fid (SfxMsg -> SfxAtomic) -> SfxMsg -> SfxAtomic
forall a b. (a -> b) -> a -> b
$ ReqFailure -> SfxMsg
SfxUnexpected ReqFailure
failureSer

-- | The semantics of server commands.
-- AI always takes time and so doesn't loop.
handleRequestAI :: MonadServerAtomic m
                => ReqAI
                -> m (Maybe RequestTimed)
handleRequestAI :: ReqAI -> m (Maybe RequestTimed)
handleRequestAI cmd :: ReqAI
cmd = case ReqAI
cmd of
  ReqAITimed cmdT :: RequestTimed
cmdT -> Maybe RequestTimed -> m (Maybe RequestTimed)
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe RequestTimed -> m (Maybe RequestTimed))
-> Maybe RequestTimed -> m (Maybe RequestTimed)
forall a b. (a -> b) -> a -> b
$ RequestTimed -> Maybe RequestTimed
forall a. a -> Maybe a
Just RequestTimed
cmdT
  ReqAINop -> Maybe RequestTimed -> m (Maybe RequestTimed)
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe RequestTimed
forall a. Maybe a
Nothing

-- | The semantics of server commands. Only the first two cases affect time.
handleRequestUI :: MonadServerAtomic m
                => FactionId -> ActorId -> ReqUI
                -> m (Maybe RequestTimed)
handleRequestUI :: FactionId -> ActorId -> ReqUI -> m (Maybe RequestTimed)
handleRequestUI fid :: FactionId
fid aid :: ActorId
aid cmd :: ReqUI
cmd = case ReqUI
cmd of
  ReqUITimed cmdT :: RequestTimed
cmdT -> Maybe RequestTimed -> m (Maybe RequestTimed)
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe RequestTimed -> m (Maybe RequestTimed))
-> Maybe RequestTimed -> m (Maybe RequestTimed)
forall a b. (a -> b) -> a -> b
$ RequestTimed -> Maybe RequestTimed
forall a. a -> Maybe a
Just RequestTimed
cmdT
  ReqUIGameRestart t :: GroupName ModeKind
t d :: Challenge
d -> ActorId -> GroupName ModeKind -> Challenge -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> GroupName ModeKind -> Challenge -> m ()
reqGameRestart ActorId
aid GroupName ModeKind
t Challenge
d m () -> m (Maybe RequestTimed) -> m (Maybe RequestTimed)
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Maybe RequestTimed -> m (Maybe RequestTimed)
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe RequestTimed
forall a. Maybe a
Nothing
  ReqUIGameDropAndExit -> ActorId -> m ()
forall (m :: * -> *). MonadServerAtomic m => ActorId -> m ()
reqGameDropAndExit ActorId
aid m () -> m (Maybe RequestTimed) -> m (Maybe RequestTimed)
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Maybe RequestTimed -> m (Maybe RequestTimed)
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe RequestTimed
forall a. Maybe a
Nothing
  ReqUIGameSaveAndExit -> ActorId -> m ()
forall (m :: * -> *). MonadServerAtomic m => ActorId -> m ()
reqGameSaveAndExit ActorId
aid m () -> m (Maybe RequestTimed) -> m (Maybe RequestTimed)
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Maybe RequestTimed -> m (Maybe RequestTimed)
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe RequestTimed
forall a. Maybe a
Nothing
  ReqUIGameSave -> m ()
forall (m :: * -> *). MonadServer m => m ()
reqGameSave m () -> m (Maybe RequestTimed) -> m (Maybe RequestTimed)
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Maybe RequestTimed -> m (Maybe RequestTimed)
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe RequestTimed
forall a. Maybe a
Nothing
  ReqUITactic toT :: Tactic
toT -> FactionId -> Tactic -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
FactionId -> Tactic -> m ()
reqTactic FactionId
fid Tactic
toT m () -> m (Maybe RequestTimed) -> m (Maybe RequestTimed)
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Maybe RequestTimed -> m (Maybe RequestTimed)
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe RequestTimed
forall a. Maybe a
Nothing
  ReqUIAutomate -> FactionId -> m ()
forall (m :: * -> *). MonadServerAtomic m => FactionId -> m ()
reqAutomate FactionId
fid m () -> m (Maybe RequestTimed) -> m (Maybe RequestTimed)
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Maybe RequestTimed -> m (Maybe RequestTimed)
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe RequestTimed
forall a. Maybe a
Nothing
  ReqUINop -> Maybe RequestTimed -> m (Maybe RequestTimed)
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe RequestTimed
forall a. Maybe a
Nothing

checkWaiting :: RequestTimed -> Maybe Bool
checkWaiting :: RequestTimed -> Maybe Bool
checkWaiting cmd :: RequestTimed
cmd = case RequestTimed
cmd of
  ReqWait -> Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
True  -- true wait, with bracing, no overhead, etc.
  ReqWait10 -> Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
False  -- false wait, only one clip at a time
  _ -> Maybe Bool
forall a. Maybe a
Nothing

-- | This is a shorthand. Instead of setting @bwatch@ in @ReqWait@
-- and unsetting in all other requests, we call this once after
-- executing a request.
-- In game state, we collect the number of server requests pertaining
-- to the actor (the number of actor's "moves"), through which
-- the actor was waiting.
processWatchfulness :: MonadServerAtomic m => Maybe Bool -> ActorId -> m ()
processWatchfulness :: Maybe Bool -> ActorId -> m ()
processWatchfulness mwait :: Maybe Bool
mwait aid :: ActorId
aid = do
  Actor
b <- (State -> Actor) -> m Actor
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
aid
  Skills
actorMaxSk <- (State -> Skills) -> m Skills
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Skills) -> m Skills) -> (State -> Skills) -> m Skills
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Skills
getActorMaxSkills ActorId
aid
  let uneasy :: Bool
uneasy = ResDelta -> Bool
deltasSerious (Actor -> ResDelta
bcalmDelta Actor
b) Bool -> Bool -> Bool
|| Bool -> Bool
not (Actor -> Skills -> Bool
calmEnough Actor
b Skills
actorMaxSk)
  case Actor -> Watchfulness
bwatch Actor
b of
    WSleep ->
      if Maybe Bool
mwait Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
/= Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
False  -- lurk can't wake up regardless; too short
         Bool -> Bool -> Bool
&& (Bool -> Bool
not (Maybe Bool -> Bool
forall a. Maybe a -> Bool
isJust Maybe Bool
mwait)  -- not a wait
             Bool -> Bool -> Bool
|| Bool
uneasy  -- spooked
             Bool -> Bool -> Bool
|| Bool -> Bool
not (ResDelta -> Bool
deltaBenign (ResDelta -> Bool) -> ResDelta -> Bool
forall a b. (a -> b) -> a -> b
$ Actor -> ResDelta
bhpDelta Actor
b))  -- any HP lost
      then UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ ActorId -> Watchfulness -> Watchfulness -> UpdAtomic
UpdWaitActor ActorId
aid Watchfulness
WSleep Watchfulness
WWake
      else UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ ActorId -> Int64 -> UpdAtomic
UpdRefillHP ActorId
aid 10000
             -- no @xM@, so slow, but each turn HP gauge green;
             -- this is 1HP per 100 turns, so it's 10 slower than a necklace
             -- that gives 1HP per 10 turns;
             -- so if an actor sleeps for the duration of a 1000 turns,
             -- which may be the time it takes to fully explore a level,
             -- 10HP would be gained, so weak actors would wake up
    WWake -> Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Maybe Bool
mwait Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
False) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$  -- lurk can't wake up; too fast
      ActorId -> m ()
forall (m :: * -> *). MonadServerAtomic m => ActorId -> m ()
removeSleepSingle ActorId
aid
    WWait 0 -> case Maybe Bool
mwait of  -- actor couldn't brace last time
      Just True -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()  -- if he still waits, keep him stuck unbraced
      _ -> UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ ActorId -> Watchfulness -> Watchfulness -> UpdAtomic
UpdWaitActor ActorId
aid (Int -> Watchfulness
WWait 0) Watchfulness
WWatch
    WWait n :: Int
n -> case Maybe Bool
mwait of
      Just True ->  -- only proper wait prevents switching to watchfulness
        if Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= 500 then  -- enough dozing to fall asleep
          if Bool -> Bool
not Bool
uneasy  -- won't wake up at once
             Bool -> Bool -> Bool
&& Skills -> Bool
canSleep Skills
actorMaxSk  -- enough skills
          then do
            Int
nAll <- GroupName ItemKind -> ActorId -> m Int
forall (m :: * -> *).
MonadServerAtomic m =>
GroupName ItemKind -> ActorId -> m Int
removeConditionSingle "braced" ActorId
aid
            let !_A :: ()
_A = Bool -> () -> ()
forall a. (?callStack::CallStack) => Bool -> a -> a
assert (Int
nAll Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== 0) ()
            ActorId -> m ()
forall (m :: * -> *). MonadServerAtomic m => ActorId -> m ()
addSleep ActorId
aid
          else
            -- Start dozing from scratch to prevent hopeless skill checks.
            UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ ActorId -> Watchfulness -> Watchfulness -> UpdAtomic
UpdWaitActor ActorId
aid (Int -> Watchfulness
WWait Int
n) (Int -> Watchfulness
WWait 1)
        else
          -- Doze some more before checking sleep eligibility.
          UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ ActorId -> Watchfulness -> Watchfulness -> UpdAtomic
UpdWaitActor ActorId
aid (Int -> Watchfulness
WWait Int
n) (Int -> Watchfulness
WWait (Int -> Watchfulness) -> Int -> Watchfulness
forall a b. (a -> b) -> a -> b
$ Int
n Int -> Int -> Int
forall a. Num a => a -> a -> a
+ 1)
      _ -> do
        Int
nAll <- GroupName ItemKind -> ActorId -> m Int
forall (m :: * -> *).
MonadServerAtomic m =>
GroupName ItemKind -> ActorId -> m Int
removeConditionSingle "braced" ActorId
aid
        let !_A :: ()
_A = Bool -> () -> ()
forall a. (?callStack::CallStack) => Bool -> a -> a
assert (Int
nAll Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== 0) ()
        UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ ActorId -> Watchfulness -> Watchfulness -> UpdAtomic
UpdWaitActor ActorId
aid (Int -> Watchfulness
WWait Int
n) Watchfulness
WWatch
    WWatch ->
      Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Maybe Bool
mwait Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
True) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$  -- only long wait switches to wait state
        if Skill -> Skills -> Int
Ability.getSk Skill
Ability.SkWait Skills
actorMaxSk Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= 2 then do
          GroupName ItemKind -> ActorId -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
GroupName ItemKind -> ActorId -> m ()
addCondition "braced" ActorId
aid
          UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ ActorId -> Watchfulness -> Watchfulness -> UpdAtomic
UpdWaitActor ActorId
aid Watchfulness
WWatch (Int -> Watchfulness
WWait 1)
        else
          UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ ActorId -> Watchfulness -> Watchfulness -> UpdAtomic
UpdWaitActor ActorId
aid Watchfulness
WWatch (Int -> Watchfulness
WWait 0)

handleRequestTimed :: MonadServerAtomic m
                   => FactionId -> ActorId -> RequestTimed -> m Bool
handleRequestTimed :: FactionId -> ActorId -> RequestTimed -> m Bool
handleRequestTimed fid :: FactionId
fid aid :: ActorId
aid cmd :: RequestTimed
cmd = do
  let mwait :: Maybe Bool
mwait = RequestTimed -> Maybe Bool
checkWaiting RequestTimed
cmd
  Actor
b <- (State -> Actor) -> m Actor
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
aid
  -- Note that only the ordinary 1-turn wait eliminates overhead.
  -- The more fine-graned waits don't make actors braced and induce
  -- overhead, so that they have some drawbacks in addition to the
  -- benefit of seeing approaching danger up to almost a turn faster.
  -- It may be too late to block then, but not too late to sidestep or attack.
  Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Maybe Bool
mwait Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
True) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ FactionId -> LevelId -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
FactionId -> LevelId -> m ()
overheadActorTime FactionId
fid (Actor -> LevelId
blid Actor
b)
  ActorId -> Int -> Bool -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> Int -> Bool -> m ()
advanceTime ActorId
aid (if Maybe Bool
mwait Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
False then 10 else 100) Bool
True
  ActorId -> RequestTimed -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> RequestTimed -> m ()
handleRequestTimedCases ActorId
aid RequestTimed
cmd
  ActorId -> m ()
forall (m :: * -> *). MonadServerAtomic m => ActorId -> m ()
managePerRequest ActorId
aid
  -- Note that due to the order, actor was still braced or sleeping
  -- throughout request processing, etc. So, if he hits himself kinetically,
  -- his armor from bracing previous turn is still in effect.
  Maybe Bool -> ActorId -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
Maybe Bool -> ActorId -> m ()
processWatchfulness Maybe Bool
mwait ActorId
aid
  Bool -> m Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> m Bool) -> Bool -> m Bool
forall a b. (a -> b) -> a -> b
$! Maybe Bool -> Bool
forall a. Maybe a -> Bool
isNothing Maybe Bool
mwait  -- for speed, we report if @cmd@ harmless

-- | Clear deltas for Calm and HP for proper UI display and AI hints.
managePerRequest :: MonadServerAtomic m => ActorId -> m ()
managePerRequest :: ActorId -> m ()
managePerRequest aid :: ActorId
aid = do
  Actor
b <- (State -> Actor) -> m Actor
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
aid
  let clearMark :: Int64
clearMark = 0
  Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Actor -> ResDelta
bcalmDelta Actor
b ResDelta -> ResDelta -> Bool
forall a. Eq a => a -> a -> Bool
== (Int64, Int64) -> (Int64, Int64) -> ResDelta
ResDelta (0, 0) (0, 0)) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
    -- Clear delta for the next actor move.
    UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ ActorId -> Int64 -> UpdAtomic
UpdRefillCalm ActorId
aid Int64
clearMark
  Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Actor -> ResDelta
bhpDelta Actor
b ResDelta -> ResDelta -> Bool
forall a. Eq a => a -> a -> Bool
== (Int64, Int64) -> (Int64, Int64) -> ResDelta
ResDelta (0, 0) (0, 0)) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
    -- Clear delta for the next actor move.
    UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ ActorId -> Int64 -> UpdAtomic
UpdRefillHP ActorId
aid Int64
clearMark

handleRequestTimedCases :: MonadServerAtomic m
                        => ActorId -> RequestTimed -> m ()
handleRequestTimedCases :: ActorId -> RequestTimed -> m ()
handleRequestTimedCases aid :: ActorId
aid cmd :: RequestTimed
cmd = case RequestTimed
cmd of
  ReqMove target :: Vector
target -> ActorId -> Vector -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> Vector -> m ()
reqMove ActorId
aid Vector
target
  ReqMelee target :: ActorId
target iid :: ItemId
iid cstore :: CStore
cstore -> ActorId -> ActorId -> ItemId -> CStore -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> ActorId -> ItemId -> CStore -> m ()
reqMelee ActorId
aid ActorId
target ItemId
iid CStore
cstore
  ReqDisplace target :: ActorId
target -> ActorId -> ActorId -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> ActorId -> m ()
reqDisplace ActorId
aid ActorId
target
  ReqAlter tpos :: Point
tpos -> ActorId -> Point -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> Point -> m ()
reqAlter ActorId
aid Point
tpos
  ReqWait -> ActorId -> m ()
forall (m :: * -> *). MonadServerAtomic m => ActorId -> m ()
reqWait ActorId
aid
  ReqWait10 -> ActorId -> m ()
forall (m :: * -> *). MonadServerAtomic m => ActorId -> m ()
reqWait10 ActorId
aid
  ReqYell -> ActorId -> m ()
forall (m :: * -> *). MonadServerAtomic m => ActorId -> m ()
reqYell ActorId
aid
  ReqMoveItems l :: [(ItemId, Int, CStore, CStore)]
l -> ActorId -> [(ItemId, Int, CStore, CStore)] -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> [(ItemId, Int, CStore, CStore)] -> m ()
reqMoveItems ActorId
aid [(ItemId, Int, CStore, CStore)]
l
  ReqProject p :: Point
p eps :: Int
eps iid :: ItemId
iid cstore :: CStore
cstore -> ActorId -> Point -> Int -> ItemId -> CStore -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> Point -> Int -> ItemId -> CStore -> m ()
reqProject ActorId
aid Point
p Int
eps ItemId
iid CStore
cstore
  ReqApply iid :: ItemId
iid cstore :: CStore
cstore -> ActorId -> ItemId -> CStore -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> ItemId -> CStore -> m ()
reqApply ActorId
aid ItemId
iid CStore
cstore

switchLeader :: MonadServerAtomic m => FactionId -> ActorId -> m ()
{-# INLINE switchLeader #-}
switchLeader :: FactionId -> ActorId -> m ()
switchLeader fid :: FactionId
fid aidNew :: ActorId
aidNew = do
  Faction
fact <- (State -> Faction) -> m Faction
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Faction) -> m Faction)
-> (State -> Faction) -> m Faction
forall a b. (a -> b) -> a -> b
$ (EnumMap FactionId Faction -> FactionId -> Faction
forall k a. Enum k => EnumMap k a -> k -> a
EM.! FactionId
fid) (EnumMap FactionId Faction -> Faction)
-> (State -> EnumMap FactionId Faction) -> State -> Faction
forall b c a. (b -> c) -> (a -> b) -> a -> c
. State -> EnumMap FactionId Faction
sfactionD
  Actor
bPre <- (State -> Actor) -> m Actor
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
aidNew
  let mleader :: Maybe ActorId
mleader = Faction -> Maybe ActorId
gleader Faction
fact
      !_A1 :: ()
_A1 = Bool -> () -> ()
forall a. (?callStack::CallStack) => Bool -> a -> a
assert (ActorId -> Maybe ActorId
forall a. a -> Maybe a
Just ActorId
aidNew Maybe ActorId -> Maybe ActorId -> Bool
forall a. Eq a => a -> a -> Bool
/= Maybe ActorId
mleader
                     Bool -> Bool -> Bool
&& Bool -> Bool
not (Actor -> Bool
bproj Actor
bPre)
                     Bool -> (ActorId, Actor, FactionId, Faction) -> Bool
forall a. Show a => Bool -> a -> Bool
`blame` (ActorId
aidNew, Actor
bPre, FactionId
fid, Faction
fact)) ()
      !_A2 :: ()
_A2 = Bool -> () -> ()
forall a. (?callStack::CallStack) => Bool -> a -> a
assert (Actor -> FactionId
bfid Actor
bPre FactionId -> FactionId -> Bool
forall a. Eq a => a -> a -> Bool
== FactionId
fid
                     Bool -> (String, (ActorId, Actor, FactionId, Faction)) -> Bool
forall a. Show a => Bool -> a -> Bool
`blame` "client tries to move other faction actors"
                     String
-> (ActorId, Actor, FactionId, Faction)
-> (String, (ActorId, Actor, FactionId, Faction))
forall v. String -> v -> (String, v)
`swith` (ActorId
aidNew, Actor
bPre, FactionId
fid, Faction
fact)) ()
  let (autoDun :: Bool
autoDun, _) = Faction -> (Bool, Bool)
autoDungeonLevel Faction
fact
  LevelId
arena <- case Maybe ActorId
mleader of
    Nothing -> LevelId -> m LevelId
forall (m :: * -> *) a. Monad m => a -> m a
return (LevelId -> m LevelId) -> LevelId -> m LevelId
forall a b. (a -> b) -> a -> b
$! Actor -> LevelId
blid Actor
bPre
    Just leader :: ActorId
leader -> do
      Actor
b <- (State -> Actor) -> m Actor
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
leader
      LevelId -> m LevelId
forall (m :: * -> *) a. Monad m => a -> m a
return (LevelId -> m LevelId) -> LevelId -> m LevelId
forall a b. (a -> b) -> a -> b
$! Actor -> LevelId
blid Actor
b
  if | Actor -> LevelId
blid Actor
bPre LevelId -> LevelId -> Bool
forall a. Eq a => a -> a -> Bool
/= LevelId
arena Bool -> Bool -> Bool
&& Bool
autoDun ->
       ActorId -> RequestTimed -> ReqFailure -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure ActorId
aidNew RequestTimed
ReqWait{-hack-} ReqFailure
NoChangeDunLeader
     | Bool
otherwise -> do
       UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ FactionId -> Maybe ActorId -> Maybe ActorId -> UpdAtomic
UpdLeadFaction FactionId
fid Maybe ActorId
mleader (ActorId -> Maybe ActorId
forall a. a -> Maybe a
Just ActorId
aidNew)
     -- We exchange times of the old and new leader.
     -- This permits an abuse, because a slow tank can be moved fast
     -- by alternating between it and many fast actors (until all of them
     -- get slowed down by this and none remain). But at least the sum
     -- of all times of a faction is conserved. And we avoid double moves
     -- against the UI player caused by his leader changes. There may still
     -- happen double moves caused by AI leader changes, but that's rare.
     -- The flip side is the possibility of multi-moves of the UI player
     -- as in the case of the tank.
     -- Warning: when the action is performed on the server,
     -- the time of the actor is different than when client prepared that
     -- action, so any client checks involving time should discount this.
       case Maybe ActorId
mleader of
         Just aidOld :: ActorId
aidOld | ActorId
aidOld ActorId -> ActorId -> Bool
forall a. Eq a => a -> a -> Bool
/= ActorId
aidNew -> ActorId -> ActorId -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> ActorId -> m ()
swapTime ActorId
aidOld ActorId
aidNew
         _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

-- * ReqMove

-- | Add a smell trace for the actor to the level. If smell already there
-- and the actor can smell, remove smell. Projectiles are ignored.
-- As long as an actor can smell, he doesn't leave any smell ever.
-- Smell trace is never left in water tiles.
affectSmell :: MonadServerAtomic m => ActorId -> m ()
affectSmell :: ActorId -> m ()
affectSmell aid :: ActorId
aid = do
  COps{TileSpeedup
coTileSpeedup :: COps -> TileSpeedup
coTileSpeedup :: TileSpeedup
coTileSpeedup} <- (State -> COps) -> m COps
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState State -> COps
scops
  Actor
b <- (State -> Actor) -> m Actor
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
aid
  Level
lvl <- LevelId -> m Level
forall (m :: * -> *). MonadStateRead m => LevelId -> m Level
getLevel (LevelId -> m Level) -> LevelId -> m Level
forall a b. (a -> b) -> a -> b
$ Actor -> LevelId
blid Actor
b
  let aquatic :: Bool
aquatic = TileSpeedup -> ContentId TileKind -> Bool
Tile.isAquatic TileSpeedup
coTileSpeedup (ContentId TileKind -> Bool) -> ContentId TileKind -> Bool
forall a b. (a -> b) -> a -> b
$ Level
lvl Level -> Point -> ContentId TileKind
`at` Actor -> Point
bpos Actor
b
  Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Actor -> Bool
bproj Actor
b Bool -> Bool -> Bool
|| Bool
aquatic) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
    Skills
actorMaxSk <- (State -> Skills) -> m Skills
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Skills) -> m Skills) -> (State -> Skills) -> m Skills
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Skills
getActorMaxSkills ActorId
aid
    let smellRadius :: Int
smellRadius = Skill -> Skills -> Int
Ability.getSk Skill
Ability.SkSmell Skills
actorMaxSk
        hasOdor :: Bool
hasOdor = Skill -> Skills -> Int
Ability.getSk Skill
Ability.SkOdor Skills
actorMaxSk Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> 0
    Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool
hasOdor Bool -> Bool -> Bool
|| Int
smellRadius Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> 0) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
      Time
localTime <- (State -> Time) -> m Time
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Time) -> m Time) -> (State -> Time) -> m Time
forall a b. (a -> b) -> a -> b
$ LevelId -> State -> Time
getLocalTime (LevelId -> State -> Time) -> LevelId -> State -> Time
forall a b. (a -> b) -> a -> b
$ Actor -> LevelId
blid Actor
b
      let oldS :: Time
oldS = Time -> Maybe Time -> Time
forall a. a -> Maybe a -> a
fromMaybe Time
timeZero (Maybe Time -> Time) -> Maybe Time -> Time
forall a b. (a -> b) -> a -> b
$ Point -> EnumMap Point Time -> Maybe Time
forall k a. Enum k => k -> EnumMap k a -> Maybe a
EM.lookup (Actor -> Point
bpos Actor
b) (EnumMap Point Time -> Maybe Time)
-> (Level -> EnumMap Point Time) -> Level -> Maybe Time
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Level -> EnumMap Point Time
lsmell (Level -> Maybe Time) -> Level -> Maybe Time
forall a b. (a -> b) -> a -> b
$ Level
lvl
          newTime :: Time
newTime = Time -> Delta Time -> Time
timeShift Time
localTime Delta Time
smellTimeout
          newS :: Time
newS = if Int
smellRadius Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> 0
                 then Time
timeZero
                 else Time
newTime
      Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Time
oldS Time -> Time -> Bool
forall a. Eq a => a -> a -> Bool
/= Time
newS) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
        UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ LevelId -> Point -> Time -> Time -> UpdAtomic
UpdAlterSmell (Actor -> LevelId
blid Actor
b) (Actor -> Point
bpos Actor
b) Time
oldS Time
newS

-- | Actor moves or attacks.
-- Note that client may not be able to see an invisible monster
-- so it's the server that determines if melee took place, etc.
-- Also, only the server is authorized to check if a move is legal
-- and it needs full context for that, e.g., the initial actor position
-- to check if melee attack does not try to reach to a distant tile.
reqMove :: MonadServerAtomic m => ActorId -> Vector -> m ()
reqMove :: ActorId -> Vector -> m ()
reqMove = Bool -> Bool -> ActorId -> Vector -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
Bool -> Bool -> ActorId -> Vector -> m ()
reqMoveGeneric Bool
True Bool
True

reqMoveGeneric :: MonadServerAtomic m
               => Bool -> Bool -> ActorId -> Vector -> m ()
reqMoveGeneric :: Bool -> Bool -> ActorId -> Vector -> m ()
reqMoveGeneric voluntary :: Bool
voluntary mayAttack :: Bool
mayAttack source :: ActorId
source dir :: Vector
dir = do
  COps{TileSpeedup
coTileSpeedup :: TileSpeedup
coTileSpeedup :: COps -> TileSpeedup
coTileSpeedup} <- (State -> COps) -> m COps
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState State -> COps
scops
  Skills
actorSk <- ActorId -> m Skills
forall (m :: * -> *). MonadServer m => ActorId -> m Skills
currentSkillsServer ActorId
source
  Actor
sb <- (State -> Actor) -> m Actor
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
source
  let abInSkill :: Skill -> Bool
abInSkill sk :: Skill
sk = Maybe ([Vector], Speed) -> Bool
forall a. Maybe a -> Bool
isJust (Actor -> Maybe ([Vector], Speed)
btrajectory Actor
sb)
                     Bool -> Bool -> Bool
|| Skill -> Skills -> Int
Ability.getSk Skill
sk Skills
actorSk Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> 0
      lid :: LevelId
lid = Actor -> LevelId
blid Actor
sb
  Level
lvl <- LevelId -> m Level
forall (m :: * -> *). MonadStateRead m => LevelId -> m Level
getLevel LevelId
lid
  let spos :: Point
spos = Actor -> Point
bpos Actor
sb
      tpos :: Point
tpos = Point
spos Point -> Vector -> Point
`shift` Vector
dir
  -- This predicate is symmetric wrt source and target, though the effect
  -- of collision may not be (the source projectiles applies its effect
  -- on the target particles, but loses 1 HP due to the collision).
  -- The condition implies that it's impossible to shoot down a bullet
  -- with a bullet, but a bullet can shoot down a burstable target,
  -- as well as be swept away by it, and two burstable projectiles
  -- burst when meeting mid-air. Projectiles that are not bursting
  -- nor damaging never collide with any projectile.
  Actor -> Bool
collides <- (State -> Actor -> Bool) -> m (Actor -> Bool)
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor -> Bool) -> m (Actor -> Bool))
-> (State -> Actor -> Bool) -> m (Actor -> Bool)
forall a b. (a -> b) -> a -> b
$ \s :: State
s tb :: Actor
tb ->
    let sitemKind :: ItemKind
sitemKind = ItemId -> State -> ItemKind
getIidKindServer (Actor -> ItemId
btrunk Actor
sb) State
s
        titemKind :: ItemKind
titemKind = ItemId -> State -> ItemKind
getIidKindServer (Actor -> ItemId
btrunk Actor
tb) State
s
        sar :: AspectRecord
sar = State -> DiscoveryAspect
sdiscoAspect State
s DiscoveryAspect -> ItemId -> AspectRecord
forall k a. Enum k => EnumMap k a -> k -> a
EM.! Actor -> ItemId
btrunk Actor
sb
        tar :: AspectRecord
tar = State -> DiscoveryAspect
sdiscoAspect State
s DiscoveryAspect -> ItemId -> AspectRecord
forall k a. Enum k => EnumMap k a -> k -> a
EM.! Actor -> ItemId
btrunk Actor
tb
        -- Such projectiles are prone to bursting or are themselves
        -- particles of an explosion shockwave.
        bursting :: AspectRecord -> Bool
bursting arItem :: AspectRecord
arItem =
          Flag -> AspectRecord -> Bool
IA.checkFlag Flag
Ability.Fragile AspectRecord
arItem
          Bool -> Bool -> Bool
&& Flag -> AspectRecord -> Bool
IA.checkFlag Flag
Ability.Lobable AspectRecord
arItem
        sbursting :: Bool
sbursting = AspectRecord -> Bool
bursting AspectRecord
sar
        tbursting :: Bool
tbursting = AspectRecord -> Bool
bursting AspectRecord
tar
        -- Such projectiles, even if not bursting themselves, can cause
        -- another projectile to burst.
        damaging :: ItemKind -> Bool
damaging itemKind :: ItemKind
itemKind = ItemKind -> Dice
IK.idamage ItemKind
itemKind Dice -> Dice -> Bool
forall a. Eq a => a -> a -> Bool
/= 0
        sdamaging :: Bool
sdamaging = ItemKind -> Bool
damaging ItemKind
sitemKind
        tdamaging :: Bool
tdamaging = ItemKind -> Bool
damaging ItemKind
titemKind
        -- Avoid explosion extinguishing itself via its own particles colliding.
        sameBlast :: Bool
sameBlast = Flag -> AspectRecord -> Bool
IA.checkFlag Flag
Ability.Blast AspectRecord
sar
                    Bool -> Bool -> Bool
&& ItemId -> State -> ContentId ItemKind
getIidKindIdServer (Actor -> ItemId
btrunk Actor
sb) State
s
                       ContentId ItemKind -> ContentId ItemKind -> Bool
forall a. Eq a => a -> a -> Bool
== ItemId -> State -> ContentId ItemKind
getIidKindIdServer (Actor -> ItemId
btrunk Actor
tb) State
s
    in Bool -> Bool
not Bool
sameBlast
       Bool -> Bool -> Bool
&& (Bool
sbursting Bool -> Bool -> Bool
&& (Bool
tdamaging Bool -> Bool -> Bool
|| Bool
tbursting)
           Bool -> Bool -> Bool
|| (Bool
tbursting Bool -> Bool -> Bool
&& (Bool
sdamaging Bool -> Bool -> Bool
|| Bool
sbursting)))
  -- We start by checking actors at the target position.
  [(ActorId, Actor)]
tgt <- (State -> [(ActorId, Actor)]) -> m [(ActorId, Actor)]
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> [(ActorId, Actor)]) -> m [(ActorId, Actor)])
-> (State -> [(ActorId, Actor)]) -> m [(ActorId, Actor)]
forall a b. (a -> b) -> a -> b
$ Point -> LevelId -> State -> [(ActorId, Actor)]
posToAidAssocs Point
tpos LevelId
lid
  case [(ActorId, Actor)]
tgt of
    (target :: ActorId
target, tb :: Actor
tb) : _ | Bool
mayAttack Bool -> Bool -> Bool
&& (Bool -> Bool
not (Actor -> Bool
bproj Actor
sb)
                                     Bool -> Bool -> Bool
|| Bool -> Bool
not (Actor -> Bool
bproj Actor
tb)
                                     Bool -> Bool -> Bool
|| Actor -> Bool
collides Actor
tb) -> do
      -- A projectile is too small and insubstantial to hit another projectile,
      -- unless it's large enough or tends to explode (fragile and lobable).
      -- The actor in the way is visible or not; server sees him always.
      -- Below the only weapon (the only item) of projectiles is picked.
      Maybe (ItemId, CStore)
mweapon <- ActorId -> m (Maybe (ItemId, CStore))
forall (m :: * -> *).
MonadServer m =>
ActorId -> m (Maybe (ItemId, CStore))
pickWeaponServer ActorId
source
      case Maybe (ItemId, CStore)
mweapon of
        Just (wp :: ItemId
wp, cstore :: CStore
cstore) | Skill -> Bool
abInSkill Skill
Ability.SkMelee ->
          Bool -> ActorId -> ActorId -> ItemId -> CStore -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
Bool -> ActorId -> ActorId -> ItemId -> CStore -> m ()
reqMeleeChecked Bool
voluntary ActorId
source ActorId
target ItemId
wp CStore
cstore
        _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()  -- waiting, even if no @SkWait@ skill
      -- Movement of projectiles only happens after melee and a check
      -- if they survive, so that if they don't, they explode in front
      -- of enemy, not under him, so that already first explosion blasts
      -- reach him, not only potential secondary explosions.
      Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Actor -> Bool
bproj Actor
sb) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
        Actor
b2 <- (State -> Actor) -> m Actor
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
source
        Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Actor -> Bool
actorDying Actor
b2) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ Bool -> Bool -> ActorId -> Vector -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
Bool -> Bool -> ActorId -> Vector -> m ()
reqMoveGeneric Bool
voluntary Bool
False ActorId
source Vector
dir
    _ ->
      -- Either the position is empty, or all involved actors are proj.
      -- Movement requires full access and skill.
      if TileSpeedup -> ContentId TileKind -> Bool
Tile.isWalkable TileSpeedup
coTileSpeedup (ContentId TileKind -> Bool) -> ContentId TileKind -> Bool
forall a b. (a -> b) -> a -> b
$ Level
lvl Level -> Point -> ContentId TileKind
`at` Point
tpos then
        if Skill -> Bool
abInSkill Skill
Ability.SkMove then do
          UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ ActorId -> Point -> Point -> UpdAtomic
UpdMoveActor ActorId
source Point
spos Point
tpos
          ActorId -> m ()
forall (m :: * -> *). MonadServerAtomic m => ActorId -> m ()
affectSmell ActorId
source
          m (Maybe ReqFailure) -> m ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (m (Maybe ReqFailure) -> m ()) -> m (Maybe ReqFailure) -> m ()
forall a b. (a -> b) -> a -> b
$ Bool -> ActorId -> Point -> m (Maybe ReqFailure)
forall (m :: * -> *).
MonadServerAtomic m =>
Bool -> ActorId -> Point -> m (Maybe ReqFailure)
reqAlterFail Bool
voluntary ActorId
source Point
tpos
            -- possibly alter or activate
        else ActorId -> RequestTimed -> ReqFailure -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure ActorId
source (Vector -> RequestTimed
ReqMove Vector
dir) ReqFailure
MoveUnskilled
      else
        -- Client foolishly tries to move into unwalkable tile.
        ActorId -> RequestTimed -> ReqFailure -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure ActorId
source (Vector -> RequestTimed
ReqMove Vector
dir) ReqFailure
MoveNothing

-- * ReqMelee

-- | Resolves the result of an actor moving into another.
-- Actors on unwalkable positions can be attacked without any restrictions.
-- For instance, an actor embedded in a wall can be attacked from
-- an adjacent position. This function is analogous to projectGroupItem,
-- but for melee and not using up the weapon.
-- No problem if there are many projectiles at the spot. We just
-- attack the one specified.
reqMelee :: MonadServerAtomic m
         => ActorId -> ActorId -> ItemId -> CStore -> m ()
reqMelee :: ActorId -> ActorId -> ItemId -> CStore -> m ()
reqMelee source :: ActorId
source target :: ActorId
target iid :: ItemId
iid cstore :: CStore
cstore = do
  Skills
actorSk <- ActorId -> m Skills
forall (m :: * -> *). MonadServer m => ActorId -> m Skills
currentSkillsServer ActorId
source
  if Skill -> Skills -> Int
Ability.getSk Skill
Ability.SkMelee Skills
actorSk Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> 0 then
    Bool -> ActorId -> ActorId -> ItemId -> CStore -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
Bool -> ActorId -> ActorId -> ItemId -> CStore -> m ()
reqMeleeChecked Bool
True ActorId
source ActorId
target ItemId
iid CStore
cstore
  else ActorId -> RequestTimed -> ReqFailure -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure ActorId
source (ActorId -> ItemId -> CStore -> RequestTimed
ReqMelee ActorId
target ItemId
iid CStore
cstore) ReqFailure
MeleeUnskilled

reqMeleeChecked :: forall m. MonadServerAtomic m
                => Bool -> ActorId -> ActorId -> ItemId -> CStore -> m ()
reqMeleeChecked :: Bool -> ActorId -> ActorId -> ItemId -> CStore -> m ()
reqMeleeChecked voluntary :: Bool
voluntary source :: ActorId
source target :: ActorId
target iid :: ItemId
iid cstore :: CStore
cstore = do
  Actor
sb <- (State -> Actor) -> m Actor
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
source
  Actor
tb <- (State -> Actor) -> m Actor
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
target
  let req :: RequestTimed
req = ActorId -> ItemId -> CStore -> RequestTimed
ReqMelee ActorId
target ItemId
iid CStore
cstore
  if ActorId
source ActorId -> ActorId -> Bool
forall a. Eq a => a -> a -> Bool
== ActorId
target then ActorId -> RequestTimed -> ReqFailure -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure ActorId
source RequestTimed
req ReqFailure
MeleeSelf
  else if Bool -> Bool
not (Actor -> Actor -> Bool
checkAdjacent Actor
sb Actor
tb) then ActorId -> RequestTimed -> ReqFailure -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure ActorId
source RequestTimed
req ReqFailure
MeleeDistant
  else do
    -- If @voluntary@ is set, blame is exact, otherwise, an approximation.
    ActorId
killer <- if | Bool
voluntary -> Bool -> m ActorId -> m ActorId
forall a. (?callStack::CallStack) => Bool -> a -> a
assert (Bool -> Bool
not (Actor -> Bool
bproj Actor
sb)) (m ActorId -> m ActorId) -> m ActorId -> m ActorId
forall a b. (a -> b) -> a -> b
$ ActorId -> m ActorId
forall (m :: * -> *) a. Monad m => a -> m a
return ActorId
source
                 | Actor -> Bool
bproj Actor
sb -> (StateServer -> ActorId) -> m ActorId
forall (m :: * -> *) a. MonadServer m => (StateServer -> a) -> m a
getsServer ((StateServer -> ActorId) -> m ActorId)
-> (StateServer -> ActorId) -> m ActorId
forall a b. (a -> b) -> a -> b
$ ActorId -> ActorId -> EnumMap ActorId ActorId -> ActorId
forall k a. Enum k => a -> k -> EnumMap k a -> a
EM.findWithDefault ActorId
source ActorId
source
                               (EnumMap ActorId ActorId -> ActorId)
-> (StateServer -> EnumMap ActorId ActorId)
-> StateServer
-> ActorId
forall b c a. (b -> c) -> (a -> b) -> a -> c
. StateServer -> EnumMap ActorId ActorId
strajPushedBy
                 | Bool
otherwise -> ActorId -> m ActorId
forall (m :: * -> *) a. Monad m => a -> m a
return ActorId
source
    DiscoveryAspect
discoAspect <- (State -> DiscoveryAspect) -> m DiscoveryAspect
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState State -> DiscoveryAspect
sdiscoAspect
    let arTrunk :: AspectRecord
arTrunk = DiscoveryAspect
discoAspect DiscoveryAspect -> ItemId -> AspectRecord
forall k a. Enum k => EnumMap k a -> k -> a
EM.! Actor -> ItemId
btrunk Actor
tb
        arWeapon :: AspectRecord
arWeapon = DiscoveryAspect
discoAspect DiscoveryAspect -> ItemId -> AspectRecord
forall k a. Enum k => EnumMap k a -> k -> a
EM.! ItemId
iid
        sfid :: FactionId
sfid = Actor -> FactionId
bfid Actor
sb
        tfid :: FactionId
tfid = Actor -> FactionId
bfid Actor
tb
        -- Let the missile drop down, but don't remove its trajectory
        -- so that it doesn't pretend to have hit a wall.
        haltTrajectory :: KillHow -> ActorId -> Actor -> m ()
        haltTrajectory :: KillHow -> ActorId -> Actor -> m ()
haltTrajectory killHow :: KillHow
killHow aid :: ActorId
aid b :: Actor
b = case Actor -> Maybe ([Vector], Speed)
btrajectory Actor
b of
          btra :: Maybe ([Vector], Speed)
btra@(Just (l :: [Vector]
l, speed :: Speed
speed)) | Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ [Vector] -> Bool
forall a. [a] -> Bool
null [Vector]
l -> do
            UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ ActorId
-> Maybe ([Vector], Speed) -> Maybe ([Vector], Speed) -> UpdAtomic
UpdTrajectory ActorId
aid Maybe ([Vector], Speed)
btra (Maybe ([Vector], Speed) -> UpdAtomic)
-> Maybe ([Vector], Speed) -> UpdAtomic
forall a b. (a -> b) -> a -> b
$ ([Vector], Speed) -> Maybe ([Vector], Speed)
forall a. a -> Maybe a
Just ([], Speed
speed)
            let arTrunkAid :: AspectRecord
arTrunkAid = DiscoveryAspect
discoAspect DiscoveryAspect -> ItemId -> AspectRecord
forall k a. Enum k => EnumMap k a -> k -> a
EM.! Actor -> ItemId
btrunk Actor
b
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Actor -> Bool
bproj Actor
b Bool -> Bool -> Bool
&& Bool -> Bool
not (Flag -> AspectRecord -> Bool
IA.checkFlag Flag
Ability.Blast AspectRecord
arTrunkAid)) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
              ActorId -> KillHow -> FactionId -> ItemId -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> KillHow -> FactionId -> ItemId -> m ()
addKillToAnalytics ActorId
killer KillHow
killHow (Actor -> FactionId
bfid Actor
b) (Actor -> ItemId
btrunk Actor
b)
          _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    -- Only catch if braced. Never steal trunk from an already caught
    -- projectile or one with many items inside.
    if Actor -> Bool
bproj Actor
tb
       Bool -> Bool -> Bool
&& EnumMap ItemId ItemQuant -> Int
forall k a. EnumMap k a -> Int
EM.size (Actor -> EnumMap ItemId ItemQuant
beqp Actor
tb) Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== 1
       Bool -> Bool -> Bool
&& Bool -> Bool
not (Flag -> AspectRecord -> Bool
IA.checkFlag Flag
Ability.Blast AspectRecord
arTrunk)
       Bool -> Bool -> Bool
&& Actor -> Bool
actorWaits Actor
sb  -- still valid while request being processed
    then do
      -- Catching the projectile, that is, stealing the item from its eqp.
      -- No effect from our weapon (organ) is applied to the projectile
      -- and the weapon (organ) is never destroyed, even if not durable.
      -- Pushed actor doesn't stop flight by catching the projectile
      -- nor does he lose 1HP.
      -- This is not overpowered, because usually at least one partial wait
      -- is needed to sync (if not, attacker should switch missiles)
      -- and so only every other missile can be caught. Normal sidestepping
      -- or sync and displace, if in a corridor, is as effective
      -- and blocking can be even more so, depending on powers of the missile.
      -- Missiles are really easy to defend against, but sight (and so, Calm)
      -- is the key, as well as light, ambush around a corner, etc.
      SfxAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => SfxAtomic -> m ()
execSfxAtomic (SfxAtomic -> m ()) -> SfxAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ ActorId -> ActorId -> ItemId -> CStore -> SfxAtomic
SfxSteal ActorId
source ActorId
target ItemId
iid CStore
cstore
      case EnumMap ItemId ItemQuant -> [(ItemId, ItemQuant)]
forall k a. Enum k => EnumMap k a -> [(k, a)]
EM.assocs (EnumMap ItemId ItemQuant -> [(ItemId, ItemQuant)])
-> EnumMap ItemId ItemQuant -> [(ItemId, ItemQuant)]
forall a b. (a -> b) -> a -> b
$ Actor -> EnumMap ItemId ItemQuant
beqp Actor
tb of
        [(iid2 :: ItemId
iid2, (k :: Int
k, _))] -> do
          [UpdAtomic]
upds <- Bool -> ItemId -> Int -> Container -> Container -> m [UpdAtomic]
forall (m :: * -> *).
MonadStateRead m =>
Bool -> ItemId -> Int -> Container -> Container -> m [UpdAtomic]
generalMoveItem Bool
True ItemId
iid2 Int
k (ActorId -> CStore -> Container
CActor ActorId
target CStore
CEqp)
                                              (ActorId -> CStore -> Container
CActor ActorId
source CStore
CInv)
          (UpdAtomic -> m ()) -> [UpdAtomic] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic [UpdAtomic]
upds
          ItemFull
itemFull <- (State -> ItemFull) -> m ItemFull
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> ItemFull) -> m ItemFull)
-> (State -> ItemFull) -> m ItemFull
forall a b. (a -> b) -> a -> b
$ ItemId -> State -> ItemFull
itemToFull ItemId
iid2
          Container -> ItemId -> ContentId ItemKind -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
Container -> ItemId -> ContentId ItemKind -> m ()
discoverIfMinorEffects (ActorId -> CStore -> Container
CActor ActorId
source CStore
CInv) ItemId
iid2 (ItemFull -> ContentId ItemKind
itemKindId ItemFull
itemFull)
        err :: [(ItemId, ItemQuant)]
err -> String -> m ()
forall a. (?callStack::CallStack) => String -> a
error (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "" String -> [(ItemId, ItemQuant)] -> String
forall v. Show v => String -> v -> String
`showFailure` [(ItemId, ItemQuant)]
err
      KillHow -> ActorId -> Actor -> m ()
haltTrajectory KillHow
KillCatch ActorId
target Actor
tb
    else do
      if Actor -> Bool
bproj Actor
sb Bool -> Bool -> Bool
&& Actor -> Bool
bproj Actor
tb then do
        -- Special case for collision of projectiles, because they just
        -- symmetrically ram into each other, so picking one to hit another,
        -- based on random timing, would be wrong.
        -- Instead of suffering melee attack, let the target projectile
        -- get smashed and burst (if fragile and if not piercing).
        -- The source projectile terminates flight (unless pierces) later on.
        Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Actor -> Int64
bhp Actor
tb Int64 -> Int64 -> Bool
forall a. Ord a => a -> a -> Bool
> Int64
oneM) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
          UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ ActorId -> Int64 -> UpdAtomic
UpdRefillHP ActorId
target Int64
minusM
        Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Actor -> Int64
bhp Actor
tb Int64 -> Int64 -> Bool
forall a. Ord a => a -> a -> Bool
<= Int64
oneM) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
          -- If projectile has too low HP to pierce, terminate its flight.
          let killHow :: KillHow
killHow | Flag -> AspectRecord -> Bool
IA.checkFlag Flag
Ability.Blast AspectRecord
arWeapon = KillHow
KillKineticBlast
                      | Bool
otherwise = KillHow
KillKineticRanged
          KillHow -> ActorId -> Actor -> m ()
haltTrajectory KillHow
killHow ActorId
target Actor
tb
        -- Avoid spam when two explosions collide.
        Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Flag -> AspectRecord -> Bool
IA.checkFlag Flag
Ability.Blast AspectRecord
arWeapon
                Bool -> Bool -> Bool
&& Flag -> AspectRecord -> Bool
IA.checkFlag Flag
Ability.Blast AspectRecord
arTrunk) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
          SfxAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => SfxAtomic -> m ()
execSfxAtomic (SfxAtomic -> m ()) -> SfxAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ ActorId -> ActorId -> ItemId -> CStore -> SfxAtomic
SfxStrike ActorId
source ActorId
target ItemId
iid CStore
cstore
      else do
        -- Normal hit, with effects. Msgs inside @SfxStrike@ describe
        -- the source part of the strike.
        SfxAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => SfxAtomic -> m ()
execSfxAtomic (SfxAtomic -> m ()) -> SfxAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ ActorId -> ActorId -> ItemId -> CStore -> SfxAtomic
SfxStrike ActorId
source ActorId
target ItemId
iid CStore
cstore
        let c :: Container
c = ActorId -> CStore -> Container
CActor ActorId
source CStore
cstore
            mayDestroy :: Bool
mayDestroy = Bool -> Bool
not (Actor -> Bool
bproj Actor
sb) Bool -> Bool -> Bool
|| Actor -> Int64
bhp Actor
sb Int64 -> Int64 -> Bool
forall a. Ord a => a -> a -> Bool
<= Int64
oneM
              -- piercing projectiles may not have their weapon destroyed
        -- Msgs inside @itemEffect@ describe the target part of the strike.
        -- If any effects and aspects, this is also where they are identified.
        -- Here also the kinetic damage is applied, before any effects are.
        --
        -- Note: that "hornet swarm detect items" via a scrolls is intentional,
        -- even though unrealistic and funny. Otherwise actors could protect
        -- themselves from some projectiles by lowering their apply stat.
        -- Also, the animal faction won't have too much benefit from that info,
        -- so the problem is not balance, but the goofy message.
        Bool
-> ActorId
-> ActorId
-> ActorId
-> ItemId
-> Container
-> Bool
-> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
Bool
-> ActorId
-> ActorId
-> ActorId
-> ItemId
-> Container
-> Bool
-> m ()
kineticEffectAndDestroy Bool
voluntary ActorId
killer ActorId
source ActorId
target ItemId
iid Container
c Bool
mayDestroy
      Actor
sb2 <- (State -> Actor) -> m Actor
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
source
      case Actor -> Maybe ([Vector], Speed)
btrajectory Actor
sb2 of
        Just{} -> do
          -- Deduct a hitpoint for a pierce of a projectile
          -- or due to a hurled actor colliding with another.
          -- Don't deduct if no pierce, to prevent spam.
          -- Never kill in this way.
          Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Actor -> Int64
bhp Actor
sb2 Int64 -> Int64 -> Bool
forall a. Ord a => a -> a -> Bool
> Int64
oneM) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
            UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ ActorId -> Int64 -> UpdAtomic
UpdRefillHP ActorId
source Int64
minusM
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Actor -> Bool
bproj Actor
sb2) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
              SfxAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => SfxAtomic -> m ()
execSfxAtomic (SfxAtomic -> m ()) -> SfxAtomic -> m ()
forall a b. (a -> b) -> a -> b
$
                FactionId -> SfxMsg -> SfxAtomic
SfxMsgFid (Actor -> FactionId
bfid Actor
sb2) (SfxMsg -> SfxAtomic) -> SfxMsg -> SfxAtomic
forall a b. (a -> b) -> a -> b
$ LevelId -> ActorId -> ActorId -> SfxMsg
SfxCollideActor (Actor -> LevelId
blid Actor
tb) ActorId
source ActorId
target
              Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Actor -> Bool
bproj Actor
tb) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                SfxAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => SfxAtomic -> m ()
execSfxAtomic (SfxAtomic -> m ()) -> SfxAtomic -> m ()
forall a b. (a -> b) -> a -> b
$
                  FactionId -> SfxMsg -> SfxAtomic
SfxMsgFid (Actor -> FactionId
bfid Actor
tb) (SfxMsg -> SfxAtomic) -> SfxMsg -> SfxAtomic
forall a b. (a -> b) -> a -> b
$ LevelId -> ActorId -> ActorId -> SfxMsg
SfxCollideActor (Actor -> LevelId
blid Actor
tb) ActorId
source ActorId
target
          Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool -> Bool
not (Actor -> Bool
bproj Actor
sb2) Bool -> Bool -> Bool
|| Actor -> Int64
bhp Actor
sb2 Int64 -> Int64 -> Bool
forall a. Ord a => a -> a -> Bool
<= Int64
oneM) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
            -- Non-projectiles can't pierce, so terminate their flight.
            -- If projectile has too low HP to pierce, ditto.
            KillHow -> ActorId -> Actor -> m ()
haltTrajectory KillHow
KillActorLaunch ActorId
source Actor
sb2
        _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
      -- The only way to start a war is to slap an enemy voluntarily..
      -- Being hit by and hitting projectiles, as well as via pushing,
      -- count as unintentional friendly fire.
      Faction
sfact <- (State -> Faction) -> m Faction
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Faction) -> m Faction)
-> (State -> Faction) -> m Faction
forall a b. (a -> b) -> a -> b
$ (EnumMap FactionId Faction -> FactionId -> Faction
forall k a. Enum k => EnumMap k a -> k -> a
EM.! FactionId
sfid) (EnumMap FactionId Faction -> Faction)
-> (State -> EnumMap FactionId Faction) -> State -> Faction
forall b c a. (b -> c) -> (a -> b) -> a -> c
. State -> EnumMap FactionId Faction
sfactionD
      let friendlyFire :: Bool
friendlyFire = Actor -> Bool
bproj Actor
sb2 Bool -> Bool -> Bool
|| Actor -> Bool
bproj Actor
tb Bool -> Bool -> Bool
|| Bool -> Bool
not Bool
voluntary
          fromDipl :: Diplomacy
fromDipl = Diplomacy -> FactionId -> EnumMap FactionId Diplomacy -> Diplomacy
forall k a. Enum k => a -> k -> EnumMap k a -> a
EM.findWithDefault Diplomacy
Unknown FactionId
tfid (Faction -> EnumMap FactionId Diplomacy
gdipl Faction
sfact)
      Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Bool
friendlyFire
              Bool -> Bool -> Bool
|| FactionId -> Faction -> FactionId -> Bool
isFoe FactionId
sfid Faction
sfact FactionId
tfid  -- already at war
              Bool -> Bool -> Bool
|| FactionId -> Faction -> FactionId -> Bool
isFriend FactionId
sfid Faction
sfact FactionId
tfid) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$  -- allies never at war
        UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ FactionId -> FactionId -> Diplomacy -> Diplomacy -> UpdAtomic
UpdDiplFaction FactionId
sfid FactionId
tfid Diplomacy
fromDipl Diplomacy
War

-- * ReqDisplace

-- | Actor tries to swap positions with another.
reqDisplace :: MonadServerAtomic m => ActorId -> ActorId -> m ()
reqDisplace :: ActorId -> ActorId -> m ()
reqDisplace = Bool -> ActorId -> ActorId -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
Bool -> ActorId -> ActorId -> m ()
reqDisplaceGeneric Bool
True

reqDisplaceGeneric :: MonadServerAtomic m => Bool -> ActorId -> ActorId -> m ()
reqDisplaceGeneric :: Bool -> ActorId -> ActorId -> m ()
reqDisplaceGeneric voluntary :: Bool
voluntary source :: ActorId
source target :: ActorId
target = do
  COps{TileSpeedup
coTileSpeedup :: TileSpeedup
coTileSpeedup :: COps -> TileSpeedup
coTileSpeedup} <- (State -> COps) -> m COps
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState State -> COps
scops
  Skills
actorSk <- ActorId -> m Skills
forall (m :: * -> *). MonadServer m => ActorId -> m Skills
currentSkillsServer ActorId
source
  Actor
sb <- (State -> Actor) -> m Actor
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
source
  let abInSkill :: Skill -> Bool
abInSkill sk :: Skill
sk = Maybe ([Vector], Speed) -> Bool
forall a. Maybe a -> Bool
isJust (Actor -> Maybe ([Vector], Speed)
btrajectory Actor
sb)
                     Bool -> Bool -> Bool
|| Skill -> Skills -> Int
Ability.getSk Skill
sk Skills
actorSk Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> 0
  Actor
tb <- (State -> Actor) -> m Actor
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
target
  Faction
tfact <- (State -> Faction) -> m Faction
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Faction) -> m Faction)
-> (State -> Faction) -> m Faction
forall a b. (a -> b) -> a -> b
$ (EnumMap FactionId Faction -> FactionId -> Faction
forall k a. Enum k => EnumMap k a -> k -> a
EM.! Actor -> FactionId
bfid Actor
tb) (EnumMap FactionId Faction -> Faction)
-> (State -> EnumMap FactionId Faction) -> State -> Faction
forall b c a. (b -> c) -> (a -> b) -> a -> c
. State -> EnumMap FactionId Faction
sfactionD
  let spos :: Point
spos = Actor -> Point
bpos Actor
sb
      tpos :: Point
tpos = Actor -> Point
bpos Actor
tb
      atWar :: Bool
atWar = FactionId -> Faction -> FactionId -> Bool
isFoe (Actor -> FactionId
bfid Actor
tb) Faction
tfact (Actor -> FactionId
bfid Actor
sb)
      req :: RequestTimed
req = ActorId -> RequestTimed
ReqDisplace ActorId
target
  Skills
actorMaxSk <- (State -> Skills) -> m Skills
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Skills) -> m Skills) -> (State -> Skills) -> m Skills
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Skills
getActorMaxSkills ActorId
target
  Bool
dEnemy <- (State -> Bool) -> m Bool
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Bool) -> m Bool) -> (State -> Bool) -> m Bool
forall a b. (a -> b) -> a -> b
$ ActorId -> ActorId -> Skills -> State -> Bool
dispEnemy ActorId
source ActorId
target Skills
actorMaxSk
  if | Bool -> Bool
not (Skill -> Bool
abInSkill Skill
Ability.SkDisplace) ->
         ActorId -> RequestTimed -> ReqFailure -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure ActorId
source RequestTimed
req ReqFailure
DisplaceUnskilled
     | Bool -> Bool
not (Actor -> Actor -> Bool
checkAdjacent Actor
sb Actor
tb) -> ActorId -> RequestTimed -> ReqFailure -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure ActorId
source RequestTimed
req ReqFailure
DisplaceDistant
     | Bool
atWar Bool -> Bool -> Bool
&& Bool -> Bool
not Bool
dEnemy -> do  -- if not at war, can displace always
       -- We don't fail with DisplaceImmobile and DisplaceSupported.
       -- because it's quite common they can't be determined by the attacker,
       -- and so the failure would be too alarming to the player.
       -- If the character melees instead, the player can tell displace failed.
       -- As for the other failures, they are impossible and we don't
       -- verify here that they don't occur, for simplicity.
       Maybe (ItemId, CStore)
mweapon <- ActorId -> m (Maybe (ItemId, CStore))
forall (m :: * -> *).
MonadServer m =>
ActorId -> m (Maybe (ItemId, CStore))
pickWeaponServer ActorId
source
       case Maybe (ItemId, CStore)
mweapon of
         Just (wp :: ItemId
wp, cstore :: CStore
cstore) | Skill -> Bool
abInSkill Skill
Ability.SkMelee ->
           Bool -> ActorId -> ActorId -> ItemId -> CStore -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
Bool -> ActorId -> ActorId -> ItemId -> CStore -> m ()
reqMeleeChecked Bool
voluntary ActorId
source ActorId
target ItemId
wp CStore
cstore
         _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()  -- waiting, even if no @SkWait@ skill
     | Bool
otherwise -> do
       let lid :: LevelId
lid = Actor -> LevelId
blid Actor
sb
       Level
lvl <- LevelId -> m Level
forall (m :: * -> *). MonadStateRead m => LevelId -> m Level
getLevel LevelId
lid
       -- Displacing requires full access.
       if TileSpeedup -> ContentId TileKind -> Bool
Tile.isWalkable TileSpeedup
coTileSpeedup (ContentId TileKind -> Bool) -> ContentId TileKind -> Bool
forall a b. (a -> b) -> a -> b
$ Level
lvl Level -> Point -> ContentId TileKind
`at` Point
tpos then
         case Point -> Level -> [ActorId]
posToAidsLvl Point
tpos Level
lvl of
           [] -> String -> m ()
forall a. (?callStack::CallStack) => String -> a
error (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "" String -> (ActorId, Actor, ActorId, Actor) -> String
forall v. Show v => String -> v -> String
`showFailure` (ActorId
source, Actor
sb, ActorId
target, Actor
tb)
           [_] -> do
             UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ ActorId -> ActorId -> UpdAtomic
UpdDisplaceActor ActorId
source ActorId
target
             -- We leave or wipe out smell, for consistency, but it's not
             -- absolute consistency, e.g., blinking doesn't touch smell,
             -- so sometimes smellers will backtrack once to wipe smell. OK.
             ActorId -> m ()
forall (m :: * -> *). MonadServerAtomic m => ActorId -> m ()
affectSmell ActorId
source
             ActorId -> m ()
forall (m :: * -> *). MonadServerAtomic m => ActorId -> m ()
affectSmell ActorId
target
             m (Maybe ReqFailure) -> m ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (m (Maybe ReqFailure) -> m ()) -> m (Maybe ReqFailure) -> m ()
forall a b. (a -> b) -> a -> b
$ Bool -> ActorId -> Point -> m (Maybe ReqFailure)
forall (m :: * -> *).
MonadServerAtomic m =>
Bool -> ActorId -> Point -> m (Maybe ReqFailure)
reqAlterFail Bool
voluntary ActorId
source Point
tpos
               -- possibly alter or activate
             m (Maybe ReqFailure) -> m ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (m (Maybe ReqFailure) -> m ()) -> m (Maybe ReqFailure) -> m ()
forall a b. (a -> b) -> a -> b
$ Bool -> ActorId -> Point -> m (Maybe ReqFailure)
forall (m :: * -> *).
MonadServerAtomic m =>
Bool -> ActorId -> Point -> m (Maybe ReqFailure)
reqAlterFail Bool
voluntary ActorId
target Point
spos
           _ -> ActorId -> RequestTimed -> ReqFailure -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure ActorId
source RequestTimed
req ReqFailure
DisplaceMultiple
       else
         -- Client foolishly tries to displace an actor without access.
         ActorId -> RequestTimed -> ReqFailure -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure ActorId
source RequestTimed
req ReqFailure
DisplaceAccess

-- * ReqAlter

-- | Search and/or alter the tile.
reqAlter :: MonadServerAtomic m => ActorId -> Point -> m ()
reqAlter :: ActorId -> Point -> m ()
reqAlter source :: ActorId
source tpos :: Point
tpos = do
  Maybe ReqFailure
mfail <- Bool -> ActorId -> Point -> m (Maybe ReqFailure)
forall (m :: * -> *).
MonadServerAtomic m =>
Bool -> ActorId -> Point -> m (Maybe ReqFailure)
reqAlterFail Bool
True ActorId
source Point
tpos
  let req :: RequestTimed
req = Point -> RequestTimed
ReqAlter Point
tpos
  m () -> (ReqFailure -> m ()) -> Maybe ReqFailure -> m ()
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (() -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()) (ActorId -> RequestTimed -> ReqFailure -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure ActorId
source RequestTimed
req) Maybe ReqFailure
mfail

reqAlterFail :: MonadServerAtomic m
             => Bool -> ActorId -> Point -> m (Maybe ReqFailure)
reqAlterFail :: Bool -> ActorId -> Point -> m (Maybe ReqFailure)
reqAlterFail voluntary :: Bool
voluntary source :: ActorId
source tpos :: Point
tpos = do
  cops :: COps
cops@COps{ContentData TileKind
cotile :: COps -> ContentData TileKind
cotile :: ContentData TileKind
cotile, TileSpeedup
coTileSpeedup :: TileSpeedup
coTileSpeedup :: COps -> TileSpeedup
coTileSpeedup} <- (State -> COps) -> m COps
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState State -> COps
scops
  Actor
sb <- (State -> Actor) -> m Actor
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
source
  Skills
actorMaxSk <- (State -> Skills) -> m Skills
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Skills) -> m Skills) -> (State -> Skills) -> m Skills
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Skills
getActorMaxSkills ActorId
source
  EnumMap FactionId Faction
factionD <- (State -> EnumMap FactionId Faction)
-> m (EnumMap FactionId Faction)
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState State -> EnumMap FactionId Faction
sfactionD
  let calmE :: Bool
calmE = Actor -> Skills -> Bool
calmEnough Actor
sb Skills
actorMaxSk
      lid :: LevelId
lid = Actor -> LevelId
blid Actor
sb
  State
sClient <- (StateServer -> State) -> m State
forall (m :: * -> *) a. MonadServer m => (StateServer -> a) -> m a
getsServer ((StateServer -> State) -> m State)
-> (StateServer -> State) -> m State
forall a b. (a -> b) -> a -> b
$ (EnumMap FactionId State -> FactionId -> State
forall k a. Enum k => EnumMap k a -> k -> a
EM.! Actor -> FactionId
bfid Actor
sb) (EnumMap FactionId State -> State)
-> (StateServer -> EnumMap FactionId State) -> StateServer -> State
forall b c a. (b -> c) -> (a -> b) -> a -> c
. StateServer -> EnumMap FactionId State
sclientStates
  ItemId -> ItemFull
itemToF <- (State -> ItemId -> ItemFull) -> m (ItemId -> ItemFull)
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> ItemId -> ItemFull) -> m (ItemId -> ItemFull))
-> (State -> ItemId -> ItemFull) -> m (ItemId -> ItemFull)
forall a b. (a -> b) -> a -> b
$ (ItemId -> State -> ItemFull) -> State -> ItemId -> ItemFull
forall a b c. (a -> b -> c) -> b -> a -> c
flip ItemId -> State -> ItemFull
itemToFull
  Skills
actorSk <- ActorId -> m Skills
forall (m :: * -> *). MonadServer m => ActorId -> m Skills
currentSkillsServer ActorId
source
  Time
localTime <- (State -> Time) -> m Time
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Time) -> m Time) -> (State -> Time) -> m Time
forall a b. (a -> b) -> a -> b
$ LevelId -> State -> Time
getLocalTime LevelId
lid
  let alterSkill :: Int
alterSkill = Skill -> Skills -> Int
Ability.getSk Skill
Ability.SkAlter Skills
actorSk
  EnumMap ItemId ItemQuant
embeds <- (State -> EnumMap ItemId ItemQuant) -> m (EnumMap ItemId ItemQuant)
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> EnumMap ItemId ItemQuant)
 -> m (EnumMap ItemId ItemQuant))
-> (State -> EnumMap ItemId ItemQuant)
-> m (EnumMap ItemId ItemQuant)
forall a b. (a -> b) -> a -> b
$ LevelId -> Point -> State -> EnumMap ItemId ItemQuant
getEmbedBag LevelId
lid Point
tpos
  Level
lvl <- LevelId -> m Level
forall (m :: * -> *). MonadStateRead m => LevelId -> m Level
getLevel LevelId
lid
  ItemId -> ItemKind
getKind <- (State -> ItemId -> ItemKind) -> m (ItemId -> ItemKind)
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> ItemId -> ItemKind) -> m (ItemId -> ItemKind))
-> (State -> ItemId -> ItemKind) -> m (ItemId -> ItemKind)
forall a b. (a -> b) -> a -> b
$ (ItemId -> State -> ItemKind) -> State -> ItemId -> ItemKind
forall a b c. (a -> b -> c) -> b -> a -> c
flip ItemId -> State -> ItemKind
getIidKindServer
  let serverTile :: ContentId TileKind
serverTile = Level
lvl Level -> Point -> ContentId TileKind
`at` Point
tpos
      lvlClient :: Level
lvlClient = (EnumMap LevelId Level -> LevelId -> Level
forall k a. Enum k => EnumMap k a -> k -> a
EM.! LevelId
lid) (EnumMap LevelId Level -> Level)
-> (State -> EnumMap LevelId Level) -> State -> Level
forall b c a. (b -> c) -> (a -> b) -> a -> c
. State -> EnumMap LevelId Level
sdungeon (State -> Level) -> State -> Level
forall a b. (a -> b) -> a -> b
$ State
sClient
      clientTile :: ContentId TileKind
clientTile = Level
lvlClient Level -> Point -> ContentId TileKind
`at` Point
tpos
      hiddenTile :: Maybe (ContentId TileKind)
hiddenTile = ContentData TileKind
-> ContentId TileKind -> Maybe (ContentId TileKind)
Tile.hideAs ContentData TileKind
cotile ContentId TileKind
serverTile
      revealEmbeds :: m ()
revealEmbeds = Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (EnumMap ItemId ItemQuant -> Bool
forall k a. EnumMap k a -> Bool
EM.null EnumMap ItemId ItemQuant
embeds) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
        State
s <- m State
forall (m :: * -> *). MonadStateRead m => m State
getState
        let ais :: [(ItemId, Item)]
ais = (ItemId -> (ItemId, Item)) -> [ItemId] -> [(ItemId, Item)]
forall a b. (a -> b) -> [a] -> [b]
map (\iid :: ItemId
iid -> (ItemId
iid, ItemId -> State -> Item
getItemBody ItemId
iid State
s)) (EnumMap ItemId ItemQuant -> [ItemId]
forall k a. Enum k => EnumMap k a -> [k]
EM.keys EnumMap ItemId ItemQuant
embeds)
        UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ Container
-> EnumMap ItemId ItemQuant -> [(ItemId, Item)] -> UpdAtomic
UpdSpotItemBag (LevelId -> Point -> Container
CEmbed LevelId
lid Point
tpos) EnumMap ItemId ItemQuant
embeds [(ItemId, Item)]
ais
      tryApplyEmbeds :: m ()
tryApplyEmbeds = ((ItemId, ItemQuant) -> m ()) -> [(ItemId, ItemQuant)] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (ItemId, ItemQuant) -> m ()
tryApplyEmbed
                             (COps
-> (ItemId -> ItemKind)
-> ContentId TileKind
-> EnumMap ItemId ItemQuant
-> [(ItemId, ItemQuant)]
sortEmbeds COps
cops ItemId -> ItemKind
getKind ContentId TileKind
serverTile EnumMap ItemId ItemQuant
embeds)
      tryApplyEmbed :: (ItemId, ItemQuant) -> m ()
tryApplyEmbed (iid :: ItemId
iid, kit :: ItemQuant
kit) = do
        let itemFull :: ItemFull
itemFull = ItemId -> ItemFull
itemToF ItemId
iid
            -- Let even completely apply-unskilled actors trigger basic embeds.
            -- See the note about no skill check when melee triggers effects.
            legal :: Either ReqFailure Bool
legal = Time
-> Int -> Bool -> ItemFull -> ItemQuant -> Either ReqFailure Bool
permittedApply Time
localTime Int
forall a. Bounded a => a
maxBound Bool
calmE ItemFull
itemFull ItemQuant
kit
            (object1 :: Part
object1, object2 :: Part
object2) = FactionId
-> EnumMap FactionId Faction
-> Time
-> ItemFull
-> ItemQuant
-> (Part, Part)
partItemShortest (Actor -> FactionId
bfid Actor
sb) EnumMap FactionId Faction
factionD Time
localTime
                                                  ItemFull
itemFull (1, [])
            name :: Text
name = [Part] -> Text
makePhrase [Part
object1, Part
object2]
        case Either ReqFailure Bool
legal of
          Left ApplyNoEffects -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()  -- pure flavour embed
          Left reqFail :: ReqFailure
reqFail ->
            -- The failure is fully expected, because client may choose
            -- to trigger some embeds, knowing that others won't fire.
            SfxAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => SfxAtomic -> m ()
execSfxAtomic (SfxAtomic -> m ()) -> SfxAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ FactionId -> SfxMsg -> SfxAtomic
SfxMsgFid (Actor -> FactionId
bfid Actor
sb)
            (SfxMsg -> SfxAtomic) -> SfxMsg -> SfxAtomic
forall a b. (a -> b) -> a -> b
$ Text -> ReqFailure -> SfxMsg
SfxExpected ("embedded" Text -> Text -> Text
<+> Text
name) ReqFailure
reqFail
          _ -> Bool -> ActorId -> LevelId -> Point -> ItemId -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
Bool -> ActorId -> LevelId -> Point -> ItemId -> m ()
itemEffectEmbedded Bool
voluntary ActorId
source LevelId
lid Point
tpos ItemId
iid
      underFeet :: Bool
underFeet = Point
tpos Point -> Point -> Bool
forall a. Eq a => a -> a -> Bool
== Actor -> Point
bpos Actor
sb  -- if enter and alter, be more permissive
  if Point -> Point -> Int
chessDist Point
tpos (Actor -> Point
bpos Actor
sb) Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> 1
  then Maybe ReqFailure -> m (Maybe ReqFailure)
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe ReqFailure -> m (Maybe ReqFailure))
-> Maybe ReqFailure -> m (Maybe ReqFailure)
forall a b. (a -> b) -> a -> b
$ ReqFailure -> Maybe ReqFailure
forall a. a -> Maybe a
Just ReqFailure
AlterDistant
  else if ContentId TileKind -> Maybe (ContentId TileKind)
forall a. a -> Maybe a
Just ContentId TileKind
clientTile Maybe (ContentId TileKind) -> Maybe (ContentId TileKind) -> Bool
forall a. Eq a => a -> a -> Bool
== Maybe (ContentId TileKind)
hiddenTile then  -- searches
    -- Only actors with SkAlter > 1 can search for hidden doors, etc.
    if Bool -> Bool
not Bool
underFeet Bool -> Bool -> Bool
&& Int
alterSkill Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= 1
    then Maybe ReqFailure -> m (Maybe ReqFailure)
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe ReqFailure -> m (Maybe ReqFailure))
-> Maybe ReqFailure -> m (Maybe ReqFailure)
forall a b. (a -> b) -> a -> b
$ ReqFailure -> Maybe ReqFailure
forall a. a -> Maybe a
Just ReqFailure
AlterUnskilled  -- don't leak about searching
    else do
      -- Blocking by items nor actors does not prevent searching.
      -- Searching broadcasted, in case actors from other factions are present
      -- so that they can learn the tile and learn our action.
      -- If they already know the tile, they will just consider our action
      -- a waste of time and ignore the command.
      UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ ActorId -> Point -> ContentId TileKind -> UpdAtomic
UpdSearchTile ActorId
source Point
tpos ContentId TileKind
serverTile
      -- Searching also reveals the embedded items of the tile.
      -- If the items are already seen by the client
      -- (e.g., due to item detection, despite tile being still hidden),
      -- the command is ignored on the client.
      m ()
revealEmbeds
      -- If the entries are already seen by the client
      -- the command is ignored on the client.
      case Point -> EnumMap Point PlaceEntry -> Maybe PlaceEntry
forall k a. Enum k => k -> EnumMap k a -> Maybe a
EM.lookup Point
tpos (EnumMap Point PlaceEntry -> Maybe PlaceEntry)
-> EnumMap Point PlaceEntry -> Maybe PlaceEntry
forall a b. (a -> b) -> a -> b
$ Level -> EnumMap Point PlaceEntry
lentry Level
lvl of
        Nothing -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
        Just entry :: PlaceEntry
entry -> UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ LevelId -> [(Point, PlaceEntry)] -> UpdAtomic
UpdSpotEntry LevelId
lid [(Point
tpos, PlaceEntry
entry)]
      -- Seaching triggers the embeds as well, after they are revealed.
      -- The rationale is that the items were all the time present
      -- (just invisible to the client), so they need to be triggered.
      -- The exception is changable tiles, because they are not so easy
      -- to trigger; they need subsequent altering.
      Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (TileSpeedup -> ContentId TileKind -> Bool
Tile.isDoor TileSpeedup
coTileSpeedup ContentId TileKind
serverTile
              Bool -> Bool -> Bool
|| TileSpeedup -> ContentId TileKind -> Bool
Tile.isChangable TileSpeedup
coTileSpeedup ContentId TileKind
serverTile
              Bool -> Bool -> Bool
|| EnumMap ItemId ItemQuant -> Bool
forall k a. EnumMap k a -> Bool
EM.null EnumMap ItemId ItemQuant
embeds) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
        -- Can't send @SfxTrigger@ afterwards, because actor may be moved
        -- by the embeds to another level, where @tpos@ is meaningless.
        SfxAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => SfxAtomic -> m ()
execSfxAtomic (SfxAtomic -> m ()) -> SfxAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ ActorId -> Point -> SfxAtomic
SfxTrigger ActorId
source Point
tpos
        m ()
tryApplyEmbeds
      Maybe ReqFailure -> m (Maybe ReqFailure)
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe ReqFailure
forall a. Maybe a
Nothing  -- success
  else if ContentId TileKind
clientTile ContentId TileKind -> ContentId TileKind -> Bool
forall a. Eq a => a -> a -> Bool
== ContentId TileKind
serverTile then  -- alters
    if Bool -> Bool
not Bool
underFeet Bool -> Bool -> Bool
&& Int
alterSkill Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< TileSpeedup -> ContentId TileKind -> Int
Tile.alterMinSkill TileSpeedup
coTileSpeedup ContentId TileKind
serverTile
    then Maybe ReqFailure -> m (Maybe ReqFailure)
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe ReqFailure -> m (Maybe ReqFailure))
-> Maybe ReqFailure -> m (Maybe ReqFailure)
forall a b. (a -> b) -> a -> b
$ ReqFailure -> Maybe ReqFailure
forall a. a -> Maybe a
Just ReqFailure
AlterUnskilled  -- don't leak about altering
    else do
      let changeTo :: GroupName TileKind -> m ()
changeTo tgroup :: GroupName TileKind
tgroup = do
            Level
lvl2 <- LevelId -> m Level
forall (m :: * -> *). MonadStateRead m => LevelId -> m Level
getLevel LevelId
lid
            -- No @SfxAlter@, because the effect is obvious (e.g., opened door).
            let nightCond :: TileKind -> Bool
nightCond kt :: TileKind
kt = Bool -> Bool
not (Feature -> TileKind -> Bool
Tile.kindHasFeature Feature
TK.Walkable TileKind
kt
                                    Bool -> Bool -> Bool
&& Feature -> TileKind -> Bool
Tile.kindHasFeature Feature
TK.Clear TileKind
kt)
                               Bool -> Bool -> Bool
|| (if Level -> Bool
lnight Level
lvl2 then Bool -> Bool
forall a. a -> a
id else Bool -> Bool
not)
                                    (Feature -> TileKind -> Bool
Tile.kindHasFeature Feature
TK.Dark TileKind
kt)
            -- Sometimes the tile is determined precisely by the ambient light
            -- of the source tiles. If not, default to cave day/night condition.
            Maybe (ContentId TileKind)
mtoTile <- Rnd (Maybe (ContentId TileKind)) -> m (Maybe (ContentId TileKind))
forall (m :: * -> *) a. MonadServer m => Rnd a -> m a
rndToAction (Rnd (Maybe (ContentId TileKind))
 -> m (Maybe (ContentId TileKind)))
-> Rnd (Maybe (ContentId TileKind))
-> m (Maybe (ContentId TileKind))
forall a b. (a -> b) -> a -> b
$ ContentData TileKind
-> GroupName TileKind
-> (TileKind -> Bool)
-> Rnd (Maybe (ContentId TileKind))
forall a.
Show a =>
ContentData a
-> GroupName a -> (a -> Bool) -> Rnd (Maybe (ContentId a))
opick ContentData TileKind
cotile GroupName TileKind
tgroup TileKind -> Bool
nightCond
            ContentId TileKind
toTile <- m (ContentId TileKind)
-> (ContentId TileKind -> m (ContentId TileKind))
-> Maybe (ContentId TileKind)
-> m (ContentId TileKind)
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (Rnd (ContentId TileKind) -> m (ContentId TileKind)
forall (m :: * -> *) a. MonadServer m => Rnd a -> m a
rndToAction
                             (Rnd (ContentId TileKind) -> m (ContentId TileKind))
-> Rnd (ContentId TileKind) -> m (ContentId TileKind)
forall a b. (a -> b) -> a -> b
$ ContentId TileKind
-> Maybe (ContentId TileKind) -> ContentId TileKind
forall a. a -> Maybe a -> a
fromMaybe (String -> ContentId TileKind
forall a. (?callStack::CallStack) => String -> a
error (String -> ContentId TileKind) -> String -> ContentId TileKind
forall a b. (a -> b) -> a -> b
$ "" String -> GroupName TileKind -> String
forall v. Show v => String -> v -> String
`showFailure` GroupName TileKind
tgroup)
                               (Maybe (ContentId TileKind) -> ContentId TileKind)
-> Rnd (Maybe (ContentId TileKind)) -> Rnd (ContentId TileKind)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ContentData TileKind
-> GroupName TileKind
-> (TileKind -> Bool)
-> Rnd (Maybe (ContentId TileKind))
forall a.
Show a =>
ContentData a
-> GroupName a -> (a -> Bool) -> Rnd (Maybe (ContentId a))
opick ContentData TileKind
cotile GroupName TileKind
tgroup (Bool -> TileKind -> Bool
forall a b. a -> b -> a
const Bool
True))
                            ContentId TileKind -> m (ContentId TileKind)
forall (m :: * -> *) a. Monad m => a -> m a
return
                            Maybe (ContentId TileKind)
mtoTile
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (ContentId TileKind
toTile ContentId TileKind -> ContentId TileKind -> Bool
forall a. Eq a => a -> a -> Bool
== ContentId TileKind
serverTile) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do  -- don't regenerate same tile
              -- At most one of these two will be accepted on any given client.
              UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ LevelId
-> Point -> ContentId TileKind -> ContentId TileKind -> UpdAtomic
UpdAlterTile LevelId
lid Point
tpos ContentId TileKind
serverTile ContentId TileKind
toTile
              -- This case happens when a client does not see a searching
              -- action by another faction, but sees the subsequent altering.
              case Maybe (ContentId TileKind)
hiddenTile of
                Just tHidden :: ContentId TileKind
tHidden ->
                  UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ LevelId
-> Point -> ContentId TileKind -> ContentId TileKind -> UpdAtomic
UpdAlterTile LevelId
lid Point
tpos ContentId TileKind
tHidden ContentId TileKind
toTile
                Nothing -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
              case (TileSpeedup -> ContentId TileKind -> Bool
Tile.isExplorable TileSpeedup
coTileSpeedup ContentId TileKind
serverTile,
                    TileSpeedup -> ContentId TileKind -> Bool
Tile.isExplorable TileSpeedup
coTileSpeedup ContentId TileKind
toTile) of
                (False, True) -> UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ LevelId -> Int -> UpdAtomic
UpdAlterExplorable LevelId
lid 1
                (True, False) -> UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ LevelId -> Int -> UpdAtomic
UpdAlterExplorable LevelId
lid (-1)
                _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
              -- At the end we replace old embeds (even if partially used up)
              -- with new ones.
              -- If the source tile was hidden, the items could not be visible
              -- on a client, in which case the command would be ignored
              -- on the client, without causing any problems. Otherwise,
              -- if the position is in view, client has accurate info.
              case Point
-> EnumMap Point (EnumMap ItemId ItemQuant)
-> Maybe (EnumMap ItemId ItemQuant)
forall k a. Enum k => k -> EnumMap k a -> Maybe a
EM.lookup Point
tpos (Level -> EnumMap Point (EnumMap ItemId ItemQuant)
lembed Level
lvl2) of
                Just bag :: EnumMap ItemId ItemQuant
bag -> do
                  State
s <- m State
forall (m :: * -> *). MonadStateRead m => m State
getState
                  let ais :: [(ItemId, Item)]
ais = (ItemId -> (ItemId, Item)) -> [ItemId] -> [(ItemId, Item)]
forall a b. (a -> b) -> [a] -> [b]
map (\iid :: ItemId
iid -> (ItemId
iid, ItemId -> State -> Item
getItemBody ItemId
iid State
s)) (EnumMap ItemId ItemQuant -> [ItemId]
forall k a. Enum k => EnumMap k a -> [k]
EM.keys EnumMap ItemId ItemQuant
bag)
                  UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ Container
-> EnumMap ItemId ItemQuant -> [(ItemId, Item)] -> UpdAtomic
UpdLoseItemBag (LevelId -> Point -> Container
CEmbed LevelId
lid Point
tpos) EnumMap ItemId ItemQuant
bag [(ItemId, Item)]
ais
                Nothing -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
              -- Altering always reveals the outcome tile, so it's not hidden
              -- and so its embedded items are always visible.
              LevelId -> Point -> ContentId TileKind -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
LevelId -> Point -> ContentId TileKind -> m ()
embedItem LevelId
lid Point
tpos ContentId TileKind
toTile
          feats :: [Feature]
feats = TileKind -> [Feature]
TK.tfeature (TileKind -> [Feature]) -> TileKind -> [Feature]
forall a b. (a -> b) -> a -> b
$ ContentData TileKind -> ContentId TileKind -> TileKind
forall a. ContentData a -> ContentId a -> a
okind ContentData TileKind
cotile ContentId TileKind
serverTile
          toAlter :: Feature -> Maybe (GroupName TileKind)
toAlter feat :: Feature
feat =
            case Feature
feat of
              TK.OpenTo tgroup :: GroupName TileKind
tgroup -> GroupName TileKind -> Maybe (GroupName TileKind)
forall a. a -> Maybe a
Just GroupName TileKind
tgroup
              TK.CloseTo tgroup :: GroupName TileKind
tgroup -> GroupName TileKind -> Maybe (GroupName TileKind)
forall a. a -> Maybe a
Just GroupName TileKind
tgroup
              TK.ChangeTo tgroup :: GroupName TileKind
tgroup -> GroupName TileKind -> Maybe (GroupName TileKind)
forall a. a -> Maybe a
Just GroupName TileKind
tgroup
              _ -> Maybe (GroupName TileKind)
forall a. Maybe a
Nothing
          groupsToAlterTo :: [GroupName TileKind]
groupsToAlterTo | Bool
underFeet = []  -- don't autoclose doors under actor
                          | Bool
otherwise = (Feature -> Maybe (GroupName TileKind))
-> [Feature] -> [GroupName TileKind]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Feature -> Maybe (GroupName TileKind)
toAlter [Feature]
feats
      if [GroupName TileKind] -> Bool
forall a. [a] -> Bool
null [GroupName TileKind]
groupsToAlterTo Bool -> Bool -> Bool
&& EnumMap ItemId ItemQuant -> Bool
forall k a. EnumMap k a -> Bool
EM.null EnumMap ItemId ItemQuant
embeds then
        Maybe ReqFailure -> m (Maybe ReqFailure)
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe ReqFailure -> m (Maybe ReqFailure))
-> Maybe ReqFailure -> m (Maybe ReqFailure)
forall a b. (a -> b) -> a -> b
$ ReqFailure -> Maybe ReqFailure
forall a. a -> Maybe a
Just ReqFailure
AlterNothing  -- no altering possible; silly client
      else
        if Bool
underFeet Bool -> Bool -> Bool
|| Point -> EnumMap Point (EnumMap ItemId ItemQuant) -> Bool
forall k a. Enum k => k -> EnumMap k a -> Bool
EM.notMember Point
tpos (Level -> EnumMap Point (EnumMap ItemId ItemQuant)
lfloor Level
lvl) then
          if Bool
underFeet Bool -> Bool -> Bool
|| Bool -> Bool
not (Point -> Level -> Bool
occupiedBigLvl Point
tpos Level
lvl)
                          Bool -> Bool -> Bool
&& Bool -> Bool
not (Point -> Level -> Bool
occupiedProjLvl Point
tpos Level
lvl) then do
            -- If the only thing that happens is the change of the tile,
            -- don't display a message, because the change
            -- is visible on the map (unless it changes into itself)
            -- and there's nothing more to speak about.
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (EnumMap ItemId ItemQuant -> Bool
forall k a. EnumMap k a -> Bool
EM.null EnumMap ItemId ItemQuant
embeds) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
              -- Can't send @SfxTrigger@ afterwards, because actor may be moved
              -- by the embeds to another level, where @tpos@ is meaningless.
              -- However, don't spam with projectiles on ice.
              Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Actor -> Bool
bproj Actor
sb Bool -> Bool -> Bool
|| Bool
underFeet) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                SfxAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => SfxAtomic -> m ()
execSfxAtomic (SfxAtomic -> m ()) -> SfxAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ ActorId -> Point -> SfxAtomic
SfxTrigger ActorId
source Point
tpos
              -- The embeds of the initial tile are activated before the tile
              -- is altered. This prevents, e.g., trying to activate items
              -- where none are present any more, or very different to what
              -- the client expected. Surprise only comes through searching
              -- as implemented above.
              -- The items are first revealed for the sake of clients that
              -- may see the tile as hidden. Note that the tile is not revealed
              -- (unless it's altered later on, in which case the new one is).
              m ()
revealEmbeds
              m ()
tryApplyEmbeds
            case [GroupName TileKind]
groupsToAlterTo of
              [] -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
              [groupToAlterTo :: GroupName TileKind
groupToAlterTo] -> GroupName TileKind -> m ()
changeTo GroupName TileKind
groupToAlterTo
              l :: [GroupName TileKind]
l -> String -> m ()
forall a. (?callStack::CallStack) => String -> a
error (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "tile changeable in many ways" String -> [GroupName TileKind] -> String
forall v. Show v => String -> v -> String
`showFailure` [GroupName TileKind]
l
            Maybe ReqFailure -> m (Maybe ReqFailure)
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe ReqFailure
forall a. Maybe a
Nothing  -- success
          else Maybe ReqFailure -> m (Maybe ReqFailure)
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe ReqFailure -> m (Maybe ReqFailure))
-> Maybe ReqFailure -> m (Maybe ReqFailure)
forall a b. (a -> b) -> a -> b
$ ReqFailure -> Maybe ReqFailure
forall a. a -> Maybe a
Just ReqFailure
AlterBlockActor
        else Maybe ReqFailure -> m (Maybe ReqFailure)
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe ReqFailure -> m (Maybe ReqFailure))
-> Maybe ReqFailure -> m (Maybe ReqFailure)
forall a b. (a -> b) -> a -> b
$ ReqFailure -> Maybe ReqFailure
forall a. a -> Maybe a
Just ReqFailure
AlterBlockItem
  else  -- client is misguided re tile at that position, so bail out
    Maybe ReqFailure -> m (Maybe ReqFailure)
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe ReqFailure -> m (Maybe ReqFailure))
-> Maybe ReqFailure -> m (Maybe ReqFailure)
forall a b. (a -> b) -> a -> b
$ ReqFailure -> Maybe ReqFailure
forall a. a -> Maybe a
Just ReqFailure
AlterNothing

-- * ReqWait

-- | Do nothing. Wait skill 1 required. Bracing requires 2, sleep 3, lurking 4.
--
-- Something is sometimes done in 'processWatchfulness'.
reqWait :: MonadServerAtomic m => ActorId -> m ()
{-# INLINE reqWait #-}
reqWait :: ActorId -> m ()
reqWait source :: ActorId
source = do
  Skills
actorSk <- ActorId -> m Skills
forall (m :: * -> *). MonadServer m => ActorId -> m Skills
currentSkillsServer ActorId
source
  Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Skill -> Skills -> Int
Ability.getSk Skill
Ability.SkWait Skills
actorSk Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> 0) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
    ActorId -> RequestTimed -> ReqFailure -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure ActorId
source RequestTimed
ReqWait ReqFailure
WaitUnskilled

-- * ReqWait10

-- | Do nothing.
--
-- Something is sometimes done in 'processWatchfulness'.
reqWait10 :: MonadServerAtomic m => ActorId -> m ()
{-# INLINE reqWait10 #-}
reqWait10 :: ActorId -> m ()
reqWait10 source :: ActorId
source = do
  Skills
actorSk <- ActorId -> m Skills
forall (m :: * -> *). MonadServer m => ActorId -> m Skills
currentSkillsServer ActorId
source
  Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Skill -> Skills -> Int
Ability.getSk Skill
Ability.SkWait Skills
actorSk Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= 4) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
    ActorId -> RequestTimed -> ReqFailure -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure ActorId
source RequestTimed
ReqWait10 ReqFailure
WaitUnskilled

-- * ReqYell

-- | Yell/yawn/stretch/taunt.
-- Wakes up (gradually) from sleep. Causes noise heard by enemies on the level
-- even if out of their hearing range.
--
-- Governed by the waiting skill (because everyone is supposed to have it).
-- unlike @ReqWait@, induces overhead.
--
-- This is similar to the effect @Yell@, but always voluntary.
reqYell :: MonadServerAtomic m => ActorId -> m ()
reqYell :: ActorId -> m ()
reqYell source :: ActorId
source = do
  Skills
actorSk <- ActorId -> m Skills
forall (m :: * -> *). MonadServer m => ActorId -> m Skills
currentSkillsServer ActorId
source
  if | Skill -> Skills -> Int
Ability.getSk Skill
Ability.SkWait Skills
actorSk Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> 0 ->
       -- Last yawn before waking up is displayed as a yell, but that's fine.
       -- To fix that, we'd need to move the @SfxTaunt@
       -- to @processWatchfulness@.
       SfxAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => SfxAtomic -> m ()
execSfxAtomic (SfxAtomic -> m ()) -> SfxAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ Bool -> ActorId -> SfxAtomic
SfxTaunt Bool
True ActorId
source
     | Skill -> Skills -> Int
Ability.getSk Skill
Ability.SkMove Skills
actorSk Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= 0
       Bool -> Bool -> Bool
|| Skill -> Skills -> Int
Ability.getSk Skill
Ability.SkDisplace Skills
actorSk Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= 0
       Bool -> Bool -> Bool
|| Skill -> Skills -> Int
Ability.getSk Skill
Ability.SkMelee Skills
actorSk Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= 0 ->
       -- Potentially, only waiting is possible, so given that it's drained,
       -- don't let the actor be stuck nor alarm about server failure.
       SfxAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => SfxAtomic -> m ()
execSfxAtomic (SfxAtomic -> m ()) -> SfxAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ Bool -> ActorId -> SfxAtomic
SfxTaunt Bool
False ActorId
source
     | Bool
otherwise ->
       -- In most situation one of the 3 actions above
       -- can be performed and waiting skill is not needed for that,
       -- so given the 3 skills are available, waste turn
       -- but don't alarm, because it does happen sometimes in crowds.
       --   execFailure source ReqYell YellUnskilled
       () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

-- * ReqMoveItems

reqMoveItems :: MonadServerAtomic m
             => ActorId -> [(ItemId, Int, CStore, CStore)] -> m ()
reqMoveItems :: ActorId -> [(ItemId, Int, CStore, CStore)] -> m ()
reqMoveItems source :: ActorId
source l :: [(ItemId, Int, CStore, CStore)]
l = do
  Skills
actorSk <- ActorId -> m Skills
forall (m :: * -> *). MonadServer m => ActorId -> m Skills
currentSkillsServer ActorId
source
  if Skill -> Skills -> Int
Ability.getSk Skill
Ability.SkMoveItem Skills
actorSk Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> 0 then do
    Actor
b <- (State -> Actor) -> m Actor
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
source
    Skills
actorMaxSk <- (State -> Skills) -> m Skills
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Skills) -> m Skills) -> (State -> Skills) -> m Skills
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Skills
getActorMaxSkills ActorId
source
    -- Server accepts item movement based on calm at the start, not end
    -- or in the middle, to avoid interrupted or partially ignored commands.
    let calmE :: Bool
calmE = Actor -> Skills -> Bool
calmEnough Actor
b Skills
actorMaxSk
    ((ItemId, Int, CStore, CStore) -> m ())
-> [(ItemId, Int, CStore, CStore)] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (ActorId -> Bool -> (ItemId, Int, CStore, CStore) -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> Bool -> (ItemId, Int, CStore, CStore) -> m ()
reqMoveItem ActorId
source Bool
calmE) [(ItemId, Int, CStore, CStore)]
l
  else ActorId -> RequestTimed -> ReqFailure -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure ActorId
source ([(ItemId, Int, CStore, CStore)] -> RequestTimed
ReqMoveItems [(ItemId, Int, CStore, CStore)]
l) ReqFailure
MoveItemUnskilled

reqMoveItem :: MonadServerAtomic m
            => ActorId -> Bool -> (ItemId, Int, CStore, CStore) -> m ()
reqMoveItem :: ActorId -> Bool -> (ItemId, Int, CStore, CStore) -> m ()
reqMoveItem aid :: ActorId
aid calmE :: Bool
calmE (iid :: ItemId
iid, k :: Int
k, fromCStore :: CStore
fromCStore, toCStore :: CStore
toCStore) = do
  Actor
b <- (State -> Actor) -> m Actor
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
aid
  let fromC :: Container
fromC = ActorId -> CStore -> Container
CActor ActorId
aid CStore
fromCStore
      req :: RequestTimed
req = [(ItemId, Int, CStore, CStore)] -> RequestTimed
ReqMoveItems [(ItemId
iid, Int
k, CStore
fromCStore, CStore
toCStore)]
  Container
toC <- case CStore
toCStore of
    CGround -> Bool -> ActorId -> Actor -> m Container
forall (m :: * -> *).
MonadStateRead m =>
Bool -> ActorId -> Actor -> m Container
pickDroppable Bool
False ActorId
aid Actor
b  -- drop over fog, etc.
    _ -> Container -> m Container
forall (m :: * -> *) a. Monad m => a -> m a
return (Container -> m Container) -> Container -> m Container
forall a b. (a -> b) -> a -> b
$! ActorId -> CStore -> Container
CActor ActorId
aid CStore
toCStore
  EnumMap ItemId ItemQuant
bagBefore <- (State -> EnumMap ItemId ItemQuant) -> m (EnumMap ItemId ItemQuant)
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> EnumMap ItemId ItemQuant)
 -> m (EnumMap ItemId ItemQuant))
-> (State -> EnumMap ItemId ItemQuant)
-> m (EnumMap ItemId ItemQuant)
forall a b. (a -> b) -> a -> b
$ Container -> State -> EnumMap ItemId ItemQuant
getContainerBag Container
toC
  if
   | Int
k Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< 1 Bool -> Bool -> Bool
|| CStore
fromCStore CStore -> CStore -> Bool
forall a. Eq a => a -> a -> Bool
== CStore
toCStore -> ActorId -> RequestTimed -> ReqFailure -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure ActorId
aid RequestTimed
req ReqFailure
ItemNothing
   | CStore
toCStore CStore -> CStore -> Bool
forall a. Eq a => a -> a -> Bool
== CStore
CEqp Bool -> Bool -> Bool
&& Actor -> Int -> Bool
eqpOverfull Actor
b Int
k ->
     ActorId -> RequestTimed -> ReqFailure -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure ActorId
aid RequestTimed
req ReqFailure
EqpOverfull
   | (CStore
fromCStore CStore -> CStore -> Bool
forall a. Eq a => a -> a -> Bool
== CStore
CSha Bool -> Bool -> Bool
|| CStore
toCStore CStore -> CStore -> Bool
forall a. Eq a => a -> a -> Bool
== CStore
CSha) Bool -> Bool -> Bool
&& Bool -> Bool
not Bool
calmE ->
     ActorId -> RequestTimed -> ReqFailure -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure ActorId
aid RequestTimed
req ReqFailure
ItemNotCalm
   | Bool
otherwise -> do
    [UpdAtomic]
upds <- Bool -> ItemId -> Int -> Container -> Container -> m [UpdAtomic]
forall (m :: * -> *).
MonadStateRead m =>
Bool -> ItemId -> Int -> Container -> Container -> m [UpdAtomic]
generalMoveItem Bool
True ItemId
iid Int
k Container
fromC Container
toC
    (UpdAtomic -> m ()) -> [UpdAtomic] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic [UpdAtomic]
upds
    ItemFull
itemFull <- (State -> ItemFull) -> m ItemFull
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> ItemFull) -> m ItemFull)
-> (State -> ItemFull) -> m ItemFull
forall a b. (a -> b) -> a -> b
$ ItemId -> State -> ItemFull
itemToFull ItemId
iid
    Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (CStore
fromCStore CStore -> CStore -> Bool
forall a. Eq a => a -> a -> Bool
== CStore
CGround) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$  -- pick up
      Container -> ItemId -> ContentId ItemKind -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
Container -> ItemId -> ContentId ItemKind -> m ()
discoverIfMinorEffects Container
toC ItemId
iid (ItemFull -> ContentId ItemKind
itemKindId ItemFull
itemFull)
    -- The first recharging period after equipping is random,
    -- between 1 and 2 standard timeouts of the item.
    -- We reset timeout for equipped periodic items and also for items
    -- moved out of the shared stash, in which timeouts are not consistent
    -- wrt some local time, because actors from many levels put items there
    -- all the time (and don't rebase it to any common clock).
    -- If wrong local time in shared stash causes an item to recharge
    -- for a very long time, the player can reset it by moving it to pack
    -- and back to stash (as a flip side, a charging item in stash may sometimes
    -- be used at once on another level, with different local time, but only
    -- once, because after first use, the timeout is set to local time).
    -- This is not terribly consistent, but not recharging in stash is
    -- not better, because either we block activation of any items with timeout,
    -- or encourage moving items out of stash, recharging and moving in.
    -- Which is not fun at all, but one more thing to remember doing regularly.
    Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (CStore
toCStore CStore -> [CStore] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [CStore
CEqp, CStore
COrgan]
          Bool -> Bool -> Bool
&& CStore
fromCStore CStore -> [CStore] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [CStore
CEqp, CStore
COrgan]
          Bool -> Bool -> Bool
|| CStore
fromCStore CStore -> CStore -> Bool
forall a. Eq a => a -> a -> Bool
== CStore
CSha) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
      let beforeIt :: [Time]
beforeIt = case ItemId
iid ItemId -> EnumMap ItemId ItemQuant -> Maybe ItemQuant
forall k a. Enum k => k -> EnumMap k a -> Maybe a
`EM.lookup` EnumMap ItemId ItemQuant
bagBefore of
            Nothing -> []  -- no such items before move
            Just (_, it2 :: [Time]
it2) -> [Time]
it2
      Int -> ItemId -> ItemFull -> [Time] -> Container -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
Int -> ItemId -> ItemFull -> [Time] -> Container -> m ()
randomResetTimeout Int
k ItemId
iid ItemFull
itemFull [Time]
beforeIt Container
toC

-- * ReqProject

reqProject :: MonadServerAtomic m
           => ActorId    -- ^ actor projecting the item (is on current lvl)
           -> Point      -- ^ target position of the projectile
           -> Int        -- ^ digital line parameter
           -> ItemId     -- ^ the item to be projected
           -> CStore     -- ^ whether the items comes from floor or inventory
           -> m ()
reqProject :: ActorId -> Point -> Int -> ItemId -> CStore -> m ()
reqProject source :: ActorId
source tpxy :: Point
tpxy eps :: Int
eps iid :: ItemId
iid cstore :: CStore
cstore = do
  let req :: RequestTimed
req = Point -> Int -> ItemId -> CStore -> RequestTimed
ReqProject Point
tpxy Int
eps ItemId
iid CStore
cstore
  Actor
b <- (State -> Actor) -> m Actor
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
source
  Skills
actorMaxSk <- (State -> Skills) -> m Skills
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Skills) -> m Skills) -> (State -> Skills) -> m Skills
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Skills
getActorMaxSkills ActorId
source
  let calmE :: Bool
calmE = Actor -> Skills -> Bool
calmEnough Actor
b Skills
actorMaxSk
  if CStore
cstore CStore -> CStore -> Bool
forall a. Eq a => a -> a -> Bool
== CStore
CSha Bool -> Bool -> Bool
&& Bool -> Bool
not Bool
calmE then ActorId -> RequestTimed -> ReqFailure -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure ActorId
source RequestTimed
req ReqFailure
ItemNotCalm
  else do
    Maybe ReqFailure
mfail <- ActorId
-> ActorId
-> Point
-> Int
-> Bool
-> ItemId
-> CStore
-> Bool
-> m (Maybe ReqFailure)
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId
-> ActorId
-> Point
-> Int
-> Bool
-> ItemId
-> CStore
-> Bool
-> m (Maybe ReqFailure)
projectFail ActorId
source ActorId
source Point
tpxy Int
eps Bool
False ItemId
iid CStore
cstore Bool
False
    m () -> (ReqFailure -> m ()) -> Maybe ReqFailure -> m ()
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (() -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()) (ActorId -> RequestTimed -> ReqFailure -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure ActorId
source RequestTimed
req) Maybe ReqFailure
mfail

-- * ReqApply

reqApply :: MonadServerAtomic m
         => ActorId  -- ^ actor applying the item (is on current level)
         -> ItemId   -- ^ the item to be applied
         -> CStore   -- ^ the location of the item
         -> m ()
reqApply :: ActorId -> ItemId -> CStore -> m ()
reqApply aid :: ActorId
aid iid :: ItemId
iid cstore :: CStore
cstore = do
  let req :: RequestTimed
req = ItemId -> CStore -> RequestTimed
ReqApply ItemId
iid CStore
cstore
  Actor
b <- (State -> Actor) -> m Actor
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
aid
  Skills
actorMaxSk <- (State -> Skills) -> m Skills
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Skills) -> m Skills) -> (State -> Skills) -> m Skills
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Skills
getActorMaxSkills ActorId
aid
  let calmE :: Bool
calmE = Actor -> Skills -> Bool
calmEnough Actor
b Skills
actorMaxSk
  if CStore
cstore CStore -> CStore -> Bool
forall a. Eq a => a -> a -> Bool
== CStore
CSha Bool -> Bool -> Bool
&& Bool -> Bool
not Bool
calmE then ActorId -> RequestTimed -> ReqFailure -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure ActorId
aid RequestTimed
req ReqFailure
ItemNotCalm
  else do
    EnumMap ItemId ItemQuant
bag <- (State -> EnumMap ItemId ItemQuant) -> m (EnumMap ItemId ItemQuant)
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> EnumMap ItemId ItemQuant)
 -> m (EnumMap ItemId ItemQuant))
-> (State -> EnumMap ItemId ItemQuant)
-> m (EnumMap ItemId ItemQuant)
forall a b. (a -> b) -> a -> b
$ Actor -> CStore -> State -> EnumMap ItemId ItemQuant
getBodyStoreBag Actor
b CStore
cstore
    case ItemId -> EnumMap ItemId ItemQuant -> Maybe ItemQuant
forall k a. Enum k => k -> EnumMap k a -> Maybe a
EM.lookup ItemId
iid EnumMap ItemId ItemQuant
bag of
      Nothing -> ActorId -> RequestTimed -> ReqFailure -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure ActorId
aid RequestTimed
req ReqFailure
ApplyOutOfReach
      Just kit :: ItemQuant
kit -> do
        ItemFull
itemFull <- (State -> ItemFull) -> m ItemFull
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> ItemFull) -> m ItemFull)
-> (State -> ItemFull) -> m ItemFull
forall a b. (a -> b) -> a -> b
$ ItemId -> State -> ItemFull
itemToFull ItemId
iid
        Skills
actorSk <- ActorId -> m Skills
forall (m :: * -> *). MonadServer m => ActorId -> m Skills
currentSkillsServer ActorId
aid
        Time
localTime <- (State -> Time) -> m Time
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Time) -> m Time) -> (State -> Time) -> m Time
forall a b. (a -> b) -> a -> b
$ LevelId -> State -> Time
getLocalTime (Actor -> LevelId
blid Actor
b)
        let skill :: Int
skill = Skill -> Skills -> Int
Ability.getSk Skill
Ability.SkApply Skills
actorSk
            legal :: Either ReqFailure Bool
legal = Time
-> Int -> Bool -> ItemFull -> ItemQuant -> Either ReqFailure Bool
permittedApply Time
localTime Int
skill Bool
calmE ItemFull
itemFull ItemQuant
kit
        case Either ReqFailure Bool
legal of
          Left reqFail :: ReqFailure
reqFail -> ActorId -> RequestTimed -> ReqFailure -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> RequestTimed -> ReqFailure -> m ()
execFailure ActorId
aid RequestTimed
req ReqFailure
reqFail
          Right _ -> ActorId -> ItemId -> CStore -> m ()
forall (m :: * -> *).
MonadServerAtomic m =>
ActorId -> ItemId -> CStore -> m ()
applyItem ActorId
aid ItemId
iid CStore
cstore

-- * ReqGameRestart

reqGameRestart :: MonadServerAtomic m
               => ActorId -> GroupName ModeKind -> Challenge
               -> m ()
reqGameRestart :: ActorId -> GroupName ModeKind -> Challenge -> m ()
reqGameRestart aid :: ActorId
aid groupName :: GroupName ModeKind
groupName scurChalSer :: Challenge
scurChalSer = do
  -- This call to `revealItems` is really needed, because the other
  -- happens only at natural game conclusion, not at forced quitting.
  Bool
isNoConfirms <- m Bool
forall (m :: * -> *). MonadStateRead m => m Bool
isNoConfirmsGame
  EnumMap FactionId Faction
factionD <- (State -> EnumMap FactionId Faction)
-> m (EnumMap FactionId Faction)
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState State -> EnumMap FactionId Faction
sfactionD
  let fidsUI :: [FactionId]
fidsUI = ((FactionId, Faction) -> FactionId)
-> [(FactionId, Faction)] -> [FactionId]
forall a b. (a -> b) -> [a] -> [b]
map (FactionId, Faction) -> FactionId
forall a b. (a, b) -> a
fst ([(FactionId, Faction)] -> [FactionId])
-> [(FactionId, Faction)] -> [FactionId]
forall a b. (a -> b) -> a -> b
$ ((FactionId, Faction) -> Bool)
-> [(FactionId, Faction)] -> [(FactionId, Faction)]
forall a. (a -> Bool) -> [a] -> [a]
filter (\(_, fact :: Faction
fact) -> Player -> Bool
fhasUI (Faction -> Player
gplayer Faction
fact))
                                (EnumMap FactionId Faction -> [(FactionId, Faction)]
forall k a. Enum k => EnumMap k a -> [(k, a)]
EM.assocs EnumMap FactionId Faction
factionD)
  ItemDict
itemD <- (State -> ItemDict) -> m ItemDict
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState State -> ItemDict
sitemD
  EnumMap LevelId Level
dungeon <- (State -> EnumMap LevelId Level) -> m (EnumMap LevelId Level)
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState State -> EnumMap LevelId Level
sdungeon
  let ais :: [(ItemId, Item)]
ais = ItemDict -> [(ItemId, Item)]
forall k a. Enum k => EnumMap k a -> [(k, a)]
EM.assocs ItemDict
itemD
      minLid :: LevelId
minLid = (LevelId, Level) -> LevelId
forall a b. (a, b) -> a
fst ((LevelId, Level) -> LevelId) -> (LevelId, Level) -> LevelId
forall a b. (a -> b) -> a -> b
$ ((LevelId, Level) -> (LevelId, Level) -> Ordering)
-> [(LevelId, Level)] -> (LevelId, Level)
forall (t :: * -> *) a.
Foldable t =>
(a -> a -> Ordering) -> t a -> a
minimumBy (((LevelId, Level) -> AbsDepth)
-> (LevelId, Level) -> (LevelId, Level) -> Ordering
forall a b. Ord a => (b -> a) -> b -> b -> Ordering
Ord.comparing (Level -> AbsDepth
ldepth (Level -> AbsDepth)
-> ((LevelId, Level) -> Level) -> (LevelId, Level) -> AbsDepth
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (LevelId, Level) -> Level
forall a b. (a, b) -> b
snd))
                   ([(LevelId, Level)] -> (LevelId, Level))
-> [(LevelId, Level)] -> (LevelId, Level)
forall a b. (a -> b) -> a -> b
$ EnumMap LevelId Level -> [(LevelId, Level)]
forall k a. Enum k => EnumMap k a -> [(k, a)]
EM.assocs EnumMap LevelId Level
dungeon
  Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
isNoConfirms (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
    (FactionId -> m ()) -> [FactionId] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (\fid :: FactionId
fid -> do
      UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ Container
-> EnumMap ItemId ItemQuant -> [(ItemId, Item)] -> UpdAtomic
UpdSpotItemBag (FactionId -> LevelId -> Point -> Container
CTrunk FactionId
fid LevelId
minLid Point
originPoint)
                                     EnumMap ItemId ItemQuant
forall k a. EnumMap k a
EM.empty [(ItemId, Item)]
ais
      FactionId -> m ()
forall (m :: * -> *). MonadServerAtomic m => FactionId -> m ()
revealItems FactionId
fid) [FactionId]
fidsUI
  -- Announcing end of game, we send lore, because game is over.
  Actor
b <- (State -> Actor) -> m Actor
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
aid
  Maybe Status
oldSt <- (State -> Maybe Status) -> m (Maybe Status)
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Maybe Status) -> m (Maybe Status))
-> (State -> Maybe Status) -> m (Maybe Status)
forall a b. (a -> b) -> a -> b
$ Faction -> Maybe Status
gquit (Faction -> Maybe Status)
-> (State -> Faction) -> State -> Maybe Status
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (EnumMap FactionId Faction -> FactionId -> Faction
forall k a. Enum k => EnumMap k a -> k -> a
EM.! Actor -> FactionId
bfid Actor
b) (EnumMap FactionId Faction -> Faction)
-> (State -> EnumMap FactionId Faction) -> State -> Faction
forall b c a. (b -> c) -> (a -> b) -> a -> c
. State -> EnumMap FactionId Faction
sfactionD
  FactionAnalytics
factionAn <- (StateServer -> FactionAnalytics) -> m FactionAnalytics
forall (m :: * -> *) a. MonadServer m => (StateServer -> a) -> m a
getsServer StateServer -> FactionAnalytics
sfactionAn
  GenerationAnalytics
generationAn <- (StateServer -> GenerationAnalytics) -> m GenerationAnalytics
forall (m :: * -> *) a. MonadServer m => (StateServer -> a) -> m a
getsServer StateServer -> GenerationAnalytics
sgenerationAn
  UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ FactionId
-> Maybe Status
-> Maybe Status
-> Maybe (FactionAnalytics, GenerationAnalytics)
-> UpdAtomic
UpdQuitFaction
                    (Actor -> FactionId
bfid Actor
b)
                    Maybe Status
oldSt
                    (Status -> Maybe Status
forall a. a -> Maybe a
Just (Status -> Maybe Status) -> Status -> Maybe Status
forall a b. (a -> b) -> a -> b
$ Outcome -> Int -> Maybe (GroupName ModeKind) -> Status
Status Outcome
Restart (LevelId -> Int
forall a. Enum a => a -> Int
fromEnum (LevelId -> Int) -> LevelId -> Int
forall a b. (a -> b) -> a -> b
$ Actor -> LevelId
blid Actor
b) (GroupName ModeKind -> Maybe (GroupName ModeKind)
forall a. a -> Maybe a
Just GroupName ModeKind
groupName))
                    ((FactionAnalytics, GenerationAnalytics)
-> Maybe (FactionAnalytics, GenerationAnalytics)
forall a. a -> Maybe a
Just (FactionAnalytics
factionAn, GenerationAnalytics
generationAn))
  -- We don't save game and don't wait for clips end. ASAP.
  (StateServer -> StateServer) -> m ()
forall (m :: * -> *).
MonadServer m =>
(StateServer -> StateServer) -> m ()
modifyServer ((StateServer -> StateServer) -> m ())
-> (StateServer -> StateServer) -> m ()
forall a b. (a -> b) -> a -> b
$ \ser :: StateServer
ser -> StateServer
ser { sbreakASAP :: Bool
sbreakASAP = Bool
True
                             , soptionsNxt :: ServerOptions
soptionsNxt = (StateServer -> ServerOptions
soptionsNxt StateServer
ser) {Challenge
scurChalSer :: Challenge
scurChalSer :: Challenge
scurChalSer} }

-- * ReqGameDropAndExit

-- After we break out of the game loop, we will notice from @Camping@
-- we shouldn exit the game.
reqGameDropAndExit :: MonadServerAtomic m => ActorId -> m ()
reqGameDropAndExit :: ActorId -> m ()
reqGameDropAndExit aid :: ActorId
aid = do
  Actor
b <- (State -> Actor) -> m Actor
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
aid
  Maybe Status
oldSt <- (State -> Maybe Status) -> m (Maybe Status)
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Maybe Status) -> m (Maybe Status))
-> (State -> Maybe Status) -> m (Maybe Status)
forall a b. (a -> b) -> a -> b
$ Faction -> Maybe Status
gquit (Faction -> Maybe Status)
-> (State -> Faction) -> State -> Maybe Status
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (EnumMap FactionId Faction -> FactionId -> Faction
forall k a. Enum k => EnumMap k a -> k -> a
EM.! Actor -> FactionId
bfid Actor
b) (EnumMap FactionId Faction -> Faction)
-> (State -> EnumMap FactionId Faction) -> State -> Faction
forall b c a. (b -> c) -> (a -> b) -> a -> c
. State -> EnumMap FactionId Faction
sfactionD
  UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ FactionId
-> Maybe Status
-> Maybe Status
-> Maybe (FactionAnalytics, GenerationAnalytics)
-> UpdAtomic
UpdQuitFaction
                    (Actor -> FactionId
bfid Actor
b)
                    Maybe Status
oldSt
                    (Status -> Maybe Status
forall a. a -> Maybe a
Just (Status -> Maybe Status) -> Status -> Maybe Status
forall a b. (a -> b) -> a -> b
$ Outcome -> Int -> Maybe (GroupName ModeKind) -> Status
Status Outcome
Camping (LevelId -> Int
forall a. Enum a => a -> Int
fromEnum (LevelId -> Int) -> LevelId -> Int
forall a b. (a -> b) -> a -> b
$ Actor -> LevelId
blid Actor
b) Maybe (GroupName ModeKind)
forall a. Maybe a
Nothing)
                    Maybe (FactionAnalytics, GenerationAnalytics)
forall a. Maybe a
Nothing
  (StateServer -> StateServer) -> m ()
forall (m :: * -> *).
MonadServer m =>
(StateServer -> StateServer) -> m ()
modifyServer ((StateServer -> StateServer) -> m ())
-> (StateServer -> StateServer) -> m ()
forall a b. (a -> b) -> a -> b
$ \ser :: StateServer
ser -> StateServer
ser { sbreakASAP :: Bool
sbreakASAP = Bool
True
                             , sbreakLoop :: Bool
sbreakLoop = Bool
True }

-- * ReqGameSaveAndExit

-- After we break out of the game loop, we will notice from @Camping@
-- we shouldn exit the game.
reqGameSaveAndExit :: MonadServerAtomic m => ActorId -> m ()
reqGameSaveAndExit :: ActorId -> m ()
reqGameSaveAndExit aid :: ActorId
aid = do
  Actor
b <- (State -> Actor) -> m Actor
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
aid
  Maybe Status
oldSt <- (State -> Maybe Status) -> m (Maybe Status)
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Maybe Status) -> m (Maybe Status))
-> (State -> Maybe Status) -> m (Maybe Status)
forall a b. (a -> b) -> a -> b
$ Faction -> Maybe Status
gquit (Faction -> Maybe Status)
-> (State -> Faction) -> State -> Maybe Status
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (EnumMap FactionId Faction -> FactionId -> Faction
forall k a. Enum k => EnumMap k a -> k -> a
EM.! Actor -> FactionId
bfid Actor
b) (EnumMap FactionId Faction -> Faction)
-> (State -> EnumMap FactionId Faction) -> State -> Faction
forall b c a. (b -> c) -> (a -> b) -> a -> c
. State -> EnumMap FactionId Faction
sfactionD
  UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ FactionId
-> Maybe Status
-> Maybe Status
-> Maybe (FactionAnalytics, GenerationAnalytics)
-> UpdAtomic
UpdQuitFaction
                    (Actor -> FactionId
bfid Actor
b)
                    Maybe Status
oldSt
                    (Status -> Maybe Status
forall a. a -> Maybe a
Just (Status -> Maybe Status) -> Status -> Maybe Status
forall a b. (a -> b) -> a -> b
$ Outcome -> Int -> Maybe (GroupName ModeKind) -> Status
Status Outcome
Camping (LevelId -> Int
forall a. Enum a => a -> Int
fromEnum (LevelId -> Int) -> LevelId -> Int
forall a b. (a -> b) -> a -> b
$ Actor -> LevelId
blid Actor
b) Maybe (GroupName ModeKind)
forall a. Maybe a
Nothing)
                    Maybe (FactionAnalytics, GenerationAnalytics)
forall a. Maybe a
Nothing
  (StateServer -> StateServer) -> m ()
forall (m :: * -> *).
MonadServer m =>
(StateServer -> StateServer) -> m ()
modifyServer ((StateServer -> StateServer) -> m ())
-> (StateServer -> StateServer) -> m ()
forall a b. (a -> b) -> a -> b
$ \ser :: StateServer
ser -> StateServer
ser { sbreakASAP :: Bool
sbreakASAP = Bool
True
                             , swriteSave :: Bool
swriteSave = Bool
True }

-- * ReqGameSave

-- After we break out of the game loop, we will notice we shouldn't quit
-- the game and we will enter the game loop again.
reqGameSave :: MonadServer m => m ()
reqGameSave :: m ()
reqGameSave =
  (StateServer -> StateServer) -> m ()
forall (m :: * -> *).
MonadServer m =>
(StateServer -> StateServer) -> m ()
modifyServer ((StateServer -> StateServer) -> m ())
-> (StateServer -> StateServer) -> m ()
forall a b. (a -> b) -> a -> b
$ \ser :: StateServer
ser -> StateServer
ser { sbreakASAP :: Bool
sbreakASAP = Bool
True
                             , swriteSave :: Bool
swriteSave = Bool
True }

-- * ReqTactic

reqTactic :: MonadServerAtomic m => FactionId -> Ability.Tactic -> m ()
reqTactic :: FactionId -> Tactic -> m ()
reqTactic fid :: FactionId
fid toT :: Tactic
toT = do
  Tactic
fromT <- (State -> Tactic) -> m Tactic
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Tactic) -> m Tactic) -> (State -> Tactic) -> m Tactic
forall a b. (a -> b) -> a -> b
$ Player -> Tactic
ftactic (Player -> Tactic) -> (State -> Player) -> State -> Tactic
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Faction -> Player
gplayer (Faction -> Player) -> (State -> Faction) -> State -> Player
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (EnumMap FactionId Faction -> FactionId -> Faction
forall k a. Enum k => EnumMap k a -> k -> a
EM.! FactionId
fid) (EnumMap FactionId Faction -> Faction)
-> (State -> EnumMap FactionId Faction) -> State -> Faction
forall b c a. (b -> c) -> (a -> b) -> a -> c
. State -> EnumMap FactionId Faction
sfactionD
  UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ FactionId -> Tactic -> Tactic -> UpdAtomic
UpdTacticFaction FactionId
fid Tactic
toT Tactic
fromT

-- * ReqAutomate

reqAutomate :: MonadServerAtomic m => FactionId -> m ()
reqAutomate :: FactionId -> m ()
reqAutomate fid :: FactionId
fid = UpdAtomic -> m ()
forall (m :: * -> *). MonadServerAtomic m => UpdAtomic -> m ()
execUpdAtomic (UpdAtomic -> m ()) -> UpdAtomic -> m ()
forall a b. (a -> b) -> a -> b
$ FactionId -> Bool -> UpdAtomic
UpdAutoFaction FactionId
fid Bool
True