Spawn monster by monstat record (#508)

* DS1 reader no longer looks up objects

* Start of enteties managing their own equipment

* stringer and string2enum CompositeType

String2enum

* Use CompositeType stringer to simplify composite

* Finally fix GetDelimitedList

And lint issues

* NPC selects random equipment
This commit is contained in:
Ziemas 2020-07-01 06:06:06 +02:00 committed by GitHub
parent c27fb572bc
commit fa20b9ee51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 205 additions and 78 deletions

View File

@ -2,7 +2,6 @@ package d2data
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
)
// Object is a game world object
@ -13,6 +12,4 @@ type Object struct {
Y int
Flags int
Paths []d2common.Path
Lookup *d2datadict.ObjectLookupRecord
ObjectInfo *d2datadict.ObjectRecord
}

View File

@ -6,53 +6,55 @@ type CompositeType int
const (
// CompositeTypeHead is a composite type for heads
CompositeTypeHead CompositeType = iota
CompositeTypeHead CompositeType = iota // HD
// CompositeTypeTorso is a composite type for torsos
CompositeTypeTorso
CompositeTypeTorso // TR
// CompositeTypeLegs is a composite type for legs
CompositeTypeLegs
CompositeTypeLegs // LG
// CompositeTypeRightArm is a composite type for right arms
CompositeTypeRightArm
CompositeTypeRightArm // RA
// CompositeTypeLeftArm is a composite type for left arms
CompositeTypeLeftArm
CompositeTypeLeftArm // LA
// CompositeTypeRightHand is a composite type for right hands
CompositeTypeRightHand
CompositeTypeRightHand // RH
// CompositeTypeLeftHand is a composite type for left hands
CompositeTypeLeftHand
CompositeTypeLeftHand // LH
// CompositeTypeShield is a composite type for shields
CompositeTypeShield
CompositeTypeShield // SH
// CompositeTypeSpecial1 is a composite type for special type 1s
CompositeTypeSpecial1
CompositeTypeSpecial1 // S1
// CompositeTypeSpecial2 is a composite type for special type 2s
CompositeTypeSpecial2
CompositeTypeSpecial2 // S2
// CompositeTypeSpecial3 is a composite type for special type 3s
CompositeTypeSpecial3
CompositeTypeSpecial3 // S3
// CompositeTypeSpecial4 is a composite type for special type 4s
CompositeTypeSpecial4
CompositeTypeSpecial4 // S4
// CompositeTypeSpecial5 is a composite type for special type 5s
CompositeTypeSpecial5
CompositeTypeSpecial5 // S5
// CompositeTypeSpecial6 is a composite type for special type 6s
CompositeTypeSpecial6
CompositeTypeSpecial6 // S6
// CompositeTypeSpecial7 is a composite type for special type 7s
CompositeTypeSpecial7
CompositeTypeSpecial7 // S7
// CompositeTypeSpecial8 is a composite type for special type 8s
CompositeTypeSpecial8
CompositeTypeSpecial8 // S8
// CompositeTypeMax is used to determine the max number of composite types
CompositeTypeMax
)
//go:generate stringer -linecomment -type CompositeType

View File

@ -0,0 +1,39 @@
// Code generated by "stringer -linecomment -type CompositeType"; DO NOT EDIT.
package d2enum
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[CompositeTypeHead-0]
_ = x[CompositeTypeTorso-1]
_ = x[CompositeTypeLegs-2]
_ = x[CompositeTypeRightArm-3]
_ = x[CompositeTypeLeftArm-4]
_ = x[CompositeTypeRightHand-5]
_ = x[CompositeTypeLeftHand-6]
_ = x[CompositeTypeShield-7]
_ = x[CompositeTypeSpecial1-8]
_ = x[CompositeTypeSpecial2-9]
_ = x[CompositeTypeSpecial3-10]
_ = x[CompositeTypeSpecial4-11]
_ = x[CompositeTypeSpecial5-12]
_ = x[CompositeTypeSpecial6-13]
_ = x[CompositeTypeSpecial7-14]
_ = x[CompositeTypeSpecial8-15]
_ = x[CompositeTypeMax-16]
}
const _CompositeType_name = "HDTRLGRALARHLHSHS1S2S3S4S5S6S7S8CompositeTypeMax"
var _CompositeType_index = [...]uint8{0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 48}
func (i CompositeType) String() string {
if i < 0 || i >= CompositeType(len(_CompositeType_index)-1) {
return "CompositeType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _CompositeType_name[_CompositeType_index[i]:_CompositeType_index[i+1]]
}

View File

@ -0,0 +1,58 @@
// Code generated by "string2enum -samepkg -linecomment -type CompositeType"; DO NOT EDIT.
package d2enum
import "fmt"
// CompositeTypeFromString returns the CompositeType enum corresponding to s.
func CompositeTypeFromString(s string) CompositeType {
if len(s) == 0 {
return 0
}
for i := range _CompositeType_index[:len(_CompositeType_index)-1] {
if s == _CompositeType_name[_CompositeType_index[i]:_CompositeType_index[i+1]] {
return CompositeType(i)
}
}
panic(fmt.Errorf("unable to locate CompositeType enum corresponding to %q", s))
}
func _(s string) {
// Check for duplicate string values in type "CompositeType".
switch s {
// 0
case "HD":
// 1
case "TR":
// 2
case "LG":
// 3
case "RA":
// 4
case "LA":
// 5
case "RH":
// 6
case "LH":
// 7
case "SH":
// 8
case "S1":
// 9
case "S2":
// 10
case "S3":
// 11
case "S4":
// 12
case "S5":
// 13
case "S6":
// 14
case "S7":
// 15
case "S8":
// 16
case "CompositeTypeMax":
}
}

View File

@ -4,7 +4,6 @@ package d2ds1
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
)
@ -120,15 +119,6 @@ func (ds1 *DS1) loadObjects(br *d2common.StreamReader) {
newObject.X = int(br.GetInt32())
newObject.Y = int(br.GetInt32())
newObject.Flags = int(br.GetInt32())
newObject.Lookup = d2datadict.LookupObject(int(ds1.Act), newObject.Type, newObject.Id)
if newObject.Lookup != nil && newObject.Type == 1 {
newObject.Lookup.Name = d2datadict.MonPresets[ds1.Act][newObject.Id]
}
if newObject.Lookup != nil && newObject.Lookup.ObjectsTxtId != -1 {
newObject.ObjectInfo = d2datadict.Objects[newObject.Lookup.ObjectsTxtId]
}
ds1.Objects[objIdx] = newObject
}

View File

@ -12,63 +12,70 @@ type DataDictionary struct {
Data [][]string
}
// LoadDataDictionary loads the contents of a spreadsheet style txt file
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, len(lines)-2)
for i, line := range lines[1:] {
if len(strings.TrimSpace(line)) == 0 {
if strings.TrimSpace(line) == "" {
continue
}
values := strings.Split(line, "\t")
if len(values) != len(result.FieldNameLookup) {
continue
}
result.Data[i] = values
}
return result
}
// GetString gets a string from the given column and row
func (v *DataDictionary) GetString(fieldName string, index int) string {
return v.Data[index][v.FieldNameLookup[fieldName]]
}
// GetNumber gets a number for the given column and row
func (v *DataDictionary) GetNumber(fieldName string, index int) int {
str := v.GetString(fieldName, index)
str = EmptyToZero(AsterToEmpty(str))
result, err := strconv.Atoi(str)
if err != nil {
log.Panic(err)
}
return result
}
// GetDelimitedList splits a delimited list from the given column and row
func (v *DataDictionary) GetDelimitedList(fieldName string, index int) []string {
unsplit := v.GetString(fieldName, index)
// Commo delimited fields are quoted, not terribly pretty to do it here but...
s := []byte(unsplit)
j := 0
// Comma delimited fields are quoted, not terribly pretty to fix that here but...
unsplit = strings.TrimRight(unsplit, "\"")
unsplit = strings.TrimLeft(unsplit, "\"")
for i := range s {
if s[i] != '"' {
s[j] = s[i]
j++
}
}
return strings.Split(string(s), ",")
return strings.Split(unsplit, ",")
}
// GetBool gets a bool value for the given column and row
func (v *DataDictionary) GetBool(fieldName string, index int) bool {
n := v.GetNumber(fieldName, index)
if n > 1 {
log.Panic("GetBool on non-bool field")
}
return n == 1
}

View File

@ -171,55 +171,40 @@ func (c *Composite) createMode(animationMode, weaponClass string, direction int)
}
for _, cofLayer := range cof.CofLayers {
var layerKey, layerValue string
var layerValue string
switch cofLayer.Type {
case d2enum.CompositeTypeHead:
layerKey = "HD"
layerValue = c.object.HD
case d2enum.CompositeTypeTorso:
layerKey = "TR"
layerValue = c.object.TR
case d2enum.CompositeTypeLegs:
layerKey = "LG"
layerValue = c.object.LG
case d2enum.CompositeTypeRightArm:
layerKey = "RA"
layerValue = c.object.RA
case d2enum.CompositeTypeLeftArm:
layerKey = "LA"
layerValue = c.object.LA
case d2enum.CompositeTypeRightHand:
layerKey = "RH"
layerValue = c.object.RH
case d2enum.CompositeTypeLeftHand:
layerKey = "LH"
layerValue = c.object.LH
case d2enum.CompositeTypeShield:
layerKey = "SH"
layerValue = c.object.SH
case d2enum.CompositeTypeSpecial1:
layerKey = "S1"
layerValue = c.object.S1
case d2enum.CompositeTypeSpecial2:
layerKey = "S2"
layerValue = c.object.S2
case d2enum.CompositeTypeSpecial3:
layerKey = "S3"
layerValue = c.object.S3
case d2enum.CompositeTypeSpecial4:
layerKey = "S4"
layerValue = c.object.S4
case d2enum.CompositeTypeSpecial5:
layerKey = "S5"
layerValue = c.object.S5
case d2enum.CompositeTypeSpecial6:
layerKey = "S6"
layerValue = c.object.S6
case d2enum.CompositeTypeSpecial7:
layerKey = "S7"
layerValue = c.object.S7
case d2enum.CompositeTypeSpecial8:
layerKey = "S8"
layerValue = c.object.S8
default:
return nil, errors.New("unknown layer type")
@ -240,7 +225,7 @@ func (c *Composite) createMode(animationMode, weaponClass string, direction int)
}
}
layer, err := loadCompositeLayer(c.object, layerKey, layerValue, animationMode, weaponClass, c.palettePath, transparency)
layer, err := loadCompositeLayer(c.object, cofLayer.Type.String(), layerValue, animationMode, weaponClass, c.palettePath, transparency)
if err == nil {
layer.SetPlaySpeed(mode.animationSpeed)
layer.PlayForward()

View File

@ -24,24 +24,42 @@ type NPC struct {
direction int
objectLookup *d2datadict.ObjectLookupRecord
monstatRecord *d2datadict.MonStatsRecord
monstatEx *d2datadict.MonStats2Record
name string
}
func CreateNPC(x, y int, object *d2datadict.ObjectLookupRecord, direction int) *NPC {
func CreateNPC(x, y int, monstat *d2datadict.MonStatsRecord, direction int) *NPC {
result := &NPC{
mapEntity: createMapEntity(x, y),
HasPaths: false,
monstatRecord: monstat,
monstatEx: d2datadict.MonStats2[monstat.ExtraDataKey],
}
object := &d2datadict.ObjectLookupRecord{
Base: "/Data/Global/Monsters",
Token: monstat.AnimationDirectoryToken,
Mode: result.monstatEx.ResurrectMode.String(),
Class: result.monstatEx.BaseWeaponClass,
TR: selectEquip(result.monstatEx.TRv),
LG: selectEquip(result.monstatEx.LGv),
RH: selectEquip(result.monstatEx.RHv),
SH: selectEquip(result.monstatEx.SHv),
RA: selectEquip(result.monstatEx.Rav),
LA: selectEquip(result.monstatEx.Lav),
LH: selectEquip(result.monstatEx.LHv),
HD: selectEquip(result.monstatEx.HDv),
}
result.objectLookup = object
composite, err := d2asset.LoadComposite(object, d2resource.PaletteUnits)
result.composite = composite
if err != nil {
panic(err)
}
result := &NPC{
mapEntity: createMapEntity(x, y),
composite: composite,
objectLookup: object,
HasPaths: false,
}
result.SetMode(object.Mode, object.Class, direction)
result.mapEntity.directioner = result.rotate
result.monstatRecord = d2datadict.MonStats[object.Name]
if result.monstatRecord != nil && result.monstatRecord.IsInteractable {
result.name = d2common.TranslateString(result.monstatRecord.NameStringTableKey)
@ -50,6 +68,15 @@ func CreateNPC(x, y int, object *d2datadict.ObjectLookupRecord, direction int) *
return result
}
func selectEquip(slice []string) string {
if len(slice) != 0 {
return slice[rand.Intn(len(slice))]
}
return ""
}
func (v *NPC) Render(target d2interface.Surface) {
target.PushTranslation(
v.offsetX+int((v.subcellX-v.subcellY)*16),

View File

@ -37,6 +37,8 @@ func CreateObject(x, y int, object *d2datadict.ObjectLookupRecord, palettePath s
entity.mapEntity.directioner = entity.rotate
entity.objectRecord = d2datadict.Objects[object.ObjectsTxtId]
entity.drawLayer = entity.objectRecord.OrderFlag[d2enum.AnimationModeObjectNeutral]
entity.SetMode(object.Mode, object.Class, 0)
return entity, nil
}

View File

@ -6,13 +6,12 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
)
@ -116,21 +115,42 @@ func (mr *Stamp) Entities(tileOffsetX, tileOffsetY int) []d2mapentity.MapEntity
entities := make([]d2mapentity.MapEntity, 0)
for _, object := range mr.ds1.Objects {
switch object.Lookup.Type {
case d2enum.ObjectTypeCharacter:
if object.Lookup.Base != "" && object.Lookup.Token != "" && object.Lookup.TR != "" {
npc := d2mapentity.CreateNPC((tileOffsetX*5)+object.X, (tileOffsetY*5)+object.Y, object.Lookup, 0)
if object.Type == int(d2enum.ObjectTypeCharacter) {
monstat := d2datadict.MonStats[d2datadict.MonPresets[mr.ds1.Act][object.Id]]
// If monstat is nil here it is a place_ type object, idk how to handle those yet.
// (See monpreset and monplace txts for reference)
if monstat != nil {
// Temorary use of Lookup.
npc := d2mapentity.CreateNPC((tileOffsetX*5)+object.X, (tileOffsetY*5)+object.Y, monstat, 0)
npc.SetPaths(convertPaths(tileOffsetX, tileOffsetY, object.Paths))
entities = append(entities, npc)
}
case d2enum.ObjectTypeItem:
if object.ObjectInfo != nil && object.ObjectInfo.Draw && object.Lookup.Base != "" && object.Lookup.Token != "" {
entity, err := d2mapentity.CreateObject((tileOffsetX*5)+object.X, (tileOffsetY*5)+object.Y, object.Lookup, d2resource.PaletteUnits)
}
if object.Type == int(d2enum.ObjectTypeItem) {
// For objects the DS1 ID to objectID is hardcoded in the game
// use the lookup table
lookup := d2datadict.LookupObject(int(mr.ds1.Act), object.Type, object.Id)
if lookup == nil {
continue
}
objectRecord := d2datadict.Objects[lookup.ObjectsTxtId]
if objectRecord != nil {
// The lookup is used deeper in for crap without checking other sources :(
// Bail out here for now
if !objectRecord.Draw || lookup.Base == "" || objectRecord.Token == "" {
continue
}
entity, err := d2mapentity.CreateObject((tileOffsetX*5)+object.X, (tileOffsetY*5)+object.Y, lookup, d2resource.PaletteUnits)
if err != nil {
panic(err)
}
entity.SetMode(object.Lookup.Mode, object.Lookup.Class, 0)
entities = append(entities, entity)
}
}