module Octane.Type.Replay
( Replay(..)
) where
import Data.Aeson ((.=))
import Data.Function ((&))
import qualified Data.Aeson as Aeson
import qualified Data.Binary as Binary
import qualified Data.Bits as Bits
import qualified Data.ByteString.Lazy as LazyBytes
import qualified Data.Default.Class as Default
import qualified Data.Map.Strict as Map
import qualified Data.Maybe as Maybe
import qualified Data.OverloadedRecords.TH as OverloadedRecords
import qualified Data.Text as StrictText
import qualified Data.Version as Version
import qualified Octane.Type.Boolean as Boolean
import qualified Octane.Type.CompressedWord as CompressedWord
import qualified Octane.Type.Dictionary as Dictionary
import qualified Octane.Type.Float32 as Float32
import qualified Octane.Type.Frame as Frame
import qualified Octane.Type.Initialization as Initialization
import qualified Octane.Type.Int32 as Int32
import qualified Octane.Type.Int8 as Int8
import qualified Octane.Type.List as List
import qualified Octane.Type.Property as Property
import qualified Octane.Type.RemoteId as RemoteId
import qualified Octane.Type.Replication as Replication
import qualified Octane.Type.State as State
import qualified Octane.Type.Text as Text
import qualified Octane.Type.Value as Value
import qualified Octane.Type.Vector as Vector
import qualified Octane.Type.Word16 as Word16
import qualified Octane.Type.Word32 as Word32
import qualified Octane.Type.Word64 as Word64
import qualified Octane.Type.Word8 as Word8
import qualified Octane.Utility.Optimizer as Optimizer
import qualified Rattletrap
data Replay = Replay
{ replayVersion :: Version.Version
, replayMetadata :: Map.Map StrictText.Text Property.Property
, replayLevels :: [StrictText.Text]
, replayMessages :: Map.Map StrictText.Text StrictText.Text
, replayTickMarks :: Map.Map StrictText.Text StrictText.Text
, replayPackages :: [StrictText.Text]
, replayFrames :: [Frame.Frame]
} deriving (Eq, Show)
$(OverloadedRecords.overloadedRecord Default.def ''Replay)
instance Binary.Binary Replay where
get = do
rawReplay <- Rattletrap.getReplay
let replay = fromRawReplay rawReplay
pure replay
put = undefined
instance Aeson.ToJSON Replay where
toJSON replay =
Aeson.object
[ "Version" .= #version replay
, "Metadata" .= #metadata replay
, "Levels" .= #levels replay
, "Messages" .= #messages replay
, "TickMarks" .= #tickMarks replay
, "Packages" .= #packages replay
, "Frames" .= #frames replay
]
fromRawReplay :: Rattletrap.Replay -> Replay
fromRawReplay replay =
let header = replay & Rattletrap.replayHeader & Rattletrap.sectionBody
fromWord32 x = x & Rattletrap.word32Value & fromIntegral
content = replay & Rattletrap.replayContent & Rattletrap.sectionBody
version =
Version.makeVersion
[ header & Rattletrap.headerEngineVersion & fromWord32
, header & Rattletrap.headerLicenseeVersion & fromWord32
]
metadata =
header & Rattletrap.headerProperties & Rattletrap.dictionaryValue &
Map.map toProperty
levels =
content & Rattletrap.contentLevels & Rattletrap.listValue &
map Rattletrap.textToString &
map StrictText.pack
messages =
content & Rattletrap.contentMessages & Rattletrap.listValue &
map
(\message ->
( message & Rattletrap.messageFrame & Rattletrap.word32Value & show &
StrictText.pack
, message & Rattletrap.messageValue & Rattletrap.textToString &
StrictText.pack)) &
Map.fromList
tickMarks =
content & Rattletrap.contentMarks & Rattletrap.listValue &
map
(\mark ->
( mark & Rattletrap.markFrame & Rattletrap.word32Value & show &
StrictText.pack
, mark & Rattletrap.markValue & Rattletrap.textToString &
StrictText.pack)) &
Map.fromList
packages =
content & Rattletrap.contentPackages & Rattletrap.listValue &
map Rattletrap.textToString &
map StrictText.pack
(frames, _actorMap) =
content & Rattletrap.contentFrames & zip [0 ..] &
foldl
(\(fs, m) f ->
let (f', m') = toFrame m f
in (f' : fs, m'))
([], Map.empty)
in Replay
{ replayVersion = version
, replayMetadata = metadata
, replayLevels = levels
, replayMessages = messages
, replayTickMarks = tickMarks
, replayPackages = packages
, replayFrames = frames & reverse & Optimizer.optimizeFrames
}
toProperty :: Rattletrap.Property -> Property.Property
toProperty property =
let size =
property & Rattletrap.propertySize & Rattletrap.word64Value &
Word64.Word64
in case Rattletrap.propertyValue property of
Rattletrap.ArrayProperty x ->
let content =
x & Rattletrap.listValue &
map
(\y ->
y & Rattletrap.dictionaryValue & Map.map toProperty &
Map.mapKeys Text.Text &
Dictionary.Dictionary) &
List.List
in Property.PropertyArray (Property.ArrayProperty size content)
Rattletrap.BoolProperty x ->
let content = x & Rattletrap.word8Value & (/= 0) & Boolean.Boolean
in Property.PropertyBool (Property.BoolProperty size content)
Rattletrap.ByteProperty k mv ->
let (key, value) =
case mv of
Nothing -> ("OnlinePlatform", toText k)
Just v -> (toText k, toText v)
in Property.PropertyByte (Property.ByteProperty size key value)
Rattletrap.FloatProperty x ->
let content = toFloat32 x
in Property.PropertyFloat (Property.FloatProperty size content)
Rattletrap.IntProperty x ->
let content = x & Rattletrap.int32Value & Int32.Int32
in Property.PropertyInt (Property.IntProperty size content)
Rattletrap.NameProperty x ->
let content = toText x
in Property.PropertyName (Property.NameProperty size content)
Rattletrap.QWordProperty x ->
let content = x & Rattletrap.word64Value & Word64.Word64
in Property.PropertyQWord (Property.QWordProperty size content)
Rattletrap.StrProperty x ->
let content = toText x
in Property.PropertyStr (Property.StrProperty size content)
toText :: Rattletrap.Text -> Text.Text
toText text = text & Rattletrap.textToString & StrictText.pack & Text.Text
type ActorMap = Map.Map CompressedWord.CompressedWord (StrictText.Text, StrictText.Text)
toFrame :: ActorMap -> (Word, Rattletrap.Frame) -> (Frame.Frame, ActorMap)
toFrame actorMap (number, frame) =
let (replications, newActorMap) =
frame & Rattletrap.frameReplications &
foldl
(\(rs, m) r ->
let (r', m') = toReplication m r
in (r' : rs, m'))
([], actorMap)
newFrame =
Frame.Frame
{ Frame.frameNumber = number
, Frame.frameIsKeyFrame = number == 0
, Frame.frameTime = frame & Rattletrap.frameTime & toFloat32
, Frame.frameDelta = frame & Rattletrap.frameDelta & toFloat32
, Frame.frameReplications = reverse replications
}
in (newFrame, newActorMap)
toFloat32 :: Rattletrap.Float32 -> Float32.Float32
toFloat32 float32 = float32 & Rattletrap.float32Value & Float32.Float32
toReplication :: ActorMap
-> Rattletrap.Replication
-> (Replication.Replication, ActorMap)
toReplication actorMap replication =
let actorId = replication & Rattletrap.replicationActorId & toCompressedWord
replicationValue = Rattletrap.replicationValue replication
maybeNames =
case replicationValue of
Rattletrap.SpawnedReplicationValue spawned ->
Just
( spawned & Rattletrap.spawnedReplicationObjectName &
Rattletrap.textToString &
StrictText.pack
, spawned & Rattletrap.spawnedReplicationClassName &
Rattletrap.textToString &
StrictText.pack)
_ -> Nothing
newActorMap =
case maybeNames of
Nothing -> actorMap
Just names -> Map.insert actorId names actorMap
objectName =
newActorMap & Map.lookup actorId & fmap fst & Maybe.fromMaybe "unknown"
className =
newActorMap & Map.lookup actorId & fmap snd & Maybe.fromMaybe "unknown"
newReplication =
Replication.Replication
{ Replication.replicationActorId = actorId
, Replication.replicationObjectName = objectName
, Replication.replicationClassName = className
, Replication.replicationState =
case replicationValue of
Rattletrap.SpawnedReplicationValue _ -> State.Opening
Rattletrap.UpdatedReplicationValue _ -> State.Existing
Rattletrap.DestroyedReplicationValue _ -> State.Closing
, Replication.replicationInitialization =
case replicationValue of
Rattletrap.SpawnedReplicationValue value ->
Just
Initialization.Initialization
{ Initialization.initializationLocation =
value & Rattletrap.spawnedReplicationInitialization &
Rattletrap.initializationLocation &
fmap toIntVector
, Initialization.initializationRotation =
value & Rattletrap.spawnedReplicationInitialization &
Rattletrap.initializationRotation &
fmap toInt8Vector
}
_ -> Nothing
, Replication.replicationProperties =
case replicationValue of
Rattletrap.UpdatedReplicationValue updated ->
updated & Rattletrap.updatedReplicationAttributes & map toValue &
Map.fromList
_ -> Map.empty
}
in (newReplication, newActorMap)
toValue :: Rattletrap.Attribute -> (StrictText.Text, Value.Value)
toValue attribute =
let key =
attribute & Rattletrap.attributeName & Rattletrap.textToString &
StrictText.pack
value =
case Rattletrap.attributeValue attribute of
Rattletrap.BooleanAttributeValue x ->
Value.ValueBoolean
(Value.BooleanValue
(Boolean.Boolean (Rattletrap.booleanAttributeValue x)))
Rattletrap.ByteAttributeValue x ->
Value.ValueByte
(Value.ByteValue
(Word8.Word8
(Rattletrap.word8Value (Rattletrap.byteAttributeValue x))))
Rattletrap.CamSettingsAttributeValue x ->
Value.ValueCamSettings
(Value.CamSettingsValue
(toFloat32 (Rattletrap.camSettingsAttributeFov x))
(toFloat32 (Rattletrap.camSettingsAttributeHeight x))
(toFloat32 (Rattletrap.camSettingsAttributeAngle x))
(toFloat32 (Rattletrap.camSettingsAttributeDistance x))
(toFloat32 (Rattletrap.camSettingsAttributeStiffness x))
(toFloat32 (Rattletrap.camSettingsAttributeSwivelSpeed x)))
Rattletrap.ClubColorsAttributeValue x ->
Value.ValueClubColors
(Value.ClubColorsValue
(Boolean.Boolean (Rattletrap.clubColorsAttributeBlueFlag x))
(toWord8 (Rattletrap.clubColorsAttributeBlueColor x))
(Boolean.Boolean (Rattletrap.clubColorsAttributeOrangeFlag x))
(toWord8 (Rattletrap.clubColorsAttributeOrangeColor x)))
Rattletrap.DemolishAttributeValue x ->
Value.ValueDemolish
(Value.DemolishValue
(Boolean.Boolean (Rattletrap.demolishAttributeAttackerFlag x))
(toWord32 (Rattletrap.demolishAttributeAttackerActorId x))
(Boolean.Boolean (Rattletrap.demolishAttributeVictimFlag x))
(toWord32 (Rattletrap.demolishAttributeVictimActorId x))
(toIntVector (Rattletrap.demolishAttributeAttackerVelocity x))
(toIntVector (Rattletrap.demolishAttributeVictimVelocity x)))
Rattletrap.EnumAttributeValue x ->
Value.ValueEnum
(Value.EnumValue
(Word16.Word16 (Rattletrap.enumAttributeValue x))
(Boolean.Boolean False))
Rattletrap.ExplosionAttributeValue x ->
Value.ValueExplosion
(Value.ExplosionValue
(Boolean.Boolean False)
(Just (toInt32 (Rattletrap.explosionAttributeActorId x)))
(toIntVector (Rattletrap.explosionAttributeLocation x)))
Rattletrap.FlaggedIntAttributeValue x ->
Value.ValueFlaggedInt
(Value.FlaggedIntValue
(Boolean.Boolean (Rattletrap.flaggedIntAttributeFlag x))
(toInt32 (Rattletrap.flaggedIntAttributeInt x)))
Rattletrap.FloatAttributeValue x ->
Value.ValueFloat
(Value.FloatValue (toFloat32 (Rattletrap.floatAttributeValue x)))
Rattletrap.GameModeAttributeValue x ->
Value.ValueGameMode
(Value.GameModeValue
(Word8.Word8 (Rattletrap.gameModeAttributeWord x)))
Rattletrap.IntAttributeValue x ->
Value.ValueInt
(Value.IntValue (toInt32 (Rattletrap.intAttributeValue x)))
Rattletrap.LoadoutAttributeValue x -> Value.ValueLoadout (toLoadout x)
Rattletrap.LoadoutOnlineAttributeValue x ->
Value.ValueLoadoutOnline (toLoadoutOnline x)
Rattletrap.LoadoutsAttributeValue x ->
Value.ValueLoadouts
(Value.LoadoutsValue
(toLoadout (Rattletrap.loadoutsAttributeBlue x))
(toLoadout (Rattletrap.loadoutsAttributeOrange x)))
Rattletrap.LoadoutsOnlineAttributeValue x ->
Value.ValueLoadoutsOnline
(Value.LoadoutsOnlineValue
(toLoadoutOnline (Rattletrap.loadoutsOnlineAttributeBlue x))
(toLoadoutOnline (Rattletrap.loadoutsOnlineAttributeOrange x))
(Boolean.Boolean (Rattletrap.loadoutsOnlineAttributeUnknown1 x))
(Boolean.Boolean (Rattletrap.loadoutsOnlineAttributeUnknown2 x)))
Rattletrap.LocationAttributeValue x ->
Value.ValueLocation
(Value.LocationValue
(toIntVector (Rattletrap.locationAttributeValue x)))
Rattletrap.MusicStingerAttributeValue x ->
Value.ValueMusicStinger
(Value.MusicStingerValue
(Boolean.Boolean (Rattletrap.musicStingerAttributeFlag x))
(toWord32 (Rattletrap.musicStingerAttributeCue x))
(toWord8 (Rattletrap.musicStingerAttributeTrigger x)))
Rattletrap.PartyLeaderAttributeValue x ->
Value.ValueUniqueId
(Value.UniqueIdValue
(toWord8 (Rattletrap.partyLeaderAttributeSystemId x))
(maybe
(RemoteId.RemoteSplitscreenId
(RemoteId.SplitscreenId Nothing))
(\(r, _) -> toRemoteId r)
(Rattletrap.partyLeaderAttributeId x))
(fmap (toWord8 . snd) (Rattletrap.partyLeaderAttributeId x)))
Rattletrap.PickupAttributeValue x ->
Value.ValuePickup
(Value.PickupValue
(Boolean.Boolean
(maybe
False
(const True)
(Rattletrap.pickupAttributeInstigatorId x)))
(fmap toWord32 (Rattletrap.pickupAttributeInstigatorId x))
(Boolean.Boolean (Rattletrap.pickupAttributePickedUp x)))
Rattletrap.PrivateMatchSettingsAttributeValue x ->
Value.ValuePrivateMatchSettings
(Value.PrivateMatchSettingsValue
(toText (Rattletrap.privateMatchSettingsAttributeMutators x))
(toWord32
(Rattletrap.privateMatchSettingsAttributeJoinableBy x))
(toWord32
(Rattletrap.privateMatchSettingsAttributeMaxPlayers x))
(toText (Rattletrap.privateMatchSettingsAttributeGameName x))
(toText (Rattletrap.privateMatchSettingsAttributePassword x))
(Boolean.Boolean
(Rattletrap.privateMatchSettingsAttributeFlag x)))
Rattletrap.QWordAttributeValue x ->
Value.ValueQWord
(Value.QWordValue (toWord64 (Rattletrap.qWordAttributeValue x)))
Rattletrap.ReservationAttributeValue x ->
Value.ValueReservation
(Value.ReservationValue
(toCompressedWord (Rattletrap.reservationAttributeNumber x))
(toWord8
(Rattletrap.uniqueIdAttributeSystemId
(Rattletrap.reservationAttributeUniqueId x)))
(toRemoteId
(Rattletrap.uniqueIdAttributeRemoteId
(Rattletrap.reservationAttributeUniqueId x)))
(Just
(toWord8
(Rattletrap.uniqueIdAttributeLocalId
(Rattletrap.reservationAttributeUniqueId x))))
(fmap toText (Rattletrap.reservationAttributeName x))
(Boolean.Boolean (Rattletrap.reservationAttributeUnknown1 x))
(Boolean.Boolean (Rattletrap.reservationAttributeUnknown2 x)))
Rattletrap.RigidBodyStateAttributeValue x ->
Value.ValueRigidBodyState
(Value.RigidBodyStateValue
(Boolean.Boolean (Rattletrap.rigidBodyStateAttributeSleeping x))
(toIntVector (Rattletrap.rigidBodyStateAttributeLocation x))
(toFloatVector (Rattletrap.rigidBodyStateAttributeRotation x))
(fmap
toIntVector
(Rattletrap.rigidBodyStateAttributeLinearVelocity x))
(fmap
toIntVector
(Rattletrap.rigidBodyStateAttributeAngularVelocity x)))
Rattletrap.StringAttributeValue x ->
Value.ValueString
(Value.StringValue (toText (Rattletrap.stringAttributeValue x)))
Rattletrap.TeamPaintAttributeValue x ->
Value.ValueTeamPaint
(Value.TeamPaintValue
(toWord8 (Rattletrap.teamPaintAttributeTeam x))
(toWord8 (Rattletrap.teamPaintAttributePrimaryColor x))
(toWord8 (Rattletrap.teamPaintAttributeAccentColor x))
(toWord32 (Rattletrap.teamPaintAttributePrimaryFinish x))
(toWord32 (Rattletrap.teamPaintAttributeAccentFinish x)))
Rattletrap.UniqueIdAttributeValue x ->
Value.ValueUniqueId
(Value.UniqueIdValue
(toWord8 (Rattletrap.uniqueIdAttributeSystemId x))
(toRemoteId (Rattletrap.uniqueIdAttributeRemoteId x))
(Just (toWord8 (Rattletrap.uniqueIdAttributeLocalId x))))
Rattletrap.WeldedInfoAttributeValue x ->
Value.ValueWeldedInfo
(Value.WeldedInfoValue
(Boolean.Boolean (Rattletrap.weldedInfoAttributeActive x))
(toInt32 (Rattletrap.weldedInfoAttributeActorId x))
(toIntVector (Rattletrap.weldedInfoAttributeOffset x))
(toFloat32 (Rattletrap.weldedInfoAttributeMass x))
(toInt8Vector (Rattletrap.weldedInfoAttributeRotation x)))
in (key, value)
toLoadout :: Rattletrap.LoadoutAttribute -> Value.LoadoutValue
toLoadout x =
Value.LoadoutValue
(toWord8 (Rattletrap.loadoutAttributeVersion x))
(toWord32 (Rattletrap.loadoutAttributeBody x))
(toWord32 (Rattletrap.loadoutAttributeDecal x))
(toWord32 (Rattletrap.loadoutAttributeWheels x))
(toWord32 (Rattletrap.loadoutAttributeRocketTrail x))
(toWord32 (Rattletrap.loadoutAttributeAntenna x))
(toWord32 (Rattletrap.loadoutAttributeTopper x))
(toWord32 (Rattletrap.loadoutAttributeUnknown1 x))
(fmap toWord32 (Rattletrap.loadoutAttributeUnknown2 x))
toLoadoutOnline :: Rattletrap.LoadoutOnlineAttribute -> Value.LoadoutOnlineValue
toLoadoutOnline x =
Value.LoadoutOnlineValue
(map
(map (\(k, v) -> (toWord32 k, toCompressedWord v)))
(Rattletrap.loadoutAttributeValue x))
toRemoteId :: Rattletrap.RemoteId -> RemoteId.RemoteId
toRemoteId remoteId =
case remoteId of
Rattletrap.PlayStationId a b ->
RemoteId.RemotePlayStationId
(RemoteId.PlayStationId (Text.Text a) (LazyBytes.pack b))
Rattletrap.SplitscreenId _ ->
RemoteId.RemoteSplitscreenId (RemoteId.SplitscreenId (Just 0))
Rattletrap.SteamId x ->
RemoteId.RemoteSteamId (RemoteId.SteamId (toWord64 x))
Rattletrap.XboxId x -> RemoteId.RemoteXboxId (RemoteId.XboxId (toWord64 x))
toCompressedWord :: Rattletrap.CompressedWord -> CompressedWord.CompressedWord
toCompressedWord compressedWord =
CompressedWord.CompressedWord
{ CompressedWord.compressedWordLimit =
compressedWord & Rattletrap.compressedWordLimit
, CompressedWord.compressedWordValue =
compressedWord & Rattletrap.compressedWordValue
}
toIntVector :: Rattletrap.Vector -> Vector.Vector Int
toIntVector vector =
Vector.Vector
(vector & Rattletrap.vectorX)
(vector & Rattletrap.vectorY)
(vector & Rattletrap.vectorZ)
toInt8Vector :: Rattletrap.Int8Vector -> Vector.Vector Int8.Int8
toInt8Vector vector =
let convert a = a & fmap Rattletrap.int8Value & Maybe.fromMaybe 0 & Int8.Int8
x = vector & Rattletrap.int8VectorX & convert
y = vector & Rattletrap.int8VectorY & convert
z = vector & Rattletrap.int8VectorZ & convert
in Vector.Vector x y z
toWord32 :: Rattletrap.Word32 -> Word32.Word32
toWord32 word32 = Word32.Word32 (Rattletrap.word32Value word32)
toInt32 :: Rattletrap.Int32 -> Int32.Int32
toInt32 int32 = Int32.Int32 (Rattletrap.int32Value int32)
toWord8 :: Rattletrap.Word8 -> Word8.Word8
toWord8 word8 = Word8.Word8 (Rattletrap.word8Value word8)
toWord64 :: Rattletrap.Word64 -> Word64.Word64
toWord64 word64 = Word64.Word64 (Rattletrap.word64Value word64)
toFloatVector :: Rattletrap.CompressedWordVector -> Vector.Vector Float
toFloatVector vector =
Vector.Vector
(toFloat (Rattletrap.compressedWordVectorX vector))
(toFloat (Rattletrap.compressedWordVectorY vector))
(toFloat (Rattletrap.compressedWordVectorZ vector))
toFloat :: Rattletrap.CompressedWord -> Float
toFloat compressedWord =
let serIntMax = Rattletrap.compressedWordLimit compressedWord
numBits = ceiling (log (fromIntegral serIntMax :: Float) / log 2)
bias = Bits.shiftL 1 (numBits 1)
delta = fromIntegral (Rattletrap.compressedWordValue compressedWord)
unscaledValue = (delta :: Int) bias
maxBitValue = (Bits.shiftL 1 (numBits 1)) 1
maxValue = 1 :: Int
invScale =
if maxValue > maxBitValue
then fromIntegral maxValue / fromIntegral maxBitValue
else 1 / (fromIntegral maxBitValue / fromIntegral maxValue)
in fromIntegral unscaledValue * invScale