{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Game.LambdaHack.Server.ItemRev
( ItemKnown, ItemRev, UniqueSet, buildItem, newItem
, DiscoveryKindRev, ItemSeedDict, serverDiscos
, FlavourMap, emptyFlavourMap, dungeonFlavourMap
) where
import Prelude ()
import Game.LambdaHack.Common.Prelude
import Data.Binary
import qualified Data.EnumMap.Strict as EM
import qualified Data.EnumSet as ES
import qualified Data.HashMap.Strict as HM
import qualified Data.Set as S
import qualified Game.LambdaHack.Common.Dice as Dice
import Game.LambdaHack.Common.Flavour
import Game.LambdaHack.Common.Frequency
import Game.LambdaHack.Common.Item
import qualified Game.LambdaHack.Common.Kind as Kind
import Game.LambdaHack.Common.Misc
import Game.LambdaHack.Common.Random
import Game.LambdaHack.Common.Time
import Game.LambdaHack.Content.ItemKind (ItemKind)
import qualified Game.LambdaHack.Content.ItemKind as IK
type ItemKnown = (ItemKindIx, AspectRecord, Dice.Dice, Maybe FactionId)
type ItemRev = HM.HashMap ItemKnown ItemId
type UniqueSet = ES.EnumSet (Kind.Id ItemKind)
buildItem :: FlavourMap -> DiscoveryKindRev -> Kind.Id ItemKind -> ItemKind
-> LevelId -> Dice.Dice
-> Item
buildItem (FlavourMap flavour) discoRev ikChosen kind jlid jdamage =
let jkindIx = discoRev EM.! ikChosen
jfid = Nothing
jsymbol = IK.isymbol kind
jname = IK.iname kind
jflavour =
case IK.iflavour kind of
[fl] -> fl
_ -> flavour EM.! ikChosen
jfeature = IK.ifeature kind
jweight = IK.iweight kind
in Item{..}
newItem :: Kind.COps -> FlavourMap
-> DiscoveryKind -> DiscoveryKindRev -> UniqueSet
-> Freqs ItemKind -> Int -> LevelId -> AbsDepth -> AbsDepth
-> Rnd (Maybe ( ItemKnown, ItemFull, ItemDisco
, ItemSeed, GroupName ItemKind ))
newItem Kind.COps{coitem=Kind.Ops{ofoldlGroup'}}
flavour disco discoRev uniqueSet itemFreq lvlSpawned lid
ldepth@(AbsDepth ldAbs) totalDepth@(AbsDepth depth) = do
let scaledDepth = ldAbs * 10 `div` depth
numSpawnedCoeff = lvlSpawned `div` 2
ldSpawned = max ldAbs
$ min depth
$ ldAbs + numSpawnedCoeff - scaledDepth
findInterval _ x1y1 [] = (x1y1, (11, 0))
findInterval !ld !x1y1 ((!x, !y) : rest) =
if fromIntegral ld * 10 <= x * fromIntegral depth
then (x1y1, (x, y))
else findInterval ld (x, y) rest
linearInterpolation !ld !dataset =
let ((x1, y1), (x2, y2)) = findInterval ld (0, 0) dataset
in ceiling
$ fromIntegral y1
+ fromIntegral (y2 - y1)
* (fromIntegral ld * 10 - x1 * fromIntegral depth)
/ ((x2 - x1) * fromIntegral depth)
f _ _ acc _ ik _ | ik `ES.member` uniqueSet = acc
f !itemGroup !q !acc !p !ik !kind =
let ld = if IK.Unique `elem` IK.ieffects kind then ldAbs else ldSpawned
rarity = linearInterpolation ld (IK.irarity kind)
in (q * p * rarity, ((ik, kind), itemGroup)) : acc
g (itemGroup, q) = ofoldlGroup' itemGroup (f itemGroup q) []
freqDepth = concatMap g itemFreq
freq = toFreq ("newItem ('" <> tshow ldSpawned <> ")") freqDepth
if nullFreq freq then return Nothing
else do
((itemKindId, itemKind), itemGroup) <- frequency freq
itemN <- castDice ldepth totalDepth (IK.icount itemKind)
seed <- toEnum <$> random
jdamage <- frequency $ toFreq "jdamage" $ IK.idamage itemKind
let itemBase = buildItem flavour discoRev itemKindId itemKind lid jdamage
kindIx = jkindIx itemBase
itemK = max 1 itemN
itemTimer = [timeZero | IK.Periodic `elem` IK.ieffects itemKind]
km = EM.findWithDefault (error $ "" `showFailure` kindIx) kindIx disco
itemAspectMean = kmMean km
itemConst = kmConst km
itemAspect = Just aspectRecord
itemDiscoData = ItemDisco {..}
itemDisco = Just itemDiscoData
aspectRecord = seedToAspect seed itemKind ldepth totalDepth
itemFull = ItemFull {..}
return $ Just ( (kindIx, aspectRecord, jdamage, jfid itemBase)
, itemFull
, itemDiscoData
, seed
, itemGroup )
type DiscoveryKindRev = EM.EnumMap (Kind.Id ItemKind) ItemKindIx
type ItemSeedDict = EM.EnumMap ItemId ItemSeed
serverDiscos :: Kind.COps -> Rnd (DiscoveryKind, DiscoveryKindRev)
serverDiscos Kind.COps{coitem=Kind.Ops{olength, ofoldlWithKey', okind}} = do
let ixs = [toEnum 0..toEnum (olength-1)]
shuffle :: Eq a => [a] -> Rnd [a]
shuffle [] = return []
shuffle l = do
x <- oneOf l
(x :) <$> shuffle (delete x l)
shuffled <- shuffle ixs
let f (!ikMap, !ikRev, ix : rest) kmKind _ =
let kind = okind kmKind
kmMean = meanAspect kind
kmConst = not $ aspectsRandom kind
in (EM.insert ix KindMean{..} ikMap, EM.insert kmKind ix ikRev, rest)
f (ikMap, _, []) ik _ =
error $ "too short ixs" `showFailure` (ik, ikMap)
(discoS, discoRev, _) =
ofoldlWithKey' f (EM.empty, EM.empty, shuffled)
return (discoS, discoRev)
newtype FlavourMap = FlavourMap (EM.EnumMap (Kind.Id ItemKind) Flavour)
deriving (Show, Binary)
emptyFlavourMap :: FlavourMap
emptyFlavourMap = FlavourMap EM.empty
rollFlavourMap :: S.Set Flavour
-> Rnd ( EM.EnumMap (Kind.Id ItemKind) Flavour
, EM.EnumMap Char (S.Set Flavour) )
-> Kind.Id ItemKind -> ItemKind
-> Rnd ( EM.EnumMap (Kind.Id ItemKind) Flavour
, EM.EnumMap Char (S.Set Flavour) )
rollFlavourMap fullFlavSet rnd key ik =
let flavours = IK.iflavour ik
in if length flavours == 1
then rnd
else do
(!assocs, !availableMap) <- rnd
let available =
EM.findWithDefault fullFlavSet (IK.isymbol ik) availableMap
proper = S.fromList flavours `S.intersection` available
assert (not (S.null proper)
`blame` "not enough flavours for items"
`swith` (flavours, available, ik, availableMap)) $ do
flavour <- oneOf $ S.toList proper
let availableReduced = S.delete flavour available
return ( EM.insert key flavour assocs
, EM.insert (IK.isymbol ik) availableReduced availableMap)
dungeonFlavourMap :: Kind.COps -> Rnd FlavourMap
dungeonFlavourMap Kind.COps{coitem=Kind.Ops{ofoldlWithKey'}} =
liftM (FlavourMap . fst) $
ofoldlWithKey' (rollFlavourMap (S.fromList stdFlav))
(return (EM.empty, EM.empty))