packages feed

rattletrap-13.0.1: src/lib/Rattletrap/Type/ClassAttributeMap.hs

module Rattletrap.Type.ClassAttributeMap where

import qualified Data.IntMap as IntMap
import qualified Data.List as List
import qualified Data.Map as Map
import qualified Data.Maybe as Maybe
import qualified Data.Set as Set
import qualified Data.Text as Text
import qualified Data.Tuple as Tuple
import qualified Rattletrap.Data as Data
import qualified Rattletrap.Type.AttributeMapping as AttributeMapping
import qualified Rattletrap.Type.Cache as Cache
import qualified Rattletrap.Type.ClassMapping as ClassMapping
import qualified Rattletrap.Type.CompressedWord as CompressedWord
import qualified Rattletrap.Type.List as RList
import qualified Rattletrap.Type.Str as Str
import qualified Rattletrap.Type.U32 as U32

-- | This data structure holds all the information about classes, objects, and
-- attributes in the replay. The class hierarchy is not fixed; it is encoded
-- in the 'Rattletrap.Content.Content'. Similarly, the attributes that belong
-- to each class are not fixed either. Converting the raw data into a usable
-- structure is tedious; see 'make'.
data ClassAttributeMap = ClassAttributeMap
  { -- | A map from object IDs to their names.
    objectMap :: Map.Map U32.U32 Str.Str,
    -- | A map from object IDs to their class IDs.
    objectClassMap :: Map.Map U32.U32 U32.U32,
    -- | A map from class IDs to a map from attribute stream IDs to attribute
    -- IDs.
    value :: Map.Map U32.U32 (Map.Map U32.U32 U32.U32),
    nameMap :: IntMap.IntMap Str.Str
  }
  deriving (Eq, Show)

type Bimap l r = (Map.Map l r, Map.Map r l)

bimap :: (Ord l, Ord r) => [(l, r)] -> Bimap l r
bimap xs = (Map.fromList xs, Map.fromList (fmap Tuple.swap xs))

lookupL :: (Ord l) => l -> Bimap l r -> Maybe r
lookupL k = Map.lookup k . fst

lookupR :: (Ord r) => r -> Bimap l r -> Maybe l
lookupR k = Map.lookup k . snd

-- | Makes a 'ClassAttributeMap' given the necessary fields from the
-- 'Rattletrap.Content.Content'.
make ::
  -- | From 'Rattletrap.Content.objects'.
  RList.List Str.Str ->
  -- | From 'Rattletrap.Content.classMappings'.
  RList.List ClassMapping.ClassMapping ->
  -- | From 'Rattletrap.Content.caches'.
  RList.List Cache.Cache ->
  -- | From 'Rattletrap.Content.names'.
  RList.List Str.Str ->
  ClassAttributeMap
make objects classMappings caches names =
  let objectMap_ = makeObjectMap objects
      classMap = makeClassMap classMappings
      objectClassMap_ = makeObjectClassMap objectMap_ classMap
      classCache = makeClassCache classMap caches
      attributeMap = makeAttributeMap caches
      classIds = fmap (\(_, classId, _, _) -> classId) classCache
      parentMap = makeParentMap classCache
      value_ =
        Map.fromList
          ( fmap
              ( \classId ->
                  let ownAttributes =
                        Maybe.fromMaybe Map.empty (Map.lookup classId attributeMap)
                      parentsAttributes = case Map.lookup classId parentMap of
                        Nothing -> []
                        Just parentClassIds ->
                          fmap
                            ( \parentClassId ->
                                Maybe.fromMaybe
                                  Map.empty
                                  (Map.lookup parentClassId attributeMap)
                            )
                            parentClassIds
                      attributes = ownAttributes : parentsAttributes
                   in (classId, Map.fromList (concatMap Map.toList attributes))
              )
              classIds
          )
      nameMap_ = makeNameMap names
   in ClassAttributeMap objectMap_ objectClassMap_ value_ nameMap_

makeNameMap :: RList.List Str.Str -> IntMap.IntMap Str.Str
makeNameMap names =
  IntMap.fromDistinctAscList (zip [0 ..] (RList.toList names))

getName :: IntMap.IntMap Str.Str -> U32.U32 -> Maybe Str.Str
getName nameMap_ nameIndex =
  IntMap.lookup (fromIntegral (U32.toWord32 nameIndex)) nameMap_

makeObjectClassMap ::
  Map.Map U32.U32 Str.Str ->
  Bimap U32.U32 Str.Str ->
  Map.Map U32.U32 U32.U32
makeObjectClassMap objectMap_ classMap = do
  let objectIds = Map.keys objectMap_
  let classIds = fmap (getClassId objectMap_ classMap) objectIds
  let rawPairs = zip objectIds classIds
  let pairs =
        Maybe.mapMaybe
          ( \(objectId, maybeClassId) -> case maybeClassId of
              Nothing -> Nothing
              Just classId -> Just (objectId, classId)
          )
          rawPairs
  Map.fromList pairs

getClassId ::
  Map.Map U32.U32 Str.Str ->
  Bimap U32.U32 Str.Str ->
  U32.U32 ->
  Maybe U32.U32
getClassId objectMap_ classMap objectId = do
  objectName <- getObjectName objectMap_ objectId
  className <- getClassName objectName
  lookupR className classMap

makeClassCache ::
  Bimap U32.U32 Str.Str ->
  RList.List Cache.Cache ->
  [(Maybe Str.Str, U32.U32, U32.U32, U32.U32)]
makeClassCache classMap caches =
  fmap
    ( \cache ->
        let classId = Cache.classId cache
         in ( lookupL classId classMap,
              classId,
              Cache.cacheId cache,
              Cache.parentCacheId cache
            )
    )
    (RList.toList caches)

makeClassMap :: RList.List ClassMapping.ClassMapping -> Bimap U32.U32 Str.Str
makeClassMap classMappings =
  bimap
    ( fmap
        ( \classMapping ->
            (ClassMapping.streamId classMapping, ClassMapping.name classMapping)
        )
        (RList.toList classMappings)
    )

makeAttributeMap ::
  RList.List Cache.Cache -> Map.Map U32.U32 (Map.Map U32.U32 U32.U32)
makeAttributeMap caches =
  Map.fromList
    ( fmap
        ( \cache ->
            ( Cache.classId cache,
              Map.fromList
                ( fmap
                    ( \attributeMapping ->
                        ( AttributeMapping.streamId attributeMapping,
                          AttributeMapping.objectId attributeMapping
                        )
                    )
                    (RList.toList (Cache.attributeMappings cache))
                )
            )
        )
        (RList.toList caches)
    )

makeShallowParentMap ::
  [(Maybe Str.Str, U32.U32, U32.U32, U32.U32)] -> Map.Map U32.U32 U32.U32
makeShallowParentMap classCache =
  Map.fromList
    ( Maybe.mapMaybe
        ( \xs -> case xs of
            [] -> Nothing
            (maybeClassName, classId, _, parentCacheId) : rest -> do
              parentClassId <- getParentClass maybeClassName parentCacheId rest
              pure (classId, parentClassId)
        )
        (List.tails (reverse classCache))
    )

makeParentMap ::
  [(Maybe Str.Str, U32.U32, U32.U32, U32.U32)] -> Map.Map U32.U32 [U32.U32]
makeParentMap classCache =
  let shallowParentMap = makeShallowParentMap classCache
   in Map.mapWithKey
        (\classId _ -> getParentClasses shallowParentMap classId)
        shallowParentMap

getParentClasses :: Map.Map U32.U32 U32.U32 -> U32.U32 -> [U32.U32]
getParentClasses shallowParentMap classId =
  case Map.lookup classId shallowParentMap of
    Nothing -> []
    Just parentClassId ->
      parentClassId : getParentClasses shallowParentMap parentClassId

getParentClass ::
  Maybe Str.Str ->
  U32.U32 ->
  [(Maybe Str.Str, U32.U32, U32.U32, U32.U32)] ->
  Maybe U32.U32
getParentClass maybeClassName parentCacheId xs = case maybeClassName of
  Nothing -> getParentClassById parentCacheId xs
  Just className -> getParentClassByName className parentCacheId xs

getParentClassById ::
  U32.U32 -> [(Maybe Str.Str, U32.U32, U32.U32, U32.U32)] -> Maybe U32.U32
getParentClassById parentCacheId xs =
  case dropWhile (\(_, _, cacheId, _) -> cacheId /= parentCacheId) xs of
    [] ->
      if parentCacheId == U32.fromWord32 0
        then Nothing
        else
          getParentClassById
            (U32.fromWord32 (U32.toWord32 parentCacheId - 1))
            xs
    (_, parentClassId, _, _) : _ -> Just parentClassId

getParentClassByName ::
  Str.Str ->
  U32.U32 ->
  [(Maybe Str.Str, U32.U32, U32.U32, U32.U32)] ->
  Maybe U32.U32
getParentClassByName className parentCacheId xs =
  case Map.lookup (Str.toText className) Data.parentClasses of
    Nothing -> getParentClassById parentCacheId xs
    Just parentClassName ->
      Maybe.maybe
        (getParentClassById parentCacheId xs)
        Just
        ( Maybe.listToMaybe
            ( fmap
                (\(_, parentClassId, _, _) -> parentClassId)
                ( filter
                    (\(_, _, cacheId, _) -> cacheId <= parentCacheId)
                    ( filter
                        ( \(maybeClassName, _, _, _) ->
                            fmap Str.toText maybeClassName == Just parentClassName
                        )
                        xs
                    )
                )
            )
        )

makeObjectMap :: RList.List Str.Str -> Map.Map U32.U32 Str.Str
makeObjectMap objects =
  Map.fromAscList (zip (fmap U32.fromWord32 [0 ..]) (RList.toList objects))

getObjectName :: Map.Map U32.U32 Str.Str -> U32.U32 -> Maybe Str.Str
getObjectName objectMap_ objectId = Map.lookup objectId objectMap_

getClassName :: Str.Str -> Maybe Str.Str
getClassName rawObjectName =
  fmap Str.fromText $
    Map.lookup
      (Str.toText $ normalizeObjectName rawObjectName)
      Data.objectClasses

normalizeObjectName :: Str.Str -> Str.Str
normalizeObjectName objectName =
  let name = Str.toText objectName
      crowdActor = Text.pack "TheWorld:PersistentLevel.CrowdActor_TA"
      crowdManager = Text.pack "TheWorld:PersistentLevel.CrowdManager_TA"
      boostPickup = Text.pack "TheWorld:PersistentLevel.VehiclePickup_Boost_TA"
      mapScoreboard = Text.pack "TheWorld:PersistentLevel.InMapScoreboard_TA"
      breakout = Text.pack "TheWorld:PersistentLevel.BreakOutActor_Platform_TA"
   in if Text.isInfixOf crowdActor name
        then Str.fromText crowdActor
        else
          if Text.isInfixOf crowdManager name
            then Str.fromText crowdManager
            else
              if Text.isInfixOf boostPickup name
                then Str.fromText boostPickup
                else
                  if Text.isInfixOf mapScoreboard name
                    then Str.fromText mapScoreboard
                    else
                      if Text.isInfixOf breakout name
                        then Str.fromText breakout
                        else objectName

classHasLocation :: Str.Str -> Bool
classHasLocation className =
  Set.member (Str.toText className) Data.classesWithLocation

classHasRotation :: Str.Str -> Bool
classHasRotation className =
  Set.member (Str.toText className) Data.classesWithRotation

getAttributeIdLimit :: Map.Map U32.U32 U32.U32 -> Maybe Word
getAttributeIdLimit attributeMap = do
  ((streamId, _), _) <- Map.maxViewWithKey attributeMap
  pure (fromIntegral (U32.toWord32 streamId))

getAttributeName ::
  ClassAttributeMap ->
  Map.Map U32.U32 U32.U32 ->
  CompressedWord.CompressedWord ->
  Maybe Str.Str
getAttributeName classAttributeMap attributeMap streamId = do
  let key = U32.fromWord32 (fromIntegral (CompressedWord.value streamId))
  attributeId <- Map.lookup key attributeMap
  let objectMap_ = objectMap classAttributeMap
  Map.lookup attributeId objectMap_

getAttributeMap ::
  ClassAttributeMap ->
  Map.Map CompressedWord.CompressedWord U32.U32 ->
  CompressedWord.CompressedWord ->
  Maybe (Map.Map U32.U32 U32.U32)
getAttributeMap classAttributeMap actorMap actorId = do
  objectId <- Map.lookup actorId actorMap
  let objectClassMap_ = objectClassMap classAttributeMap
  classId <- Map.lookup objectId objectClassMap_
  let value_ = value classAttributeMap
  Map.lookup classId value_