-- | Descripitons of items. module Game.LambdaHack.Client.UI.ItemDescription ( partItem, partItemShort, partItemHigh, partItemWs, partItemWsRanged , partItemShortAW, partItemMediumAW, partItemShortWownW , viewItem, itemDesc #ifdef EXPOSE_INTERNAL -- * Internal operations , show64With2, partItemN, textAllAE, partItemWsR #endif ) where import Prelude () import Game.LambdaHack.Common.Prelude import qualified Data.EnumMap.Strict as EM import Data.Int (Int64) import qualified Data.Text as T import qualified NLP.Miniutter.English as MU import Game.LambdaHack.Client.UI.EffectDescription import Game.LambdaHack.Client.UI.Overlay import qualified Game.LambdaHack.Common.Color as Color import qualified Game.LambdaHack.Common.Dice as Dice import Game.LambdaHack.Common.Faction import Game.LambdaHack.Common.Flavour import Game.LambdaHack.Common.Item import Game.LambdaHack.Common.ItemStrongest import Game.LambdaHack.Common.Misc import Game.LambdaHack.Common.Time import qualified Game.LambdaHack.Content.ItemKind as IK show64With2 :: Int64 -> Text show64With2 n = let k = 100 * n `div` oneM l = k `div` 100 x = k - l * 100 in tshow l <> if | x == 0 -> "" | x < 10 -> ".0" <> tshow x | otherwise -> "." <> tshow x -- | The part of speech describing the item parameterized by the number -- of effects/aspects to show.. partItemN :: FactionId -> FactionDict -> Bool -> Int -> Int -> CStore -> Time -> ItemFull -> (Bool, Bool, MU.Part, MU.Part) partItemN side factionD ranged fullInfo n cstore localTime itemFull = let genericName = jname $ itemBase itemFull in case itemDisco itemFull of Nothing -> let flav = flavourToName $ jflavour $ itemBase itemFull in (False, False, MU.Text $ flav <+> genericName, "") Just iDisco -> let timeout = aTimeout $ aspectRecordFull itemFull timeoutTurns = timeDeltaScale (Delta timeTurn) timeout temporary = not (null $ itemTimer itemFull) && timeout == 0 charging startT = timeShift startT timeoutTurns > localTime it1 = filter charging (itemTimer itemFull) lenCh = length it1 timer | lenCh == 0 || temporary = "" | itemK itemFull == 1 && lenCh == 1 = "(charging)" | itemK itemFull == lenCh = "(all charging)" | otherwise = "(" <> tshow lenCh <+> "charging)" skipRecharging = fullInfo <= 4 && lenCh >= itemK itemFull (effTsRaw, rangedDamage) = textAllAE fullInfo skipRecharging cstore itemFull effTs = filter (not . T.null) effTsRaw ++ if ranged then rangedDamage else [] lsource = case jfid $ itemBase itemFull of Just fid | jname (itemBase itemFull) `elem` ["impressed"] -> ["by" <+> if fid == side then "us" else gname (factionD EM.! fid)] _ -> [] ts = lsource ++ take n effTs ++ ["(...)" | length effTs > n] ++ [timer] unique = IK.Unique `elem` IK.ieffects (itemKind iDisco) name | temporary = "temporarily" <+> genericName | otherwise = genericName capName = if unique then MU.Capitalize $ MU.Text name else MU.Text name in ( not (null lsource) || temporary , unique, capName, MU.Phrase $ map MU.Text ts ) textAllAE :: Int -> Bool -> CStore -> ItemFull -> ([Text], [Text]) textAllAE fullInfo skipRecharging cstore ItemFull{itemBase, itemDisco} = let features | fullInfo >= 9 = map featureToSuff $ sort $ jfeature itemBase | otherwise = [] in case itemDisco of Nothing -> (features, []) Just ItemDisco{itemKind, itemAspect} -> let timeoutAspect :: IK.Aspect -> Bool timeoutAspect IK.Timeout{} = True timeoutAspect _ = False hurtMeleeAspect :: IK.Aspect -> Bool hurtMeleeAspect IK.AddHurtMelee{} = True hurtMeleeAspect _ = False elabel :: IK.Effect -> Bool elabel IK.ELabel{} = True elabel _ = False notDetail :: IK.Effect -> Bool notDetail IK.Explode{} = fullInfo >= 6 notDetail _ = True active = cstore `elem` [CEqp, COrgan] || cstore == CGround && goesIntoEqp itemBase splitAE :: [IK.Aspect] -> [IK.Effect] -> [Text] splitAE aspects effects = let ppA = kindAspectToSuffix ppE = effectToSuffix reduce_a = maybe "?" tshow . Dice.reduceDice periodic = IK.Periodic `elem` IK.ieffects itemKind mtimeout = find timeoutAspect aspects restAs = sort aspects -- Effects are not sorted, because they fire in the order -- specified. restEs = filter notDetail effects aes = if active then map ppA restAs ++ map ppE restEs else map ppE restEs ++ map ppA restAs rechargingTs = T.intercalate (T.singleton ' ') $ filter (not . T.null) $ map ppE $ stripRecharging restEs onSmashTs = T.intercalate (T.singleton ' ') $ filter (not . T.null) $ map ppE $ stripOnSmash restEs durable = IK.Durable `elem` jfeature itemBase fragile = IK.Fragile `elem` jfeature itemBase periodicOrTimeout = if | skipRecharging || T.null rechargingTs -> "" | periodic -> case mtimeout of Nothing | durable && not fragile -> "(each turn:" <+> rechargingTs <> ")" Nothing -> "(each turn until gone:" <+> rechargingTs <> ")" Just (IK.Timeout t) -> "(every" <+> reduce_a t <> ":" <+> rechargingTs <> ")" _ -> error $ "" `showFailure` mtimeout | otherwise -> case mtimeout of Nothing -> "" Just (IK.Timeout t) -> "(timeout" <+> reduce_a t <> ":" <+> rechargingTs <> ")" _ -> error $ "" `showFailure` mtimeout onSmash = if T.null onSmashTs then "" else "(on smash:" <+> onSmashTs <> ")" elab = case find elabel effects of Just (IK.ELabel t) -> [t] _ -> [] damage = case find hurtMeleeAspect aspects of Just (IK.AddHurtMelee hurtMelee) -> (if jdamage itemBase == 0 then "0d0" else tshow (jdamage itemBase)) <> affixDice hurtMelee <> "%" _ -> if jdamage itemBase == 0 then "" else tshow (jdamage itemBase) in elab ++ if fullInfo >= 6 || fullInfo >= 2 && null elab then [periodicOrTimeout] ++ [damage] ++ aes ++ [onSmash | fullInfo >= 7] else [damage] aets = case itemAspect of Just aspectRecord -> splitAE (aspectRecordToList aspectRecord) (IK.ieffects itemKind) Nothing -> splitAE (IK.iaspects itemKind) (IK.ieffects itemKind) IK.ThrowMod{IK.throwVelocity} = strengthToThrow itemBase speed = speedFromWeight (jweight itemBase) throwVelocity meanDmg = ceiling $ Dice.meanDice (jdamage itemBase) minDeltaHP = xM meanDmg `divUp` 100 aHurtMeleeOfItem = case itemAspect of Just aspectRecord -> aHurtMelee aspectRecord Nothing -> case find hurtMeleeAspect (IK.iaspects itemKind) of Just (IK.AddHurtMelee d) -> ceiling $ Dice.meanDice d _ -> 0 pmult = 100 + min 99 (max (-99) aHurtMeleeOfItem) prawDeltaHP = fromIntegral pmult * minDeltaHP pdeltaHP = modifyDamageBySpeed prawDeltaHP speed rangedDamage = if pdeltaHP == 0 then [] else ["{avg" <+> show64With2 pdeltaHP <+> "ranged}"] -- Note that avg melee damage would be too complex to display here, -- because in case of @MOwned@ the owner is different than leader, -- so the value would be different than when viewing the item. in (aets ++ features, rangedDamage) -- | The part of speech describing the item. partItem :: FactionId -> FactionDict -> CStore -> Time -> ItemFull -> (Bool, Bool, MU.Part, MU.Part) partItem side factionD = partItemN side factionD False 5 4 partItemShort :: FactionId -> FactionDict -> CStore -> Time -> ItemFull -> (Bool, Bool, MU.Part, MU.Part) partItemShort side factionD = partItemN side factionD False 4 4 partItemHigh :: FactionId -> FactionDict -> CStore -> Time -> ItemFull -> (Bool, Bool, MU.Part, MU.Part) partItemHigh side factionD = partItemN side factionD False 10 100 -- The @count@ can be different than @itemK@ in @ItemFull@, e.g., when picking -- a subset of items to drop. partItemWsR :: FactionId -> FactionDict -> Bool -> Int -> CStore -> Time -> ItemFull -> MU.Part partItemWsR side factionD ranged count cstore localTime itemFull = let (temporary, unique, name, stats) = partItemN side factionD ranged 5 4 cstore localTime itemFull in if | temporary && count == 1 -> MU.Phrase [name, stats] | temporary -> MU.Phrase [MU.Text $ tshow count <> "-fold", name, stats] | unique && count == 1 -> MU.Phrase ["the", name, stats] | otherwise -> MU.Phrase [MU.CarWs count name, stats] partItemWs :: FactionId -> FactionDict -> Int -> CStore -> Time -> ItemFull -> MU.Part partItemWs side factionD = partItemWsR side factionD False partItemWsRanged :: FactionId -> FactionDict -> Int -> CStore -> Time -> ItemFull -> MU.Part partItemWsRanged side factionD = partItemWsR side factionD True partItemShortAW :: FactionId -> FactionDict -> CStore -> Time -> ItemFull -> MU.Part partItemShortAW side factionD c localTime itemFull = let (_, unique, name, _) = partItemShort side factionD c localTime itemFull in if unique then MU.Phrase ["the", name] else MU.AW name partItemMediumAW :: FactionId -> FactionDict -> CStore -> Time -> ItemFull -> MU.Part partItemMediumAW side factionD c localTime itemFull = let (_, unique, name, stats) = partItemN side factionD False 5 100 c localTime itemFull in if unique then MU.Phrase ["the", name, stats] else MU.AW $ MU.Phrase [name, stats] partItemShortWownW :: FactionId -> FactionDict -> MU.Part -> CStore -> Time -> ItemFull -> MU.Part partItemShortWownW side factionD partA c localTime itemFull = let (_, _, name, _) = partItemShort side factionD c localTime itemFull in MU.WownW partA name viewItem :: Item -> Color.AttrCharW32 {-# INLINE viewItem #-} viewItem item = Color.attrChar2ToW32 (flavourToColor $ jflavour item) (jsymbol item) itemDesc :: FactionId -> FactionDict -> Int -> CStore -> Time -> ItemFull -> AttrLine itemDesc side factionD aHurtMeleeOfOwner store localTime itemFull@ItemFull{itemBase} = let (_, unique, name, stats) = partItemHigh side factionD store localTime itemFull nstats = makePhrase [name, stats] IK.ThrowMod{IK.throwVelocity, IK.throwLinger} = strengthToThrow itemBase speed = speedFromWeight (jweight itemBase) throwVelocity range = rangeFromSpeedAndLinger speed throwLinger tspeed | speed < speedLimp = "When thrown, it drops at once." | speed < speedWalk = "When thrown, it travels only one meter and drops immediately." | otherwise = "When thrown, it flies with speed of" <+> tshow (fromSpeed speed `div` 10) <> if throwLinger /= 100 then " m/s and range" <+> tshow range <+> "m." else " m/s." (desc, featureSentences, damageAnalysis) = case itemDisco itemFull of Nothing -> ("This item is as unremarkable as can be.", "", tspeed) Just ItemDisco{itemKind, itemAspect} -> let sentences = mapMaybe featureToSentence (IK.ifeature itemKind) hurtMeleeAspect :: IK.Aspect -> Bool hurtMeleeAspect IK.AddHurtMelee{} = True hurtMeleeAspect _ = False aHurtMeleeOfItem = case itemAspect of Just aspectRecord -> aHurtMelee aspectRecord Nothing -> case find hurtMeleeAspect (IK.iaspects itemKind) of Just (IK.AddHurtMelee d) -> ceiling $ Dice.meanDice d _ -> 0 meanDmg = ceiling $ Dice.meanDice (jdamage itemBase) dmgAn = if meanDmg <= 0 then "" else let multRaw = aHurtMeleeOfOwner + if store `elem` [CEqp, COrgan] then 0 else aHurtMeleeOfItem mult = 100 + min 99 (max (-99) multRaw) minDeltaHP = xM meanDmg `divUp` 100 rawDeltaHP = fromIntegral mult * minDeltaHP pmult = 100 + min 99 (max (-99) aHurtMeleeOfItem) prawDeltaHP = fromIntegral pmult * minDeltaHP pdeltaHP = modifyDamageBySpeed prawDeltaHP speed mDeltaHP = modifyDamageBySpeed minDeltaHP speed in "Against defenceless targets you would inflict around" -- rounding and non-id items <+> tshow meanDmg <> "*" <> tshow mult <> "%" <> "=" <> show64With2 rawDeltaHP <+> "melee damage (min" <+> show64With2 minDeltaHP <> ") and" <+> tshow meanDmg <> "*" <> tshow pmult <> "%" <> "*" <> "speed^2" <> "/" <> tshow (fromSpeed speedThrust `divUp` 10) <> "^2" <> "=" <> show64With2 pdeltaHP <+> "ranged damage (min" <+> show64With2 mDeltaHP <> ") with it" <> if Dice.minDice (jdamage itemBase) == Dice.maxDice (jdamage itemBase) then "." else "on average." in (IK.idesc itemKind, T.intercalate " " sentences, tspeed <+> dmgAn) eqpSlotSentence = case strengthEqpSlot itemFull of Just es -> slotToSentence es Nothing -> "" weight = jweight itemBase (scaledWeight, unitWeight) | weight > 1000 = (tshow $ fromIntegral weight / (1000 :: Double), "kg") | otherwise = (tshow weight, "g") onLevel = "on level" <+> tshow (abs $ fromEnum $ jlid itemBase) <> "." discoFirst = (if unique then "Discovered" else "First seen") <+> onLevel whose fid = gname (factionD EM.! fid) sourceDesc = case jfid itemBase of Just fid | jsymbol itemBase `elem` ['+'] -> "Caused by" <+> (if fid == side then "us" else whose fid) <> ". First observed" <+> onLevel Just fid -> "Coming from" <+> whose fid <> "." <+> discoFirst _ -> discoFirst colorSymbol = viewItem itemBase blurb = " " <> nstats <> ":" <+> desc <+> (if weight > 0 then makeSentence ["Weighs", MU.Text scaledWeight <> unitWeight] else "") <+> featureSentences <+> eqpSlotSentence <+> sourceDesc <+> damageAnalysis in colorSymbol : textToAL blurb