mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-09-27 21:56:19 -04:00
Initial NPC support (#111)
* Started adding support for NPCs. Added Monstats dictionary.
This commit is contained in:
parent
4cd1eae21a
commit
920a4f51b0
@ -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
47
common/DataDictionary.go
Normal 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
9
common/MonStats.go
Normal 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
25
common/NPC.go
Normal 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
12
common/Object.go
Normal 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
7
common/Path.go
Normal file
@ -0,0 +1,7 @@
|
||||
package common
|
||||
|
||||
type Path struct {
|
||||
X int32
|
||||
Y int32
|
||||
Action int32
|
||||
}
|
@ -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)
|
||||
|
51
map/DS1.go
51
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 {
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -247,7 +247,7 @@ const (
|
||||
|
||||
// --- Enemy Data ---
|
||||
|
||||
MonStats = "/data//global//excel//monstats.txt"
|
||||
MonStats = "/data/global/excel/monstats.txt"
|
||||
|
||||
// --- Skill Data ---
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user