octane 0.7.0 → 0.8.0
raw patch · 7 files changed
+503/−94 lines, 7 files
Files
- library/Octane.hs +2/−0
- library/Octane/Data.hs +11/−11
- library/Octane/FullReplay.hs +453/−0
- library/Octane/Main.hs +9/−68
- library/Octane/Parser.hs +25/−13
- octane.cabal +2/−1
- package.yaml +1/−1
library/Octane.hs view
@@ -1,5 +1,6 @@ module Octane ( module Octane.Data+ , module Octane.FullReplay , module Octane.Main , module Octane.Parser , module Octane.Type@@ -7,6 +8,7 @@ ) where import Octane.Data+import Octane.FullReplay import Octane.Main import Octane.Parser import Octane.Type
library/Octane/Data.hs view
@@ -1052,17 +1052,17 @@ , ("Washington Wizards", 1275) -- Video Games , ("Blacklight", 385)- , ("Blacklight: Retribution", todo)- , ("Chivalry - Agatha Knights", todo)- , ("Chivalry - Mason Order", todo)- , ("Edge Of Space", todo)- , ("Euro Truck Simulator Rig", todo)- , ("Fallout - Vault Boy", todo)- , ("Fenix Rage", todo)- , ("Goat Simulator - G2", todo)- , ("Goat Simulator - Goatenna", todo)- , ("Oddworld - Abe", todo)- , ("Oddworld - Molluck", todo)+ , ("Blacklight: Retribution", 211)+ , ("Chivalry - Agatha Knights", 525)+ , ("Chivalry - Mason Order", 526)+ , ("Edge Of Space", 213)+ , ("Euro Truck Simulator Rig", 813)+ , ("Fallout - Vault Boy", 673)+ , ("Fenix Rage", 216)+ , ("Goat Simulator - G2", 927)+ , ("Goat Simulator - Goatenna", 867)+ , ("Oddworld - Abe", 678)+ , ("Oddworld - Molluck", 679) , ("Oddworld - Necrum", todo) , ("Oddworld - RuptureFarms", todo) , ("Portal - Aperture Laboratories", todo)
+ library/Octane/FullReplay.hs view
@@ -0,0 +1,453 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}++module Octane.FullReplay+ ( FullReplay+ , parseReplay+ , parseReplayFile+ , unsafeParseReplay+ , unsafeParseReplayFile+ ) where++import Data.Aeson ((.=))+import Data.Function ((&))+import Data.Monoid ((<>))+import Prelude ((==), (/=), (&&))++import qualified Control.DeepSeq as DeepSeq+import qualified Control.Monad as Monad+import qualified Data.Aeson as Aeson+import qualified Data.Binary as Binary+import qualified Data.ByteString.Lazy as ByteString+import qualified Data.Foldable as Foldable+import qualified Data.IntMap.Strict as IntMap+import qualified Data.Map.Strict as Map+import qualified Data.Text as Text+import qualified Data.Version as Version+import qualified GHC.Generics as Generics+import qualified Octane.Parser as Parser+import qualified Octane.Type as Type+import qualified Prelude+++newtype FullReplay = FullReplay+ { unpackFullReplay :: (Type.Replay, [Parser.Frame])+ } deriving (Generics.Generic, Prelude.Show)++instance DeepSeq.NFData FullReplay++instance Aeson.ToJSON FullReplay where+ toJSON fullReplay = do+ Aeson.object+ [ "Version" .= getVersion fullReplay+ , "Metadata" .= getMetadata fullReplay+ , "Levels" .= getLevels fullReplay+ , "Messages" .= getMessages fullReplay+ , "TickMarks" .= getTickMarks fullReplay+ , "Frames" .= getFrames fullReplay+ ]+++newFullReplay :: Type.Replay -> [Parser.Frame] -> FullReplay+newFullReplay replay frames = FullReplay (replay, frames)+++getVersion :: FullReplay -> Prelude.String+getVersion fullReplay =+ [ fullReplay+ & unpackFullReplay+ & Prelude.fst+ & Type.replayVersion1+ & Type.unpackWord32LE+ & Prelude.fromIntegral+ , fullReplay+ & unpackFullReplay+ & Prelude.fst+ & Type.replayVersion2+ & Type.unpackWord32LE+ & Prelude.fromIntegral+ ] & Version.makeVersion & Version.showVersion+++getMetadata :: FullReplay -> Map.Map Text.Text Aeson.Value+getMetadata fullReplay = fullReplay+ & unpackFullReplay+ & Prelude.fst+ & Type.replayProperties+ & Type.unpackDictionary+ & Map.mapKeys Type.unpackPCString+ & Map.map Aeson.toJSON+++getLevels :: FullReplay -> [Text.Text]+getLevels fullReplay = fullReplay+ & unpackFullReplay+ & Prelude.fst+ & Type.replayLevels+ & Type.unpackList+ & Prelude.map Type.unpackPCString+++getMessages :: FullReplay -> Map.Map Text.Text Type.PCString+getMessages fullReplay = fullReplay+ & unpackFullReplay+ & Prelude.fst+ & Type.replayMessages+ & Type.unpackList+ & Prelude.map (\ message ->+ ( message+ & Type.messageFrame+ & Type.unpackWord32LE+ & Prelude.show+ & Text.pack+ , message & Type.messageContent+ ))+ & Map.fromList+++getTickMarks :: FullReplay -> Map.Map Text.Text Type.PCString+getTickMarks fullReplay = fullReplay+ & unpackFullReplay+ & Prelude.fst+ & Type.replayMarks+ & Type.unpackList+ & Prelude.map (\ mark ->+ ( mark+ & Type.markFrame+ & Type.unpackWord32LE+ & Prelude.show+ & Text.pack+ , mark & Type.markLabel+ ))+ & Map.fromList+++getFrames :: FullReplay -> [Map.Map Text.Text Aeson.Value]+getFrames fullReplay = fullReplay+ & unpackFullReplay+ & Prelude.snd+ & Foldable.foldl'+ (\ (state, frames) frame -> let+ newState = updateState frame state+ minimalFrame = getDelta state frame+ in (newState, minimalFrame : frames))+ (initialState, [])+ & Prelude.snd+ & Prelude.reverse+ & Prelude.map (\ frame -> Map.fromList+ [ ("IsKeyFrame", frame & Parser.frameIsKeyFrame & Aeson.toJSON)+ , ("Number", frame & Parser.frameNumber & Aeson.toJSON)+ , ("Time", frame & Parser.frameTime & Aeson.toJSON)+ , ("Delta", frame & Parser.frameDelta & Aeson.toJSON)+ , ("Spawned", getSpawnedActors frame)+ , ("Updated", getUpdatedActors frame)+ , ("Destroyed", getDestroyedActors frame)+ ])+++-- { actor id => (alive?, { property name => property value } ) }+type State = IntMap.IntMap (Prelude.Bool, Map.Map Text.Text Parser.PropValue)+++initialState :: State+initialState = IntMap.empty+++updateState :: Parser.Frame -> State -> State+updateState frame state1 = let+ spawned = frame+ & Parser.frameReplications+ & Prelude.filter (\ replication -> replication+ & Parser.replicationState+ & (== Parser.RSOpening))+ & Prelude.map Parser.replicationActorId+ state2 = spawned+ & Prelude.foldr+ (IntMap.alter (\ maybeValue -> Prelude.Just (case maybeValue of+ Prelude.Nothing -> (Prelude.True, Map.empty)+ Prelude.Just (_, properties) -> (Prelude.True, properties))))+ state1++ destroyed = frame+ & Parser.frameReplications+ & Prelude.filter (\ replication -> replication+ & Parser.replicationState+ & (== Parser.RSClosing))+ & Prelude.map Parser.replicationActorId+ state3 = destroyed+ & Prelude.foldr+ (IntMap.alter (\ maybeValue -> Prelude.Just (case maybeValue of+ Prelude.Nothing -> (Prelude.False, Map.empty)+ Prelude.Just (_, properties) -> (Prelude.False, properties))))+ state2++ updated = frame+ & Parser.frameReplications+ & Prelude.filter (\ replication -> replication+ & Parser.replicationState+ & (== Parser.RSExisting))+ state4 = updated+ & Prelude.foldr+ (\ replication -> IntMap.alter+ (\ maybeValue -> Prelude.Just (case maybeValue of+ Prelude.Nothing ->+ (Prelude.True, Parser.replicationProperties replication)+ Prelude.Just (alive, properties) ->+ ( alive+ , Map.union+ (Parser.replicationProperties replication)+ properties+ )))+ (Parser.replicationActorId replication))+ state3++ in state4+++getDelta :: State -> Parser.Frame -> Parser.Frame+getDelta state frame = let+ newReplications = frame+ & Parser.frameReplications+ -- Remove replications that aren't actually new.+ & reject (\ replication -> let+ isOpening = Parser.replicationState replication == Parser.RSOpening+ actorId = Parser.replicationActorId replication+ currentState = IntMap.lookup actorId state+ isAlive = Prelude.fmap Prelude.fst currentState+ wasAlreadyAlive = isAlive == Prelude.Just Prelude.True+ in isOpening && wasAlreadyAlive)+ -- Remove properties that haven't changed.+ & Prelude.map (\ replication ->+ if Parser.replicationState replication == Parser.RSExisting+ then let+ actorId = Parser.replicationActorId replication+ currentState = IntMap.findWithDefault+ (Prelude.True, Map.empty) actorId state+ currentProperties = Prelude.snd currentState+ newProperties = Parser.replicationProperties replication+ changes = newProperties+ & Map.filterWithKey (\ name newValue -> let+ oldValue = Map.lookup name currentProperties+ in Prelude.Just newValue /= oldValue)+ in replication { Parser.replicationProperties = changes }+ else replication)+ in frame { Parser.frameReplications = newReplications }+++reject :: (a -> Prelude.Bool) -> [a] -> [a]+reject p xs = Prelude.filter (\ x -> Prelude.not (p x)) xs+++getSpawnedActors :: Parser.Frame -> Aeson.Value+getSpawnedActors frame = frame+ & Parser.frameReplications+ & Prelude.filter (\ replication -> replication+ & Parser.replicationState+ & (== Parser.RSOpening))+ & Prelude.map (\ replication ->+ ( replication+ & Parser.replicationActorId+ & Prelude.show+ & Text.pack+ , Aeson.object+ [ ("Name", replication+ & Parser.replicationObjectName+ & Aeson.toJSON)+ , ("Class", replication+ & Parser.replicationClassName+ & Aeson.toJSON)+ , ("Position", replication+ & Parser.replicationInitialization+ & Prelude.fmap Parser.classInitLocation+ & Monad.join+ & Aeson.toJSON)+ , ("Rotation", replication+ & Parser.replicationInitialization+ & Prelude.fmap Parser.classInitRotation+ & Monad.join+ & Aeson.toJSON)+ ]+ ))+ & Aeson.object+++getUpdatedActors :: Parser.Frame -> Aeson.Value+getUpdatedActors frame = frame+ & Parser.frameReplications+ & Prelude.filter (\ replication -> replication+ & Parser.replicationState+ & (== Parser.RSExisting))+ & Prelude.map (\ replication ->+ ( replication+ & Parser.replicationActorId+ & Prelude.show+ & Text.pack+ , replication+ & Parser.replicationProperties+ & Map.map (\ property -> Aeson.object+ [ ("Type", getPropertyName property)+ , ("Value", getPropertyValue property)+ ])+ ))+ & reject (\ (_, properties) -> Map.null properties)+ & Prelude.map (\ (actorId, properties) ->+ (actorId, Aeson.toJSON properties))+ & Aeson.object+++getPropertyName :: Parser.PropValue -> Aeson.Value+getPropertyName property = case property of+ Parser.PBoolean _ -> "Boolean"+ Parser.PByte _ -> "Byte"+ Parser.PCamSettings _ _ _ _ _ _ -> "CameraSettings"+ Parser.PDemolish _ _ _ _ _ _ -> "Demolition"+ Parser.PEnum _ _ -> "Enum"+ Parser.PExplosion _ _ _ -> "Explosion"+ Parser.PFlaggedInt _ _ -> "FlaggedInt"+ Parser.PFloat _ -> "Float"+ Parser.PGameMode _ -> "GameMode"+ Parser.PInt _ -> "Int"+ Parser.PLoadout _ _ _ _ _ _ _ _ _ -> "Loadout"+ Parser.PLoadoutOnline _ _ _ _ -> "OnlineLoadout"+ Parser.PLocation _ -> "Position"+ Parser.PMusicStinger _ _ _ -> "MusicStinger"+ Parser.PPickup _ _ _ -> "Pickup"+ Parser.PPrivateMatchSettings _ _ _ _ _ _ -> "PrivateMatchSettings"+ Parser.PQWord _ _ -> "QWord"+ Parser.PRelativeRotation _ -> "RelativeRotation"+ Parser.PReservation _ _ _ _ _ _ _ -> "Reservation"+ Parser.PRigidBodyState _ _ _ _ _ -> "RigidBodyState"+ Parser.PString _ -> "String"+ Parser.PTeamPaint _ _ _ _ _ -> "Paint"+ Parser.PUniqueId _ _ _ -> "UniqueId"+++getPropertyValue :: Parser.PropValue -> Aeson.Value+getPropertyValue property = case property of+ Parser.PBoolean x -> Aeson.toJSON x+ Parser.PByte x -> Aeson.toJSON x+ Parser.PCamSettings fov height angle distance stiffness swivelSpeed -> Aeson.object+ [ ("FOV", Aeson.toJSON fov)+ , ("Height", Aeson.toJSON height)+ , ("Angle", Aeson.toJSON angle)+ , ("Distance", Aeson.toJSON distance)+ , ("Stiffness", Aeson.toJSON stiffness)+ , ("SwivelSpeed", Aeson.toJSON swivelSpeed)+ ]+ Parser.PDemolish a b c d e f -> Aeson.toJSON (a, b, c, d, e, f)+ Parser.PEnum x y -> Aeson.toJSON (x, y)+ Parser.PExplosion a b c -> Aeson.toJSON (a, b, c)+ Parser.PFlaggedInt x y -> Aeson.toJSON (x, y)+ Parser.PFloat x -> Aeson.toJSON x+ Parser.PGameMode x -> case x of+ 1 -> "Hockey"+ 2 -> "Hoops"+ _ -> Aeson.String ("Unknown game mode " <> Text.pack (Prelude.show x))+ Parser.PInt x -> Aeson.toJSON x+ Parser.PLoadout version body decal wheels rocketTrail antenna topper x y -> Aeson.object+ [ ("Version", Aeson.toJSON version)+ , ("Body", Aeson.toJSON body)+ , ("Decal", Aeson.toJSON decal)+ , ("Wheels", Aeson.toJSON wheels)+ , ("RocketTrail", Aeson.toJSON rocketTrail)+ , ("Antenna", Aeson.toJSON antenna)+ , ("Topper", Aeson.toJSON topper)+ , ("Unknown1", Aeson.toJSON x)+ , ("Unknown2", Aeson.toJSON y)+ ]+ Parser.PLoadoutOnline a b c d -> Aeson.toJSON (a, b, c, d)+ Parser.PLocation x -> Aeson.toJSON x+ Parser.PMusicStinger a b c -> Aeson.toJSON (a, b, c)+ Parser.PPickup a b c -> Aeson.toJSON (a, b, c)+ Parser.PPrivateMatchSettings mutators joinableBy maxPlayers name password x -> Aeson.object+ [ ("Mutators", Aeson.toJSON mutators)+ , ("JoinableBy", Aeson.toJSON joinableBy)+ , ("MaxPlayers", Aeson.toJSON maxPlayers)+ , ("Name", Aeson.toJSON name)+ , ("Password", Aeson.toJSON password)+ , ("Unknown", Aeson.toJSON x)+ ]+ Parser.PQWord a b -> Aeson.toJSON (a, b)+ Parser.PRelativeRotation x -> Aeson.toJSON x+ Parser.PReservation number systemId remoteId localId name x y -> Aeson.object+ [ ("Number", Aeson.toJSON number)+ , ("SystemId", Aeson.toJSON systemId)+ , ("RemoteId", Aeson.toJSON remoteId)+ , ("LocalId", Aeson.toJSON localId)+ , ("Name", Aeson.toJSON name)+ , ("Unknown1", Aeson.toJSON x)+ , ("Unknown2", Aeson.toJSON y)+ ]+ Parser.PRigidBodyState sleeping position rotation linear angular -> Aeson.object+ [ ("Sleeping", Aeson.toJSON sleeping)+ , ("Position", Aeson.toJSON position)+ , ("Rotation", Aeson.toJSON rotation)+ , ("LinearVelocity", Aeson.toJSON linear)+ , ("AngularVelocity", Aeson.toJSON angular)+ ]+ Parser.PString x -> Aeson.toJSON x+ Parser.PTeamPaint team color1 color2 finish1 finish2 -> Aeson.object+ [ ("Team", Aeson.toJSON team)+ , ("PrimaryColor", Aeson.toJSON color1)+ , ("AccentColor", Aeson.toJSON color2)+ , ("PrimaryFinish", Aeson.toJSON finish1)+ , ("AccentFinish", Aeson.toJSON finish2)+ ]+ Parser.PUniqueId systemId remoteId localId -> Aeson.object+ [ ("System", case systemId of+ 0 -> "Local"+ 1 -> "Steam"+ 2 -> "PlayStation"+ 4 -> "Xbox"+ _ -> Aeson.String ("Unknown system " <> Text.pack (Prelude.show systemId)))+ , ("Remote", case remoteId of+ Parser.SplitscreenId x -> Aeson.toJSON x+ Parser.SteamId x -> Aeson.toJSON x+ Parser.PlayStationId x -> Aeson.toJSON x+ Parser.XboxId x -> Aeson.toJSON x)+ , ("Local", Aeson.toJSON localId)+ ]++getDestroyedActors :: Parser.Frame -> Aeson.Value+getDestroyedActors frame = frame+ & Parser.frameReplications+ & Prelude.filter (\ replication -> replication+ & Parser.replicationState+ & (== Parser.RSClosing))+ & Prelude.map Parser.replicationActorId+ & Aeson.toJSON+++parseReplay :: ByteString.ByteString -> Prelude.Either Text.Text FullReplay+parseReplay bytes = do+ case Binary.decodeOrFail bytes of+ Prelude.Left (_, _, message) -> do+ Prelude.Left (Text.pack message)+ Prelude.Right (_, _, replay) -> do+ let frames = Parser.parseFrames replay+ Prelude.Right (newFullReplay replay frames)+++parseReplayFile :: Prelude.FilePath -> Prelude.IO (Prelude.Either Text.Text FullReplay)+parseReplayFile file = do+ result <- Binary.decodeFileOrFail file+ case result of+ Prelude.Left (_, message) -> do+ Prelude.pure (Prelude.Left (Text.pack message))+ Prelude.Right replay -> do+ let frames = Parser.parseFrames replay+ Prelude.pure (Prelude.Right (newFullReplay replay frames))+++unsafeParseReplay :: ByteString.ByteString -> FullReplay+unsafeParseReplay bytes = do+ let replay = Binary.decode bytes+ let frames = Parser.parseFrames replay+ newFullReplay replay frames+++unsafeParseReplayFile :: Prelude.FilePath -> Prelude.IO FullReplay+unsafeParseReplayFile file = do+ replay <- Binary.decodeFile file+ let frames = Parser.parseFrames replay+ Prelude.pure (newFullReplay replay frames)
library/Octane/Main.hs view
@@ -1,75 +1,16 @@ module Octane.Main (main) where import qualified Control.DeepSeq as DeepSeq-import qualified Control.Monad as Monad import qualified Data.Aeson as Aeson-import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString as BS-import qualified Data.ByteString.Lazy as BSL-import qualified Data.ByteString.Lazy.Char8 as BSL8-import Data.Function ((&))-import qualified Data.Map as Map-import qualified Data.Maybe as Maybe-import qualified Data.Text as Text-import qualified Octane.Parser as Parser-import qualified Octane.Type as Type+import qualified Data.ByteString.Lazy.Char8 as ByteString+import qualified Octane.FullReplay as FullReplay+import qualified Prelude import qualified System.Environment as Environment-import qualified System.IO as IO -main :: IO ()-main = do- files <- Environment.getArgs- contents <- mapM BS.readFile files- results <- mapM Binary.decodeFileOrFail files- mapM_ debug (zip3 files contents results) -debug :: (String, BS.ByteString, Either (Binary.ByteOffset, String) Type.Replay)- -> IO ()-debug (file,contents,result) =- case result of- Left (offset,message) -> error- ( file- ++ " @ byte "- ++ show offset- ++ ": "- ++ message- )- Right replay -> do- let inputSize = contents & BS.length & fromIntegral- let outputSize = replay- & Binary.encode- & BSL.length- & DeepSeq.deepseq replay- Monad.when (inputSize /= outputSize) (IO.hPutStrLn IO.stderr- ( "input size "- ++ show inputSize- ++ " not equal to output size "- ++ show outputSize- ))-- let frames = Parser.parseFrames replay- let expectedFrames = replay- & Type.replayProperties- & Type.unpackDictionary- & Map.lookup ("NumFrames" & Text.pack & Type.PCString)- & Maybe.fromMaybe (Type.IntProperty (Type.Word64LE 4) (Type.Word32LE 0))- let actualFrames = frames- & length- & fromIntegral- & Type.Word32LE- & Type.IntProperty (Type.Word64LE 4)- & DeepSeq.deepseq frames- Monad.when (expectedFrames /= actualFrames) (error- ( "expected "- ++ show expectedFrames- ++ " frames but found "- ++ show actualFrames- ++ " frames"- ))-- putStr "{\"meta\":"- replay & Aeson.encode & BSL8.putStrLn- putStr ",\"frames\":"- frames & Aeson.encode & BSL8.putStrLn- putStrLn "}"+main :: Prelude.IO ()+main = do+ [file] <- Environment.getArgs+ fullReplay <- FullReplay.unsafeParseReplayFile file+ DeepSeq.deepseq fullReplay (Prelude.pure ())+ ByteString.putStrLn (Aeson.encode fullReplay)
library/Octane/Parser.hs view
@@ -36,29 +36,29 @@ & (\ property -> case property of Just (Type.IntProperty _ x) -> x & Type.unpackWord32LE & fromIntegral _ -> 0)- get = replay & extractContext & getFrames numFrames & Bits.runBitGet+ get = replay & extractContext & getFrames 0 numFrames & Bits.runBitGet stream = replay & Type.replayStream & Type.unpackStream & BSL.fromStrict (_context, frames) = Binary.runGet get stream in frames -getFrames :: Int -> Context -> Bits.BitGet (Context, [Frame])-getFrames numFrames context = do- if numFrames < 1+getFrames :: Int -> Int -> Context -> Bits.BitGet (Context, [Frame])+getFrames number numFrames context = do+ if number >= numFrames then return (context, []) else do isEmpty <- Bits.isEmpty if isEmpty then return (context, []) else do- maybeFrame <- getMaybeFrame context+ maybeFrame <- getMaybeFrame context number case maybeFrame of Nothing -> return (context, []) Just (newContext, frame) -> do- (newerContext, frames) <- getFrames (numFrames - 1) newContext+ (newerContext, frames) <- getFrames (number + 1) numFrames newContext return (newerContext, (frame : frames)) -getMaybeFrame :: Context -> Bits.BitGet (Maybe (Context, Frame))-getMaybeFrame context = do+getMaybeFrame :: Context -> Int -> Bits.BitGet (Maybe (Context, Frame))+getMaybeFrame context number = do time <- getFloat32 delta <- getFloat32 if time == 0 && delta == 0@@ -66,15 +66,17 @@ else if time < 0.001 || delta < 0.001 then error ("parsing previous frame probably failed. time: " ++ show time ++ ", delta: " ++ show delta) else do- (newContext, frame) <- getFrame context time delta+ (newContext, frame) <- getFrame context number time delta return (Just (newContext, frame)) -getFrame :: Context -> Time -> Delta -> Bits.BitGet (Context, Frame)-getFrame context time delta = do+getFrame :: Context -> Int -> Time -> Delta -> Bits.BitGet (Context, Frame)+getFrame context number time delta = do (newContext, replications) <- getReplications context let frame = Frame- { frameTime = time+ { frameNumber = number+ , frameIsKeyFrame = context & contextKeyFrames & Set.member number+ , frameTime = time , frameDelta = delta , frameReplications = replications }@@ -606,7 +608,9 @@ -- | A frame in the net stream. Each frame has the time since the beginning of -- the match, the time since the last frame, and a list of replications. data Frame = Frame- { frameTime :: !Float+ { frameNumber :: !Int+ , frameIsKeyFrame :: !Bool+ , frameTime :: !Float , frameDelta :: !Float , frameReplications :: ![Replication] } deriving (Eq,Generics.Generic,Show)@@ -696,6 +700,7 @@ , contextClassPropertyMap :: !ClassPropertyMap , contextThings :: !(IntMap.IntMap Thing) , contextClassMap :: !ClassMap+ , contextKeyFrames :: !(Set.Set Int) } deriving (Eq, Generics.Generic, Show) instance DeepSeq.NFData Context@@ -707,6 +712,13 @@ , contextClassPropertyMap = CPM.getClassPropertyMap replay , contextThings = IntMap.empty , contextClassMap = CPM.getActorMap replay+ , contextKeyFrames = replay+ & Type.replayKeyFrames+ & Type.unpackList+ & map Type.keyFrameFrame+ & map Type.unpackWord32LE+ & map fromIntegral+ & Set.fromList } byteStringToFloat :: BS.ByteString -> Float
octane.cabal view
@@ -3,7 +3,7 @@ -- see: https://github.com/sol/hpack name: octane-version: 0.7.0+version: 0.8.0 synopsis: Parse Rocket League replays. description: Octane parses Rocket League replays. category: Game@@ -45,6 +45,7 @@ Octane Octane.Analyzer Octane.Data+ Octane.FullReplay Octane.Json Octane.Main Octane.Parser
package.yaml view
@@ -69,4 +69,4 @@ - -with-rtsopts=-N main: TestSuite.hs source-dirs: test-suite-version: '0.7.0'+version: '0.8.0'