1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-17 12:55: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
import "log"
type Hero int
const (
@ -13,5 +15,27 @@ const (
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 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
characterStatsLabel [8]d2ui.Label
characterExpLabel [8]d2ui.Label
characterImage [8]*d2core.NPC
characterImage [8]*d2core.Hero
gameStates []*d2core.GameState
selectedCharacter int
mouseButtonPressed bool
@ -167,7 +167,7 @@ func (v *CharacterSelect) onScrollUpdate() {
}
func (v *CharacterSelect) updateCharacterBoxes() {
expText := d2common.TranslateString("expansionchar2x")
expText := d2common.TranslateString("#803")
for i := 0; i < 8; i++ {
idx := i + (v.charScrollbar.GetCurrentOffset() * 2)
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.characterExpLabel[i].SetText(expText)
// 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.characterStatsLabel[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 {
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)
d2data.LoadAnimationData(&result)
d2datadict.LoadMonStats(&result)
LoadHeroObjects()
result.SoundManager = d2audio.CreateManager(&result)
result.SoundManager.SetVolumes(result.Settings.BgmVolume, result.Settings.SfxVolume)
result.UIManager = d2ui.CreateManager(&result, *result.SoundManager)

View File

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

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
type AnimatedEntity struct {
fileProvider d2interface.FileProvider
// LocationX represents the tile X position of the entity
LocationX float64
// LocationY represents the tile Y position of the entity
@ -56,10 +57,11 @@ type AnimatedEntity struct {
// CreateAnimatedEntity creates an instance of AnimatedEntity
func CreateAnimatedEntity(x, y int32, object *d2datadict.ObjectLookupRecord, fileProvider d2interface.FileProvider, palette d2enum.PaletteType) AnimatedEntity {
result := AnimatedEntity{
base: object.Base,
token: object.Token,
object: object,
palette: palette,
fileProvider: fileProvider,
base: object.Base,
token: object.Token,
object: object,
palette: palette,
}
result.dccLayers = make(map[string]d2dcc.DCC)
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}
// 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)
v.Cof = d2cof.LoadCOF(cofPath, provider)
v.Cof = d2cof.LoadCOF(cofPath, v.fileProvider)
v.animationMode = animationMode
v.weaponClass = weaponClass
v.direction = direction
@ -89,7 +91,7 @@ func (v *AnimatedEntity) SetMode(animationMode, weaponClass string, direction in
v.dccLayers = make(map[string]d2dcc.DCC)
for _, cofLayer := range v.Cof.CofLayers {
layerName := DccLayerNames[cofLayer.Type]
v.dccLayers[layerName] = v.LoadLayer(layerName, provider)
v.dccLayers[layerName] = v.LoadLayer(layerName, v.fileProvider)
if !v.dccLayers[layerName].IsValid() {
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 {
layerName := "tr"
layerName := "TR"
switch strings.ToUpper(layer) {
case "HD": // Head
layerName = v.object.HD
@ -138,7 +140,12 @@ func (v *AnimatedEntity) LoadLayer(layer string, fileProvider d2interface.FilePr
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)
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

View File

@ -112,7 +112,7 @@ func (v *Region) loadObjects(fileProvider d2interface.FileProvider) {
return
}
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)
}
}(object)