1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-20 22:25:24 +00:00

Initial NPC support (#111)

* Started adding support for NPCs. Added Monstats dictionary.
This commit is contained in:
Tim Sarbin 2019-11-07 23:44:03 -05:00 committed by GitHub
parent 4cd1eae21a
commit 920a4f51b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 162 additions and 49 deletions

View File

@ -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()

47
common/DataDictionary.go Normal file
View File

@ -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
}

9
common/MonStats.go Normal file
View File

@ -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)))
}

25
common/NPC.go Normal file
View File

@ -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)
}

12
common/Object.go Normal file
View File

@ -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
}

7
common/Path.go Normal file
View File

@ -0,0 +1,7 @@
package common
type Path struct {
X int32
Y int32
Action int32
}

View File

@ -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)

View File

@ -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 {

View File

@ -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

View File

@ -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) {

View File

@ -247,7 +247,7 @@ const (
// --- Enemy Data ---
MonStats = "/data//global//excel//monstats.txt"
MonStats = "/data/global/excel/monstats.txt"
// --- Skill Data ---