1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-26 09:05:24 +00:00

Added inventory objects. (#177)

This commit is contained in:
Tim Sarbin 2019-11-14 22:20:01 -05:00 committed by GitHub
parent ea134afe90
commit b97bf6353d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 319 additions and 67 deletions

View File

@ -1,5 +1,7 @@
package d2enum package d2enum
import "log"
type Hero int type Hero int
const ( const (
@ -13,5 +15,27 @@ const (
HeroDruid Hero = 7 // Druid HeroDruid Hero = 7 // Druid
) )
func (v Hero) GetToken() string {
switch v {
case HeroBarbarian:
return "BA"
case HeroNecromancer:
return "NE"
case HeroPaladin:
return "PA"
case HeroAssassin:
return "AI"
case HeroSorceress:
return "SO"
case HeroAmazon:
return "AM"
case HeroDruid:
return "DZ"
default:
log.Fatalf("Unknown hero token: %d", v)
}
return ""
}
//go:generate stringer -linecomment -type Hero //go:generate stringer -linecomment -type Hero
//go:generate string2enum -samepkg -linecomment -type Hero //go:generate string2enum -samepkg -linecomment -type Hero

View File

@ -0,0 +1,9 @@
package d2enum
type InventoryItemType int
const (
InventoryItemTypeItem InventoryItemType = 0 // Item
InventoryItemTypeWeapon InventoryItemType = 1 // Weapon
InventoryItemTypeArmor InventoryItemType = 2 // Armor
)

View File

@ -0,0 +1,16 @@
package d2interface
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
type InventoryItem interface {
// GetInventoryItemName returns the name of this inventory item
GetInventoryItemName() string
// GetInventoryItemType returns the type of item this is
GetInventoryItemType() d2enum.InventoryItemType
// GetInventoryGridSize returns the width/height grid size of this inventory item
GetInventoryGridSize() (int, int)
// Returns the item code
GetItemCode() string
// Serializes the object for transport
Serialize() []byte
}

View File

@ -0,0 +1,13 @@
package d2core
type CharacterEquipment struct {
Head InventoryItemArmor // Head
Torso InventoryItemArmor // TR
Legs InventoryItemArmor // Legs
RightArm InventoryItemArmor // RA
LeftArm InventoryItemArmor // LA
LeftHand InventoryItemWeapon // LH
RightHand InventoryItemWeapon // RH
Shield InventoryItemArmor // SH
// S1-S8?
}

View File

@ -42,7 +42,7 @@ type CharacterSelect struct {
characterNameLabel [8]d2ui.Label characterNameLabel [8]d2ui.Label
characterStatsLabel [8]d2ui.Label characterStatsLabel [8]d2ui.Label
characterExpLabel [8]d2ui.Label characterExpLabel [8]d2ui.Label
characterImage [8]*d2core.NPC characterImage [8]*d2core.Hero
gameStates []*d2core.GameState gameStates []*d2core.GameState
selectedCharacter int selectedCharacter int
mouseButtonPressed bool mouseButtonPressed bool
@ -167,7 +167,7 @@ func (v *CharacterSelect) onScrollUpdate() {
} }
func (v *CharacterSelect) updateCharacterBoxes() { func (v *CharacterSelect) updateCharacterBoxes() {
expText := d2common.TranslateString("expansionchar2x") expText := d2common.TranslateString("#803")
for i := 0; i < 8; i++ { for i := 0; i < 8; i++ {
idx := i + (v.charScrollbar.GetCurrentOffset() * 2) idx := i + (v.charScrollbar.GetCurrentOffset() * 2)
if idx >= len(v.gameStates) { if idx >= len(v.gameStates) {
@ -181,7 +181,14 @@ func (v *CharacterSelect) updateCharacterBoxes() {
v.characterStatsLabel[i].SetText("Level 1 " + v.gameStates[idx].HeroType.String()) v.characterStatsLabel[i].SetText("Level 1 " + v.gameStates[idx].HeroType.String())
v.characterExpLabel[i].SetText(expText) v.characterExpLabel[i].SetText(expText)
// TODO: Generate or load the object from the actual player data... // TODO: Generate or load the object from the actual player data...
v.characterImage[i] = d2core.CreateNPC(0, 0, d2datadict.HeroObjects[v.gameStates[idx].HeroType], v.fileProvider, 5) v.characterImage[i] = d2core.CreateHero(
0,
0,
5,
v.gameStates[idx].HeroType,
d2core.HeroObjects[v.gameStates[idx].HeroType],
v.fileProvider,
)
} }
} }
@ -213,7 +220,7 @@ func (v *CharacterSelect) Render(screen *ebiten.Image) {
v.characterNameLabel[i].Draw(screen) v.characterNameLabel[i].Draw(screen)
v.characterStatsLabel[i].Draw(screen) v.characterStatsLabel[i].Draw(screen)
v.characterExpLabel[i].Draw(screen) v.characterExpLabel[i].Draw(screen)
v.characterImage[i].Render(screen, v.characterNameLabel[i].X-40, v.characterNameLabel[i].Y+30) v.characterImage[i].Render(screen, v.characterNameLabel[i].X-40, v.characterNameLabel[i].Y+50)
} }
if v.showDeleteConfirmation { if v.showDeleteConfirmation {
ebitenutil.DrawRect(screen, 0.0, 0.0, 800.0, 600.0, color.RGBA{0, 0, 0, 128}) ebitenutil.DrawRect(screen, 0.0, 0.0, 800.0, 600.0, color.RGBA{0, 0, 0, 128})

View File

@ -78,6 +78,7 @@ func CreateEngine() Engine {
d2datadict.LoadSounds(&result) d2datadict.LoadSounds(&result)
d2data.LoadAnimationData(&result) d2data.LoadAnimationData(&result)
d2datadict.LoadMonStats(&result) d2datadict.LoadMonStats(&result)
LoadHeroObjects()
result.SoundManager = d2audio.CreateManager(&result) result.SoundManager = d2audio.CreateManager(&result)
result.SoundManager.SetVolumes(result.Settings.BgmVolume, result.Settings.SfxVolume) result.SoundManager.SetVolumes(result.Settings.BgmVolume, result.Settings.SfxVolume)
result.UIManager = d2ui.CreateManager(&result, *result.SoundManager) result.UIManager = d2ui.CreateManager(&result, *result.SoundManager)

View File

@ -21,6 +21,7 @@ import (
UINT32 GameState Version UINT32 GameState Version
INT64 Game Seed INT64 Game Seed
BYTE Hero Type BYTE Hero Type
BYTE Hero Level
BYTE Act BYTE Act
BYTE Hero Name Length BYTE Hero Name Length
BYTE[] Hero Name BYTE[] Hero Name
@ -28,14 +29,16 @@ import (
*/ */
type GameState struct { type GameState struct {
Seed int64 Seed int64
HeroName string HeroName string
HeroType d2enum.Hero HeroType d2enum.Hero
Act int HeroLevel int
FilePath string Act int
FilePath string
Equipment CharacterEquipment
} }
const GameStateVersion = uint32(1) // Update this when you make breaking changes const GameStateVersion = uint32(2) // Update this when you make breaking changes
func HasGameStates() bool { func HasGameStates() bool {
files, _ := ioutil.ReadDir(getGameBaseSavePath()) files, _ := ioutil.ReadDir(getGameBaseSavePath())
@ -83,12 +86,13 @@ func LoadGameState(path string) *GameState {
} }
defer f.Close() defer f.Close()
sr := d2common.CreateStreamReader(bytes) sr := d2common.CreateStreamReader(bytes)
if sr.GetUInt32() > GameStateVersion { if sr.GetUInt32() != GameStateVersion {
// Unknown game version // Unknown game version
return nil return nil
} }
result.Seed = sr.GetInt64() result.Seed = sr.GetInt64()
result.HeroType = d2enum.Hero(sr.GetByte()) result.HeroType = d2enum.Hero(sr.GetByte())
result.HeroLevel = int(sr.GetByte())
result.Act = int(sr.GetByte()) result.Act = int(sr.GetByte())
heroNameLen := sr.GetByte() heroNameLen := sr.GetByte()
heroName, _ := sr.ReadBytes(int(heroNameLen)) heroName, _ := sr.ReadBytes(int(heroNameLen))
@ -129,7 +133,7 @@ func getGameBaseSavePath() string {
return basePath return basePath
} }
func getFirstFreefileName() string { func getFirstFreeFileName() string {
i := 0 i := 0
basePath := getGameBaseSavePath() basePath := getGameBaseSavePath()
for { for {
@ -143,7 +147,7 @@ func getFirstFreefileName() string {
func (v *GameState) Save() { func (v *GameState) Save() {
if v.FilePath == "" { if v.FilePath == "" {
v.FilePath = getFirstFreefileName() v.FilePath = getFirstFreeFileName()
} }
f, err := os.Create(v.FilePath) f, err := os.Create(v.FilePath)
if err != nil { if err != nil {
@ -154,6 +158,7 @@ func (v *GameState) Save() {
sr.PushUint32(GameStateVersion) sr.PushUint32(GameStateVersion)
sr.PushInt64(v.Seed) sr.PushInt64(v.Seed)
sr.PushByte(byte(v.HeroType)) sr.PushByte(byte(v.HeroType))
sr.PushByte(byte(v.HeroLevel))
sr.PushByte(byte(v.Act)) sr.PushByte(byte(v.Act))
sr.PushByte(byte(len(v.HeroName))) sr.PushByte(byte(len(v.HeroName)))
for _, ch := range v.HeroName { for _, ch := range v.HeroName {

48
d2core/hero.go Normal file
View File

@ -0,0 +1,48 @@
package d2core
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2render"
"github.com/hajimehoshi/ebiten"
)
type Hero struct {
AnimatedEntity d2render.AnimatedEntity
Equipment CharacterEquipment
mode d2enum.AnimationMode
direction int
}
func CreateHero(x, y int32, direction int, heroType d2enum.Hero, equipment CharacterEquipment, fileProvider d2interface.FileProvider) *Hero {
result := &Hero{
AnimatedEntity: d2render.CreateAnimatedEntity(x, y, &d2datadict.ObjectLookupRecord{
Mode: d2enum.AnimationModePlayerNeutral.String(),
Base: "/data/global/chars",
Token: heroType.GetToken(),
Class: equipment.RightHand.GetWeaponClass(),
SH: equipment.Shield.GetItemCode(),
// TODO: Offhand class?
HD: equipment.Head.GetArmorClass(),
TR: equipment.Torso.GetArmorClass(),
LG: equipment.Legs.GetArmorClass(),
RA: equipment.RightArm.GetArmorClass(),
LA: equipment.LeftArm.GetArmorClass(),
RH: equipment.RightHand.GetItemCode(),
LH: equipment.LeftHand.GetItemCode(),
},
fileProvider,
d2enum.Units,
),
Equipment: equipment,
mode: d2enum.AnimationModePlayerTownNeutral,
direction: direction,
}
result.AnimatedEntity.SetMode(result.mode.String(), equipment.RightHand.GetWeaponClass(), direction)
return result
}
func (v *Hero) Render(target *ebiten.Image, offsetX, offsetY int) {
v.AnimatedEntity.Render(target, offsetX, offsetY)
}

41
d2core/hero_objects.go Normal file
View File

@ -0,0 +1,41 @@
package d2core
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
)
var HeroObjects map[d2enum.Hero]CharacterEquipment
func LoadHeroObjects() {
//Mode: d2enum.AnimationModePlayerNeutral.String(),
//Base: "/data/global/chars",
HeroObjects = map[d2enum.Hero]CharacterEquipment{
d2enum.HeroBarbarian: {
RightHand: GetWeaponItemByCode("hax"),
Shield: GetArmorItemByCode("buc"),
},
d2enum.HeroNecromancer: {
RightHand: GetWeaponItemByCode("wnd"),
},
d2enum.HeroPaladin: {
RightHand: GetWeaponItemByCode("ssd"),
Shield: GetArmorItemByCode("buc"),
},
d2enum.HeroAssassin: {
RightHand: GetWeaponItemByCode("ktr"),
Shield: GetArmorItemByCode("buc"),
},
d2enum.HeroSorceress: {
RightHand: GetWeaponItemByCode("sst"),
LeftHand: GetWeaponItemByCode("sst"),
},
d2enum.HeroAmazon: {
RightHand: GetWeaponItemByCode("jav"),
Shield: GetArmorItemByCode("buc"),
},
d2enum.HeroDruid: {
RightHand: GetWeaponItemByCode("clb"),
Shield: GetArmorItemByCode("buc"),
},
}
}

View File

@ -0,0 +1,57 @@
package d2core
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
)
type InventoryItemArmor struct {
inventorySizeX int
inventorySizeY int
itemName string
itemCode string
armorClass string
}
func GetArmorItemByCode(code string) InventoryItemArmor {
result := d2datadict.Armors[code]
if result == nil {
log.Fatalf("Could not find armor entry for code '%s'", code)
}
return InventoryItemArmor{
inventorySizeX: result.InventoryWidth,
inventorySizeY: result.InventoryHeight,
itemName: result.Name,
itemCode: result.Code,
armorClass: "lit", // TODO: Where does this come from?
}
}
func (v InventoryItemArmor) GetArmorClass() string {
if v.itemCode == "" {
return "lit"
}
return v.armorClass
}
func (v InventoryItemArmor) GetInventoryItemName() string {
return v.itemName
}
func (v InventoryItemArmor) GetInventoryItemType() d2enum.InventoryItemType {
return d2enum.InventoryItemTypeArmor
}
func (v InventoryItemArmor) GetInventoryGridSize() (int, int) {
return v.inventorySizeX, v.inventorySizeY
}
func (v InventoryItemArmor) Serialize() []byte {
return []byte{}
}
func (v InventoryItemArmor) GetItemCode() string {
return v.itemCode
}

View File

@ -0,0 +1,67 @@
package d2core
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
)
type InventoryItemWeapon struct {
inventorySizeX int
inventorySizeY int
itemName string
itemCode string
weaponClass string
weaponClassOffHand string
}
func GetWeaponItemByCode(code string) InventoryItemWeapon {
// TODO: Non-normal codes will fail here...
result := d2datadict.Weapons[code]
if result == nil {
log.Fatalf("Could not find weapon entry for code '%s'", code)
}
return InventoryItemWeapon{
inventorySizeX: result.InventoryWidth,
inventorySizeY: result.InventoryHeight,
itemName: result.Name,
itemCode: result.Code,
weaponClass: result.WeaponClass,
weaponClassOffHand: result.WeaponClass2Hand,
}
}
func (v InventoryItemWeapon) GetWeaponClass() string {
if v.itemCode == "" {
return "hth"
}
return v.weaponClass
}
func (v InventoryItemWeapon) GetWeaponClassOffHand() string {
if v.itemCode == "" {
return ""
}
return v.weaponClassOffHand
}
func (v InventoryItemWeapon) GetInventoryItemName() string {
return v.itemName
}
func (v InventoryItemWeapon) GetInventoryItemType() d2enum.InventoryItemType {
return d2enum.InventoryItemTypeWeapon
}
func (v InventoryItemWeapon) GetInventoryGridSize() (int, int) {
return v.inventorySizeX, v.inventorySizeY
}
func (v InventoryItemWeapon) Serialize() []byte {
return []byte{}
}
func (v InventoryItemWeapon) GetItemCode() string {
return v.itemCode
}

View File

@ -18,7 +18,7 @@ func CreateNPC(x, y int32, object *d2datadict.ObjectLookupRecord, fileProvider d
result := &NPC{ result := &NPC{
AnimatedEntity: d2render.CreateAnimatedEntity(x, y, object, fileProvider, d2enum.Units), AnimatedEntity: d2render.CreateAnimatedEntity(x, y, object, fileProvider, d2enum.Units),
} }
result.AnimatedEntity.SetMode(object.Mode, object.Class, direction, fileProvider) result.AnimatedEntity.SetMode(object.Mode, object.Class, direction)
return result return result
} }

View File

@ -1,43 +0,0 @@
package d2datadict
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
)
var HeroObjects = map[d2enum.Hero]*ObjectLookupRecord{
d2enum.HeroBarbarian: &ObjectLookupRecord{
Mode: d2enum.AnimationModePlayerNeutral.String(),
Base: "/data/global/chars",
Token: "BA", Class: "HTH", HD: "LIT", TR: "LIT", LG: "LIT", RA: "LIT", LA: "LIT", RH: "", LH: "",
},
d2enum.HeroNecromancer: &ObjectLookupRecord{
Mode: d2enum.AnimationModePlayerNeutral.String(),
Base: "/data/global/chars",
Token: "NE", Class: "HTH", HD: "LIT", TR: "LIT", LG: "LIT", RA: "LIT", LA: "LIT", RH: "", LH: "",
},
d2enum.HeroPaladin: &ObjectLookupRecord{
Mode: d2enum.AnimationModePlayerNeutral.String(),
Base: "/data/global/chars",
Token: "PA", Class: "1HS", HD: "LIT", TR: "LIT", LG: "LIT", RA: "LIT", LA: "LIT", RH: "", LH: "",
},
d2enum.HeroAssassin: &ObjectLookupRecord{
Mode: d2enum.AnimationModePlayerNeutral.String(),
Base: "/data/global/chars",
Token: "AI", Class: "HTH", HD: "LIT", TR: "LIT", LG: "LIT", RA: "LIT", LA: "LIT", RH: "", LH: "",
},
d2enum.HeroSorceress: &ObjectLookupRecord{
Mode: d2enum.AnimationModePlayerNeutral.String(),
Base: "/data/global/chars",
Token: "SO", Class: "HTH", HD: "LIT", TR: "LIT", LG: "LIT", RA: "LIT", LA: "LIT", RH: "", LH: "",
},
d2enum.HeroAmazon: &ObjectLookupRecord{
Mode: d2enum.AnimationModePlayerNeutral.String(),
Base: "/data/global/chars",
Token: "AM", Class: "1HT", HD: "LIT", TR: "LIT", LG: "LIT", RA: "LIT", LA: "LIT", RH: "", LH: "",
},
d2enum.HeroDruid: &ObjectLookupRecord{
Mode: d2enum.AnimationModePlayerNeutral.String(),
Base: "/data/global/chars",
Token: "DZ", Class: "HTH", HD: "LIT", TR: "LIT", LG: "LIT", RA: "LIT", LA: "LIT", RH: "", LH: "",
},
}

View File

@ -31,6 +31,7 @@ var DccLayerNames = []string{"HD", "TR", "LG", "RA", "LA", "RH", "LH", "SH", "S1
// AnimatedEntity represents an entity on the map that can be animated // AnimatedEntity represents an entity on the map that can be animated
type AnimatedEntity struct { type AnimatedEntity struct {
fileProvider d2interface.FileProvider
// LocationX represents the tile X position of the entity // LocationX represents the tile X position of the entity
LocationX float64 LocationX float64
// LocationY represents the tile Y position of the entity // LocationY represents the tile Y position of the entity
@ -56,10 +57,11 @@ type AnimatedEntity struct {
// CreateAnimatedEntity creates an instance of AnimatedEntity // CreateAnimatedEntity creates an instance of AnimatedEntity
func CreateAnimatedEntity(x, y int32, object *d2datadict.ObjectLookupRecord, fileProvider d2interface.FileProvider, palette d2enum.PaletteType) AnimatedEntity { func CreateAnimatedEntity(x, y int32, object *d2datadict.ObjectLookupRecord, fileProvider d2interface.FileProvider, palette d2enum.PaletteType) AnimatedEntity {
result := AnimatedEntity{ result := AnimatedEntity{
base: object.Base, fileProvider: fileProvider,
token: object.Token, base: object.Base,
object: object, token: object.Token,
palette: palette, object: object,
palette: palette,
} }
result.dccLayers = make(map[string]d2dcc.DCC) result.dccLayers = make(map[string]d2dcc.DCC)
result.LocationX = float64(x) / 5 result.LocationX = float64(x) / 5
@ -75,9 +77,9 @@ func CreateAnimatedEntity(x, y int32, object *d2datadict.ObjectLookupRecord, fil
var DirectionLookup = []int{3, 15, 4, 8, 0, 9, 5, 10, 1, 11, 6, 12, 2, 13, 7, 14} var DirectionLookup = []int{3, 15, 4, 8, 0, 9, 5, 10, 1, 11, 6, 12, 2, 13, 7, 14}
// SetMode changes the graphical mode of this animated entity // SetMode changes the graphical mode of this animated entity
func (v *AnimatedEntity) SetMode(animationMode, weaponClass string, direction int, provider d2interface.FileProvider) { func (v *AnimatedEntity) SetMode(animationMode, weaponClass string, direction int) {
cofPath := fmt.Sprintf("%s/%s/COF/%s%s%s.COF", v.base, v.token, v.token, animationMode, weaponClass) cofPath := fmt.Sprintf("%s/%s/COF/%s%s%s.COF", v.base, v.token, v.token, animationMode, weaponClass)
v.Cof = d2cof.LoadCOF(cofPath, provider) v.Cof = d2cof.LoadCOF(cofPath, v.fileProvider)
v.animationMode = animationMode v.animationMode = animationMode
v.weaponClass = weaponClass v.weaponClass = weaponClass
v.direction = direction v.direction = direction
@ -89,7 +91,7 @@ func (v *AnimatedEntity) SetMode(animationMode, weaponClass string, direction in
v.dccLayers = make(map[string]d2dcc.DCC) v.dccLayers = make(map[string]d2dcc.DCC)
for _, cofLayer := range v.Cof.CofLayers { for _, cofLayer := range v.Cof.CofLayers {
layerName := DccLayerNames[cofLayer.Type] layerName := DccLayerNames[cofLayer.Type]
v.dccLayers[layerName] = v.LoadLayer(layerName, provider) v.dccLayers[layerName] = v.LoadLayer(layerName, v.fileProvider)
if !v.dccLayers[layerName].IsValid() { if !v.dccLayers[layerName].IsValid() {
continue continue
} }
@ -99,7 +101,7 @@ func (v *AnimatedEntity) SetMode(animationMode, weaponClass string, direction in
} }
func (v *AnimatedEntity) LoadLayer(layer string, fileProvider d2interface.FileProvider) d2dcc.DCC { func (v *AnimatedEntity) LoadLayer(layer string, fileProvider d2interface.FileProvider) d2dcc.DCC {
layerName := "tr" layerName := "TR"
switch strings.ToUpper(layer) { switch strings.ToUpper(layer) {
case "HD": // Head case "HD": // Head
layerName = v.object.HD layerName = v.object.HD
@ -138,7 +140,12 @@ func (v *AnimatedEntity) LoadLayer(layer string, fileProvider d2interface.FilePr
return d2dcc.DCC{} return d2dcc.DCC{}
} }
dccPath := fmt.Sprintf("%s/%s/%s/%s%s%s%s%s.dcc", v.base, v.token, layer, v.token, layer, layerName, v.animationMode, v.weaponClass) dccPath := fmt.Sprintf("%s/%s/%s/%s%s%s%s%s.dcc", v.base, v.token, layer, v.token, layer, layerName, v.animationMode, v.weaponClass)
return d2dcc.LoadDCC(dccPath, fileProvider) result := d2dcc.LoadDCC(dccPath, fileProvider)
if !result.IsValid() {
dccPath = fmt.Sprintf("%s/%s/%s/%s%s%s%s%s.dcc", v.base, v.token, layer, v.token, layer, layerName, v.animationMode, "HTH")
result = d2dcc.LoadDCC(dccPath, fileProvider)
}
return result
} }
// Render draws this animated entity onto the target // Render draws this animated entity onto the target

View File

@ -112,7 +112,7 @@ func (v *Region) loadObjects(fileProvider d2interface.FileProvider) {
return return
} }
entity := d2render.CreateAnimatedEntity(object.X, object.Y, object.Lookup, fileProvider, d2enum.Units) entity := d2render.CreateAnimatedEntity(object.X, object.Y, object.Lookup, fileProvider, d2enum.Units)
entity.SetMode(object.Lookup.Mode, object.Lookup.Class, 0, fileProvider) entity.SetMode(object.Lookup.Mode, object.Lookup.Class, 0)
v.AnimationEntities = append(v.AnimationEntities, entity) v.AnimationEntities = append(v.AnimationEntities, entity)
} }
}(object) }(object)