diff --git a/common/AnimatedEntity.go b/common/AnimatedEntity.go index 0d07f117..13a69eb3 100644 --- a/common/AnimatedEntity.go +++ b/common/AnimatedEntity.go @@ -3,7 +3,6 @@ package common import ( "fmt" "image" - "math" "strings" "time" @@ -12,18 +11,19 @@ import ( "github.com/hajimehoshi/ebiten" ) +var DccLayerNames = []string{"HD", "TR", "LG", "RA", "LA", "RH", "LH", "SH", "S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8"} + // AnimatedEntity represents an entity on the map that can be animated type AnimatedEntity struct { // LocationX represents the tile X position of the entity LocationX float64 // LocationY represents the tile Y position of the entity LocationY float64 - dcc *DCC - cof *Cof + dccLayers map[string]*DCC + Cof *Cof palette palettedefs.PaletteType base string token string - tr string animationMode string weaponClass string lastFrameTime time.Time @@ -31,8 +31,9 @@ type AnimatedEntity struct { animationSpeed int direction int currentFrame int - frames []*ebiten.Image - frameLocations []Rectangle + frames map[string][]*ebiten.Image + frameLocations map[string][]Rectangle + object Object } // CreateAnimatedEntity creates an instance of AnimatedEntity @@ -40,11 +41,12 @@ func CreateAnimatedEntity(object Object, fileProvider FileProvider, palette pale result := &AnimatedEntity{ base: object.Lookup.Base, token: object.Lookup.Token, - tr: object.Lookup.TR, + object: object, palette: palette, } - result.LocationX = math.Floor(float64(object.X) / 5) - result.LocationY = math.Floor(float64(object.Y) / 5) + result.dccLayers = make(map[string]*DCC) + result.LocationX = float64(object.X) / 5 + result.LocationY = float64(object.Y) / 5 return result } @@ -53,14 +55,69 @@ var DirectionLookup = []int{3, 15, 4, 8, 0, 9, 5, 10, 1, 11, 6, 12, 2, 13, 7, 14 // SetMode changes the graphical mode of this animated entity func (v *AnimatedEntity) SetMode(animationMode, weaponClass string, direction int, provider FileProvider) { - dccPath := fmt.Sprintf("%s/%s/tr/%str%s%s%s.dcc", v.base, v.token, v.token, v.tr, animationMode, weaponClass) - v.dcc = LoadDCC(dccPath, provider) - cofPath := fmt.Sprintf("%s/%s/cof/%s%s%s.cof", v.base, v.token, v.token, animationMode, weaponClass) - v.cof = LoadCof(cofPath, provider) + cofPath := fmt.Sprintf("%s/%s/Cof/%s%s%s.Cof", v.base, v.token, v.token, animationMode, weaponClass) + v.Cof = LoadCof(cofPath, provider) v.animationMode = animationMode v.weaponClass = weaponClass v.direction = direction - v.cacheFrames() + if v.direction >= v.Cof.NumberOfDirections { + v.direction = v.Cof.NumberOfDirections - 1 + } + v.frames = make(map[string][]*ebiten.Image) + v.frameLocations = make(map[string][]Rectangle) + v.dccLayers = make(map[string]*DCC) + for _, cofLayer := range v.Cof.CofLayers { + layerName := DccLayerNames[cofLayer.Type] + v.dccLayers[layerName] = v.LoadLayer(layerName, provider) + if v.dccLayers[layerName] == nil { + continue + } + v.cacheFrames(layerName) + } + +} + +func (v *AnimatedEntity) LoadLayer(layer string, fileProvider FileProvider) *DCC { + layerName := "tr" + switch strings.ToUpper(layer) { + case "HD": // Head + layerName = v.object.Lookup.HD + case "TR": // Torso + layerName = v.object.Lookup.TR + case "LG": // Legs + layerName = v.object.Lookup.LG + case "RA": // RightArm + layerName = v.object.Lookup.RA + case "LA": // LeftArm + layerName = v.object.Lookup.LA + case "RH": // RightHand + layerName = v.object.Lookup.RH + case "LH": // LeftHand + layerName = v.object.Lookup.LH + case "SH": // Shield + layerName = v.object.Lookup.SH + case "S1": // Special1 + layerName = v.object.Lookup.S1 + case "S2": // Special2 + layerName = v.object.Lookup.S2 + case "S3": // Special3 + layerName = v.object.Lookup.S3 + case "S4": // Special4 + layerName = v.object.Lookup.S4 + case "S5": // Special5 + layerName = v.object.Lookup.S5 + case "S6": // Special6 + layerName = v.object.Lookup.S6 + case "S7": // Special7 + layerName = v.object.Lookup.S7 + case "S8": // Special8 + layerName = v.object.Lookup.S8 + } + if len(layerName) == 0 { + return nil + } + dccPath := fmt.Sprintf("%s/%s/%s/%s%s%s%s%s.dcc", v.base, v.token, layer, v.token, layer, layerName, v.animationMode, v.weaponClass) + return LoadDCC(dccPath, fileProvider) } // Render draws this animated entity onto the target @@ -72,23 +129,35 @@ func (v *AnimatedEntity) Render(target *ebiten.Image, offsetX, offsetY int) { v.currentFrame = 0 } } - - opts := &ebiten.DrawImageOptions{} - opts.GeoM.Translate(float64(v.frameLocations[v.currentFrame].Left+offsetX), float64(v.frameLocations[v.currentFrame].Top+offsetY+40)) - target.DrawImage(v.frames[v.currentFrame], opts) + for idx := 0; idx < v.Cof.NumberOfLayers; idx++ { + priority := v.Cof.Priority[v.direction][v.currentFrame][idx] + if int(priority) >= len(DccLayerNames) { + continue + } + frameName := DccLayerNames[priority] + if v.frames[frameName] == nil { + continue + } + // TODO: Transparency op maybe, but it'l murder batch calls + opts := &ebiten.DrawImageOptions{} + opts.GeoM.Translate(float64(v.frameLocations[frameName][v.currentFrame].Left+offsetX), + float64(v.frameLocations[frameName][v.currentFrame].Top+offsetY+40)) + target.DrawImage(v.frames[frameName][v.currentFrame], opts) + } } -func (v *AnimatedEntity) cacheFrames() { +func (v *AnimatedEntity) cacheFrames(layerName string) { + dcc := v.dccLayers[layerName] v.currentFrame = 0 animationData := AnimationData[strings.ToLower(v.token+v.animationMode+v.weaponClass)][0] v.animationSpeed = int(1000.0 / ((float64(animationData.AnimationSpeed) * 25.0) / 256.0)) v.framesToAnimate = animationData.FramesPerDirection v.lastFrameTime = time.Now() - minX := int32(2147483647) - minY := int32(2147483647) - maxX := int32(-2147483648) - maxY := int32(-2147483648) - for _, layer := range v.dcc.Directions { + minX := int32(10000) + minY := int32(10000) + maxX := int32(-10000) + maxY := int32(-10000) + for _, layer := range dcc.Directions { minX = MinInt32(minX, int32(layer.Box.Left)) minY = MinInt32(minY, int32(layer.Box.Top)) maxX = MaxInt32(maxX, int32(layer.Box.Right())) @@ -96,17 +165,17 @@ func (v *AnimatedEntity) cacheFrames() { } frameW := maxX - minX frameH := maxY - minY - v.frames = make([]*ebiten.Image, v.framesToAnimate) - v.frameLocations = make([]Rectangle, v.framesToAnimate) - for frameIndex := range v.frames { - v.frames[frameIndex], _ = ebiten.NewImage(int(frameW), int(frameH), ebiten.FilterNearest) - priorityBase := (v.direction * animationData.FramesPerDirection * v.cof.NumberOfLayers) + (frameIndex * v.cof.NumberOfLayers) - for layerIdx := 0; layerIdx < v.cof.NumberOfLayers; layerIdx++ { - comp := v.cof.Priority[priorityBase+layerIdx] - if _, found := v.cof.CompositeLayers[comp]; !found { - continue + v.frames[layerName] = make([]*ebiten.Image, v.framesToAnimate) + v.frameLocations[layerName] = make([]Rectangle, v.framesToAnimate) + for frameIndex := range v.frames[layerName] { + v.frames[layerName][frameIndex], _ = ebiten.NewImage(int(frameW), int(frameH), ebiten.FilterNearest) + for layerIdx := 0; layerIdx < v.Cof.NumberOfLayers; layerIdx++ { + transparency := byte(255) + if v.Cof.CofLayers[layerIdx].Transparent { + transparency = byte(128) } - direction := v.dcc.Directions[v.direction] + + direction := dcc.Directions[v.direction] frame := direction.Frames[frameIndex] img := image.NewRGBA(image.Rect(0, 0, int(frameW), int(frameH))) for y := 0; y < direction.Box.Height; y++ { @@ -122,13 +191,13 @@ func (v *AnimatedEntity) cacheFrames() { img.Pix[(actualX*4)+(actualY*int(frameW)*4)] = color.R img.Pix[(actualX*4)+(actualY*int(frameW)*4)+1] = color.G img.Pix[(actualX*4)+(actualY*int(frameW)*4)+2] = color.B - img.Pix[(actualX*4)+(actualY*int(frameW)*4)+3] = 255 + img.Pix[(actualX*4)+(actualY*int(frameW)*4)+3] = transparency } } newImage, _ := ebiten.NewImageFromImage(img, ebiten.FilterNearest) img = nil - v.frames[frameIndex] = newImage - v.frameLocations[frameIndex] = Rectangle{ + v.frames[layerName][frameIndex] = newImage + v.frameLocations[layerName][frameIndex] = Rectangle{ Left: int(minX), Top: int(minY), Width: int(frameW), diff --git a/common/BitMuncher.go b/common/BitMuncher.go index 665745e5..e7a38646 100644 --- a/common/BitMuncher.go +++ b/common/BitMuncher.go @@ -31,6 +31,7 @@ func (v *BitMuncher) GetBit() uint32 { func (v *BitMuncher) SkipBits(bits int) { v.Offset += bits + v.BitsRead += bits } func (v *BitMuncher) GetByte() byte { diff --git a/common/Cof.go b/common/Cof.go index 1fd374f1..7731928f 100644 --- a/common/Cof.go +++ b/common/Cof.go @@ -204,7 +204,7 @@ type Cof struct { CofLayers []*CofLayer CompositeLayers map[CompositeType]int AnimationFrames []AnimationFrame - Priority []CompositeType + Priority [][][]CompositeType } func LoadCof(fileName string, fileProvider FileProvider) *Cof { @@ -235,10 +235,16 @@ func LoadCof(fileName string, fileProvider FileProvider) *Cof { result.AnimationFrames[i] = AnimationFrame(animationFrameBytes[i]) } priorityLen := result.FramesPerDirection * result.NumberOfDirections * result.NumberOfLayers - result.Priority = make([]CompositeType, priorityLen) + result.Priority = make([][][]CompositeType, result.NumberOfDirections) priorityBytes, _ := streamReader.ReadBytes(priorityLen) - for i := range priorityBytes { - result.Priority[i] = CompositeType(priorityBytes[i]) + for direction := 0; direction < result.NumberOfDirections; direction++ { + result.Priority[direction] = make([][]CompositeType, result.FramesPerDirection) + for frame := 0; frame < result.FramesPerDirection; frame++ { + result.Priority[direction][frame] = make([]CompositeType, result.NumberOfLayers) + for i := 0; i < result.NumberOfLayers; i++ { + result.Priority[direction][frame][i] = CompositeType(priorityBytes[i]) + } + } } return result } diff --git a/common/Dcc.go b/common/Dcc.go index ee9b0cdb..f538969e 100644 --- a/common/Dcc.go +++ b/common/Dcc.go @@ -2,7 +2,6 @@ package common import ( "log" - "sync" ) type DCCPixelBufferEntry struct { @@ -52,7 +51,7 @@ type DCCDirection struct { EncodingTypeBitsreamSize int RawPixelCodesBitstreamSize int Frames []*DCCDirectionFrame - PaletteEntries []byte + PaletteEntries [256]byte Box Rectangle Cells []*DCCCell PixelData []byte @@ -71,6 +70,11 @@ type DCC struct { var crazyBitTable = []byte{0, 1, 2, 4, 6, 8, 10, 12, 14, 16, 20, 24, 26, 28, 30, 32} var pixelMaskLookup = []int{0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4} +var dccDir4 = []byte{0, 1, 2, 3} +var dccDir8 = []byte{4, 0, 5, 1, 6, 2, 7, 3} +var dccDir16 = []byte{4, 8, 0, 9, 5, 10, 1, 11, 6, 12, 2, 13, 7, 14, 3, 15} +var dccDir32 = []byte{4, 16, 8, 17, 0, 18, 9, 19, 5, 20, 10, 21, 1, 22, 11, 23, + 6, 24, 12, 25, 2, 26, 13, 27, 7, 28, 14, 29, 3, 30, 15, 31} func CreateDCCDirectionFrame(bits *BitMuncher, direction *DCCDirection) *DCCDirectionFrame { result := &DCCDirectionFrame{} @@ -82,11 +86,15 @@ func CreateDCCDirectionFrame(bits *BitMuncher, direction *DCCDirection) *DCCDire result.NumberOfOptionalBytes = int(bits.GetBits(direction.OptionalDataBits)) result.NumberOfCodedBytes = int(bits.GetBits(direction.CodedBytesBits)) result.FrameIsBottomUp = bits.GetBit() == 1 - result.Box = Rectangle{ - result.XOffset, - result.YOffset - result.Height - 1, - result.Width, - result.Height, + if result.FrameIsBottomUp { + log.Panic("Bottom up frames are not implemented.") + } else { + result.Box = Rectangle{ + result.XOffset, + result.YOffset - result.Height + 1, + result.Width, + result.Height, + } } return result } @@ -164,10 +172,10 @@ func CreateDCCDirection(bm *BitMuncher, file *DCC) *DCCDirection { result.OptionalDataBits = int(crazyBitTable[bm.GetBits(4)]) result.CodedBytesBits = int(crazyBitTable[bm.GetBits(4)]) result.Frames = make([]*DCCDirectionFrame, file.FramesPerDirection) - minx := 9223372036854775807 - miny := 9223372036854775807 - maxx := -9223372036854775808 - maxy := -9223372036854775808 + minx := 100000 + miny := 100000 + maxx := -100000 + maxy := -100000 // Load the frame headers for frameIdx := 0; frameIdx < file.FramesPerDirection; frameIdx++ { result.Frames[frameIdx] = CreateDCCDirectionFrame(bm, result) @@ -176,7 +184,7 @@ func CreateDCCDirection(bm *BitMuncher, file *DCC) *DCCDirection { maxx = int(MaxInt32(int32(result.Frames[frameIdx].Box.Right()), int32(maxx))) maxy = int(MaxInt32(int32(result.Frames[frameIdx].Box.Bottom()), int32(maxy))) } - result.Box = Rectangle{minx, miny, maxx - minx, maxy - miny} + result.Box = Rectangle{minx, miny, (maxx - minx), (maxy - miny)} if result.OptionalDataBits > 0 { log.Panic("Optional bits in DCC data is not currently supported.") } @@ -189,24 +197,14 @@ func CreateDCCDirection(bm *BitMuncher, file *DCC) *DCCDirection { result.RawPixelCodesBitstreamSize = int(bm.GetBits(20)) } // PixelValuesKey - paletteEntries := make([]bool, 0) paletteEntryCount := 0 for i := 0; i < 256; i++ { valid := bm.GetBit() != 0 - paletteEntries = append(paletteEntries, valid) if valid { + result.PaletteEntries[paletteEntryCount] = byte(i) paletteEntryCount++ } } - result.PaletteEntries = make([]byte, paletteEntryCount) - paletteOffset := 0 - for i := 0; i < 256; i++ { - if !paletteEntries[i] { - continue - } - result.PaletteEntries[paletteOffset] = byte(i) - paletteOffset++ - } // HERE BE GIANTS: // Because of the way this thing mashes bits together, BIT offset matters // here. For example, if you are on byte offset 3, bit offset 6, and @@ -343,10 +341,12 @@ func (v *DCCDirection) GenerateFrames(pcd *BitMuncher) { func (v *DCCDirection) FillPixelBuffer(pcd, ec, pm, et, rp *BitMuncher) { lastPixel := uint32(0) - pixelStack := make([]uint32, 4) maxCellX := 0 maxCellY := 0 for _, frame := range v.Frames { + if frame == nil { + continue + } maxCellX += frame.HorizontalCellCount maxCellY += frame.VerticalCellCount } @@ -366,11 +366,9 @@ func (v *DCCDirection) FillPixelBuffer(pcd, ec, pm, et, rp *BitMuncher) { frameIndex++ originCellX := (frame.Box.Left - v.Box.Left) / 4 originCellY := (frame.Box.Top - v.Box.Top) / 4 - frameCellIndex := 0 for cellY := 0; cellY < frame.VerticalCellCount; cellY++ { currentCellY := cellY + originCellY for cellX := 0; cellX < frame.HorizontalCellCount; cellX++ { - frameCellIndex++ currentCell := originCellX + cellX + (currentCellY * v.HorizontalCellCount) nextCell := false tmp := 0 @@ -392,6 +390,7 @@ func (v *DCCDirection) FillPixelBuffer(pcd, ec, pm, et, rp *BitMuncher) { continue } // Decode the pixels + var pixelStack [4]uint32 lastPixel = 0 numberOfPixelBits := pixelMaskLookup[pixelMask] encodingType := 0 @@ -415,7 +414,7 @@ func (v *DCCDirection) FillPixelBuffer(pcd, ec, pm, et, rp *BitMuncher) { } if pixelStack[i] == lastPixel { pixelStack[i] = 0 - i = numberOfPixelBits // Just break here.... + break } else { lastPixel = pixelStack[i] decodedPixel++ @@ -514,15 +513,22 @@ func LoadDCC(path string, fileProvider FileProvider) *DCC { directionOffsets[i] = int(bm.GetInt32()) } result.Directions = make([]*DCCDirection, result.NumberOfDirections) - var wg sync.WaitGroup - wg.Add(result.NumberOfDirections) for i := 0; i < result.NumberOfDirections; i++ { - go func(i int) { - defer wg.Done() - result.Directions[i] = CreateDCCDirection(CreateBitMuncher(fileData, directionOffsets[i]*8), result) - }(i) + dir := byte(0) + switch result.NumberOfDirections { + case 1: + dir = 0 + case 4: + dir = dccDir4[i] + case 8: + dir = dccDir8[i] + case 16: + dir = dccDir16[i] + case 32: + dir = dccDir32[i] + } + result.Directions[dir] = CreateDCCDirection(CreateBitMuncher(fileData, directionOffsets[i]*8), result) } - wg.Wait() return result } diff --git a/common/NPC.go b/common/NPC.go index a3517641..42b061c4 100644 --- a/common/NPC.go +++ b/common/NPC.go @@ -15,8 +15,7 @@ func CreateNPC(object Object, fileProvider FileProvider) *NPC { AnimatedEntity: CreateAnimatedEntity(object, fileProvider, palettedefs.Units), Paths: object.Paths, } - result.AnimatedEntity.SetMode(object.Lookup.Mode, object.Lookup.Class, 0, fileProvider) - + result.AnimatedEntity.SetMode(object.Lookup.Mode, object.Lookup.Class, 1, fileProvider) return result } diff --git a/map/Region.go b/map/Region.go index 50e134be..f70d1356 100644 --- a/map/Region.go +++ b/map/Region.go @@ -147,7 +147,7 @@ func (v *Region) loadObjects(fileProvider common.FileProvider) { npc := common.CreateNPC(object, fileProvider) v.NPCs = append(v.NPCs, npc) case common.ObjectTypeItem: - if object.ObjectInfo == nil || !object.ObjectInfo.Draw || object.Lookup.Base == "" || object.Lookup.Token == "" || object.Lookup.TR == "" { + if object.ObjectInfo == nil || !object.ObjectInfo.Draw || object.Lookup.Base == "" || object.Lookup.Token == "" { return } entity := common.CreateAnimatedEntity(object, fileProvider, palettedefs.Units)