diff --git a/d2common/d2data/object.go b/d2common/d2data/object.go index 5a3e66b5..d5632bd4 100644 --- a/d2common/d2data/object.go +++ b/d2common/d2data/object.go @@ -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 } diff --git a/d2common/d2enum/composite_type.go b/d2common/d2enum/composite_type.go index 9f2680ad..be1a9213 100644 --- a/d2common/d2enum/composite_type.go +++ b/d2common/d2enum/composite_type.go @@ -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 diff --git a/d2common/d2enum/compositetype_string.go b/d2common/d2enum/compositetype_string.go new file mode 100644 index 00000000..9287d74e --- /dev/null +++ b/d2common/d2enum/compositetype_string.go @@ -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]] +} diff --git a/d2common/d2enum/compositetype_string2enum.go b/d2common/d2enum/compositetype_string2enum.go new file mode 100644 index 00000000..836a5d41 --- /dev/null +++ b/d2common/d2enum/compositetype_string2enum.go @@ -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": + } +} diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index dfa36f85..e2d76c94 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -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 } diff --git a/d2common/data_dictionary.go b/d2common/data_dictionary.go index 9a261b37..8a3b21a7 100644 --- a/d2common/data_dictionary.go +++ b/d2common/data_dictionary.go @@ -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 } diff --git a/d2core/d2asset/composite.go b/d2core/d2asset/composite.go index c9b87958..a3bed613 100644 --- a/d2core/d2asset/composite.go +++ b/d2core/d2asset/composite.go @@ -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() diff --git a/d2core/d2map/d2mapentity/npc.go b/d2core/d2map/d2mapentity/npc.go index 8fab901a..89bb4e6d 100644 --- a/d2core/d2map/d2mapentity/npc.go +++ b/d2core/d2map/d2mapentity/npc.go @@ -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), diff --git a/d2core/d2map/d2mapentity/object.go b/d2core/d2map/d2mapentity/object.go index 96f1f98f..2455e704 100644 --- a/d2core/d2map/d2mapentity/object.go +++ b/d2core/d2map/d2mapentity/object.go @@ -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 } diff --git a/d2core/d2map/d2mapstamp/stamp.go b/d2core/d2map/d2mapstamp/stamp.go index b19e957d..685a34b7 100644 --- a/d2core/d2map/d2mapstamp/stamp.go +++ b/d2core/d2map/d2mapstamp/stamp.go @@ -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) } }