1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
|
{-# 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
|