diff --git a/common/AnimatedEntity.go b/common/AnimatedEntity.go index 9762fb55..0468817e 100644 --- a/common/AnimatedEntity.go +++ b/common/AnimatedEntity.go @@ -2,6 +2,7 @@ package common import ( "fmt" + "math" "strings" "time" @@ -34,13 +35,15 @@ type AnimatedEntity struct { } // CreateAnimatedEntity creates an instance of AnimatedEntity -func CreateAnimatedEntity(base, token, tr string, palette palettedefs.PaletteType) *AnimatedEntity { +func CreateAnimatedEntity(object Object, fileProvider FileProvider, palette palettedefs.PaletteType) *AnimatedEntity { result := &AnimatedEntity{ - base: base, - token: token, - tr: tr, + base: object.Lookup.Base, + token: object.Lookup.Token, + tr: object.Lookup.TR, palette: palette, } + result.LocationX = math.Floor(float64(object.X) / 5) + result.LocationY = math.Floor(float64(object.Y) / 5) return result } @@ -75,7 +78,8 @@ func (v *AnimatedEntity) Render(target *ebiten.Image, offsetX, offsetY int) { } func (v *AnimatedEntity) cacheFrames() { - animationData := AnimationData[strings.ToLower(v.token+v.animationMode+v.weaponClass)][v.direction] + 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() diff --git a/common/DataDictionary.go b/common/DataDictionary.go new file mode 100644 index 00000000..8f4125e5 --- /dev/null +++ b/common/DataDictionary.go @@ -0,0 +1,47 @@ +package common + +import ( + "log" + "strconv" + "strings" +) + +// DataDictionary represents a data file (Excel) +type DataDictionary struct { + FieldNameLookup map[string]int + Data [][]string +} + +func LoadDataDictionary(text string) *DataDictionary { + result := &DataDictionary{} + lines := strings.Split(text, "\r\n") + fileNames := strings.Split(lines[0], "\t") + result.FieldNameLookup = make(map[string]int) + for i, fieldName := range fileNames { + result.FieldNameLookup[fieldName] = i + } + result.Data = make([][]string, 0) + for _, line := range lines[1:] { + if len(strings.TrimSpace(line)) == 0 { + continue + } + values := strings.Split(line, "\t") + if len(values) != len(result.FieldNameLookup) { + continue + } + result.Data = append(result.Data, values) + } + return result +} + +func (v *DataDictionary) GetString(fieldName string, index int) string { + return v.Data[index][v.FieldNameLookup[fieldName]] +} + +func (v *DataDictionary) GetNumber(fieldName string, index int) int { + result, err := strconv.Atoi(v.GetString(fieldName, index)) + if err != nil { + log.Panic(err) + } + return result +} diff --git a/common/MonStats.go b/common/MonStats.go new file mode 100644 index 00000000..537f799c --- /dev/null +++ b/common/MonStats.go @@ -0,0 +1,9 @@ +package common + +import "github.com/OpenDiablo2/OpenDiablo2/resourcepaths" + +var MonStatsDictionary *DataDictionary + +func LoadMonStats(fileProvider FileProvider) { + MonStatsDictionary = LoadDataDictionary(string(fileProvider.LoadFile(resourcepaths.MonStats))) +} diff --git a/common/NPC.go b/common/NPC.go new file mode 100644 index 00000000..a3517641 --- /dev/null +++ b/common/NPC.go @@ -0,0 +1,25 @@ +package common + +import ( + "github.com/OpenDiablo2/OpenDiablo2/palettedefs" + "github.com/hajimehoshi/ebiten" +) + +type NPC struct { + AnimatedEntity *AnimatedEntity + Paths []Path +} + +func CreateNPC(object Object, fileProvider FileProvider) *NPC { + result := &NPC{ + AnimatedEntity: CreateAnimatedEntity(object, fileProvider, palettedefs.Units), + Paths: object.Paths, + } + result.AnimatedEntity.SetMode(object.Lookup.Mode, object.Lookup.Class, 0, fileProvider) + + return result +} + +func (v *NPC) Render(target *ebiten.Image, offsetX, offsetY int) { + v.AnimatedEntity.Render(target, offsetX, offsetY) +} diff --git a/common/Object.go b/common/Object.go new file mode 100644 index 00000000..2d0f03de --- /dev/null +++ b/common/Object.go @@ -0,0 +1,12 @@ +package common + +type Object struct { + Type int32 + Id int32 + X int32 + Y int32 + Flags int32 + Paths []Path + Lookup *ObjectLookupRecord + ObjectInfo *ObjectRecord +} diff --git a/common/Path.go b/common/Path.go new file mode 100644 index 00000000..d6ccfe21 --- /dev/null +++ b/common/Path.go @@ -0,0 +1,7 @@ +package common + +type Path struct { + X int32 + Y int32 + Action int32 +} diff --git a/core/Engine.go b/core/Engine.go index 2a900354..50457f64 100644 --- a/core/Engine.go +++ b/core/Engine.go @@ -78,6 +78,7 @@ func CreateEngine() *Engine { common.LoadSounds(result) common.LoadObjectLookups() common.LoadAnimationData(result) + common.LoadMonStats(result) result.SoundManager = sound.CreateManager(result) result.SoundManager.SetVolumes(result.Settings.BgmVolume, result.Settings.SfxVolume) result.UIManager = ui.CreateManager(result, *result.SoundManager) diff --git a/map/DS1.go b/map/DS1.go index 8c38fb77..0262b3be 100644 --- a/map/DS1.go +++ b/map/DS1.go @@ -66,35 +66,19 @@ type SubstitutionGroup struct { Unknown int32 } -type Path struct { - X int32 - Y int32 - Action int32 -} - -type Object struct { - Type int32 - Id int32 - X int32 - Y int32 - Flags int32 - Paths []Path - Lookup *common.ObjectLookupRecord -} - type DS1 struct { - Version int32 // The version of the DS1 - Width int32 // Width of map, in # of tiles - Height int32 // Height of map, in # of tiles - Act int32 // Act, from 1 to 5. This tells which act table to use for the Objects list - SubstitutionType int32 // SubstitutionType (layer type): 0 if no layer, else type 1 or type 2 - Files []string // FilePtr table of file string pointers - NumberOfWalls int32 // WallNum number of wall & orientation layers used - NumberOfFloors int32 // number of floor layers used - NumberOfShadowLayers int32 // ShadowNum number of shadow layer used - NumberOfSubstitutionLayers int32 // SubstitutionNum number of substitution layer used - SubstitutionGroupsNum int32 // SubstitutionGroupsNum number of substitution groups, datas between objects & NPC paths - Objects []Object // Objects + Version int32 // The version of the DS1 + Width int32 // Width of map, in # of tiles + Height int32 // Height of map, in # of tiles + Act int32 // Act, from 1 to 5. This tells which act table to use for the Objects list + SubstitutionType int32 // SubstitutionType (layer type): 0 if no layer, else type 1 or type 2 + Files []string // FilePtr table of file string pointers + NumberOfWalls int32 // WallNum number of wall & orientation layers used + NumberOfFloors int32 // number of floor layers used + NumberOfShadowLayers int32 // ShadowNum number of shadow layer used + NumberOfSubstitutionLayers int32 // SubstitutionNum number of substitution layer used + SubstitutionGroupsNum int32 // SubstitutionGroupsNum number of substitution groups, datas between objects & NPC paths + Objects []common.Object // Objects Tiles [][]TileRecord SubstitutionGroups []SubstitutionGroup } @@ -240,17 +224,20 @@ func LoadDS1(path string, fileProvider common.FileProvider) *DS1 { } } } - ds1.Objects = make([]Object, 0) + ds1.Objects = make([]common.Object, 0) if ds1.Version >= 2 { numberOfObjects := br.GetInt32() for objIdx := 0; objIdx < int(numberOfObjects); objIdx++ { - newObject := Object{} + newObject := common.Object{} newObject.Type = br.GetInt32() newObject.Id = br.GetInt32() newObject.X = br.GetInt32() newObject.Y = br.GetInt32() newObject.Flags = br.GetInt32() newObject.Lookup = common.LookupObject(int(ds1.Act), int(newObject.Type), int(newObject.Id)) + if newObject.Lookup != nil && newObject.Lookup.ObjectsTxtId != -1 { + newObject.ObjectInfo = common.Objects[newObject.Lookup.ObjectsTxtId] + } ds1.Objects = append(ds1.Objects, newObject) } } @@ -286,10 +273,10 @@ func LoadDS1(path string, fileProvider common.FileProvider) *DS1 { } if objIdx > -1 { if ds1.Objects[objIdx].Paths == nil { - ds1.Objects[objIdx].Paths = make([]Path, numPaths) + ds1.Objects[objIdx].Paths = make([]common.Path, numPaths) } for pathIdx := 0; pathIdx < int(numPaths); pathIdx++ { - newPath := Path{} + newPath := common.Path{} newPath.X = br.GetInt32() newPath.Y = br.GetInt32() if ds1.Version >= 15 { diff --git a/map/Engine.go b/map/Engine.go index ac075062..066019ff 100644 --- a/map/Engine.go +++ b/map/Engine.go @@ -64,6 +64,10 @@ func (v *Engine) GenerateAct1Overworld() { Region: region2, }) } + + sx, sy := common.IsoToScreen(int(region.StartX), int(region.StartY), 0, 0) + v.OffsetX = float64(sx) - 400 + v.OffsetY = float64(sy) - 300 } func (v *Engine) GetRegionAt(x, y int) *EngineRegion { @@ -125,6 +129,11 @@ func (v *Engine) RenderTile(region *Region, offX, offY, x, y int, target *ebiten obj.Render(target, offX+int(v.OffsetX), offY+int(v.OffsetY)) } } + for _, npc := range region.NPCs { + if int(math.Floor(npc.AnimatedEntity.LocationX)) == x && int(math.Floor(npc.AnimatedEntity.LocationY)) == y { + npc.Render(target, offX+int(v.OffsetX), offY+int(v.OffsetY)) + } + } for i := range tile.Walls { if tile.Walls[i].Hidden || tile.Walls[i].Orientation != 15 { continue diff --git a/map/Region.go b/map/Region.go index 78a361b6..50e134be 100644 --- a/map/Region.go +++ b/map/Region.go @@ -34,6 +34,9 @@ type Region struct { ShadowCache map[uint32]*TileCacheRecord WallCache map[uint32]*TileCacheRecord AnimationEntities []*common.AnimatedEntity + NPCs []*common.NPC + StartX float64 + StartY float64 } type RegionLayerType int @@ -123,28 +126,37 @@ func LoadRegion(seed rand.Source, levelType RegionIdType, levelPreset int, fileP result.DS1 = LoadDS1("/data/global/tiles/"+levelFile, fileProvider) result.TileWidth = result.DS1.Width result.TileHeight = result.DS1.Height + result.loadObjects(fileProvider) + return result +} + +func (v *Region) loadObjects(fileProvider common.FileProvider) { var wg sync.WaitGroup - wg.Add(len(result.DS1.Objects)) - result.AnimationEntities = make([]*common.AnimatedEntity, 0) - for _, object := range result.DS1.Objects { - go func(object Object) { + wg.Add(len(v.DS1.Objects)) + v.AnimationEntities = make([]*common.AnimatedEntity, 0) + v.NPCs = make([]*common.NPC, 0) + for _, object := range v.DS1.Objects { + go func(object common.Object) { defer wg.Done() switch object.Lookup.Type { case common.ObjectTypeCharacter: - case common.ObjectTypeItem: + // Temp code, maybe.. if object.Lookup.Base == "" || object.Lookup.Token == "" || object.Lookup.TR == "" { return } - animEntity := common.CreateAnimatedEntity(object.Lookup.Base, object.Lookup.Token, object.Lookup.TR, palettedefs.Units) - animEntity.SetMode(object.Lookup.Mode, object.Lookup.Class, 0, fileProvider) - animEntity.LocationX = math.Floor(float64(object.X) / 5) - animEntity.LocationY = math.Floor(float64(object.Y) / 5) - result.AnimationEntities = append(result.AnimationEntities, animEntity) + 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 == "" { + return + } + entity := common.CreateAnimatedEntity(object, fileProvider, palettedefs.Units) + entity.SetMode(object.Lookup.Mode, object.Lookup.Class, 0, fileProvider) + v.AnimationEntities = append(v.AnimationEntities, entity) } }(object) } wg.Wait() - return result } func (v *Region) RenderTile(offsetX, offsetY, tileX, tileY int, layerType RegionLayerType, layerIndex int, target *ebiten.Image) { diff --git a/resourcepaths/ResourcePaths.go b/resourcepaths/ResourcePaths.go index 0a88bb2a..83845a28 100644 --- a/resourcepaths/ResourcePaths.go +++ b/resourcepaths/ResourcePaths.go @@ -247,7 +247,7 @@ const ( // --- Enemy Data --- - MonStats = "/data//global//excel//monstats.txt" + MonStats = "/data/global/excel/monstats.txt" // --- Skill Data ---