octane 0.4.18 → 0.4.19
raw patch · 13 files changed
+251/−124 lines, 13 filesdep ~autoexporterdep ~base
Dependency ranges changed: autoexporter, base
Files
- CHANGELOG.markdown +7/−0
- CHANGELOG.md +0/−7
- LICENSE.md +0/−23
- README.markdown +24/−0
- README.md +0/−11
- library/Octane/Parser.hs +187/−64
- library/Octane/Type/Replication.hs +1/−2
- library/Octane/Version.hs +6/−2
- octane.cabal +6/−6
- package.yaml +6/−6
- stack.yaml +1/−3
- test-suite/Octane/VersionSpec.hs +11/−0
- test-suite/OctaneSpec.hs +2/−0
+ CHANGELOG.markdown view
@@ -0,0 +1,7 @@+# Change log++Octane uses [Semantic Versioning][].+The change log is available through the [releases on GitHub][].++[Semantic Versioning]: http://semver.org/spec/v2.0.0.html+[releases on GitHub]: https://github.com/tfausak/octane/releases
− CHANGELOG.md
@@ -1,7 +0,0 @@-# Change log--Octane uses [Semantic Versioning][].-The change log is available through the [releases on GitHub][].--[Semantic Versioning]: http://semver.org/spec/v2.0.0.html-[releases on GitHub]: https://github.com/tfausak/octane/releases
− LICENSE.md
@@ -1,23 +0,0 @@-[The MIT License (MIT)][]--Copyright (c) 2016 Taylor Fausak <taylor@fausak.me>--Permission is hereby granted, free of charge, to any person obtaining a copy of-this software and associated documentation files (the "Software"), to deal in-the Software without restriction, including without limitation the rights to-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies-of the Software, and to permit persons to whom the Software is furnished to do-so, subject to the following conditions:--The above copyright notice and this permission notice shall be included in all-copies or substantial portions of the Software.--THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE-SOFTWARE.--[The MIT License (MIT)]: http://opensource.org/licenses/MIT
+ README.markdown view
@@ -0,0 +1,24 @@+# [Octane][]++[![Version badge][]][version]+[![Windows build badge][]][windows build]+[![Build badge][]][build]++Octane parses [Rocket League][] replays.++To use Octane, first download and unpack the [latest release][] for your+operating system. Then run `octane path-to/the.replay`.++The [Rocket League Replays wiki][] has links to other Rocket League replay+parsers.++[Octane]: https://github.com/tfausak/octane+[Version badge]: https://www.stackage.org/package/octane/badge/nightly?label=version+[version]: https://www.stackage.org/package/octane+[Windows build badge]: https://ci.appveyor.com/api/projects/status/github/tfausak/octane?branch=main&svg=true+[windows build]: https://ci.appveyor.com/project/TaylorFausak/octane+[Build badge]: https://travis-ci.org/tfausak/octane.svg?branch=main+[build]: https://travis-ci.org/tfausak/octane+[Rocket League]: http://www.rocketleaguegame.com+[latest release]: https://github.com/tfausak/octane/releases/latest+[Rocket League Replays wiki]: https://github.com/rocket-league-replays/rocket-league-replays/wiki/Rocket-League-Replay-Parsers
− README.md
@@ -1,11 +0,0 @@-## The Haskell Tool Stack--[](https://travis-ci.org/commercialhaskell/stack)-[](https://ci.appveyor.com/project/snoyberg/stack)-[](https://github.com/commercialhaskell/stack/releases)--`stack` is a cross-platform program for developing Haskell projects. It is aimed-at Haskellers both new and experienced.--See [haskellstack.org](http://haskellstack.org) or the `doc` directory for more-information.
library/Octane/Parser.hs view
@@ -7,36 +7,125 @@ import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as BSL import Data.Function ((&))+import qualified Data.IntMap as IntMap+import qualified Data.Maybe as Maybe+import qualified Data.Text as Text import qualified Octane.Type as Type parseFrames :: Type.Replay -> [Type.Frame] parseFrames replay =- Binary.runGet- (replay & extractContext & getFrames & Bits.runBitGet)- (replay & Type.replayStream & Newtype.unpack & BSL.fromStrict)+ let get = replay & extractContext & getFrames & Bits.runBitGet+ stream = replay & Type.replayStream & Newtype.unpack & BSL.fromStrict+ in Binary.runGet get stream --- TODO: This will need at least the actors and cache items.+type ObjectMap = IntMap.IntMap Text.Text++buildObjectMap :: Type.Replay -> ObjectMap+buildObjectMap replay =+ replay & Type.replayObjects & Newtype.unpack & map Newtype.unpack &+ zip [0 ..] &+ IntMap.fromAscList++type ClassMap = IntMap.IntMap Text.Text++buildClassMap :: Type.Replay -> ClassMap+buildClassMap replay =+ replay & Type.replayActors & Newtype.unpack &+ map+ (\x ->+ ( x & Type.actorTag & Newtype.unpack & fromIntegral+ , x & Type.actorName & Newtype.unpack)) &+ IntMap.fromList++type Cache = IntMap.IntMap Type.CacheItem++buildCache :: Type.Replay -> Cache+buildCache replay =+ replay & Type.replayCacheItems & Newtype.unpack &+ map+ (\x ->+ (x & Type.cacheItemTag & Newtype.unpack & fromIntegral, x)) &+ IntMap.fromList++type PropertyMap = IntMap.IntMap Text.Text++buildPropertyMap :: ObjectMap -> Cache -> Int -> PropertyMap+buildPropertyMap objectMap cache key =+ case IntMap.lookup key cache of+ Nothing -> IntMap.empty+ Just cacheItem ->+ let parentId =+ cacheItem & Type.cacheItemStart & Newtype.unpack &+ fromIntegral+ properties =+ cacheItem & Type.cacheItemCacheProperties & Newtype.unpack &+ map+ (\x ->+ ( x & Type.cachePropertyIndex & Newtype.unpack &+ fromIntegral+ , x & Type.cachePropertyTag & Newtype.unpack &+ fromIntegral)) &+ Maybe.mapMaybe+ (\(k,v) ->+ case IntMap.lookup v objectMap of+ Nothing -> Nothing+ Just name -> Just (k, name)) &+ IntMap.fromList+ in if key == parentId+ then properties+ else IntMap.union+ properties+ (buildPropertyMap objectMap cache parentId)++type ClassPropertyMap = IntMap.IntMap (IntMap.IntMap Text.Text)++buildClassPropertyMap :: Type.Replay -> ClassPropertyMap+buildClassPropertyMap replay =+ let objectMap = buildObjectMap replay+ classMap = buildClassMap replay+ cache = buildCache replay+ f k _ m =+ case IntMap.lookup k cache of+ Nothing -> m+ Just cacheItem ->+ let x =+ cacheItem & Type.cacheItemTag & Newtype.unpack &+ fromIntegral+ v = buildPropertyMap objectMap cache x+ in IntMap.insert k v m+ in IntMap.foldrWithKey f IntMap.empty classMap++getClass :: ObjectMap -> Int -> Maybe (Int, Text.Text)+getClass objectMap objectId =+ case IntMap.lookup objectId objectMap of+ Nothing -> Nothing+ Just name ->+ if name == Text.pack "TAGame.Default__PRI_TA" ||+ Text.isInfixOf (Text.pack "Archetype") name+ then getClass objectMap (objectId - 1)+ else Just (objectId, name)+ data Context = Context- {+ { contextObjectMap :: ObjectMap } extractContext :: Type.Replay -> Context-extractContext _replay =+extractContext replay = Context- {+ { contextObjectMap = buildObjectMap replay } getFrames :: Context -> Bits.BitGet [Type.Frame] getFrames context = do- maybeFrame <- getFrame context+ maybeFrame <- getMaybeFrame context case maybeFrame of Nothing -> return [] Just frame -> do frames <- getFrames context return (frame : frames) -getFrame :: Context -> Bits.BitGet (Maybe Type.Frame)-getFrame context = do+getMaybeFrame :: Context -> Bits.BitGet (Maybe Type.Frame)+getMaybeFrame context = do -- TODO: Convert time bytes into a float. time <- Bits.getByteString 32 -- TODO: Convert delta bytes into a float.@@ -44,72 +133,110 @@ if BS.all (== 0) time && BS.all (== 0) delta then return Nothing else do- replications <- getReplications context- let frame =- Type.Frame- { Type.frameTime = time- , Type.frameDelta = delta- , Type.frameReplications = replications- }+ frame <- getFrame context time delta return (Just frame) +type Time = BS.ByteString++type Delta = BS.ByteString++getFrame :: Context -> Time -> Delta -> Bits.BitGet Type.Frame+getFrame context time delta = do+ replications <- getReplications context+ let frame =+ Type.Frame+ { Type.frameTime = time+ , Type.frameDelta = delta+ , Type.frameReplications = replications+ }+ return frame+ getReplications :: Context -> Bits.BitGet [Type.Replication] getReplications context = do- (context',maybeReplication) <- getReplication context+ (context',maybeReplication) <- getMaybeReplication context case maybeReplication of Nothing -> return [] Just replication -> do replications <- getReplications context' return (replication : replications) -getReplication :: Context -> Bits.BitGet (Context, Maybe Type.Replication)-getReplication context = do+getMaybeReplication :: Context -> Bits.BitGet (Context, Maybe Type.Replication)+getMaybeReplication context = do hasReplication <- Bits.getBool if not hasReplication then return (context, Nothing) else do- -- TODO: Convert actor ID into an integer.- actorId <- Bits.getByteString (bitSize maxChannels)- isOpen <- Bits.getBool+ (newContext,replication) <- getReplication context+ return (newContext, Just replication)++getReplication :: Context -> Bits.BitGet (Context, Type.Replication)+getReplication context = do+ actorId <- getInt maxChannels+ isOpen <- Bits.getBool+ let go = if isOpen- then do- isNew <- Bits.getBool- if isNew- then do- _unknownFlag <- Bits.getBool- _objectId <- getInt 32- -- TODO: Parse new actor.- return- ( context- , Just- (Type.Replication- { Type.replicationActorId = actorId- , Type.replicationIsOpen = isOpen- , Type.replicationIsNew = Just isNew- }))- else do- let maybeClassId = getClassId context actorId- case maybeClassId of- Nothing -> fail "TODO: Could not get class ID."- Just _classId ->- -- TODO: Parse existing actor.- return- ( context- , Just- (Type.Replication- { Type.replicationActorId = actorId- , Type.replicationIsOpen = isOpen- , Type.replicationIsNew = Just isNew- }))- else return- ( context- , Just- (Type.Replication- { Type.replicationActorId = actorId- , Type.replicationIsOpen = isOpen- , Type.replicationIsNew = Nothing- }))+ then getOpenReplication+ else getClosedReplication+ go context actorId +type ActorId = Int++getOpenReplication :: Context+ -> ActorId+ -> Bits.BitGet (Context, Type.Replication)+getOpenReplication context actorId = do+ isNew <- Bits.getBool+ let go =+ if isNew+ then getNewReplication+ else getExistingReplication+ go context actorId++getNewReplication :: Context+ -> ActorId+ -> Bits.BitGet (Context, Type.Replication)+getNewReplication context actorId = do+ _unknownFlag <- Bits.getBool+ _objectId <- getInt 32+ -- TODO: Parse new actor.+ return+ ( context+ , Type.Replication+ { Type.replicationActorId = actorId+ , Type.replicationIsOpen = True+ , Type.replicationIsNew = Just True+ })++getExistingReplication :: Context+ -> ActorId+ -> Bits.BitGet (Context, Type.Replication)+getExistingReplication context actorId = do+ let maybeClass = getClass (contextObjectMap context) actorId+ case maybeClass of+ Nothing ->+ fail ("TODO: Could not get class for object " ++ show actorId)+ Just (_classId,_className) ->+ -- TODO: Parse existing actor.+ return+ ( context+ , Type.Replication+ { Type.replicationActorId = actorId+ , Type.replicationIsOpen = True+ , Type.replicationIsNew = Just True+ })++getClosedReplication :: Context+ -> ActorId+ -> Bits.BitGet (Context, Type.Replication)+getClosedReplication context actorId = do+ return+ ( context+ , Type.Replication+ { Type.replicationActorId = actorId+ , Type.replicationIsOpen = False+ , Type.replicationIsNew = Nothing+ })+ maxChannels :: (Integral a) => a@@ -145,7 +272,3 @@ go (i + 1) newValue else return value go 0 0---- TODO: Actually implement this.-getClassId :: Context -> BS.ByteString -> Maybe ()-getClassId _context _actorId = Nothing
library/Octane/Type/Replication.hs view
@@ -3,11 +3,10 @@ module Octane.Type.Replication (Replication(..)) where import qualified Control.DeepSeq as DeepSeq-import qualified Data.ByteString as BS import qualified GHC.Generics as Generics data Replication = Replication- { replicationActorId :: BS.ByteString+ { replicationActorId :: Int , replicationIsOpen :: Bool , replicationIsNew :: Maybe Bool } deriving (Eq,Generics.Generic,Show)
library/Octane/Version.hs view
@@ -1,3 +1,7 @@-module Octane.Version (module Paths_octane) where+module Octane.Version where -import Paths_octane (version)+import qualified Data.Version as Version+import qualified Paths_octane as This++version :: Version.Version+version = This.version
octane.cabal view
@@ -3,7 +3,7 @@ -- see: https://github.com/sol/hpack name: octane-version: 0.4.18+version: 0.4.19 synopsis: Parse Rocket League replays. description: Octane parses Rocket League replays. category: Game@@ -16,10 +16,9 @@ cabal-version: >= 1.10 extra-source-files:- CHANGELOG.md- LICENSE.md+ CHANGELOG.markdown package.yaml- README.md+ README.markdown stack.yaml source-repository head@@ -33,8 +32,8 @@ build-depends: aeson ==0.11.* , aeson-pretty ==0.7.*- , autoexporter >=0.1 && <0.3- , base >=4.8 && <4.10+ , autoexporter ==0.2.*+ , base ==4.* , binary ==0.7.* , binary-bits ==0.5.* , bytestring ==0.10.*@@ -116,6 +115,7 @@ Octane.Type.PropertySpec Octane.Type.ReplaySpec Octane.TypeSpec+ Octane.VersionSpec OctaneSpec default-language: Haskell2010
package.yaml view
@@ -27,10 +27,9 @@ main: Executable.hs source-dirs: executable extra-source-files:-- CHANGELOG.md-- LICENSE.md+- CHANGELOG.markdown - package.yaml-- README.md+- README.markdown - stack.yaml ghc-options: -Wall github: tfausak/octane@@ -38,8 +37,8 @@ dependencies: - aeson ==0.11.* - aeson-pretty ==0.7.*- - autoexporter >=0.1 && <0.3- - base >=4.8 && <4.10+ - autoexporter ==0.2.*+ - base ==4.* - binary ==0.7.* - binary-bits ==0.5.* - bytestring ==0.10.*@@ -51,6 +50,7 @@ other-modules: Paths_octane source-dirs: library license: MIT+license-file: LICENSE.markdown maintainer: Taylor Fausak name: octane synopsis: Parse Rocket League replays.@@ -70,4 +70,4 @@ - -with-rtsopts=-N main: TestSuite.hs source-dirs: test-suite-version: '0.4.18'+version: '0.4.19'
stack.yaml view
@@ -1,3 +1,1 @@-install-ghc: true-require-stack-version: ! '>=1.0.4'-resolver: nightly-2016-04-14+resolver: nightly-2016-05-02
+ test-suite/Octane/VersionSpec.hs view
@@ -0,0 +1,11 @@+module Octane.VersionSpec (spec) where++import Octane+import Test.Tasty.Hspec++import qualified Data.Version as Version++spec :: Spec+spec = describe "Version" $ do+ it "has some parts" $ do+ shouldBe (null (Version.versionBranch version)) False
test-suite/OctaneSpec.hs view
@@ -4,8 +4,10 @@ import qualified Octane.MainSpec import qualified Octane.TypeSpec+import qualified Octane.VersionSpec spec :: Spec spec = describe "Octane" $ do Octane.MainSpec.spec Octane.TypeSpec.spec+ Octane.VersionSpec.spec