{-# LANGUAGE DeriveGeneric, GeneralizedNewtypeDeriving #-} -- | The type of kinds of factions present in a game, both human -- and computer-controlled. module Game.LambdaHack.Content.FactionKind ( FactionKind(..), makeData , HiCondPoly, HiSummand, HiPolynomial, HiIndeterminant(..) , TeamContinuity(..), Outcome(..) , teamExplorer, hiHeroLong, hiHeroMedium, hiHeroShort, hiDweller , victoryOutcomes, deafeatOutcomes , nameOutcomePast, nameOutcomeVerb, endMessageOutcome #ifdef EXPOSE_INTERNAL -- * Internal operations , validateSingle, validateAll #endif ) where import Prelude () import Game.LambdaHack.Core.Prelude import Data.Binary import qualified Data.Text as T import GHC.Generics (Generic) import Game.LambdaHack.Content.ItemKind (ItemKind) import qualified Game.LambdaHack.Definition.Ability as Ability import Game.LambdaHack.Definition.ContentData import Game.LambdaHack.Definition.Defs -- | Properties of a particular faction. data FactionKind = FactionKind { fname :: Text -- ^ name of the faction , ffreq :: Freqs FactionKind -- ^ frequency within groups , fteam :: TeamContinuity -- ^ the team the faction identifies with -- across games and modes , fgroups :: Freqs ItemKind -- ^ names of actor groups that may naturally fall under faction's -- control, e.g., upon spawning; make sure all groups that may -- ever continuousely generate actors, e.g., through spawning -- or summoning, are mentioned in at least one faction kind; -- groups of initial faction actors don't need to be included , fskillsOther :: Ability.Skills -- ^ fixed skill modifiers to the non-leader -- actors; also summed with skills implied -- by @fdoctrine@ (which is not fixed) , fcanEscape :: Bool -- ^ the faction can escape the dungeon , fneverEmpty :: Bool -- ^ the faction declared killed if no actors , fhiCondPoly :: HiCondPoly -- ^ score formula (conditional polynomial) , fhasGender :: Bool -- ^ whether actors have gender , finitDoctrine :: Ability.Doctrine -- ^ initial faction's non-leaders doctrine , fspawnsFast :: Bool -- ^ spawns fast enough that switching pointman to another level -- to optimize spawning is a winning tactics, which would spoil -- the fun, so switching is disabled in UI and AI clients , fhasPointman :: Bool -- ^ whether the faction can have a pointman , fhasUI :: Bool -- ^ does the faction have a UI client -- (for control or passive observation) , finitUnderAI :: Bool -- ^ is the faction initially under AI control , fenemyTeams :: [TeamContinuity] -- ^ teams starting at war with the faction , falliedTeams :: [TeamContinuity] -- ^ teams starting allied with the faction } deriving (Show, Eq, Generic) instance Binary FactionKind -- | Team continuity index. Starting with 1. See the comment for `FactionId`. newtype TeamContinuity = TeamContinuity Int deriving (Show, Eq, Ord, Enum, Generic) instance Binary TeamContinuity -- | Conditional polynomial representing score calculation for this faction. type HiCondPoly = [HiSummand] type HiSummand = (HiPolynomial, [Outcome]) type HiPolynomial = [(HiIndeterminant, Double)] data HiIndeterminant = HiConst | HiLoot | HiSprint | HiBlitz | HiSurvival | HiKill | HiLoss deriving (Show, Eq, Generic) instance Binary HiIndeterminant -- | Outcome of a game. data Outcome = Escape -- ^ the faction escaped the dungeon alive | Conquer -- ^ the faction won by eliminating all rivals | Defeated -- ^ the faction lost the game in another way | Killed -- ^ the faction was eliminated | Restart -- ^ game is restarted; the quitter quit | Camping -- ^ game is supended deriving (Show, Eq, Ord, Enum, Bounded, Generic) instance Binary Outcome teamExplorer :: TeamContinuity teamExplorer = TeamContinuity 1 hiHeroLong, hiHeroMedium, hiHeroShort, hiDweller :: HiCondPoly hiHeroShort = [ ( [(HiLoot, 100)] , [minBound..maxBound] ) , ( [(HiConst, 100)] , victoryOutcomes ) , ( [(HiSprint, -500)] -- speed matters, but only if fast enough , victoryOutcomes ) , ( [(HiSurvival, 10)] -- few points for surviving long , deafeatOutcomes ) ] hiHeroMedium = [ ( [(HiLoot, 200)] -- usually no loot, but if so, no harm , [minBound..maxBound] ) , ( [(HiConst, 200), (HiLoss, -10)] -- normally, always positive , victoryOutcomes ) , ( [(HiSprint, -1000)] -- speed matters, but only if fast enough , victoryOutcomes ) , ( [(HiBlitz, -100)] -- speed matters always , victoryOutcomes ) , ( [(HiSurvival, 10)] -- few points for surviving long , deafeatOutcomes ) ] -- Heroes in long crawls rejoice in loot. But speedrunning just as valuable. hiHeroLong = [ ( [(HiLoot, 10000)] -- multiplied by fraction of collected , [minBound..maxBound] ) , ( [(HiConst, 15)] -- a token bonus in case all loot lost, but victory , victoryOutcomes ) , ( [(HiSprint, -20000)] -- speedrun bonus, if below this number of turns , victoryOutcomes ) , ( [(HiBlitz, -100)] -- speed matters always , victoryOutcomes ) , ( [(HiSurvival, 10)] -- few points for surviving long , deafeatOutcomes ) ] -- Spawners get no points from loot, but try to kill -- all opponents fast or at least hold up for long. hiDweller = [ ( [(HiConst, 1000)] -- no loot, so big win reward , victoryOutcomes ) , ( [(HiConst, 1000), (HiLoss, -10)] , victoryOutcomes ) , ( [(HiSprint, -1000)] -- speedrun bonus, if below , victoryOutcomes ) , ( [(HiBlitz, -100)] -- speed matters , victoryOutcomes ) , ( [(HiSurvival, 100)] , deafeatOutcomes ) ] victoryOutcomes :: [Outcome] victoryOutcomes = [Escape, Conquer] deafeatOutcomes :: [Outcome] deafeatOutcomes = [Defeated, Killed, Restart] nameOutcomePast :: Outcome -> Text nameOutcomePast = \case Escape -> "emerged victorious" Conquer -> "vanquished all opposition" Defeated -> "got decisively defeated" Killed -> "got eliminated" Restart -> "resigned prematurely" Camping -> "set camp" nameOutcomeVerb :: Outcome -> Text nameOutcomeVerb = \case Escape -> "emerge victorious" Conquer -> "vanquish all opposition" Defeated -> "be decisively defeated" Killed -> "be eliminated" Restart -> "resign prematurely" Camping -> "set camp" endMessageOutcome :: Outcome -> Text endMessageOutcome = \case Escape -> "Can it be done more efficiently, though?" Conquer -> "Can it be done in a better style, though?" Defeated -> "Let's hope your new overlords let you live." Killed -> "Let's hope a rescue party arrives in time!" Restart -> "This time for real." Camping -> "See you soon, stronger and braver!" validateSingle :: FactionKind -> [Text] validateSingle FactionKind{..} = [ "fname longer than 50" | T.length fname > 50 ] ++ [ "fskillsOther not negative:" <+> fname | any ((>= 0) . snd) $ Ability.skillsToList fskillsOther ] ++ let checkLoveHate l team = [ "love-hate relationship for" <+> tshow team | team `elem` l ] in concatMap (checkLoveHate fenemyTeams) falliedTeams ++ let checkDipl field l team = [ "self-diplomacy in" <+> field | length (elemIndices team l) > 1 ] in concatMap (checkDipl "fenemyTeams" fenemyTeams) fenemyTeams ++ concatMap (checkDipl "falliedTeams" falliedTeams) falliedTeams -- | Validate game faction kinds together. validateAll :: [FactionKind] -> ContentData FactionKind -> [Text] validateAll _ _ = [] -- so far, always valid makeData :: [FactionKind] -> [GroupName FactionKind] -> [GroupName FactionKind] -> ContentData FactionKind makeData = makeContentData "FactionKind" fname ffreq validateSingle validateAll