1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-29 02:25:23 +00:00

Added text dictionary. Continued hero select screen work.

This commit is contained in:
Tim Sarbin 2019-10-27 17:24:21 -04:00
parent 9510a8f43d
commit 4c047bf993
10 changed files with 312 additions and 57 deletions

View File

@ -39,16 +39,16 @@ func (v *StreamReader) GetByte() byte {
return result
}
// GetWord returns a uint16 word from the stream
func (v *StreamReader) GetWord() uint16 {
// GetUInt16 returns a uint16 word from the stream
func (v *StreamReader) GetUInt16() uint16 {
result := uint16(v.data[v.position])
result += uint16(v.data[v.position+1]) << 8
v.position += 2
return result
}
// GetSWord returns a int16 word from the stream
func (v *StreamReader) GetSWord() int16 {
// GetInt16 returns a int16 word from the stream
func (v *StreamReader) GetInt16() int16 {
var result int16
err := binary.Read(bytes.NewReader([]byte{v.data[v.position], v.data[v.position+1]}), binary.LittleEndian, &result)
if err != nil {
@ -58,12 +58,23 @@ func (v *StreamReader) GetSWord() int16 {
return result
}
// GetDword returns a uint32 dword from the stream
func (v *StreamReader) GetDword() uint32 {
result := uint32(v.data[v.position])
result += uint32(v.data[v.position+1]) << 8
result += uint32(v.data[v.position+2]) << 16
result += uint32(v.data[v.position+3]) << 24
func (v *StreamReader) SetPosition(newPosition uint64) {
v.position = newPosition
}
// GetUInt32 returns a uint32 word from the stream
func (v *StreamReader) GetUInt32() uint32 {
var result uint32
err := binary.Read(bytes.NewReader(
[]byte{
v.data[v.position],
v.data[v.position+1],
v.data[v.position+2],
v.data[v.position+3],
}), binary.LittleEndian, &result)
if err != nil {
log.Panic(err)
}
v.position += 4
return result
}
@ -73,6 +84,15 @@ func (v *StreamReader) ReadByte() (byte, error) {
return v.GetByte(), nil
}
// ReadBytes reads multiple bytes
func (v *StreamReader) ReadBytes(count int) ([]byte, error) {
result := make([]byte, count)
for i := 0; i < count; i++ {
result[i] = v.GetByte()
}
return result, nil
}
// Read implements io.Reader
func (v *StreamReader) Read(p []byte) (n int, err error) {
streamLength := v.GetSize()
@ -86,3 +106,7 @@ func (v *StreamReader) Read(p []byte) (n int, err error) {
p[i] = v.GetByte()
}
}
func (v *StreamReader) Eof() bool {
return v.position >= uint64(len(v.data))
}

View File

@ -1,6 +1,13 @@
package Common
import "strconv"
import (
"bytes"
"fmt"
"strconv"
"strings"
"unicode/utf16"
"unicode/utf8"
)
// StringToInt converts a string to an integer
func StringToInt(text string) int {
@ -34,3 +41,50 @@ func StringToInt8(text string) int8 {
}
return int8(result)
}
func Utf16BytesToString(b []byte) (string, error) {
if len(b)%2 != 0 {
return "", fmt.Errorf("Must have even length byte slice")
}
u16s := make([]uint16, 1)
ret := &bytes.Buffer{}
b8buf := make([]byte, 4)
lb := len(b)
for i := 0; i < lb; i += 2 {
u16s[0] = uint16(b[i]) + (uint16(b[i+1]) << 8)
r := utf16.Decode(u16s)
n := utf8.EncodeRune(b8buf, r[0])
ret.Write(b8buf[:n])
}
return ret.String(), nil
}
func SplitIntoLinesWithMaxWidth(fullSentence string, maxChars int) []string {
lines := make([]string, 0)
line := ""
totalLength := 0
words := strings.Split(fullSentence, " ")
for _, word := range words {
totalLength += 1 + len(word)
if totalLength > maxChars {
totalLength = len(word)
lines = append(lines, line)
line = ""
} else {
line += " "
}
line += word
}
if len(line) > 0 {
lines = append(lines, line)
}
return lines
}

103
Common/TextDictionary.go Normal file
View File

@ -0,0 +1,103 @@
package Common
import (
"log"
"strconv"
"github.com/essial/OpenDiablo2/ResourcePaths"
)
type textDictionaryHashEntry struct {
IsActive bool
Index uint16
HashValue uint32
IndexString uint32
NameString uint32
NameLength uint16
}
var lookupTable map[string]string
func TranslateString(key string) string {
result, ok := lookupTable[key]
if !ok {
log.Panic("Could not find a string for the key '%s'", key)
}
return result
}
func LoadTextDictionary(fileProvider FileProvider) {
lookupTable = make(map[string]string)
loadDictionary(fileProvider, ResourcePaths.PatchStringTable)
loadDictionary(fileProvider, ResourcePaths.ExpansionStringTable)
loadDictionary(fileProvider, ResourcePaths.StringTable)
log.Printf("Loaded %d entries from the string table", len(lookupTable))
}
func loadDictionary(fileProvider FileProvider, dictionaryName string) {
dictionaryData := fileProvider.LoadFile(dictionaryName)
br := CreateStreamReader(dictionaryData)
br.ReadBytes(2) // CRC
numberOfElements := br.GetUInt16()
hashTableSize := br.GetUInt32()
br.ReadByte() // Version (always 0)
br.GetUInt32() // StringOffset
br.GetUInt32() // When the number of times you have missed a match with a hash key equals this value, you give up because it is not there.
br.GetUInt32() // FileSize
elementIndex := make([]uint16, numberOfElements)
for i := 0; i < int(numberOfElements); i++ {
elementIndex[i] = br.GetUInt16()
}
hashEntries := make([]textDictionaryHashEntry, hashTableSize)
for i := 0; i < int(hashTableSize); i++ {
hashEntries[i] = textDictionaryHashEntry{
br.GetByte() == 1,
br.GetUInt16(),
br.GetUInt32(),
br.GetUInt32(),
br.GetUInt32(),
br.GetUInt16(),
}
}
for idx, hashEntry := range hashEntries {
if !hashEntry.IsActive {
continue
}
br.SetPosition(uint64(hashEntry.NameString))
nameVal, _ := br.ReadBytes(int(hashEntry.NameLength - 1))
value := string(nameVal)
br.SetPosition(uint64(hashEntry.IndexString))
key := ""
for true {
b := br.GetByte()
if b == 0 {
break
}
key += string(b)
}
if key == "x" || key == "X" {
key = "#" + strconv.Itoa(idx)
}
_, exists := lookupTable[key]
if !exists {
lookupTable[key] = value
}
// Use the following code to write out the values
/*
f, err := os.OpenFile(`C:\Users\lunat\Desktop\D2\langdict.txt`,
os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Println(err)
}
defer f.Close()
if _, err := f.WriteString("\n[" + key + "] " + value); err != nil {
log.Println(err)
}
*/
}
}

View File

@ -37,7 +37,7 @@ func WavDecompress(data []byte, channelCount int) []byte {
shift := input.GetByte()
for i := 0; i < channelCount; i++ {
temp := input.GetSWord()
temp := input.GetInt16()
Array2[i] = int(temp)
output.PushSWord(temp)
}

View File

@ -60,6 +60,7 @@ func CreateEngine() *Engine {
result.mapMpqFiles()
result.loadPalettes()
result.loadSoundEntries()
Common.LoadTextDictionary(result)
result.SoundManager = Sound.CreateManager(result)
result.SoundManager.SetVolumes(result.Settings.BgmVolume, result.Settings.SfxVolume)
result.UIManager = UI.CreateManager(result, *result.SoundManager)

View File

@ -156,8 +156,9 @@ const (
// --- Data ---
EnglishTable = "/data/local/lng/eng/English.txt"
ExpansionStringTable = "/data/local/lng/eng/expansionstring.tbl"
StringTable = "/data/local/lng/eng/string.tbl"
PatchStringTable = "/data/local/lng/eng/patchstring.tbl"
LevelPreset = "/data/global/excel/LvlPrest.txt"
LevelType = "/data/global/excel/LvlTypes.txt"
LevelDetails = "/data/global/excel/Levels.txt"

View File

@ -1,12 +1,8 @@
package Scenes
import (
"bytes"
"fmt"
"image/color"
"strings"
"unicode/utf16"
"unicode/utf8"
"github.com/essial/OpenDiablo2/Common"
"github.com/essial/OpenDiablo2/Palettes"
@ -52,29 +48,6 @@ func CreateCredits(fileProvider Common.FileProvider, sceneProvider SceneProvider
return result
}
func utf16BytesToString(b []byte) (string, error) {
if len(b)%2 != 0 {
return "", fmt.Errorf("Must have even length byte slice")
}
u16s := make([]uint16, 1)
ret := &bytes.Buffer{}
b8buf := make([]byte, 4)
lb := len(b)
for i := 0; i < lb; i += 2 {
u16s[0] = uint16(b[i]) + (uint16(b[i+1]) << 8)
r := utf16.Decode(u16s)
n := utf8.EncodeRune(b8buf, r[0])
ret.Write(b8buf[:n])
}
return ret.String(), nil
}
// Load is called to load the resources for the credits scene
func (v *Credits) Load() []func() {
return []func(){
@ -89,7 +62,7 @@ func (v *Credits) Load() []func() {
v.uiManager.AddWidget(v.exitButton)
},
func() {
fileData, _ := utf16BytesToString(v.fileProvider.LoadFile(ResourcePaths.CreditsText)[2:])
fileData, _ := Common.Utf16BytesToString(v.fileProvider.LoadFile(ResourcePaths.CreditsText)[2:])
v.creditsText = strings.Split(fileData, "\r\n")
for i := range v.creditsText {
v.creditsText[i] = strings.Trim(v.creditsText[i], " ")

View File

@ -69,7 +69,7 @@ func (v *MainMenu) Load() []func() {
func() {
v.copyrightLabel2 = UI.CreateLabel(v.fileProvider, ResourcePaths.FontFormal12, Palettes.Static)
v.copyrightLabel2.Alignment = UI.LabelAlignCenter
v.copyrightLabel2.SetText("All Rights Reserved.")
v.copyrightLabel2.SetText(Common.TranslateString("#1614"))
v.copyrightLabel2.Color = color.RGBA{188, 168, 140, 255}
v.copyrightLabel2.MoveTo(400, 525)
},
@ -109,27 +109,27 @@ func (v *MainMenu) Load() []func() {
v.diabloLogoRightBack.MoveTo(400, 120)
},
func() {
v.exitDiabloButton = UI.CreateButton(UI.ButtonTypeWide, v.fileProvider, "EXIT DIABLO II")
v.exitDiabloButton = UI.CreateButton(UI.ButtonTypeWide, v.fileProvider, Common.TranslateString("#1625"))
v.exitDiabloButton.MoveTo(264, 535)
v.exitDiabloButton.SetVisible(!v.ShowTrademarkScreen)
v.exitDiabloButton.OnActivated(func() { v.onExitButtonClicked() })
v.uiManager.AddWidget(v.exitDiabloButton)
},
func() {
v.creditsButton = UI.CreateButton(UI.ButtonTypeShort, v.fileProvider, "CREDITS")
v.creditsButton = UI.CreateButton(UI.ButtonTypeShort, v.fileProvider, Common.TranslateString("#1627"))
v.creditsButton.MoveTo(264, 505)
v.creditsButton.SetVisible(!v.ShowTrademarkScreen)
v.creditsButton.OnActivated(func() { v.onCreditsButtonClicked() })
v.uiManager.AddWidget(v.creditsButton)
},
func() {
v.cinematicsButton = UI.CreateButton(UI.ButtonTypeShort, v.fileProvider, "CINEMATICS")
v.cinematicsButton = UI.CreateButton(UI.ButtonTypeShort, v.fileProvider, Common.TranslateString("#1639"))
v.cinematicsButton.MoveTo(401, 505)
v.cinematicsButton.SetVisible(!v.ShowTrademarkScreen)
v.uiManager.AddWidget(v.cinematicsButton)
},
func() {
v.singlePlayerButton = UI.CreateButton(UI.ButtonTypeWide, v.fileProvider, "SINGLE PLAYER")
v.singlePlayerButton = UI.CreateButton(UI.ButtonTypeWide, v.fileProvider, Common.TranslateString("#1620"))
v.singlePlayerButton.MoveTo(264, 290)
v.singlePlayerButton.SetVisible(!v.ShowTrademarkScreen)
v.singlePlayerButton.OnActivated(func() { v.onSinglePlayerClicked() })

View File

@ -44,7 +44,12 @@ type SelectHeroClass struct {
bgImage *Common.Sprite
campfire *Common.Sprite
headingLabel *UI.Label
heroClassLabel *UI.Label
heroDesc1Label *UI.Label
heroDesc2Label *UI.Label
heroDesc3Label *UI.Label
heroRenderInfo map[Common.Hero]*HeroRenderInfo
selectedHero Common.Hero
}
func CreateSelectHeroClass(
@ -58,6 +63,7 @@ func CreateSelectHeroClass(
fileProvider: fileProvider,
soundManager: soundManager,
heroRenderInfo: make(map[Common.Hero]*HeroRenderInfo),
selectedHero: Common.HeroNone,
}
return result
}
@ -76,6 +82,26 @@ func (v *SelectHeroClass) Load() []func() {
v.headingLabel.SetText("Select Hero Class")
v.headingLabel.Alignment = UI.LabelAlignCenter
},
func() {
v.heroClassLabel = UI.CreateLabel(v.fileProvider, ResourcePaths.Font30, Palettes.Units)
v.heroClassLabel.Alignment = UI.LabelAlignCenter
v.heroClassLabel.MoveTo(400, 65)
},
func() {
v.heroDesc1Label = UI.CreateLabel(v.fileProvider, ResourcePaths.Font16, Palettes.Units)
v.heroDesc1Label.Alignment = UI.LabelAlignCenter
v.heroDesc1Label.MoveTo(400, 100)
},
func() {
v.heroDesc2Label = UI.CreateLabel(v.fileProvider, ResourcePaths.Font16, Palettes.Units)
v.heroDesc2Label.Alignment = UI.LabelAlignCenter
v.heroDesc2Label.MoveTo(400, 115)
},
func() {
v.heroDesc3Label = UI.CreateLabel(v.fileProvider, ResourcePaths.Font16, Palettes.Units)
v.heroDesc3Label.Alignment = UI.LabelAlignCenter
v.heroDesc3Label.MoveTo(400, 130)
},
func() {
v.campfire = v.fileProvider.LoadSprite(ResourcePaths.CharacterSelectCampfire, Palettes.Fechar)
v.campfire.MoveTo(380, 335)
@ -335,6 +361,12 @@ func (v *SelectHeroClass) Unload() {
func (v *SelectHeroClass) Render(screen *ebiten.Image) {
v.bgImage.DrawSegments(screen, 4, 3, 0)
v.headingLabel.Draw(screen)
if v.selectedHero != Common.HeroNone {
v.heroClassLabel.Draw(screen)
v.heroDesc1Label.Draw(screen)
v.heroDesc2Label.Draw(screen)
v.heroDesc3Label.Draw(screen)
}
for heroClass, heroInfo := range v.heroRenderInfo {
if heroInfo.Stance == HeroStanceIdle || heroInfo.Stance == HeroStanceIdleSelected {
v.renderHero(screen, heroClass)
@ -356,9 +388,16 @@ func (v *SelectHeroClass) Update(tickTime float64) {
break
}
}
for heroType := range v.heroRenderInfo {
allIdle := true
for heroType, data := range v.heroRenderInfo {
if allIdle && data.Stance != HeroStanceIdle {
allIdle = false
}
v.updateHeroSelectionHover(heroType, canSelect)
}
if v.selectedHero != Common.HeroNone && allIdle {
v.selectedHero = Common.HeroNone
}
}
func (v *SelectHeroClass) updateHeroSelectionHover(hero Common.Hero, canSelect bool) {
@ -409,8 +448,8 @@ func (v *SelectHeroClass) updateHeroSelectionHover(hero Common.Hero, canSelect b
heroInfo.BackWalkSpriteOverlay.ResetAnimation()
}
}
// selectedHero = hero;
// UpdateHeroText();
v.selectedHero = hero
v.updateHeroText()
renderInfo.SelectSfx.Play()
return
@ -422,13 +461,10 @@ func (v *SelectHeroClass) updateHeroSelectionHover(hero Common.Hero, canSelect b
renderInfo.Stance = HeroStanceIdle
}
/*
if (selectedHero == null && mouseHover)
{
selectedHero = hero;
UpdateHeroText();
}
*/
if v.selectedHero == Common.HeroNone && mouseHover {
v.selectedHero = hero
v.updateHeroText()
}
}
@ -456,3 +492,65 @@ func (v *SelectHeroClass) renderHero(screen *ebiten.Image, hero Common.Hero) {
}
}
}
func (v *SelectHeroClass) updateHeroText() {
switch v.selectedHero {
case Common.HeroNone:
return
case Common.HeroBarbarian:
v.heroClassLabel.SetText(Common.TranslateString("partycharbar"))
v.setDescLabels("#1709")
case Common.HeroNecromancer:
v.heroClassLabel.SetText(Common.TranslateString("partycharnec"))
v.setDescLabels("#1704")
case Common.HeroPaladin:
v.heroClassLabel.SetText(Common.TranslateString("partycharpal"))
v.setDescLabels("#1711")
case Common.HeroAssassin:
v.heroClassLabel.SetText(Common.TranslateString("partycharass"))
v.setDescLabels("#305")
case Common.HeroSorceress:
v.heroClassLabel.SetText(Common.TranslateString("partycharsor"))
v.setDescLabels("#1710")
case Common.HeroAmazon:
v.heroClassLabel.SetText(Common.TranslateString("partycharama"))
v.setDescLabels("#1698")
case Common.HeroDruid:
v.heroClassLabel.SetText(Common.TranslateString("partychardru"))
v.setDescLabels("#304")
}
/*
if (selectedHero == null)
return;
switch (selectedHero.Value)
{
}
heroClassLabel.Location = new Point(400 - (heroClassLabel.TextArea.Width / 2), 65);
heroDesc1Label.Location = new Point(400 - (heroDesc1Label.TextArea.Width / 2), 100);
heroDesc2Label.Location = new Point(400 - (heroDesc2Label.TextArea.Width / 2), 115);
heroDesc3Label.Location = new Point(400 - (heroDesc3Label.TextArea.Width / 2), 130);
*/
}
func (v *SelectHeroClass) setDescLabels(descKey string) {
heroDesc := Common.TranslateString(descKey)
parts := Common.SplitIntoLinesWithMaxWidth(heroDesc, 37)
if len(parts) > 1 {
v.heroDesc1Label.SetText(parts[0])
} else {
v.heroDesc1Label.SetText("")
}
if len(parts) > 1 {
v.heroDesc2Label.SetText(parts[1])
} else {
v.heroDesc2Label.SetText("")
}
if len(parts) > 2 {
v.heroDesc3Label.SetText(parts[2])
} else {
v.heroDesc3Label.SetText("")
}
}

View File

@ -1,9 +1,10 @@
package main
import (
"github.com/essial/OpenDiablo2/Core"
"log"
"github.com/essial/OpenDiablo2/Core"
"github.com/essial/OpenDiablo2/MPQ"
"github.com/hajimehoshi/ebiten"
)