mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2025-02-20 07:27:19 -05:00
Added more functionality to character select screen. (#143)
This commit is contained in:
parent
f81eb5764d
commit
a6a9434267
@ -3,12 +3,15 @@ package d2enum
|
||||
type Hero int
|
||||
|
||||
const (
|
||||
HeroNone Hero = 0
|
||||
HeroBarbarian Hero = 1
|
||||
HeroNecromancer Hero = 2
|
||||
HeroPaladin Hero = 3
|
||||
HeroAssassin Hero = 4
|
||||
HeroSorceress Hero = 5
|
||||
HeroAmazon Hero = 6
|
||||
HeroDruid Hero = 7
|
||||
HeroNone Hero = 0 //
|
||||
HeroBarbarian Hero = 1 // Barbarian
|
||||
HeroNecromancer Hero = 2 // Necromancer
|
||||
HeroPaladin Hero = 3 // Paladin
|
||||
HeroAssassin Hero = 4 // Assassin
|
||||
HeroSorceress Hero = 5 // Sorceress
|
||||
HeroAmazon Hero = 6 // Amazon
|
||||
HeroDruid Hero = 7 // Druid
|
||||
)
|
||||
|
||||
//go:generate stringer -linecomment -type Hero
|
||||
//go:generate string2enum -samepkg -linecomment -type Hero
|
||||
|
30
d2common/d2enum/hero_string.go
Normal file
30
d2common/d2enum/hero_string.go
Normal file
@ -0,0 +1,30 @@
|
||||
// Code generated by "stringer -linecomment -type Hero"; 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[HeroNone-0]
|
||||
_ = x[HeroBarbarian-1]
|
||||
_ = x[HeroNecromancer-2]
|
||||
_ = x[HeroPaladin-3]
|
||||
_ = x[HeroAssassin-4]
|
||||
_ = x[HeroSorceress-5]
|
||||
_ = x[HeroAmazon-6]
|
||||
_ = x[HeroDruid-7]
|
||||
}
|
||||
|
||||
const _Hero_name = "BarbarianNecromancerPaladinAssassinSorceressAmazonDruid"
|
||||
|
||||
var _Hero_index = [...]uint8{0, 0, 9, 20, 27, 35, 44, 50, 55}
|
||||
|
||||
func (i Hero) String() string {
|
||||
if i < 0 || i >= Hero(len(_Hero_index)-1) {
|
||||
return "Hero(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Hero_name[_Hero_index[i]:_Hero_index[i+1]]
|
||||
}
|
18
d2common/d2enum/hero_string2enum.go
Normal file
18
d2common/d2enum/hero_string2enum.go
Normal file
@ -0,0 +1,18 @@
|
||||
// Code generated by "string2enum -samepkg -linecomment -type Hero"; DO NOT EDIT.
|
||||
|
||||
package d2enum
|
||||
|
||||
import "fmt"
|
||||
|
||||
// HeroFromString returns the Hero enum corresponding to s.
|
||||
func HeroFromString(s string) Hero {
|
||||
if len(s) == 0 {
|
||||
return 0
|
||||
}
|
||||
for i := range _Hero_index[:len(_Hero_index)-1] {
|
||||
if s == _Hero_name[_Hero_index[i]:_Hero_index[i+1]] {
|
||||
return Hero(i)
|
||||
}
|
||||
}
|
||||
panic(fmt.Errorf("unable to locate Hero enum corresponding to %q", s))
|
||||
}
|
41
d2common/d2enum/region_id.go
Normal file
41
d2common/d2enum/region_id.go
Normal file
@ -0,0 +1,41 @@
|
||||
package d2enum
|
||||
|
||||
type RegionIdType int
|
||||
|
||||
const (
|
||||
RegionAct1Town RegionIdType = 1
|
||||
RegionAct1Wilderness RegionIdType = 2
|
||||
RegionAct1Cave RegionIdType = 3
|
||||
RegionAct1Crypt RegionIdType = 4
|
||||
RegionAct1Monestary RegionIdType = 5
|
||||
RegionAct1Courtyard RegionIdType = 6
|
||||
RegionAct1Barracks RegionIdType = 7
|
||||
RegionAct1Jail RegionIdType = 8
|
||||
RegionAct1Cathedral RegionIdType = 9
|
||||
RegionAct1Catacombs RegionIdType = 10
|
||||
RegionAct1Tristram RegionIdType = 11
|
||||
RegionAct2Town RegionIdType = 12
|
||||
RegionAct2Sewer RegionIdType = 13
|
||||
RegionAct2Harem RegionIdType = 14
|
||||
RegionAct2Basement RegionIdType = 15
|
||||
RegionAct2Desert RegionIdType = 16
|
||||
RegionAct2Tomb RegionIdType = 17
|
||||
RegionAct2Lair RegionIdType = 18
|
||||
RegionAct2Arcane RegionIdType = 19
|
||||
RegionAct3Town RegionIdType = 20
|
||||
RegionAct3Jungle RegionIdType = 21
|
||||
RegionAct3Kurast RegionIdType = 22
|
||||
RegionAct3Spider RegionIdType = 23
|
||||
RegionAct3Dungeon RegionIdType = 24
|
||||
RegionAct3Sewer RegionIdType = 25
|
||||
RegionAct4Town RegionIdType = 26
|
||||
RegionAct4Mesa RegionIdType = 27
|
||||
RegionAct4Lava RegionIdType = 28
|
||||
RegonAct5Town RegionIdType = 29
|
||||
RegionAct5Siege RegionIdType = 30
|
||||
RegionAct5Barricade RegionIdType = 31
|
||||
RegionAct5Temple RegionIdType = 32
|
||||
RegionAct5IceCaves RegionIdType = 33
|
||||
RegionAct5Baal RegionIdType = 34
|
||||
RegionAct5Lava RegionIdType = 35
|
||||
)
|
9
d2common/d2enum/region_layer.go
Normal file
9
d2common/d2enum/region_layer.go
Normal file
@ -0,0 +1,9 @@
|
||||
package d2enum
|
||||
|
||||
type RegionLayerType int
|
||||
|
||||
const (
|
||||
RegionLayerTypeFloors RegionLayerType = 0
|
||||
RegionLayerTypeWalls RegionLayerType = 1
|
||||
RegionLayerTypeShadows RegionLayerType = 2
|
||||
)
|
@ -80,6 +80,8 @@ const (
|
||||
// -- Character Selection
|
||||
|
||||
CharacterSelectionBackground = "/data/global/ui/CharSelect/characterselectscreenEXP.dc6"
|
||||
CharacterSelectionSelectBox = "/data/global/ui/CharSelect/charselectbox.dc6"
|
||||
PopUpOkCancel = "/data/global/ui/FrontEnd/PopUpOKCancel.dc6"
|
||||
|
||||
// --- Game ---
|
||||
|
||||
@ -101,6 +103,7 @@ const (
|
||||
Font16 = "/data/local/font/latin/font16"
|
||||
Font24 = "/data/local/font/latin/font24"
|
||||
Font30 = "/data/local/font/latin/font30"
|
||||
Font42 = "/data/local/font/latin/font42"
|
||||
FontFormal12 = "/data/local/font/latin/fontformal12"
|
||||
FontFormal11 = "/data/local/font/latin/fontformal11"
|
||||
FontFormal10 = "/data/local/font/latin/fontformal10"
|
||||
|
@ -55,20 +55,48 @@ func (v *StreamReader) SetPosition(newPosition uint64) {
|
||||
v.position = newPosition
|
||||
}
|
||||
|
||||
// GetUInt32 returns a uint32 word from the stream
|
||||
// GetUInt32 returns a uint32 dword from the stream
|
||||
func (v *StreamReader) GetUInt32() uint32 {
|
||||
result := (uint32(v.data[v.position+3]) << uint(24)) + (uint32(v.data[v.position+2]) << uint(16)) + (uint32(v.data[v.position+1]) << uint(8)) + uint32(v.data[v.position])
|
||||
v.position += 4
|
||||
return result
|
||||
}
|
||||
|
||||
// GetInt32 returns an int32 word from the stream
|
||||
// GetInt32 returns an int32 dword from the stream
|
||||
func (v *StreamReader) GetInt32() int32 {
|
||||
result := (int32(v.data[v.position+3]) << uint(24)) + (int32(v.data[v.position+2]) << uint(16)) + (int32(v.data[v.position+1]) << uint(8)) + int32(v.data[v.position])
|
||||
v.position += 4
|
||||
return result
|
||||
}
|
||||
|
||||
// GetUint64 returns a uint64 qword from the stream
|
||||
func (v *StreamReader) GetUint64() uint64 {
|
||||
result := (uint64(v.data[v.position+7]) << uint(56)) +
|
||||
(uint64(v.data[v.position+6]) << uint(48)) +
|
||||
(uint64(v.data[v.position+5]) << uint(40)) +
|
||||
(uint64(v.data[v.position+4]) << uint(32)) +
|
||||
(uint64(v.data[v.position+3]) << uint(24)) +
|
||||
(uint64(v.data[v.position+2]) << uint(16)) +
|
||||
(uint64(v.data[v.position+1]) << uint(8)) +
|
||||
uint64(v.data[v.position])
|
||||
v.position += 8
|
||||
return result
|
||||
}
|
||||
|
||||
// GetInt64 returns a uint64 qword from the stream
|
||||
func (v *StreamReader) GetInt64() int64 {
|
||||
result := (uint64(v.data[v.position+7]) << uint(56)) +
|
||||
(uint64(v.data[v.position+6]) << uint(48)) +
|
||||
(uint64(v.data[v.position+5]) << uint(40)) +
|
||||
(uint64(v.data[v.position+4]) << uint(32)) +
|
||||
(uint64(v.data[v.position+3]) << uint(24)) +
|
||||
(uint64(v.data[v.position+2]) << uint(16)) +
|
||||
(uint64(v.data[v.position+1]) << uint(8)) +
|
||||
uint64(v.data[v.position])
|
||||
v.position += 8
|
||||
return int64(result)
|
||||
}
|
||||
|
||||
// ReadByte implements io.ByteReader
|
||||
func (v *StreamReader) ReadByte() (byte, error) {
|
||||
return v.GetByte(), nil
|
||||
|
@ -18,26 +18,51 @@ func (v *StreamWriter) PushByte(val byte) {
|
||||
v.data = append(v.data, val)
|
||||
}
|
||||
|
||||
// PushWord writes an uint16 word to the stream
|
||||
func (v *StreamWriter) PushWord(val uint16) {
|
||||
// PushUint16 writes an uint16 word to the stream
|
||||
func (v *StreamWriter) PushUint16(val uint16) {
|
||||
v.data = append(v.data, byte(val&0xFF))
|
||||
v.data = append(v.data, byte((val>>8)&0xFF))
|
||||
}
|
||||
|
||||
// PushSWord writes a int16 word to the stream
|
||||
func (v *StreamWriter) PushSWord(val int16) {
|
||||
// PushInt16 writes a int16 word to the stream
|
||||
func (v *StreamWriter) PushInt16(val int16) {
|
||||
v.data = append(v.data, byte(val&0xFF))
|
||||
v.data = append(v.data, byte((val>>8)&0xFF))
|
||||
}
|
||||
|
||||
// PushDword writes a uint32 dword to the stream
|
||||
func (v *StreamWriter) PushDword(val uint32) {
|
||||
// PushUint32 writes a uint32 dword to the stream
|
||||
func (v *StreamWriter) PushUint32(val uint32) {
|
||||
v.data = append(v.data, byte(val&0xFF))
|
||||
v.data = append(v.data, byte((val>>8)&0xFF))
|
||||
v.data = append(v.data, byte((val>>16)&0xFF))
|
||||
v.data = append(v.data, byte((val>>24)&0xFF))
|
||||
}
|
||||
|
||||
// PushUint64 writes a uint64 qword to the stream
|
||||
func (v *StreamWriter) PushUint64(val uint64) {
|
||||
v.data = append(v.data, byte(val&0xFF))
|
||||
v.data = append(v.data, byte((val>>8)&0xFF))
|
||||
v.data = append(v.data, byte((val>>16)&0xFF))
|
||||
v.data = append(v.data, byte((val>>24)&0xFF))
|
||||
v.data = append(v.data, byte((val>>32)&0xFF))
|
||||
v.data = append(v.data, byte((val>>40)&0xFF))
|
||||
v.data = append(v.data, byte((val>>48)&0xFF))
|
||||
v.data = append(v.data, byte((val>>56)&0xFF))
|
||||
}
|
||||
|
||||
// PushInt64 writes a uint64 qword to the stream
|
||||
func (v *StreamWriter) PushInt64(val int64) {
|
||||
result := uint64(val)
|
||||
v.data = append(v.data, byte(result&0xFF))
|
||||
v.data = append(v.data, byte((result>>8)&0xFF))
|
||||
v.data = append(v.data, byte((result>>16)&0xFF))
|
||||
v.data = append(v.data, byte((result>>24)&0xFF))
|
||||
v.data = append(v.data, byte((result>>32)&0xFF))
|
||||
v.data = append(v.data, byte((result>>40)&0xFF))
|
||||
v.data = append(v.data, byte((result>>48)&0xFF))
|
||||
v.data = append(v.data, byte((result>>56)&0xFF))
|
||||
}
|
||||
|
||||
// GetBytes returns the the byte slice of the underlying data
|
||||
func (v *StreamWriter) GetBytes() []byte {
|
||||
return v.data
|
||||
|
@ -21,8 +21,8 @@ func TestStreamWriterByte(t *testing.T) {
|
||||
func TestStreamWriterWord(t *testing.T) {
|
||||
sr := CreateStreamWriter()
|
||||
data := []byte{0x12, 0x34, 0x56, 0x78}
|
||||
sr.PushWord(0x3412)
|
||||
sr.PushWord(0x7856)
|
||||
sr.PushUint16(0x3412)
|
||||
sr.PushUint16(0x7856)
|
||||
output := sr.GetBytes()
|
||||
for i, d := range data {
|
||||
if output[i] != d {
|
||||
@ -34,7 +34,7 @@ func TestStreamWriterWord(t *testing.T) {
|
||||
func TestStreamWriterDword(t *testing.T) {
|
||||
sr := CreateStreamWriter()
|
||||
data := []byte{0x12, 0x34, 0x56, 0x78}
|
||||
sr.PushDword(0x78563412)
|
||||
sr.PushUint32(0x78563412)
|
||||
output := sr.GetBytes()
|
||||
for i, d := range data {
|
||||
if output[i] != d {
|
||||
|
@ -1,11 +1,18 @@
|
||||
package d2scene
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/ebitenutil"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
|
||||
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render"
|
||||
@ -14,16 +21,31 @@ import (
|
||||
)
|
||||
|
||||
type CharacterSelect struct {
|
||||
uiManager *d2ui.Manager
|
||||
soundManager *d2audio.Manager
|
||||
fileProvider d2interface.FileProvider
|
||||
sceneProvider d2interface.SceneProvider
|
||||
background d2render.Sprite
|
||||
newCharButton d2ui.Button
|
||||
convertCharButton d2ui.Button
|
||||
deleteCharButton d2ui.Button
|
||||
exitButton d2ui.Button
|
||||
okButton d2ui.Button
|
||||
uiManager *d2ui.Manager
|
||||
soundManager *d2audio.Manager
|
||||
fileProvider d2interface.FileProvider
|
||||
sceneProvider d2interface.SceneProvider
|
||||
background d2render.Sprite
|
||||
newCharButton d2ui.Button
|
||||
convertCharButton d2ui.Button
|
||||
deleteCharButton d2ui.Button
|
||||
exitButton d2ui.Button
|
||||
okButton d2ui.Button
|
||||
deleteCharCancelButton d2ui.Button
|
||||
deleteCharOkButton d2ui.Button
|
||||
selectionBox d2render.Sprite
|
||||
okCancelBox d2render.Sprite
|
||||
d2HeroTitle d2ui.Label
|
||||
deleteCharConfirmLabel d2ui.Label
|
||||
characterNameLabel [8]d2ui.Label
|
||||
characterStatsLabel [8]d2ui.Label
|
||||
characterExpLabel [8]d2ui.Label
|
||||
characterImage [8]*d2core.NPC
|
||||
gameStates []*d2core.GameState
|
||||
scrollOffset int
|
||||
selectedCharacter int
|
||||
mouseButtonPressed bool
|
||||
showDeleteConfirmation bool
|
||||
}
|
||||
|
||||
func CreateCharacterSelect(
|
||||
@ -33,10 +55,11 @@ func CreateCharacterSelect(
|
||||
soundManager *d2audio.Manager,
|
||||
) *CharacterSelect {
|
||||
result := &CharacterSelect{
|
||||
uiManager: uiManager,
|
||||
sceneProvider: sceneProvider,
|
||||
fileProvider: fileProvider,
|
||||
soundManager: soundManager,
|
||||
selectedCharacter: -1,
|
||||
uiManager: uiManager,
|
||||
sceneProvider: sceneProvider,
|
||||
fileProvider: fileProvider,
|
||||
soundManager: soundManager,
|
||||
}
|
||||
return result
|
||||
}
|
||||
@ -62,8 +85,8 @@ func (v *CharacterSelect) Load() []func() {
|
||||
},
|
||||
func() {
|
||||
v.deleteCharButton = d2ui.CreateButton(d2ui.ButtonTypeTall, v.fileProvider, dh.CombineStrings(dh.SplitIntoLinesWithMaxWidth(d2common.TranslateString("#832"), 15)))
|
||||
v.deleteCharButton.OnActivated(func() { v.onDeleteCharButtonClicked() })
|
||||
v.deleteCharButton.MoveTo(433, 468)
|
||||
v.deleteCharButton.SetEnabled(false)
|
||||
v.uiManager.AddWidget(&v.deleteCharButton)
|
||||
},
|
||||
func() {
|
||||
@ -72,12 +95,81 @@ func (v *CharacterSelect) Load() []func() {
|
||||
v.exitButton.OnActivated(func() { v.onExitButtonClicked() })
|
||||
v.uiManager.AddWidget(&v.exitButton)
|
||||
},
|
||||
func() {
|
||||
v.deleteCharCancelButton = d2ui.CreateButton(d2ui.ButtonTypeOkCancel, v.fileProvider, d2common.TranslateString("#4231"))
|
||||
v.deleteCharCancelButton.MoveTo(282, 308)
|
||||
v.deleteCharCancelButton.SetVisible(false)
|
||||
v.deleteCharCancelButton.OnActivated(func() { v.onDeleteCharacterCancelClicked() })
|
||||
v.uiManager.AddWidget(&v.deleteCharCancelButton)
|
||||
},
|
||||
func() {
|
||||
v.deleteCharOkButton = d2ui.CreateButton(d2ui.ButtonTypeOkCancel, v.fileProvider, d2common.TranslateString("#4227"))
|
||||
v.deleteCharOkButton.MoveTo(422, 308)
|
||||
v.deleteCharOkButton.SetVisible(false)
|
||||
v.deleteCharOkButton.OnActivated(func() { v.onDeleteCharacterConfirmClicked() })
|
||||
v.uiManager.AddWidget(&v.deleteCharOkButton)
|
||||
},
|
||||
func() {
|
||||
v.okButton = d2ui.CreateButton(d2ui.ButtonTypeMedium, v.fileProvider, d2common.TranslateString("#971"))
|
||||
v.okButton.MoveTo(625, 537)
|
||||
v.okButton.SetEnabled(false)
|
||||
v.uiManager.AddWidget(&v.okButton)
|
||||
},
|
||||
func() {
|
||||
v.d2HeroTitle = d2ui.CreateLabel(v.fileProvider, d2resource.Font42, d2enum.Units)
|
||||
v.d2HeroTitle.MoveTo(320, 23)
|
||||
v.d2HeroTitle.Alignment = d2ui.LabelAlignCenter
|
||||
},
|
||||
func() {
|
||||
v.deleteCharConfirmLabel = d2ui.CreateLabel(v.fileProvider, d2resource.Font16, d2enum.Units)
|
||||
lines := dh.SplitIntoLinesWithMaxWidth(d2common.TranslateString("#1878"), 29)
|
||||
v.deleteCharConfirmLabel.SetText(strings.Join(lines, "\n"))
|
||||
v.deleteCharConfirmLabel.Alignment = d2ui.LabelAlignCenter
|
||||
v.deleteCharConfirmLabel.MoveTo(400, 185)
|
||||
},
|
||||
func() {
|
||||
v.selectionBox = d2render.CreateSprite(v.fileProvider.LoadFile(d2resource.CharacterSelectionSelectBox), d2datadict.Palettes[d2enum.Sky])
|
||||
v.selectionBox.MoveTo(37, 86)
|
||||
},
|
||||
func() {
|
||||
v.okCancelBox = d2render.CreateSprite(v.fileProvider.LoadFile(d2resource.PopUpOkCancel), d2datadict.Palettes[d2enum.Fechar])
|
||||
v.okCancelBox.MoveTo(270, 175)
|
||||
},
|
||||
func() {
|
||||
for i := 0; i < 8; i++ {
|
||||
xOffset := 115
|
||||
if i&1 > 0 {
|
||||
xOffset = 385
|
||||
}
|
||||
v.characterNameLabel[i] = d2ui.CreateLabel(v.fileProvider, d2resource.Font16, d2enum.Units)
|
||||
v.characterNameLabel[i].Color = color.RGBA{188, 168, 140, 255}
|
||||
v.characterNameLabel[i].MoveTo(xOffset, 100+((i/2)*95))
|
||||
v.characterStatsLabel[i] = d2ui.CreateLabel(v.fileProvider, d2resource.Font16, d2enum.Units)
|
||||
v.characterStatsLabel[i].MoveTo(xOffset, 115+((i/2)*95))
|
||||
v.characterExpLabel[i] = d2ui.CreateLabel(v.fileProvider, d2resource.Font16, d2enum.Static)
|
||||
v.characterExpLabel[i].Color = color.RGBA{24, 255, 0, 255}
|
||||
v.characterExpLabel[i].MoveTo(xOffset, 130+((i/2)*95))
|
||||
}
|
||||
v.refreshGameStates()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (v *CharacterSelect) updateCharacterBoxes() {
|
||||
expText := d2common.TranslateString("expansionchar2x")
|
||||
for i := 0; i < 8; i++ {
|
||||
idx := i + (v.scrollOffset * 2)
|
||||
if idx >= len(v.gameStates) {
|
||||
v.characterNameLabel[i].SetText("")
|
||||
v.characterStatsLabel[i].SetText("")
|
||||
v.characterExpLabel[i].SetText("")
|
||||
v.characterImage[i] = nil
|
||||
continue
|
||||
}
|
||||
v.characterNameLabel[i].SetText(v.gameStates[idx].HeroName)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,7 +188,100 @@ func (v *CharacterSelect) Unload() {
|
||||
|
||||
func (v *CharacterSelect) Render(screen *ebiten.Image) {
|
||||
v.background.DrawSegments(screen, 4, 3, 0)
|
||||
v.d2HeroTitle.Draw(screen)
|
||||
if v.selectedCharacter > -1 {
|
||||
v.selectionBox.DrawSegments(screen, 2, 1, 0)
|
||||
}
|
||||
for i := 0; i < 8; i++ {
|
||||
idx := i + (v.scrollOffset * 2)
|
||||
if idx >= len(v.gameStates) {
|
||||
continue
|
||||
}
|
||||
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)
|
||||
}
|
||||
if v.showDeleteConfirmation {
|
||||
ebitenutil.DrawRect(screen, 0.0, 0.0, 800.0, 600.0, color.RGBA{0, 0, 0, 128})
|
||||
v.okCancelBox.DrawSegments(screen, 2, 1, 0)
|
||||
v.deleteCharConfirmLabel.Draw(screen)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *CharacterSelect) moveSelectionBox() {
|
||||
bw := 272
|
||||
bh := 92
|
||||
selectedIndex := v.selectedCharacter - (v.scrollOffset * 2)
|
||||
v.selectionBox.MoveTo(37+((selectedIndex&1)*int(bw)), 86+(int(bh)*(selectedIndex/2)))
|
||||
v.d2HeroTitle.SetText(v.gameStates[v.selectedCharacter].HeroName)
|
||||
}
|
||||
|
||||
func (v *CharacterSelect) Update(tickTime float64) {
|
||||
if !v.showDeleteConfirmation {
|
||||
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
|
||||
if !v.mouseButtonPressed {
|
||||
v.mouseButtonPressed = true
|
||||
mx, my := ebiten.CursorPosition()
|
||||
bw := 272
|
||||
bh := 92
|
||||
localMouseX := mx - 37
|
||||
localMouseY := my - 86
|
||||
if localMouseX > 0 && localMouseX < int(bw*2) && localMouseY >= 0 && localMouseY < int(bh*4) {
|
||||
adjustY := localMouseY / int(bh)
|
||||
selectedIndex := adjustY * 2
|
||||
if localMouseX > int(bw) {
|
||||
selectedIndex += 1
|
||||
}
|
||||
if (v.scrollOffset*2)+selectedIndex < len(v.gameStates) {
|
||||
v.selectedCharacter = (v.scrollOffset * 2) + selectedIndex
|
||||
v.moveSelectionBox()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
v.mouseButtonPressed = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (v *CharacterSelect) onDeleteCharButtonClicked() {
|
||||
v.toggleDeleteCharacterDialog(true)
|
||||
}
|
||||
|
||||
func (v *CharacterSelect) onDeleteCharacterConfirmClicked() {
|
||||
_ = os.Remove(v.gameStates[v.selectedCharacter].FilePath)
|
||||
v.selectedCharacter = -1
|
||||
v.refreshGameStates()
|
||||
v.toggleDeleteCharacterDialog(false)
|
||||
}
|
||||
|
||||
func (v *CharacterSelect) onDeleteCharacterCancelClicked() {
|
||||
v.toggleDeleteCharacterDialog(false)
|
||||
}
|
||||
|
||||
func (v *CharacterSelect) toggleDeleteCharacterDialog(showDialog bool) {
|
||||
v.showDeleteConfirmation = showDialog
|
||||
v.okButton.SetEnabled(!showDialog)
|
||||
v.deleteCharButton.SetEnabled(!showDialog)
|
||||
v.exitButton.SetEnabled(!showDialog)
|
||||
v.newCharButton.SetEnabled(!showDialog)
|
||||
v.deleteCharOkButton.SetVisible(showDialog)
|
||||
v.deleteCharCancelButton.SetVisible(showDialog)
|
||||
}
|
||||
|
||||
func (v *CharacterSelect) refreshGameStates() {
|
||||
v.gameStates = d2core.GetAllGameStates()
|
||||
v.updateCharacterBoxes()
|
||||
if len(v.gameStates) > 0 {
|
||||
v.selectedCharacter = 0
|
||||
v.d2HeroTitle.SetText(v.gameStates[0].HeroName)
|
||||
} else {
|
||||
v.selectedCharacter = -1
|
||||
}
|
||||
v.deleteCharButton.SetEnabled(v.selectedCharacter > -1)
|
||||
v.okButton.SetEnabled(v.selectedCharacter > -1)
|
||||
v.moveSelectionBox()
|
||||
|
||||
}
|
||||
|
54
d2core/d2scene/game.go
Normal file
54
d2core/d2scene/game.go
Normal file
@ -0,0 +1,54 @@
|
||||
package d2scene
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
)
|
||||
|
||||
type Game struct {
|
||||
gameState *d2core.GameState
|
||||
uiManager *d2ui.Manager
|
||||
soundManager *d2audio.Manager
|
||||
fileProvider d2interface.FileProvider
|
||||
sceneProvider d2interface.SceneProvider
|
||||
}
|
||||
|
||||
func CreateGame(
|
||||
fileProvider d2interface.FileProvider,
|
||||
sceneProvider d2interface.SceneProvider,
|
||||
uiManager *d2ui.Manager,
|
||||
soundManager *d2audio.Manager,
|
||||
gameState *d2core.GameState,
|
||||
) *Game {
|
||||
result := &Game{
|
||||
gameState: gameState,
|
||||
fileProvider: fileProvider,
|
||||
uiManager: uiManager,
|
||||
soundManager: soundManager,
|
||||
sceneProvider: sceneProvider,
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (v *Game) Load() []func() {
|
||||
return []func(){
|
||||
func() {
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Game) Unload() {
|
||||
|
||||
}
|
||||
|
||||
func (v Game) Render(screen *ebiten.Image) {
|
||||
|
||||
}
|
||||
|
||||
func (v *Game) Update(tickTime float64) {
|
||||
|
||||
}
|
@ -8,6 +8,8 @@ import (
|
||||
"os/exec"
|
||||
"runtime"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
|
||||
@ -202,6 +204,10 @@ func openbrowser(url string) {
|
||||
|
||||
func (v *MainMenu) onSinglePlayerClicked() {
|
||||
// Go here only if existing characters are available to select
|
||||
if d2core.HasGameStates() {
|
||||
v.sceneProvider.SetNextScene(CreateCharacterSelect(v.fileProvider, v.sceneProvider, v.uiManager, v.soundManager))
|
||||
return
|
||||
}
|
||||
v.sceneProvider.SetNextScene(CreateSelectHeroClass(v.fileProvider, v.sceneProvider, v.uiManager, v.soundManager))
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ func CreateMapEngineTest(
|
||||
soundManager: soundManager,
|
||||
sceneProvider: sceneProvider,
|
||||
}
|
||||
result.gameState = d2core.CreateGameState()
|
||||
result.gameState = d2core.CreateTestGameState()
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,8 @@ import (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
|
||||
@ -420,6 +422,10 @@ func (v *SelectHeroClass) Load() []func() {
|
||||
}
|
||||
|
||||
func (v *SelectHeroClass) Unload() {
|
||||
for i := range v.heroRenderInfo {
|
||||
v.heroRenderInfo[i].SelectSfx.Stop()
|
||||
v.heroRenderInfo[i].DeselectSfx.Stop()
|
||||
}
|
||||
v.heroRenderInfo = nil
|
||||
}
|
||||
|
||||
@ -428,7 +434,8 @@ func (v SelectHeroClass) onExitButtonClicked() {
|
||||
}
|
||||
|
||||
func (v SelectHeroClass) onOkButtonClicked() {
|
||||
// TODO: Start the game
|
||||
gameState := d2core.CreateGameState(v.heroNameTextbox.GetText(), v.selectedHero, v.hardcoreCheckbox.GetCheckState())
|
||||
v.sceneProvider.SetNextScene(CreateGame(v.fileProvider, v.sceneProvider, v.uiManager, v.soundManager, gameState))
|
||||
}
|
||||
|
||||
func (v *SelectHeroClass) Render(screen *ebiten.Image) {
|
||||
|
@ -226,7 +226,7 @@ func (v Engine) Draw(screen *ebiten.Image) {
|
||||
v.CurrentScene.Render(screen)
|
||||
v.UIManager.Draw(screen)
|
||||
}
|
||||
if v.showFPS == true {
|
||||
if v.showFPS {
|
||||
ebitenutil.DebugPrintAt(screen, "vsync:"+strconv.FormatBool(ebiten.IsVsyncEnabled())+"\nFPS:"+strconv.Itoa(int(ebiten.CurrentFPS())), 5, 565)
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
|
@ -1,15 +1,68 @@
|
||||
package d2core
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
)
|
||||
|
||||
/*
|
||||
File Spec
|
||||
--------------------------------------------
|
||||
UINT32 GameState Version
|
||||
INT64 Game Seed
|
||||
BYTE Hero Type
|
||||
BYTE Act
|
||||
BYTE Hero Name Length
|
||||
BYTE[] Hero Name
|
||||
--------------------------------------------
|
||||
*/
|
||||
|
||||
type GameState struct {
|
||||
Seed int64
|
||||
Seed int64
|
||||
HeroName string
|
||||
HeroType d2enum.Hero
|
||||
Act int
|
||||
FilePath string
|
||||
}
|
||||
|
||||
func CreateGameState() *GameState {
|
||||
const GameStateVersion = uint32(1) // Update this when you make breaking changes
|
||||
|
||||
func HasGameStates() bool {
|
||||
files, _ := ioutil.ReadDir(getGameBaseSavePath())
|
||||
return len(files) > 0
|
||||
}
|
||||
|
||||
func GetAllGameStates() []*GameState {
|
||||
// TODO: Make this not crash tf out on bad files
|
||||
basePath := getGameBaseSavePath()
|
||||
files, _ := ioutil.ReadDir(basePath)
|
||||
result := make([]*GameState, 0)
|
||||
for _, file := range files {
|
||||
fileName := file.Name()
|
||||
if file.IsDir() || len(fileName) < 5 || strings.ToLower(fileName[len(fileName)-4:]) != ".od2" {
|
||||
continue
|
||||
}
|
||||
gameState := LoadGameState(path.Join(basePath, file.Name()))
|
||||
if gameState == nil {
|
||||
continue
|
||||
}
|
||||
result = append(result, gameState)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// CreateTestGameState is used for the map engine previewer
|
||||
func CreateTestGameState() *GameState {
|
||||
result := &GameState{
|
||||
Seed: time.Now().UnixNano(),
|
||||
}
|
||||
@ -17,6 +70,95 @@ func CreateGameState() *GameState {
|
||||
}
|
||||
|
||||
func LoadGameState(path string) *GameState {
|
||||
log.Fatal("LoadGameState not implemented.")
|
||||
return nil
|
||||
result := &GameState{
|
||||
FilePath: path,
|
||||
}
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
log.Panicf(err.Error())
|
||||
}
|
||||
bytes, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
log.Panicf(err.Error())
|
||||
}
|
||||
sr := d2common.CreateStreamReader(bytes)
|
||||
if sr.GetUInt32() > GameStateVersion {
|
||||
// Unknown game version
|
||||
return nil
|
||||
}
|
||||
result.Seed = sr.GetInt64()
|
||||
result.HeroType = d2enum.Hero(sr.GetByte())
|
||||
result.Act = int(sr.GetByte())
|
||||
heroNameLen := sr.GetByte()
|
||||
heroName, _ := sr.ReadBytes(int(heroNameLen))
|
||||
result.HeroName = string(heroName)
|
||||
return result
|
||||
}
|
||||
|
||||
func CreateGameState(heroName string, hero d2enum.Hero, hardcore bool) *GameState {
|
||||
result := &GameState{
|
||||
HeroName: heroName,
|
||||
HeroType: hero,
|
||||
Act: 1,
|
||||
Seed: time.Now().UnixNano(),
|
||||
FilePath: "",
|
||||
}
|
||||
result.Save()
|
||||
return result
|
||||
}
|
||||
|
||||
func getGameBaseSavePath() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
appDataPath := os.Getenv("APPDATA")
|
||||
basePath := path.Join(appDataPath, "OpenDiablo2", "Saves")
|
||||
if err := os.MkdirAll(basePath, os.ModeDir); err != nil {
|
||||
log.Panicf(err.Error())
|
||||
}
|
||||
return basePath
|
||||
}
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
log.Panicf(err.Error())
|
||||
}
|
||||
basePath := path.Join(homeDir, ".OpenDiablo2", "Saves")
|
||||
if err := os.MkdirAll(basePath, os.ModeDir); err != nil {
|
||||
log.Panicf(err.Error())
|
||||
}
|
||||
// TODO: Is mac supposed to have a super special place for the save games?
|
||||
return basePath
|
||||
}
|
||||
|
||||
func getFirstFreefileName() string {
|
||||
i := 0
|
||||
basePath := getGameBaseSavePath()
|
||||
for {
|
||||
filePath := path.Join(basePath, strconv.Itoa(i)+".od2")
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
return filePath
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func (v *GameState) Save() {
|
||||
if v.FilePath == "" {
|
||||
v.FilePath = getFirstFreefileName()
|
||||
}
|
||||
f, err := os.Create(v.FilePath)
|
||||
if err != nil {
|
||||
log.Panicf(err.Error())
|
||||
}
|
||||
defer f.Close()
|
||||
sr := d2common.CreateStreamWriter()
|
||||
sr.PushUint32(GameStateVersion)
|
||||
sr.PushInt64(v.Seed)
|
||||
sr.PushByte(byte(v.HeroType))
|
||||
sr.PushByte(byte(v.Act))
|
||||
sr.PushByte(byte(len(v.HeroName)))
|
||||
for _, ch := range v.HeroName {
|
||||
sr.PushByte(byte(ch))
|
||||
}
|
||||
if _, err := f.Write(sr.GetBytes()); err != nil {
|
||||
log.Panicf(err.Error())
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render"
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
)
|
||||
@ -14,15 +14,18 @@ type NPC struct {
|
||||
Paths []d2common.Path
|
||||
}
|
||||
|
||||
func CreateNPC(object d2data.Object, fileProvider d2interface.FileProvider) *NPC {
|
||||
func CreateNPC(x, y int32, object *d2datadict.ObjectLookupRecord, fileProvider d2interface.FileProvider, direction int) *NPC {
|
||||
result := &NPC{
|
||||
AnimatedEntity: d2render.CreateAnimatedEntity(object, fileProvider, d2enum.Units),
|
||||
Paths: object.Paths,
|
||||
AnimatedEntity: d2render.CreateAnimatedEntity(x, y, object, fileProvider, d2enum.Units),
|
||||
}
|
||||
result.AnimatedEntity.SetMode(object.Lookup.Mode, object.Lookup.Class, 1, fileProvider)
|
||||
result.AnimatedEntity.SetMode(object.Mode, object.Class, direction, fileProvider)
|
||||
return result
|
||||
}
|
||||
|
||||
func (v *NPC) SetPaths(paths []d2common.Path) {
|
||||
v.Paths = paths
|
||||
}
|
||||
|
||||
func (v *NPC) Render(target *ebiten.Image, offsetX, offsetY int) {
|
||||
v.AnimatedEntity.Render(target, offsetX, offsetY)
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ func WavDecompress(data []byte, channelCount int) []byte {
|
||||
for i := 0; i < channelCount; i++ {
|
||||
temp := input.GetInt16()
|
||||
Array2[i] = int(temp)
|
||||
output.PushSWord(temp)
|
||||
output.PushInt16(temp)
|
||||
}
|
||||
|
||||
channel := channelCount - 1
|
||||
@ -56,7 +56,7 @@ func WavDecompress(data []byte, channelCount int) []byte {
|
||||
if Array1[channel] != 0 {
|
||||
Array1[channel]--
|
||||
}
|
||||
output.PushSWord(int16(Array2[channel]))
|
||||
output.PushInt16(int16(Array2[channel]))
|
||||
break
|
||||
case 1:
|
||||
Array1[channel] += 8
|
||||
@ -115,7 +115,7 @@ func WavDecompress(data []byte, channelCount int) []byte {
|
||||
}
|
||||
}
|
||||
Array2[channel] = temp3
|
||||
output.PushSWord(int16(temp3))
|
||||
output.PushInt16(int16(temp3))
|
||||
Array1[channel] += sLookup2[value&0x1f]
|
||||
|
||||
if Array1[channel] < 0 {
|
||||
|
43
d2data/d2datadict/hero_objects.go
Normal file
43
d2data/d2datadict/hero_objects.go
Normal file
@ -0,0 +1,43 @@
|
||||
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: "",
|
||||
},
|
||||
}
|
@ -48,20 +48,20 @@ type AnimatedEntity struct {
|
||||
currentFrame int
|
||||
frames map[string][]*ebiten.Image
|
||||
frameLocations map[string][]d2common.Rectangle
|
||||
object d2data.Object
|
||||
object *d2datadict.ObjectLookupRecord
|
||||
}
|
||||
|
||||
// CreateAnimatedEntity creates an instance of AnimatedEntity
|
||||
func CreateAnimatedEntity(object d2data.Object, fileProvider d2interface.FileProvider, palette d2enum.PaletteType) AnimatedEntity {
|
||||
func CreateAnimatedEntity(x, y int32, object *d2datadict.ObjectLookupRecord, fileProvider d2interface.FileProvider, palette d2enum.PaletteType) AnimatedEntity {
|
||||
result := AnimatedEntity{
|
||||
base: object.Lookup.Base,
|
||||
token: object.Lookup.Token,
|
||||
base: object.Base,
|
||||
token: object.Token,
|
||||
object: object,
|
||||
palette: palette,
|
||||
}
|
||||
result.dccLayers = make(map[string]d2dcc.DCC)
|
||||
result.LocationX = float64(object.X) / 5
|
||||
result.LocationY = float64(object.Y) / 5
|
||||
result.LocationX = float64(x) / 5
|
||||
result.LocationY = float64(y) / 5
|
||||
return result
|
||||
}
|
||||
|
||||
@ -96,37 +96,37 @@ func (v *AnimatedEntity) LoadLayer(layer string, fileProvider d2interface.FilePr
|
||||
layerName := "tr"
|
||||
switch strings.ToUpper(layer) {
|
||||
case "HD": // Head
|
||||
layerName = v.object.Lookup.HD
|
||||
layerName = v.object.HD
|
||||
case "TR": // Torso
|
||||
layerName = v.object.Lookup.TR
|
||||
layerName = v.object.TR
|
||||
case "LG": // Legs
|
||||
layerName = v.object.Lookup.LG
|
||||
layerName = v.object.LG
|
||||
case "RA": // RightArm
|
||||
layerName = v.object.Lookup.RA
|
||||
layerName = v.object.RA
|
||||
case "LA": // LeftArm
|
||||
layerName = v.object.Lookup.LA
|
||||
layerName = v.object.LA
|
||||
case "RH": // RightHand
|
||||
layerName = v.object.Lookup.RH
|
||||
layerName = v.object.RH
|
||||
case "LH": // LeftHand
|
||||
layerName = v.object.Lookup.LH
|
||||
layerName = v.object.LH
|
||||
case "SH": // Shield
|
||||
layerName = v.object.Lookup.SH
|
||||
layerName = v.object.SH
|
||||
case "S1": // Special1
|
||||
layerName = v.object.Lookup.S1
|
||||
layerName = v.object.S1
|
||||
case "S2": // Special2
|
||||
layerName = v.object.Lookup.S2
|
||||
layerName = v.object.S2
|
||||
case "S3": // Special3
|
||||
layerName = v.object.Lookup.S3
|
||||
layerName = v.object.S3
|
||||
case "S4": // Special4
|
||||
layerName = v.object.Lookup.S4
|
||||
layerName = v.object.S4
|
||||
case "S5": // Special5
|
||||
layerName = v.object.Lookup.S5
|
||||
layerName = v.object.S5
|
||||
case "S6": // Special6
|
||||
layerName = v.object.Lookup.S6
|
||||
layerName = v.object.S6
|
||||
case "S7": // Special7
|
||||
layerName = v.object.Lookup.S7
|
||||
layerName = v.object.S7
|
||||
case "S8": // Special8
|
||||
layerName = v.object.Lookup.S8
|
||||
layerName = v.object.S8
|
||||
}
|
||||
if len(layerName) == 0 {
|
||||
return d2dcc.DCC{}
|
||||
|
@ -5,6 +5,8 @@ import (
|
||||
"math/rand"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2helper"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
@ -40,7 +42,7 @@ func CreateMapEngine(gameState *d2core.GameState, soundManager *d2audio.Manager,
|
||||
return result
|
||||
}
|
||||
|
||||
func (v *Engine) GenerateMap(regionType RegionIdType, levelPreset int) {
|
||||
func (v *Engine) GenerateMap(regionType d2enum.RegionIdType, levelPreset int) {
|
||||
randomSource := rand.NewSource(v.gameState.Seed)
|
||||
region := LoadRegion(randomSource, regionType, levelPreset, v.fileProvider)
|
||||
v.regions = append(v.regions, EngineRegion{
|
||||
@ -52,19 +54,19 @@ func (v *Engine) GenerateMap(regionType RegionIdType, levelPreset int) {
|
||||
func (v *Engine) GenerateAct1Overworld() {
|
||||
v.soundManager.PlayBGM("/data/global/music/Act1/town1.wav") // TODO: Temp stuff here
|
||||
randomSource := rand.NewSource(v.gameState.Seed)
|
||||
region := LoadRegion(randomSource, RegionAct1Town, 1, v.fileProvider)
|
||||
region := LoadRegion(randomSource, d2enum.RegionAct1Town, 1, v.fileProvider)
|
||||
v.regions = append(v.regions, EngineRegion{
|
||||
Rect: d2common.Rectangle{0, 0, int(region.TileWidth), int(region.TileHeight)},
|
||||
Region: region,
|
||||
})
|
||||
if strings.Contains(region.RegionPath, "E1") {
|
||||
region2 := LoadRegion(randomSource, RegionAct1Town, 2, v.fileProvider)
|
||||
region2 := LoadRegion(randomSource, d2enum.RegionAct1Town, 2, v.fileProvider)
|
||||
v.regions = append(v.regions, EngineRegion{
|
||||
Rect: d2common.Rectangle{int(region.TileWidth - 1), 0, int(region2.TileWidth), int(region2.TileHeight)},
|
||||
Region: region2,
|
||||
})
|
||||
} else if strings.Contains(region.RegionPath, "S1") {
|
||||
region2 := LoadRegion(randomSource, RegionAct1Town, 3, v.fileProvider)
|
||||
region2 := LoadRegion(randomSource, d2enum.RegionAct1Town, 3, v.fileProvider)
|
||||
v.regions = append(v.regions, EngineRegion{
|
||||
Rect: d2common.Rectangle{0, int(region.TileHeight - 1), int(region2.TileWidth), int(region2.TileHeight)},
|
||||
Region: region2,
|
||||
@ -116,19 +118,19 @@ func (v *Engine) RenderTile(region *Region, offX, offY, x, y int, target *ebiten
|
||||
if tile.Floors[i].Hidden || tile.Floors[i].Prop1 == 0 {
|
||||
continue
|
||||
}
|
||||
region.RenderTile(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, RegionLayerTypeFloors, i, target)
|
||||
region.RenderTile(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, d2enum.RegionLayerTypeFloors, i, target)
|
||||
}
|
||||
for i := range tile.Shadows {
|
||||
if tile.Shadows[i].Hidden || tile.Shadows[i].Prop1 == 0 {
|
||||
continue
|
||||
}
|
||||
region.RenderTile(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, RegionLayerTypeShadows, i, target)
|
||||
region.RenderTile(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, d2enum.RegionLayerTypeShadows, i, target)
|
||||
}
|
||||
for i := range tile.Walls {
|
||||
if tile.Walls[i].Hidden || tile.Walls[i].Orientation == 15 || tile.Walls[i].Orientation == 10 || tile.Walls[i].Orientation == 11 || tile.Walls[i].Orientation == 0 {
|
||||
continue
|
||||
}
|
||||
region.RenderTile(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, RegionLayerTypeWalls, i, target)
|
||||
region.RenderTile(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, d2enum.RegionLayerTypeWalls, i, target)
|
||||
}
|
||||
for _, obj := range region.AnimationEntities {
|
||||
if int(math.Floor(obj.LocationX)) == x && int(math.Floor(obj.LocationY)) == y {
|
||||
@ -144,6 +146,6 @@ func (v *Engine) RenderTile(region *Region, offX, offY, x, y int, target *ebiten
|
||||
if tile.Walls[i].Hidden || tile.Walls[i].Orientation != 15 {
|
||||
continue
|
||||
}
|
||||
region.RenderTile(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, RegionLayerTypeWalls, i, target)
|
||||
region.RenderTile(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, d2enum.RegionLayerTypeWalls, i, target)
|
||||
}
|
||||
}
|
||||
|
@ -29,12 +29,6 @@ import (
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
)
|
||||
|
||||
type TileCacheRecord struct {
|
||||
Image *ebiten.Image
|
||||
XOffset int
|
||||
YOffset int
|
||||
}
|
||||
|
||||
type Region struct {
|
||||
RegionPath string
|
||||
LevelType d2datadict.LevelTypeRecord
|
||||
@ -53,55 +47,7 @@ type Region struct {
|
||||
StartY float64
|
||||
}
|
||||
|
||||
type RegionLayerType int
|
||||
|
||||
const (
|
||||
RegionLayerTypeFloors RegionLayerType = 0
|
||||
RegionLayerTypeWalls RegionLayerType = 1
|
||||
RegionLayerTypeShadows RegionLayerType = 2
|
||||
)
|
||||
|
||||
type RegionIdType int
|
||||
|
||||
const (
|
||||
RegionAct1Town RegionIdType = 1
|
||||
RegionAct1Wilderness RegionIdType = 2
|
||||
RegionAct1Cave RegionIdType = 3
|
||||
RegionAct1Crypt RegionIdType = 4
|
||||
RegionAct1Monestary RegionIdType = 5
|
||||
RegionAct1Courtyard RegionIdType = 6
|
||||
RegionAct1Barracks RegionIdType = 7
|
||||
RegionAct1Jail RegionIdType = 8
|
||||
RegionAct1Cathedral RegionIdType = 9
|
||||
RegionAct1Catacombs RegionIdType = 10
|
||||
RegionAct1Tristram RegionIdType = 11
|
||||
RegionAct2Town RegionIdType = 12
|
||||
RegionAct2Sewer RegionIdType = 13
|
||||
RegionAct2Harem RegionIdType = 14
|
||||
RegionAct2Basement RegionIdType = 15
|
||||
RegionAct2Desert RegionIdType = 16
|
||||
RegionAct2Tomb RegionIdType = 17
|
||||
RegionAct2Lair RegionIdType = 18
|
||||
RegionAct2Arcane RegionIdType = 19
|
||||
RegionAct3Town RegionIdType = 20
|
||||
RegionAct3Jungle RegionIdType = 21
|
||||
RegionAct3Kurast RegionIdType = 22
|
||||
RegionAct3Spider RegionIdType = 23
|
||||
RegionAct3Dungeon RegionIdType = 24
|
||||
RegionAct3Sewer RegionIdType = 25
|
||||
RegionAct4Town RegionIdType = 26
|
||||
RegionAct4Mesa RegionIdType = 27
|
||||
RegionAct4Lava RegionIdType = 28
|
||||
RegonAct5Town RegionIdType = 29
|
||||
RegionAct5Siege RegionIdType = 30
|
||||
RegionAct5Barricade RegionIdType = 31
|
||||
RegionAct5Temple RegionIdType = 32
|
||||
RegionAct5IceCaves RegionIdType = 33
|
||||
RegionAct5Baal RegionIdType = 34
|
||||
RegionAct5Lava RegionIdType = 35
|
||||
)
|
||||
|
||||
func LoadRegion(seed rand.Source, levelType RegionIdType, levelPreset int, fileProvider d2interface.FileProvider) *Region {
|
||||
func LoadRegion(seed rand.Source, levelType d2enum.RegionIdType, levelPreset int, fileProvider d2interface.FileProvider) *Region {
|
||||
result := &Region{
|
||||
LevelType: d2datadict.LevelTypes[levelType],
|
||||
levelPreset: d2datadict.LevelPresets[levelPreset],
|
||||
@ -111,7 +57,7 @@ func LoadRegion(seed rand.Source, levelType RegionIdType, levelPreset int, fileP
|
||||
WallCache: make(map[uint32]*TileCacheRecord),
|
||||
}
|
||||
result.Palette = d2datadict.Palettes[d2enum.PaletteType("act"+strconv.Itoa(int(result.LevelType.Act)))]
|
||||
//\bm := result.levelPreset.Dt1Mask
|
||||
//bm := result.levelPreset.Dt1Mask
|
||||
for _, levelTypeDt1 := range result.LevelType.Files {
|
||||
/*
|
||||
if bm&1 == 0 {
|
||||
@ -158,13 +104,14 @@ func (v *Region) loadObjects(fileProvider d2interface.FileProvider) {
|
||||
if object.Lookup.Base == "" || object.Lookup.Token == "" || object.Lookup.TR == "" {
|
||||
return
|
||||
}
|
||||
npc := d2core.CreateNPC(object, fileProvider)
|
||||
npc := d2core.CreateNPC(object.X, object.Y, object.Lookup, fileProvider, 1)
|
||||
npc.SetPaths(object.Paths)
|
||||
v.NPCs = append(v.NPCs, npc)
|
||||
case d2datadict.ObjectTypeItem:
|
||||
if object.ObjectInfo == nil || !object.ObjectInfo.Draw || object.Lookup.Base == "" || object.Lookup.Token == "" {
|
||||
return
|
||||
}
|
||||
entity := d2render.CreateAnimatedEntity(object, 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)
|
||||
v.AnimationEntities = append(v.AnimationEntities, entity)
|
||||
}
|
||||
@ -173,14 +120,14 @@ func (v *Region) loadObjects(fileProvider d2interface.FileProvider) {
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (v *Region) RenderTile(offsetX, offsetY, tileX, tileY int, layerType RegionLayerType, layerIndex int, target *ebiten.Image) {
|
||||
func (v *Region) RenderTile(offsetX, offsetY, tileX, tileY int, layerType d2enum.RegionLayerType, layerIndex int, target *ebiten.Image) {
|
||||
offsetX -= 80
|
||||
switch layerType {
|
||||
case RegionLayerTypeFloors:
|
||||
case d2enum.RegionLayerTypeFloors:
|
||||
v.renderFloor(v.DS1.Tiles[tileY][tileX].Floors[layerIndex], offsetX, offsetY, target)
|
||||
case RegionLayerTypeWalls:
|
||||
case d2enum.RegionLayerTypeWalls:
|
||||
v.renderWall(v.DS1.Tiles[tileY][tileX].Walls[layerIndex], offsetX, offsetY, target)
|
||||
case RegionLayerTypeShadows:
|
||||
case d2enum.RegionLayerTypeShadows:
|
||||
v.renderShadow(v.DS1.Tiles[tileY][tileX].Shadows[layerIndex], offsetX, offsetY, target)
|
||||
}
|
||||
}
|
||||
@ -394,7 +341,10 @@ func (v *Region) generateWallCache(tile d2ds1.WallRecord) *TileCacheRecord {
|
||||
yAdjust = int(tileMinY) + 80
|
||||
}
|
||||
|
||||
image.ReplacePixels(pixels)
|
||||
// TODO: This may also need to be an atlas, but could get pretty large...
|
||||
if err := image.ReplacePixels(pixels); err != nil {
|
||||
log.Panicf(err.Error())
|
||||
}
|
||||
return &TileCacheRecord{
|
||||
image,
|
||||
0,
|
||||
|
9
d2render/d2mapengine/tile_cache_record.go
Normal file
9
d2render/d2mapengine/tile_cache_record.go
Normal file
@ -0,0 +1,9 @@
|
||||
package d2mapengine
|
||||
|
||||
import "github.com/hajimehoshi/ebiten"
|
||||
|
||||
type TileCacheRecord struct {
|
||||
Image *ebiten.Image
|
||||
XOffset int
|
||||
YOffset int
|
||||
}
|
@ -22,12 +22,13 @@ import (
|
||||
type ButtonType int
|
||||
|
||||
const (
|
||||
ButtonTypeWide ButtonType = 1
|
||||
ButtonTypeMedium ButtonType = 2
|
||||
ButtonTypeNarrow ButtonType = 3
|
||||
ButtonTypeCancel ButtonType = 4
|
||||
ButtonTypeTall ButtonType = 5
|
||||
ButtonTypeShort ButtonType = 6
|
||||
ButtonTypeWide ButtonType = 1
|
||||
ButtonTypeMedium ButtonType = 2
|
||||
ButtonTypeNarrow ButtonType = 3
|
||||
ButtonTypeCancel ButtonType = 4
|
||||
ButtonTypeTall ButtonType = 5
|
||||
ButtonTypeShort ButtonType = 6
|
||||
ButtonTypeOkCancel ButtonType = 7
|
||||
|
||||
// Game UI
|
||||
|
||||
@ -63,10 +64,11 @@ type ButtonLayout struct {
|
||||
|
||||
// ButtonLayouts define the type of buttons you can have
|
||||
var ButtonLayouts = map[ButtonType]ButtonLayout{
|
||||
ButtonTypeWide: {2, 1, d2resource.WideButtonBlank, d2enum.Units, false, 0, -1, d2resource.FontExocet10, nil, true, 1},
|
||||
ButtonTypeShort: {1, 1, d2resource.ShortButtonBlank, d2enum.Units, false, 0, -1, d2resource.FontRediculous, nil, true, -1},
|
||||
ButtonTypeMedium: {1, 1, d2resource.MediumButtonBlank, d2enum.Units, false, 0, 0, d2resource.FontExocet10, nil, true, 0},
|
||||
ButtonTypeTall: {1, 1, d2resource.TallButtonBlank, d2enum.Units, false, 0, 0, d2resource.FontExocet10, nil, true, 5},
|
||||
ButtonTypeWide: {2, 1, d2resource.WideButtonBlank, d2enum.Units, false, 0, -1, d2resource.FontExocet10, nil, true, 1},
|
||||
ButtonTypeShort: {1, 1, d2resource.ShortButtonBlank, d2enum.Units, false, 0, -1, d2resource.FontRediculous, nil, true, -1},
|
||||
ButtonTypeMedium: {1, 1, d2resource.MediumButtonBlank, d2enum.Units, false, 0, 0, d2resource.FontExocet10, nil, true, 0},
|
||||
ButtonTypeTall: {1, 1, d2resource.TallButtonBlank, d2enum.Units, false, 0, 0, d2resource.FontExocet10, nil, true, 5},
|
||||
ButtonTypeOkCancel: {1, 1, d2resource.CancelButton, d2enum.Units, false, 0, -1, d2resource.FontRediculous, nil, true, 0},
|
||||
/*
|
||||
{eButtonType.Wide, new ButtonLayout { XSegments = 2, ResourceName = ResourcePaths.WideButtonBlank, PaletteName = PaletteDefs.Units } },
|
||||
{eButtonType.Narrow, new ButtonLayout { ResourceName = ResourcePaths.NarrowButtonBlank, PaletteName = PaletteDefs.Units } },
|
||||
@ -171,7 +173,7 @@ func (v *Button) OnActivated(callback func()) {
|
||||
}
|
||||
|
||||
// Activate calls the on activated callback handler, if any
|
||||
func (v Button) Activate() {
|
||||
func (v *Button) Activate() {
|
||||
if v.onClick == nil {
|
||||
return
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ func TestMapGenerationPerformance(t *testing.T) {
|
||||
d2common.ConfigBasePath = "../"
|
||||
|
||||
engine := d2core.CreateEngine()
|
||||
gameState := d2core.CreateGameState()
|
||||
gameState := d2core.CreateTestGameState()
|
||||
mapEngine := _map.CreateMapEngine(gameState, engine.SoundManager, engine)
|
||||
mapEngine.GenerateAct1Overworld()
|
||||
surface, _ := ebiten.NewImage(800, 600, ebiten.FilterNearest)
|
||||
|
Loading…
x
Reference in New Issue
Block a user