diff --git a/.github/workflows/pullRequest.yml b/.github/workflows/pullRequest.yml index 471307b2..f208b494 100644 --- a/.github/workflows/pullRequest.yml +++ b/.github/workflows/pullRequest.yml @@ -1,9 +1,10 @@ +--- name: pull_request "on": [pull_request] jobs: build: - name: '' - runs-on: self-hosted + name: Build + runs-on: ubuntu-latest continue-on-error: true steps: - name: Set up Go 1.14 diff --git a/.github/workflows/pushToMaster.yml b/.github/workflows/pushToMaster.yml new file mode 100644 index 00000000..346cc565 --- /dev/null +++ b/.github/workflows/pushToMaster.yml @@ -0,0 +1,39 @@ +--- +name: build +"on": + push: + branches: + - master +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Set up Go 1.14 + uses: actions/setup-go@v2.1.3 + with: + go-version: 1.14 + id: go + + - name: Check out code + uses: actions/checkout@v2.3.4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y xvfb libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev libgl1-mesa-dev libsdl2-dev libasound2-dev > /dev/null 2>&1 + + - name: Run golangci-lint + continue-on-error: false + uses: golangci/golangci-lint-action@v2.3.0 + with: + version: v1.32 + + - name: Run tests + env: + DISPLAY: ":99.0" + run: | + xvfb-run --auto-servernum go test -v -race ./... + + - name: Build binary + run: go build . diff --git a/README.md b/README.md index 3a103eff..1b143564 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,7 @@ ALL OTHER TRADEMARKS ARE THE PROPERTY OF THEIR RESPECTIVE OWNERS. ## Status -At the moment (december 2020) the game starts, you can select any character and run around Act1 town. -You can also open any of the game's panels. +At the moment (october 2020) the game starts, you can select any character and run around Act1 town. Much work has been made in the background, but a lot of work still has to be done for the game to be playable. @@ -129,8 +128,6 @@ which will be updated over time with new requirements. ![Inventory Window](docs/Inventory.png) -![Game Panels](docs/game_panels.png) - ## Additional Credits - Diablo2 Logo diff --git a/d2app/app.go b/d2app/app.go index afdf3ced..99110921 100644 --- a/d2app/app.go +++ b/d2app/app.go @@ -6,7 +6,6 @@ import ( "container/ring" "encoding/json" "errors" - "flag" "fmt" "image" "image/gif" @@ -25,6 +24,7 @@ import ( "github.com/pkg/profile" "golang.org/x/image/colornames" + "gopkg.in/alecthomas/kingpin.v2" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math" @@ -85,10 +85,17 @@ type App struct { // Options is used to store all of the app options that can be set with arguments type Options struct { - Debug *bool - profiler *string - Server *d2networking.ServerOptions - LogLevel *d2util.LogLevel + printVersion *bool + Debug *bool + profiler *string + Server *d2networking.ServerOptions + LogLevel *d2util.LogLevel +} + +type bindTerminalEntry struct { + name string + description string + action interface{} } const ( @@ -103,24 +110,21 @@ const ( // Create creates a new instance of the application func Create(gitBranch, gitCommit string) *App { - logger := d2util.NewLogger() - logger.SetPrefix(appLoggerPrefix) + assetManager, assetError := d2asset.NewAssetManager() app := &App{ - Logger: logger, gitBranch: gitBranch, gitCommit: gitCommit, + asset: assetManager, Options: &Options{ Server: &d2networking.ServerOptions{}, }, + errorMessage: assetError, } - app.Infof("OpenDiablo2 - Open source Diablo 2 engine") - app.parseArguments() - - app.SetLevel(*app.Options.LogLevel) - - app.asset, app.errorMessage = d2asset.NewAssetManager(*app.Options.LogLevel) + app.Logger = d2util.NewLogger() + app.Logger.SetPrefix(appLoggerPrefix) + app.Logger.SetLevel(d2util.LogLevelNone) return app } @@ -136,7 +140,7 @@ func (a *App) startDedicatedServer() error { srvChanIn := make(chan int) srvChanLog := make(chan string) - srvErr := d2networking.StartDedicatedServer(a.asset, srvChanIn, srvChanLog, *a.Options.LogLevel, maxPlayers) + srvErr := d2networking.StartDedicatedServer(a.asset, srvChanIn, srvChanLog, a.config.LogLevel, maxPlayers) if srvErr != nil { return srvErr } @@ -169,7 +173,15 @@ func (a *App) loadEngine() error { return a.renderer.Run(a.updateInitError, updateNOOP, 800, 600, "OpenDiablo2") } - audio := ebiten2.CreateAudio(*a.Options.LogLevel, a.asset) + // if the log level was specified at the command line, use it + logLevel := *a.Options.LogLevel + if logLevel == d2util.LogLevelUnspecified { + logLevel = a.config.LogLevel + } + + a.asset.SetLogLevel(logLevel) + + audio := ebiten2.CreateAudio(a.config.LogLevel, a.asset) inputManager := d2input.NewInputManager() @@ -178,9 +190,14 @@ func (a *App) loadEngine() error { return err } + err = a.asset.BindTerminalCommands(term) + if err != nil { + return err + } + scriptEngine := d2script.CreateScriptEngine() - uiManager := d2ui.NewUIManager(a.asset, renderer, inputManager, *a.Options.LogLevel, audio) + uiManager := d2ui.NewUIManager(a.asset, renderer, inputManager, a.config.LogLevel, audio) a.inputManager = inputManager a.terminal = term @@ -189,48 +206,50 @@ func (a *App) loadEngine() error { a.ui = uiManager a.tAllocSamples = createZeroedRing(nSamplesTAlloc) + if a.gitBranch == "" { + a.gitBranch = "Local Build" + } + return nil } func (a *App) parseArguments() { const ( - descProfile = "Profiles the program,\none of (cpu, mem, block, goroutine, trace, thread, mutex)" - descPlayers = "Sets the number of max players for the dedicated server" - descLogging = "Enables verbose logging. Log levels will include those below it.\n" + - " 0 disables log messages\n" + - " 1 shows fatal\n" + - " 2 shows error\n" + - " 3 shows warning\n" + - " 4 shows info\n" + - " 5 shows debug\n" + versionArg = "version" + versionShort = 'v' + versionDesc = "Prints the version of the app" + + profilerArg = "profile" + profilerDesc = "Profiles the program, one of (cpu, mem, block, goroutine, trace, thread, mutex)" + + serverArg = "dedicated" + serverShort = 'd' + serverDesc = "Starts a dedicated server" + + playersArg = "players" + playersDesc = "Sets the number of max players for the dedicated server" + + loggingArg = "loglevel" + loggingShort = 'l' + loggingDesc = "Enables verbose logging. Log levels will include those below it. " + + "0 disables log messages, " + + "1 shows errors, " + + "2 shows warnings, " + + "3 shows info, " + + "4 shows debug" + + "5 uses value from config file (default)" ) - a.Options.profiler = flag.String("profile", "", descProfile) - a.Options.Server.Dedicated = flag.Bool("dedicated", false, "Starts a dedicated server") - a.Options.Server.MaxPlayers = flag.Int("players", 0, descPlayers) - a.Options.LogLevel = flag.Int("l", d2util.LogLevelDefault, descLogging) - showVersion := flag.Bool("v", false, "Show version") - showHelp := flag.Bool("h", false, "Show help") + a.Options.profiler = kingpin.Flag(profilerArg, profilerDesc).String() + a.Options.Server.Dedicated = kingpin.Flag(serverArg, serverDesc).Short(serverShort).Bool() + a.Options.printVersion = kingpin.Flag(versionArg, versionDesc).Short(versionShort).Bool() + a.Options.Server.MaxPlayers = kingpin.Flag(playersArg, playersDesc).Int() + a.Options.LogLevel = kingpin.Flag(loggingArg, loggingDesc). + Short(loggingShort). + Default(strconv.Itoa(d2util.LogLevelUnspecified)). + Int() - flag.Usage = func() { - fmt.Printf("usage: %s []\n\nFlags:\n", os.Args[0]) - flag.PrintDefaults() - } - flag.Parse() - - if *a.Options.LogLevel >= d2util.LogLevelUnspecified { - *a.Options.LogLevel = d2util.LogLevelDefault - } - - if *showVersion { - a.Infof("version: OpenDiablo2 (%s %s)", a.gitBranch, a.gitCommit) - os.Exit(0) - } - - if *showHelp { - flag.Usage() - os.Exit(0) - } + kingpin.Parse() } // LoadConfig loads the OpenDiablo2 config file @@ -268,15 +287,45 @@ func (a *App) LoadConfig() (*d2config.Configuration, error) { } // Run executes the application and kicks off the entire game process -func (a *App) Run() (err error) { +func (a *App) Run() error { + a.parseArguments() + // add our possible config directories _, _ = a.asset.AddSource(filepath.Dir(d2config.LocalConfigPath())) _, _ = a.asset.AddSource(filepath.Dir(d2config.DefaultConfigPath())) - if a.config, err = a.LoadConfig(); err != nil { + config, err := a.LoadConfig() + if err != nil { return err } + a.config = config + + a.asset.SetLogLevel(config.LogLevel) + + // print version and exit if `--version` was supplied + if *a.Options.printVersion { + fmtVersion := "OpenDiablo2 (%s %s)" + + if a.gitBranch == "" { + a.gitBranch = "local" + } + + if a.gitCommit == "" { + a.gitCommit = "build" + } + + fmt.Printf(fmtVersion, a.gitBranch, a.gitCommit) + os.Exit(0) + } + + logLevel := *a.Options.LogLevel + if logLevel == d2util.LogLevelUnspecified { + logLevel = a.config.LogLevel + } + + a.asset.SetLogLevel(logLevel) + // start profiler if argument was supplied if len(*a.Options.profiler) > 0 { profiler := enableProfiler(*a.Options.profiler, a) @@ -340,39 +389,36 @@ func (a *App) initialize() error { a.renderer.SetWindowIcon("d2logo.png") a.terminal.BindLogger() - terminalCommands := []struct { - name string - desc string - args []string - fn func(args []string) error - }{ - {"dumpheap", "dumps the heap to pprof/heap.pprof", nil, a.dumpHeap}, - {"fullscreen", "toggles fullscreen", nil, a.toggleFullScreen}, - {"capframe", "captures a still frame", []string{"filename"}, a.setupCaptureFrame}, - {"capgifstart", "captures an animation (start)", []string{"filename"}, a.startAnimationCapture}, - {"capgifstop", "captures an animation (stop)", nil, a.stopAnimationCapture}, - {"vsync", "toggles vsync", nil, a.toggleVsync}, - {"fps", "toggle fps counter", nil, a.toggleFpsCounter}, - {"timescale", "set scalar for elapsed time", []string{"float"}, a.setTimeScale}, - {"quit", "exits the game", nil, a.quitGame}, - {"screen-gui", "enters the gui playground screen", nil, a.enterGuiPlayground}, - {"js", "eval JS scripts", []string{"code"}, a.evalJS}, + terminalActions := [...]bindTerminalEntry{ + {"dumpheap", "dumps the heap to pprof/heap.pprof", a.dumpHeap}, + {"fullscreen", "toggles fullscreen", a.toggleFullScreen}, + {"capframe", "captures a still frame", a.setupCaptureFrame}, + {"capgifstart", "captures an animation (start)", a.startAnimationCapture}, + {"capgifstop", "captures an animation (stop)", a.stopAnimationCapture}, + {"vsync", "toggles vsync", a.toggleVsync}, + {"fps", "toggle fps counter", a.toggleFpsCounter}, + {"timescale", "set scalar for elapsed time", a.setTimeScale}, + {"quit", "exits the game", a.quitGame}, + {"screen-gui", "enters the gui playground screen", a.enterGuiPlayground}, + {"js", "eval JS scripts", a.evalJS}, } - for _, cmd := range terminalCommands { - if err := a.terminal.Bind(cmd.name, cmd.desc, cmd.args, cmd.fn); err != nil { - a.Fatalf("failed to bind action %q: %v", cmd.name, err.Error()) + for idx := range terminalActions { + action := &terminalActions[idx] + + if err := a.terminal.BindAction(action.name, action.description, action.action); err != nil { + a.Fatal(err.Error()) } } - gui, err := d2gui.CreateGuiManager(a.asset, *a.Options.LogLevel, a.inputManager) + gui, err := d2gui.CreateGuiManager(a.asset, a.config.LogLevel, a.inputManager) if err != nil { return err } a.guiManager = gui - a.screen = d2screen.NewScreenManager(a.ui, *a.Options.LogLevel, a.guiManager) + a.screen = d2screen.NewScreenManager(a.ui, a.config.LogLevel, a.guiManager) a.audio.SetVolumes(a.config.BgmVolume, a.config.SfxVolume) @@ -636,7 +682,7 @@ func (a *App) allocRate(totalAlloc uint64, fps float64) float64 { return deltaAllocPerFrame * fps / bytesToMegabyte } -func (a *App) dumpHeap([]string) error { +func (a *App) dumpHeap() { if _, err := os.Stat("./pprof/"); os.IsNotExist(err) { if err := os.Mkdir("./pprof/", 0750); err != nil { a.Fatal(err.Error()) @@ -655,56 +701,48 @@ func (a *App) dumpHeap([]string) error { if err := fileOut.Close(); err != nil { a.Fatal(err.Error()) } - - return nil } -func (a *App) evalJS(args []string) error { - val, err := a.scriptEngine.Eval(args[0]) +func (a *App) evalJS(code string) { + val, err := a.scriptEngine.Eval(code) if err != nil { - a.terminal.Errorf(err.Error()) - return nil + a.terminal.OutputErrorf("%s", err) + return } a.Info("%s" + val) - - return nil } -func (a *App) toggleFullScreen([]string) error { +func (a *App) toggleFullScreen() { fullscreen := !a.renderer.IsFullScreen() a.renderer.SetFullScreen(fullscreen) - a.terminal.Infof("fullscreen is now: %v", fullscreen) - - return nil + a.terminal.OutputInfof("fullscreen is now: %v", fullscreen) } -func (a *App) setupCaptureFrame(args []string) error { +func (a *App) setupCaptureFrame(path string) { a.captureState = captureStateFrame - a.capturePath = args[0] + a.capturePath = path a.captureFrames = nil - - return nil } func (a *App) doCaptureFrame(target d2interface.Surface) error { fp, err := os.Create(a.capturePath) if err != nil { - a.terminal.Errorf("failed to create %q", a.capturePath) return err } + defer func() { + if err := fp.Close(); err != nil { + a.Fatal(err.Error()) + } + }() + screenshot := target.Screenshot() if err := png.Encode(fp, screenshot); err != nil { return err } - if err := fp.Close(); err != nil { - a.terminal.Errorf("failed to create %q", a.capturePath) - return nil - } - - a.terminal.Infof("saved frame to %s", a.capturePath) + a.Info(fmt.Sprintf("saved frame to %s", a.capturePath)) return nil } @@ -764,61 +802,47 @@ func (a *App) convertFramesToGif() error { return err } - a.Infof("saved animation to %s", a.capturePath) + a.Info(fmt.Sprintf("saved animation to %s", a.capturePath)) return nil } -func (a *App) startAnimationCapture(args []string) error { +func (a *App) startAnimationCapture(path string) { a.captureState = captureStateGif - a.capturePath = args[0] + a.capturePath = path a.captureFrames = nil - - return nil } -func (a *App) stopAnimationCapture([]string) error { +func (a *App) stopAnimationCapture() { a.captureState = captureStateNone - - return nil } -func (a *App) toggleVsync([]string) error { +func (a *App) toggleVsync() { vsync := !a.renderer.GetVSyncEnabled() a.renderer.SetVSyncEnabled(vsync) - a.terminal.Infof("vsync is now: %v", vsync) - - return nil + a.terminal.OutputInfof("vsync is now: %v", vsync) } -func (a *App) toggleFpsCounter([]string) error { +func (a *App) toggleFpsCounter() { a.showFPS = !a.showFPS - a.terminal.Infof("fps counter is now: %v", a.showFPS) - - return nil + a.terminal.OutputInfof("fps counter is now: %v", a.showFPS) } -func (a *App) setTimeScale(args []string) error { - timeScale, err := strconv.ParseFloat(args[0], 64) - if err != nil || timeScale <= 0 { - a.terminal.Errorf("invalid time scale value") - return nil +func (a *App) setTimeScale(timeScale float64) { + if timeScale <= 0 { + a.terminal.OutputErrorf("invalid time scale value") + } else { + a.terminal.OutputInfof("timescale changed from %f to %f", a.timeScale, timeScale) + a.timeScale = timeScale } - - a.terminal.Infof("timescale changed from %f to %f", a.timeScale, timeScale) - a.timeScale = timeScale - - return nil } -func (a *App) quitGame([]string) error { +func (a *App) quitGame() { os.Exit(0) - return nil } -func (a *App) enterGuiPlayground([]string) error { - a.screen.SetNextScreen(d2gamescreen.CreateGuiTestMain(a.renderer, a.guiManager, *a.Options.LogLevel, a.asset)) - return nil +func (a *App) enterGuiPlayground() { + a.screen.SetNextScreen(d2gamescreen.CreateGuiTestMain(a.renderer, a.guiManager, a.config.LogLevel, a.asset)) } func createZeroedRing(n int) *ring.Ring { @@ -887,7 +911,7 @@ func (a *App) ToMainMenu(errorMessageOptional ...string) { buildInfo := d2gamescreen.BuildInfo{Branch: a.gitBranch, Commit: a.gitCommit} mainMenu, err := d2gamescreen.CreateMainMenu(a, a.asset, a.renderer, a.inputManager, a.audio, a.ui, buildInfo, - *a.Options.LogLevel, errorMessageOptional...) + a.config.LogLevel, errorMessageOptional...) if err != nil { a.Error(err.Error()) return @@ -898,7 +922,7 @@ func (a *App) ToMainMenu(errorMessageOptional ...string) { // ToSelectHero forces the game to transition to the Select Hero (create character) screen func (a *App) ToSelectHero(connType d2clientconnectiontype.ClientConnectionType, host string) { - selectHero, err := d2gamescreen.CreateSelectHeroClass(a, a.asset, a.renderer, a.audio, a.ui, connType, *a.Options.LogLevel, host) + selectHero, err := d2gamescreen.CreateSelectHeroClass(a, a.asset, a.renderer, a.audio, a.ui, connType, a.config.LogLevel, host) if err != nil { a.Error(err.Error()) return @@ -909,18 +933,18 @@ func (a *App) ToSelectHero(connType d2clientconnectiontype.ClientConnectionType, // ToCreateGame forces the game to transition to the Create Game screen func (a *App) ToCreateGame(filePath string, connType d2clientconnectiontype.ClientConnectionType, host string) { - gameClient, err := d2client.Create(connType, a.asset, *a.Options.LogLevel, a.scriptEngine) + gameClient, err := d2client.Create(connType, a.asset, a.config.LogLevel, a.scriptEngine) if err != nil { a.Error(err.Error()) } if err = gameClient.Open(host, filePath); err != nil { errorMessage := fmt.Sprintf("can not connect to the host: %s", host) - a.Error(errorMessage) + fmt.Println(errorMessage) a.ToMainMenu(errorMessage) } else { game, err := d2gamescreen.CreateGame( - a, a.asset, a.ui, a.renderer, a.inputManager, a.audio, gameClient, a.terminal, *a.Options.LogLevel, a.guiManager, + a, a.asset, a.ui, a.renderer, a.inputManager, a.audio, gameClient, a.terminal, a.config.LogLevel, a.guiManager, ) if err != nil { a.Error(err.Error()) @@ -933,9 +957,9 @@ func (a *App) ToCreateGame(filePath string, connType d2clientconnectiontype.Clie // ToCharacterSelect forces the game to transition to the Character Select (load character) screen func (a *App) ToCharacterSelect(connType d2clientconnectiontype.ClientConnectionType, connHost string) { characterSelect, err := d2gamescreen.CreateCharacterSelect(a, a.asset, a.renderer, a.inputManager, - a.audio, a.ui, connType, *a.Options.LogLevel, connHost) + a.audio, a.ui, connType, a.config.LogLevel, connHost) if err != nil { - a.Errorf("unable to create character select screen: %s", err) + fmt.Printf("unable to create character select screen: %s", err) } a.screen.SetNextScreen(characterSelect) @@ -944,7 +968,7 @@ func (a *App) ToCharacterSelect(connType d2clientconnectiontype.ClientConnection // ToMapEngineTest forces the game to transition to the map engine test screen func (a *App) ToMapEngineTest(region, level int) { met, err := d2gamescreen.CreateMapEngineTest(region, level, a.asset, a.terminal, a.renderer, a.inputManager, a.audio, - *a.Options.LogLevel, a.screen) + a.config.LogLevel, a.screen) if err != nil { a.Error(err.Error()) return @@ -955,10 +979,10 @@ func (a *App) ToMapEngineTest(region, level int) { // ToCredits forces the game to transition to the credits screen func (a *App) ToCredits() { - a.screen.SetNextScreen(d2gamescreen.CreateCredits(a, a.asset, a.renderer, *a.Options.LogLevel, a.ui)) + a.screen.SetNextScreen(d2gamescreen.CreateCredits(a, a.asset, a.renderer, a.config.LogLevel, a.ui)) } // ToCinematics forces the game to transition to the cinematics menu func (a *App) ToCinematics() { - a.screen.SetNextScreen(d2gamescreen.CreateCinematics(a, a.asset, a.renderer, a.audio, *a.Options.LogLevel, a.ui)) + a.screen.SetNextScreen(d2gamescreen.CreateCinematics(a, a.asset, a.renderer, a.audio, a.config.LogLevel, a.ui)) } diff --git a/d2common/d2datautils/stream_reader.go b/d2common/d2datautils/stream_reader.go index 31e06c96..c66d8212 100644 --- a/d2common/d2datautils/stream_reader.go +++ b/d2common/d2datautils/stream_reader.go @@ -4,6 +4,12 @@ import ( "io" ) +const ( + bytesPerInt16 = 2 + bytesPerInt32 = 4 + bytesPerInt64 = 8 +) + // StreamReader allows you to read data from a byte array in various formats type StreamReader struct { data []byte @@ -20,6 +26,16 @@ func CreateStreamReader(source []byte) *StreamReader { return result } +// GetPosition returns the current stream position +func (v *StreamReader) GetPosition() uint64 { + return v.position +} + +// GetSize returns the total size of the stream in bytes +func (v *StreamReader) GetSize() uint64 { + return uint64(len(v.data)) +} + // GetByte returns a byte from the stream func (v *StreamReader) GetByte() byte { result := v.data[v.position] @@ -28,46 +44,32 @@ func (v *StreamReader) GetByte() byte { return result } +// GetUInt16 returns a uint16 word from the stream +func (v *StreamReader) GetUInt16() uint16 { + var result uint16 + + for offset := uint64(0); offset < bytesPerInt16; offset++ { + shift := uint8(bitsPerByte * offset) + result += uint16(v.data[v.position+offset]) << shift + } + + v.position += bytesPerInt16 + + return result +} + // GetInt16 returns a int16 word from the stream func (v *StreamReader) GetInt16() int16 { - return int16(v.GetUInt16()) -} + var result int16 -// GetUInt16 returns a uint16 word from the stream -//nolint -func (v *StreamReader) GetUInt16() uint16 { - b := v.ReadBytes(2) - return uint16(b[0]) | uint16(b[1])<<8 -} + for offset := uint64(0); offset < bytesPerInt16; offset++ { + shift := uint8(bitsPerByte * offset) + result += int16(v.data[v.position+offset]) << shift + } -// GetInt32 returns an int32 dword from the stream -func (v *StreamReader) GetInt32() int32 { - return int32(v.GetUInt32()) -} + v.position += bytesPerInt16 -// GetUInt32 returns a uint32 dword from the stream -//nolint -func (v *StreamReader) GetUInt32() uint32 { - b := v.ReadBytes(4) - return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 -} - -// GetInt64 returns a uint64 qword from the stream -func (v *StreamReader) GetInt64() int64 { - return int64(v.GetUInt64()) -} - -// GetUInt64 returns a uint64 qword from the stream -//nolint -func (v *StreamReader) GetUInt64() uint64 { - b := v.ReadBytes(8) - return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | - uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 -} - -// GetPosition returns the current stream position -func (v *StreamReader) GetPosition() uint64 { - return v.position + return result } // SetPosition sets the stream position with the given position @@ -75,9 +77,51 @@ func (v *StreamReader) SetPosition(newPosition uint64) { v.position = newPosition } -// GetSize returns the total size of the stream in bytes -func (v *StreamReader) GetSize() uint64 { - return uint64(len(v.data)) +// GetUInt32 returns a uint32 dword from the stream +func (v *StreamReader) GetUInt32() uint32 { + var result uint32 + + for offset := uint64(0); offset < bytesPerInt32; offset++ { + shift := uint8(bitsPerByte * offset) + result += uint32(v.data[v.position+offset]) << shift + } + + v.position += bytesPerInt32 + + return result +} + +// GetInt32 returns an int32 dword from the stream +func (v *StreamReader) GetInt32() int32 { + var result int32 + + for offset := uint64(0); offset < bytesPerInt32; offset++ { + shift := uint8(bitsPerByte * offset) + result += int32(v.data[v.position+offset]) << shift + } + + v.position += bytesPerInt32 + + return result +} + +// GetUint64 returns a uint64 qword from the stream +func (v *StreamReader) GetUint64() uint64 { + var result uint64 + + for offset := uint64(0); offset < bytesPerInt64; offset++ { + shift := uint8(bitsPerByte * offset) + result += uint64(v.data[v.position+offset]) << shift + } + + v.position += bytesPerInt64 + + return result +} + +// GetInt64 returns a uint64 qword from the stream +func (v *StreamReader) GetInt64() int64 { + return int64(v.GetUint64()) } // ReadByte implements io.ByteReader diff --git a/d2common/d2datautils/stream_writer.go b/d2common/d2datautils/stream_writer.go index 849cbdb2..10870a73 100644 --- a/d2common/d2datautils/stream_writer.go +++ b/d2common/d2datautils/stream_writer.go @@ -2,6 +2,10 @@ package d2datautils import "bytes" +const ( + byteMask = 0xFF +) + // StreamWriter allows you to create a byte array by streaming in writes of various sizes type StreamWriter struct { data *bytes.Buffer @@ -16,40 +20,41 @@ func CreateStreamWriter() *StreamWriter { return result } -// GetBytes returns the the byte slice of the underlying data -func (v *StreamWriter) GetBytes() []byte { - return v.data.Bytes() -} - // PushByte writes a byte to the stream func (v *StreamWriter) PushByte(val byte) { v.data.WriteByte(val) } +// PushUint16 writes an uint16 word to the stream +func (v *StreamWriter) PushUint16(val uint16) { + for count := 0; count < bytesPerInt16; count++ { + shift := count * bitsPerByte + v.data.WriteByte(byte(val>>shift) & byteMask) + } +} + // PushInt16 writes a int16 word to the stream func (v *StreamWriter) PushInt16(val int16) { - v.PushUint16(uint16(val)) -} - -// PushUint16 writes an uint16 word to the stream -//nolint -func (v *StreamWriter) PushUint16(val uint16) { - v.data.WriteByte(byte(val)) - v.data.WriteByte(byte(val >> 8)) -} - -// PushInt32 writes a int32 dword to the stream -func (v *StreamWriter) PushInt32(val int32) { - v.PushUint32(uint32(val)) + for count := 0; count < bytesPerInt16; count++ { + shift := count * bitsPerByte + v.data.WriteByte(byte(val>>shift) & byteMask) + } } // PushUint32 writes a uint32 dword to the stream -//nolint func (v *StreamWriter) PushUint32(val uint32) { - v.data.WriteByte(byte(val)) - v.data.WriteByte(byte(val >> 8)) - v.data.WriteByte(byte(val >> 16)) - v.data.WriteByte(byte(val >> 24)) + for count := 0; count < bytesPerInt32; count++ { + shift := count * bitsPerByte + v.data.WriteByte(byte(val>>shift) & byteMask) + } +} + +// PushUint64 writes a uint64 qword to the stream +func (v *StreamWriter) PushUint64(val uint64) { + for count := 0; count < bytesPerInt64; count++ { + shift := count * bitsPerByte + v.data.WriteByte(byte(val>>shift) & byteMask) + } } // PushInt64 writes a uint64 qword to the stream @@ -57,15 +62,7 @@ func (v *StreamWriter) PushInt64(val int64) { v.PushUint64(uint64(val)) } -// PushUint64 writes a uint64 qword to the stream -//nolint -func (v *StreamWriter) PushUint64(val uint64) { - v.data.WriteByte(byte(val)) - v.data.WriteByte(byte(val >> 8)) - v.data.WriteByte(byte(val >> 16)) - v.data.WriteByte(byte(val >> 24)) - v.data.WriteByte(byte(val >> 32)) - v.data.WriteByte(byte(val >> 40)) - v.data.WriteByte(byte(val >> 48)) - v.data.WriteByte(byte(val >> 56)) +// GetBytes returns the the byte slice of the underlying data +func (v *StreamWriter) GetBytes() []byte { + return v.data.Bytes() } diff --git a/d2common/d2enum/numeric_labels.go b/d2common/d2enum/numeric_labels.go index eb2253b6..6383b6e5 100644 --- a/d2common/d2enum/numeric_labels.go +++ b/d2common/d2enum/numeric_labels.go @@ -2,9 +2,7 @@ package d2enum // there are labels for "numeric labels (see AssetManager.TranslateLabel) const ( - RepairAll = iota - _ - CancelLabel + CancelLabel = iota CopyrightLabel AllRightsReservedLabel SinglePlayerLabel @@ -64,8 +62,6 @@ const ( // BaseLabelNumbers returns base label value (#n in english string table table) func BaseLabelNumbers(idx int) int { baseLabelNumbers := []int{ - 128, // repairAll - 127, // main menu labels 1612, // CANCEL 1613, // (c) 2000 Blizzard Entertainment diff --git a/d2common/d2enum/scene_state.go b/d2common/d2enum/scene_state.go deleted file mode 100644 index 83d08634..00000000 --- a/d2common/d2enum/scene_state.go +++ /dev/null @@ -1,11 +0,0 @@ -package d2enum - -// SceneState enumerates the different states a scene can be in -type SceneState int - -// Scene states -const ( - SceneStateUninitialized SceneState = iota - SceneStateBooting - SceneStateBooted -) diff --git a/d2common/d2fileformats/d2mpq/crypto.go b/d2common/d2fileformats/d2mpq/crypto.go deleted file mode 100644 index 636c8bc2..00000000 --- a/d2common/d2fileformats/d2mpq/crypto.go +++ /dev/null @@ -1,131 +0,0 @@ -package d2mpq - -import ( - "encoding/binary" - "io" - "strings" -) - -var cryptoBuffer [0x500]uint32 //nolint:gochecknoglobals // will fix later.. -var cryptoBufferReady bool //nolint:gochecknoglobals // will fix later.. - -func cryptoLookup(index uint32) uint32 { - if !cryptoBufferReady { - cryptoInitialize() - - cryptoBufferReady = true - } - - return cryptoBuffer[index] -} - -//nolint:gomnd // Decryption magic -func cryptoInitialize() { - seed := uint32(0x00100001) - - for index1 := 0; index1 < 0x100; index1++ { - index2 := index1 - - for i := 0; i < 5; i++ { - seed = (seed*125 + 3) % 0x2AAAAB - temp1 := (seed & 0xFFFF) << 0x10 - seed = (seed*125 + 3) % 0x2AAAAB - temp2 := seed & 0xFFFF - cryptoBuffer[index2] = temp1 | temp2 - index2 += 0x100 - } - } -} - -//nolint:gomnd // Decryption magic -func decrypt(data []uint32, seed uint32) { - seed2 := uint32(0xeeeeeeee) - - for i := 0; i < len(data); i++ { - seed2 += cryptoLookup(0x400 + (seed & 0xff)) - result := data[i] - result ^= seed + seed2 - - seed = ((^seed << 21) + 0x11111111) | (seed >> 11) - seed2 = result + seed2 + (seed2 << 5) + 3 - data[i] = result - } -} - -//nolint:gomnd // Decryption magic -func decryptBytes(data []byte, seed uint32) { - seed2 := uint32(0xEEEEEEEE) - for i := 0; i < len(data)-3; i += 4 { - seed2 += cryptoLookup(0x400 + (seed & 0xFF)) - result := binary.LittleEndian.Uint32(data[i : i+4]) - result ^= seed + seed2 - seed = ((^seed << 21) + 0x11111111) | (seed >> 11) - seed2 = result + seed2 + (seed2 << 5) + 3 - - data[i+0] = uint8(result & 0xff) - data[i+1] = uint8((result >> 8) & 0xff) - data[i+2] = uint8((result >> 16) & 0xff) - data[i+3] = uint8((result >> 24) & 0xff) - } -} - -//nolint:gomnd // Decryption magic -func decryptTable(r io.Reader, size uint32, name string) ([]uint32, error) { - seed := hashString(name, 3) - seed2 := uint32(0xEEEEEEEE) - size *= 4 - - table := make([]uint32, size) - buf := make([]byte, 4) - - for i := uint32(0); i < size; i++ { - seed2 += cryptoBuffer[0x400+(seed&0xff)] - - if _, err := r.Read(buf); err != nil { - return table, err - } - - result := binary.LittleEndian.Uint32(buf) - result ^= seed + seed2 - - seed = ((^seed << 21) + 0x11111111) | (seed >> 11) - seed2 = result + seed2 + (seed2 << 5) + 3 - table[i] = result - } - - return table, nil -} - -func hashFilename(key string) uint64 { - a, b := hashString(key, 1), hashString(key, 2) - return uint64(a)<<32 | uint64(b) -} - -//nolint:gomnd // Decryption magic -func hashString(key string, hashType uint32) uint32 { - seed1 := uint32(0x7FED7FED) - seed2 := uint32(0xEEEEEEEE) - - /* prepare seeds. */ - for _, char := range strings.ToUpper(key) { - seed1 = cryptoLookup((hashType*0x100)+uint32(char)) ^ (seed1 + seed2) - seed2 = uint32(char) + seed1 + seed2 + (seed2 << 5) + 3 - } - - return seed1 -} - -//nolint:unused,deadcode,gomnd // will use this for creating mpq's -func encrypt(data []uint32, seed uint32) { - seed2 := uint32(0xeeeeeeee) - - for i := 0; i < len(data); i++ { - seed2 += cryptoLookup(0x400 + (seed & 0xff)) - result := data[i] - result ^= seed + seed2 - - seed = ((^seed << 21) + 0x11111111) | (seed >> 11) - seed2 = data[i] + seed2 + (seed2 << 5) + 3 - data[i] = result - } -} diff --git a/d2common/d2fileformats/d2mpq/crypto_buff.go b/d2common/d2fileformats/d2mpq/crypto_buff.go new file mode 100644 index 00000000..7618743b --- /dev/null +++ b/d2common/d2fileformats/d2mpq/crypto_buff.go @@ -0,0 +1,32 @@ +package d2mpq + +var cryptoBuffer [0x500]uint32 //nolint:gochecknoglobals // will fix later.. +var cryptoBufferReady bool //nolint:gochecknoglobals // will fix later.. + +func cryptoLookup(index uint32) uint32 { + if !cryptoBufferReady { + cryptoInitialize() + + cryptoBufferReady = true + } + + return cryptoBuffer[index] +} + +//nolint:gomnd // magic cryptographic stuff here... +func cryptoInitialize() { + seed := uint32(0x00100001) + + for index1 := 0; index1 < 0x100; index1++ { + index2 := index1 + + for i := 0; i < 5; i++ { + seed = (seed*125 + 3) % 0x2AAAAB + temp1 := (seed & 0xFFFF) << 0x10 + seed = (seed*125 + 3) % 0x2AAAAB + temp2 := seed & 0xFFFF + cryptoBuffer[index2] = temp1 | temp2 + index2 += 0x100 + } + } +} diff --git a/d2common/d2fileformats/d2mpq/hash_entry_map.go b/d2common/d2fileformats/d2mpq/hash_entry_map.go new file mode 100644 index 00000000..ab9c0ca1 --- /dev/null +++ b/d2common/d2fileformats/d2mpq/hash_entry_map.go @@ -0,0 +1,35 @@ +package d2mpq + +// HashEntryMap represents a hash entry map +type HashEntryMap struct { + entries map[uint64]HashTableEntry +} + +// Insert inserts a hash entry into the table +func (hem *HashEntryMap) Insert(entry *HashTableEntry) { + if hem.entries == nil { + hem.entries = make(map[uint64]HashTableEntry) + } + + hem.entries[uint64(entry.NamePartA)<<32|uint64(entry.NamePartB)] = *entry +} + +// Find finds a hash entry +func (hem *HashEntryMap) Find(fileName string) (*HashTableEntry, bool) { + if hem.entries == nil { + return nil, false + } + + hashA := hashString(fileName, 1) + hashB := hashString(fileName, 2) + + entry, found := hem.entries[uint64(hashA)<<32|uint64(hashB)] + + return &entry, found +} + +// Contains returns true if the hash entry contains the values +func (hem *HashEntryMap) Contains(fileName string) bool { + _, found := hem.Find(fileName) + return found +} diff --git a/d2common/d2fileformats/d2mpq/mpq.go b/d2common/d2fileformats/d2mpq/mpq.go index 59f3a8b1..23deed54 100644 --- a/d2common/d2fileformats/d2mpq/mpq.go +++ b/d2common/d2fileformats/d2mpq/mpq.go @@ -2,9 +2,10 @@ package d2mpq import ( "bufio" + "encoding/binary" "errors" - "fmt" "io/ioutil" + "log" "os" "path" "path/filepath" @@ -18,11 +19,33 @@ var _ d2interface.Archive = &MPQ{} // Static check to confirm struct conforms to // MPQ represents an MPQ archive type MPQ struct { - filePath string - file *os.File - hashes map[uint64]*Hash - blocks []*Block - header Header + filePath string + file *os.File + hashEntryMap HashEntryMap + blockTableEntries []BlockTableEntry + data Data +} + +// Data Represents a MPQ file +type Data struct { + Magic [4]byte + HeaderSize uint32 + ArchiveSize uint32 + FormatVersion uint16 + BlockSize uint16 + HashTableOffset uint32 + BlockTableOffset uint32 + HashTableEntries uint32 + BlockTableEntries uint32 +} + +// HashTableEntry represents a hashed file entry in the MPQ file +type HashTableEntry struct { // 16 bytes + NamePartA uint32 + NamePartB uint32 + Locale uint16 + Platform uint16 + BlockIndex uint32 } // PatchInfo represents patch info for the MPQ. @@ -30,153 +53,71 @@ type PatchInfo struct { Length uint32 // Length of patch info header, in bytes Flags uint32 // Flags. 0x80000000 = MD5 (?) DataSize uint32 // Uncompressed size of the patch file - MD5 [16]byte // MD5 of the entire patch file after decompression + Md5 [16]byte // MD5 of the entire patch file after decompression } -// New loads an MPQ file and only reads the header -func New(fileName string) (*MPQ, error) { - mpq := &MPQ{filePath: fileName} +// FileFlag represents flags for a file record in the MPQ archive +type FileFlag uint32 + +const ( + // FileImplode - File is compressed using PKWARE Data compression library + FileImplode FileFlag = 0x00000100 + // FileCompress - File is compressed using combination of compression methods + FileCompress FileFlag = 0x00000200 + // FileEncrypted - The file is encrypted + FileEncrypted FileFlag = 0x00010000 + // FileFixKey - The decryption key for the file is altered according to the position of the file in the archive + FileFixKey FileFlag = 0x00020000 + // FilePatchFile - The file contains incremental patch for an existing file in base MPQ + FilePatchFile FileFlag = 0x00100000 + // FileSingleUnit - Instead of being divided to 0x1000-bytes blocks, the file is stored as single unit + FileSingleUnit FileFlag = 0x01000000 + // FileDeleteMarker - File is a deletion marker, indicating that the file no longer exists. This is used to allow patch + // archives to delete files present in lower-priority archives in the search chain. The file usually + // has length of 0 or 1 byte and its name is a hash + FileDeleteMarker FileFlag = 0x02000000 + // FileSectorCrc - File has checksums for each sector. Ignored if file is not compressed or imploded. + FileSectorCrc FileFlag = 0x04000000 + // FileExists - Set if file exists, reset when the file was deleted + FileExists FileFlag = 0x80000000 +) + +// BlockTableEntry represents an entry in the block table +type BlockTableEntry struct { // 16 bytes + FilePosition uint32 + CompressedFileSize uint32 + UncompressedFileSize uint32 + Flags FileFlag + // Local Stuff... + FileName string + EncryptionSeed uint32 +} + +// HasFlag returns true if the specified flag is present +func (v BlockTableEntry) HasFlag(flag FileFlag) bool { + return (v.Flags & flag) != 0 +} + +// Load loads an MPQ file and returns a MPQ structure +func Load(fileName string) (d2interface.Archive, error) { + result := &MPQ{filePath: fileName} var err error if runtime.GOOS == "linux" { - mpq.file, err = openIgnoreCase(fileName) + result.file, err = openIgnoreCase(fileName) } else { - mpq.file, err = os.Open(fileName) //nolint:gosec // Will fix later + result.file, err = os.Open(fileName) //nolint:gosec // Will fix later } if err != nil { return nil, err } - if err := mpq.readHeader(); err != nil { - return nil, fmt.Errorf("failed to read reader: %v", err) - } - - return mpq, nil -} - -// FromFile loads an MPQ file and returns a MPQ structure -func FromFile(fileName string) (*MPQ, error) { - mpq, err := New(fileName) - if err != nil { + if err := result.readHeader(); err != nil { return nil, err } - if err := mpq.readHashTable(); err != nil { - return nil, fmt.Errorf("failed to read hash table: %v", err) - } - - if err := mpq.readBlockTable(); err != nil { - return nil, fmt.Errorf("failed to read block table: %v", err) - } - - return mpq, nil -} - -// getFileBlockData gets a block table entry -func (mpq *MPQ) getFileBlockData(fileName string) (*Block, error) { - fileEntry, ok := mpq.hashes[hashFilename(fileName)] - if !ok { - return nil, errors.New("file not found") - } - - if fileEntry.BlockIndex >= uint32(len(mpq.blocks)) { - return nil, errors.New("invalid block index") - } - - return mpq.blocks[fileEntry.BlockIndex], nil -} - -// Close closes the MPQ file -func (mpq *MPQ) Close() error { - return mpq.file.Close() -} - -// ReadFile reads a file from the MPQ and returns a memory stream -func (mpq *MPQ) ReadFile(fileName string) ([]byte, error) { - fileBlockData, err := mpq.getFileBlockData(fileName) - if err != nil { - return []byte{}, err - } - - fileBlockData.FileName = strings.ToLower(fileName) - - stream, err := CreateStream(mpq, fileBlockData, fileName) - if err != nil { - return []byte{}, err - } - - buffer := make([]byte, fileBlockData.UncompressedFileSize) - if _, err := stream.Read(buffer, 0, fileBlockData.UncompressedFileSize); err != nil { - return []byte{}, err - } - - return buffer, nil -} - -// ReadFileStream reads the mpq file data and returns a stream -func (mpq *MPQ) ReadFileStream(fileName string) (d2interface.DataStream, error) { - fileBlockData, err := mpq.getFileBlockData(fileName) - if err != nil { - return nil, err - } - - fileBlockData.FileName = strings.ToLower(fileName) - - stream, err := CreateStream(mpq, fileBlockData, fileName) - if err != nil { - return nil, err - } - - return &MpqDataStream{stream: stream}, nil -} - -// ReadTextFile reads a file and returns it as a string -func (mpq *MPQ) ReadTextFile(fileName string) (string, error) { - data, err := mpq.ReadFile(fileName) - - if err != nil { - return "", err - } - - return string(data), nil -} - -// Listfile returns the list of files in this MPQ -func (mpq *MPQ) Listfile() ([]string, error) { - data, err := mpq.ReadFile("(listfile)") - - if err != nil { - return nil, err - } - - raw := strings.TrimRight(string(data), "\x00") - s := bufio.NewScanner(strings.NewReader(raw)) - - var filePaths []string - - for s.Scan() { - filePath := s.Text() - filePaths = append(filePaths, filePath) - } - - return filePaths, nil -} - -// Path returns the MPQ file path -func (mpq *MPQ) Path() string { - return mpq.filePath -} - -// Contains returns bool for whether the given filename exists in the mpq -func (mpq *MPQ) Contains(filename string) bool { - _, ok := mpq.hashes[hashFilename(filename)] - return ok -} - -// Size returns the size of the mpq in bytes -func (mpq *MPQ) Size() uint32 { - return mpq.header.ArchiveSize + return result, nil } func openIgnoreCase(mpqPath string) (*os.File, error) { @@ -201,5 +142,258 @@ func openIgnoreCase(mpqPath string) (*os.File, error) { } } - return os.Open(path.Join(mpqDir, mpqName)) //nolint:gosec // Will fix later + file, err := os.Open(path.Join(mpqDir, mpqName)) //nolint:gosec // Will fix later + + return file, err +} + +func (v *MPQ) readHeader() error { + err := binary.Read(v.file, binary.LittleEndian, &v.data) + + if err != nil { + return err + } + + if string(v.data.Magic[:]) != "MPQ\x1A" { + return errors.New("invalid mpq header") + } + + err = v.loadHashTable() + if err != nil { + return err + } + + v.loadBlockTable() + + return nil +} + +func (v *MPQ) loadHashTable() error { + _, err := v.file.Seek(int64(v.data.HashTableOffset), 0) + if err != nil { + log.Panic(err) + } + + hashData := make([]uint32, v.data.HashTableEntries*4) //nolint:gomnd // // Decryption magic + hash := make([]byte, 4) + + for i := range hashData { + _, err := v.file.Read(hash) + if err != nil { + log.Print(err) + } + + hashData[i] = binary.LittleEndian.Uint32(hash) + } + + decrypt(hashData, hashString("(hash table)", 3)) + + for i := uint32(0); i < v.data.HashTableEntries; i++ { + v.hashEntryMap.Insert(&HashTableEntry{ + NamePartA: hashData[i*4], + NamePartB: hashData[(i*4)+1], + // https://github.com/OpenDiablo2/OpenDiablo2/issues/812 + Locale: uint16(hashData[(i*4)+2] >> 16), //nolint:gomnd // // binary data + Platform: uint16(hashData[(i*4)+2] & 0xFFFF), //nolint:gomnd // // binary data + BlockIndex: hashData[(i*4)+3], + }) + } + + return nil +} + +func (v *MPQ) loadBlockTable() { + _, err := v.file.Seek(int64(v.data.BlockTableOffset), 0) + if err != nil { + log.Panic(err) + } + + blockData := make([]uint32, v.data.BlockTableEntries*4) //nolint:gomnd // // binary data + hash := make([]byte, 4) + + for i := range blockData { + _, err = v.file.Read(hash) //nolint:errcheck // Will fix later + if err != nil { + log.Print(err) + } + + blockData[i] = binary.LittleEndian.Uint32(hash) + } + + decrypt(blockData, hashString("(block table)", 3)) + + for i := uint32(0); i < v.data.BlockTableEntries; i++ { + v.blockTableEntries = append(v.blockTableEntries, BlockTableEntry{ + FilePosition: blockData[(i * 4)], + CompressedFileSize: blockData[(i*4)+1], + UncompressedFileSize: blockData[(i*4)+2], + Flags: FileFlag(blockData[(i*4)+3]), + }) + } +} + +func decrypt(data []uint32, seed uint32) { + seed2 := uint32(0xeeeeeeee) //nolint:gomnd // Decryption magic + + for i := 0; i < len(data); i++ { + seed2 += cryptoLookup(0x400 + (seed & 0xff)) //nolint:gomnd // Decryption magic + result := data[i] + result ^= seed + seed2 + + seed = ((^seed << 21) + 0x11111111) | (seed >> 11) + seed2 = result + seed2 + (seed2 << 5) + 3 //nolint:gomnd // Decryption magic + data[i] = result + } +} + +func decryptBytes(data []byte, seed uint32) { + seed2 := uint32(0xEEEEEEEE) //nolint:gomnd // Decryption magic + for i := 0; i < len(data)-3; i += 4 { + seed2 += cryptoLookup(0x400 + (seed & 0xFF)) //nolint:gomnd // Decryption magic + result := binary.LittleEndian.Uint32(data[i : i+4]) + result ^= seed + seed2 + seed = ((^seed << 21) + 0x11111111) | (seed >> 11) + seed2 = result + seed2 + (seed2 << 5) + 3 //nolint:gomnd // Decryption magic + + data[i+0] = uint8(result & 0xff) //nolint:gomnd // Decryption magic + data[i+1] = uint8((result >> 8) & 0xff) //nolint:gomnd // Decryption magic + data[i+2] = uint8((result >> 16) & 0xff) //nolint:gomnd // Decryption magic + data[i+3] = uint8((result >> 24) & 0xff) //nolint:gomnd // Decryption magic + } +} + +func hashString(key string, hashType uint32) uint32 { + seed1 := uint32(0x7FED7FED) //nolint:gomnd // Decryption magic + seed2 := uint32(0xEEEEEEEE) //nolint:gomnd // Decryption magic + + /* prepare seeds. */ + for _, char := range strings.ToUpper(key) { + seed1 = cryptoLookup((hashType*0x100)+uint32(char)) ^ (seed1 + seed2) + seed2 = uint32(char) + seed1 + seed2 + (seed2 << 5) + 3 //nolint:gomnd // Decryption magic + } + + return seed1 +} + +// GetFileBlockData gets a block table entry +func (v *MPQ) getFileBlockData(fileName string) (BlockTableEntry, error) { + fileEntry, found := v.hashEntryMap.Find(fileName) + + if !found || fileEntry.BlockIndex >= uint32(len(v.blockTableEntries)) { + return BlockTableEntry{}, errors.New("file not found") + } + + return v.blockTableEntries[fileEntry.BlockIndex], nil +} + +// Close closes the MPQ file +func (v *MPQ) Close() { + err := v.file.Close() + if err != nil { + log.Panic(err) + } +} + +// FileExists checks the mpq to see if the file exists +func (v *MPQ) FileExists(fileName string) bool { + return v.hashEntryMap.Contains(fileName) +} + +// ReadFile reads a file from the MPQ and returns a memory stream +func (v *MPQ) ReadFile(fileName string) ([]byte, error) { + fileBlockData, err := v.getFileBlockData(fileName) + if err != nil { + return []byte{}, err + } + + fileBlockData.FileName = strings.ToLower(fileName) + + fileBlockData.calculateEncryptionSeed() + mpqStream, err := CreateStream(v, fileBlockData, fileName) + + if err != nil { + return []byte{}, err + } + + buffer := make([]byte, fileBlockData.UncompressedFileSize) + mpqStream.Read(buffer, 0, fileBlockData.UncompressedFileSize) + + return buffer, nil +} + +// ReadFileStream reads the mpq file data and returns a stream +func (v *MPQ) ReadFileStream(fileName string) (d2interface.DataStream, error) { + fileBlockData, err := v.getFileBlockData(fileName) + + if err != nil { + return nil, err + } + + fileBlockData.FileName = strings.ToLower(fileName) + fileBlockData.calculateEncryptionSeed() + + mpqStream, err := CreateStream(v, fileBlockData, fileName) + if err != nil { + return nil, err + } + + return &MpqDataStream{stream: mpqStream}, nil +} + +// ReadTextFile reads a file and returns it as a string +func (v *MPQ) ReadTextFile(fileName string) (string, error) { + data, err := v.ReadFile(fileName) + + if err != nil { + return "", err + } + + return string(data), nil +} + +func (v *BlockTableEntry) calculateEncryptionSeed() { + fileName := path.Base(v.FileName) + v.EncryptionSeed = hashString(fileName, 3) + + if !v.HasFlag(FileFixKey) { + return + } + + v.EncryptionSeed = (v.EncryptionSeed + v.FilePosition) ^ v.UncompressedFileSize +} + +// GetFileList returns the list of files in this MPQ +func (v *MPQ) GetFileList() ([]string, error) { + data, err := v.ReadFile("(listfile)") + + if err != nil { + return nil, err + } + + raw := strings.TrimRight(string(data), "\x00") + s := bufio.NewScanner(strings.NewReader(raw)) + + var filePaths []string + + for s.Scan() { + filePath := s.Text() + filePaths = append(filePaths, filePath) + } + + return filePaths, nil +} + +// Path returns the MPQ file path +func (v *MPQ) Path() string { + return v.filePath +} + +// Contains returns bool for whether the given filename exists in the mpq +func (v *MPQ) Contains(filename string) bool { + return v.hashEntryMap.Contains(filename) +} + +// Size returns the size of the mpq in bytes +func (v *MPQ) Size() uint32 { + return v.data.ArchiveSize } diff --git a/d2common/d2fileformats/d2mpq/mpq_block.go b/d2common/d2fileformats/d2mpq/mpq_block.go deleted file mode 100644 index 112e0e89..00000000 --- a/d2common/d2fileformats/d2mpq/mpq_block.go +++ /dev/null @@ -1,77 +0,0 @@ -package d2mpq - -import ( - "io" - "strings" -) - -// FileFlag represents flags for a file record in the MPQ archive -type FileFlag uint32 - -const ( - // FileImplode - File is compressed using PKWARE Data compression library - FileImplode FileFlag = 0x00000100 - // FileCompress - File is compressed using combination of compression methods - FileCompress FileFlag = 0x00000200 - // FileEncrypted - The file is encrypted - FileEncrypted FileFlag = 0x00010000 - // FileFixKey - The decryption key for the file is altered according to the position of the file in the archive - FileFixKey FileFlag = 0x00020000 - // FilePatchFile - The file contains incremental patch for an existing file in base MPQ - FilePatchFile FileFlag = 0x00100000 - // FileSingleUnit - Instead of being divided to 0x1000-bytes blocks, the file is stored as single unit - FileSingleUnit FileFlag = 0x01000000 - // FileDeleteMarker - File is a deletion marker, indicating that the file no longer exists. This is used to allow patch - // archives to delete files present in lower-priority archives in the search chain. The file usually - // has length of 0 or 1 byte and its name is a hash - FileDeleteMarker FileFlag = 0x02000000 - // FileSectorCrc - File has checksums for each sector. Ignored if file is not compressed or imploded. - FileSectorCrc FileFlag = 0x04000000 - // FileExists - Set if file exists, reset when the file was deleted - FileExists FileFlag = 0x80000000 -) - -// Block represents an entry in the block table -type Block struct { // 16 bytes - FilePosition uint32 - CompressedFileSize uint32 - UncompressedFileSize uint32 - Flags FileFlag - // Local Stuff... - FileName string - EncryptionSeed uint32 -} - -// HasFlag returns true if the specified flag is present -func (b *Block) HasFlag(flag FileFlag) bool { - return (b.Flags & flag) != 0 -} - -func (b *Block) calculateEncryptionSeed(fileName string) { - fileName = fileName[strings.LastIndex(fileName, `\`)+1:] - seed := hashString(fileName, 3) - b.EncryptionSeed = (seed + b.FilePosition) ^ b.UncompressedFileSize -} - -//nolint:gomnd // number -func (mpq *MPQ) readBlockTable() error { - if _, err := mpq.file.Seek(int64(mpq.header.BlockTableOffset), io.SeekStart); err != nil { - return err - } - - blockData, err := decryptTable(mpq.file, mpq.header.BlockTableEntries, "(block table)") - if err != nil { - return err - } - - for n, i := uint32(0), uint32(0); i < mpq.header.BlockTableEntries; n, i = n+4, i+1 { - mpq.blocks = append(mpq.blocks, &Block{ - FilePosition: blockData[n], - CompressedFileSize: blockData[n+1], - UncompressedFileSize: blockData[n+2], - Flags: FileFlag(blockData[n+3]), - }) - } - - return nil -} diff --git a/d2common/d2fileformats/d2mpq/mpq_data_stream.go b/d2common/d2fileformats/d2mpq/mpq_data_stream.go index 3a92064a..db66260c 100644 --- a/d2common/d2fileformats/d2mpq/mpq_data_stream.go +++ b/d2common/d2fileformats/d2mpq/mpq_data_stream.go @@ -11,14 +11,14 @@ type MpqDataStream struct { // Read reads data from the data stream func (m *MpqDataStream) Read(p []byte) (n int, err error) { - totalRead, err := m.stream.Read(p, 0, uint32(len(p))) - return int(totalRead), err + totalRead := m.stream.Read(p, 0, uint32(len(p))) + return int(totalRead), nil } // Seek sets the position of the data stream func (m *MpqDataStream) Seek(offset int64, whence int) (int64, error) { - m.stream.Position = uint32(offset + int64(whence)) - return int64(m.stream.Position), nil + m.stream.CurrentPosition = uint32(offset + int64(whence)) + return int64(m.stream.CurrentPosition), nil } // Close closes the data stream diff --git a/d2common/d2fileformats/d2mpq/mpq_hash.go b/d2common/d2fileformats/d2mpq/mpq_hash.go deleted file mode 100644 index 3f1f744a..00000000 --- a/d2common/d2fileformats/d2mpq/mpq_hash.go +++ /dev/null @@ -1,45 +0,0 @@ -package d2mpq - -import "io" - -// Hash represents a hashed file entry in the MPQ file -type Hash struct { // 16 bytes - A uint32 - B uint32 - Locale uint16 - Platform uint16 - BlockIndex uint32 -} - -// Name64 returns part A and B as uint64 -func (h *Hash) Name64() uint64 { - return uint64(h.A)<<32 | uint64(h.B) -} - -//nolint:gomnd // number -func (mpq *MPQ) readHashTable() error { - if _, err := mpq.file.Seek(int64(mpq.header.HashTableOffset), io.SeekStart); err != nil { - return err - } - - hashData, err := decryptTable(mpq.file, mpq.header.HashTableEntries, "(hash table)") - if err != nil { - return err - } - - mpq.hashes = make(map[uint64]*Hash) - - for n, i := uint32(0), uint32(0); i < mpq.header.HashTableEntries; n, i = n+4, i+1 { - e := &Hash{ - A: hashData[n], - B: hashData[n+1], - // https://github.com/OpenDiablo2/OpenDiablo2/issues/812 - Locale: uint16(hashData[n+2] >> 16), //nolint:gomnd // // binary data - Platform: uint16(hashData[n+2] & 0xFFFF), //nolint:gomnd // // binary data - BlockIndex: hashData[n+3], - } - mpq.hashes[e.Name64()] = e - } - - return nil -} diff --git a/d2common/d2fileformats/d2mpq/mpq_header.go b/d2common/d2fileformats/d2mpq/mpq_header.go deleted file mode 100644 index f27cfaf7..00000000 --- a/d2common/d2fileformats/d2mpq/mpq_header.go +++ /dev/null @@ -1,36 +0,0 @@ -package d2mpq - -import ( - "encoding/binary" - "errors" - "io" -) - -// Header Represents a MPQ file -type Header struct { - Magic [4]byte - HeaderSize uint32 - ArchiveSize uint32 - FormatVersion uint16 - BlockSize uint16 - HashTableOffset uint32 - BlockTableOffset uint32 - HashTableEntries uint32 - BlockTableEntries uint32 -} - -func (mpq *MPQ) readHeader() error { - if _, err := mpq.file.Seek(0, io.SeekStart); err != nil { - return err - } - - if err := binary.Read(mpq.file, binary.LittleEndian, &mpq.header); err != nil { - return err - } - - if string(mpq.header.Magic[:]) != "MPQ\x1A" { - return errors.New("invalid mpq header") - } - - return nil -} diff --git a/d2common/d2fileformats/d2mpq/mpq_stream.go b/d2common/d2fileformats/d2mpq/mpq_stream.go index 5be4951d..b6156322 100644 --- a/d2common/d2fileformats/d2mpq/mpq_stream.go +++ b/d2common/d2fileformats/d2mpq/mpq_stream.go @@ -6,7 +6,8 @@ import ( "encoding/binary" "errors" "fmt" - "io" + "log" + "strings" "github.com/JoshVarga/blast" @@ -16,63 +17,80 @@ import ( // Stream represents a stream of data in an MPQ archive type Stream struct { - Data []byte - Positions []uint32 - MPQ *MPQ - Block *Block - Index uint32 - Size uint32 - Position uint32 + BlockTableEntry BlockTableEntry + BlockPositions []uint32 + CurrentData []byte + FileName string + MPQData *MPQ + EncryptionSeed uint32 + CurrentPosition uint32 + CurrentBlockIndex uint32 + BlockSize uint32 } // CreateStream creates an MPQ stream -func CreateStream(mpq *MPQ, block *Block, fileName string) (*Stream, error) { - s := &Stream{ - MPQ: mpq, - Block: block, - Index: 0xFFFFFFFF, //nolint:gomnd // MPQ magic +func CreateStream(mpq *MPQ, blockTableEntry BlockTableEntry, fileName string) (*Stream, error) { + result := &Stream{ + MPQData: mpq, + BlockTableEntry: blockTableEntry, + CurrentBlockIndex: 0xFFFFFFFF, //nolint:gomnd // MPQ magic + } + fileSegs := strings.Split(fileName, `\`) + result.EncryptionSeed = hashString(fileSegs[len(fileSegs)-1], 3) + + if result.BlockTableEntry.HasFlag(FileFixKey) { + result.EncryptionSeed = (result.EncryptionSeed + result.BlockTableEntry.FilePosition) ^ result.BlockTableEntry.UncompressedFileSize } - if s.Block.HasFlag(FileFixKey) { - s.Block.calculateEncryptionSeed(fileName) + result.BlockSize = 0x200 << result.MPQData.data.BlockSize //nolint:gomnd // MPQ magic + + if result.BlockTableEntry.HasFlag(FilePatchFile) { + log.Fatal("Patching is not supported") } - s.Size = 0x200 << s.MPQ.header.BlockSize //nolint:gomnd // MPQ magic + var err error - if s.Block.HasFlag(FilePatchFile) { - return nil, errors.New("patching is not supported") + if (result.BlockTableEntry.HasFlag(FileCompress) || result.BlockTableEntry.HasFlag(FileImplode)) && + !result.BlockTableEntry.HasFlag(FileSingleUnit) { + err = result.loadBlockOffsets() } - if (s.Block.HasFlag(FileCompress) || s.Block.HasFlag(FileImplode)) && !s.Block.HasFlag(FileSingleUnit) { - if err := s.loadBlockOffsets(); err != nil { - return nil, err - } - } - - return s, nil + return result, err } func (v *Stream) loadBlockOffsets() error { - if _, err := v.MPQ.file.Seek(int64(v.Block.FilePosition), io.SeekStart); err != nil { + blockPositionCount := ((v.BlockTableEntry.UncompressedFileSize + v.BlockSize - 1) / v.BlockSize) + 1 + v.BlockPositions = make([]uint32, blockPositionCount) + + _, err := v.MPQData.file.Seek(int64(v.BlockTableEntry.FilePosition), 0) + if err != nil { return err } - blockPositionCount := ((v.Block.UncompressedFileSize + v.Size - 1) / v.Size) + 1 - v.Positions = make([]uint32, blockPositionCount) + mpqBytes := make([]byte, blockPositionCount*4) //nolint:gomnd // MPQ magic - if err := binary.Read(v.MPQ.file, binary.LittleEndian, &v.Positions); err != nil { + _, err = v.MPQData.file.Read(mpqBytes) + if err != nil { return err } - if v.Block.HasFlag(FileEncrypted) { - decrypt(v.Positions, v.Block.EncryptionSeed-1) + for i := range v.BlockPositions { + idx := i * 4 //nolint:gomnd // MPQ magic + v.BlockPositions[i] = binary.LittleEndian.Uint32(mpqBytes[idx : idx+4]) + } - blockPosSize := blockPositionCount << 2 //nolint:gomnd // MPQ magic - if v.Positions[0] != blockPosSize { + blockPosSize := blockPositionCount << 2 //nolint:gomnd // MPQ magic + + if v.BlockTableEntry.HasFlag(FileEncrypted) { + decrypt(v.BlockPositions, v.EncryptionSeed-1) + + if v.BlockPositions[0] != blockPosSize { + log.Println("Decryption of MPQ failed!") return errors.New("decryption of MPQ failed") } - if v.Positions[1] > v.Size+blockPosSize { + if v.BlockPositions[1] > v.BlockSize+blockPosSize { + log.Println("Decryption of MPQ failed!") return errors.New("decryption of MPQ failed") } } @@ -80,18 +98,16 @@ func (v *Stream) loadBlockOffsets() error { return nil } -func (v *Stream) Read(buffer []byte, offset, count uint32) (readTotal uint32, err error) { - if v.Block.HasFlag(FileSingleUnit) { +func (v *Stream) Read(buffer []byte, offset, count uint32) uint32 { + if v.BlockTableEntry.HasFlag(FileSingleUnit) { return v.readInternalSingleUnit(buffer, offset, count) } - var read uint32 - toRead := count + readTotal := uint32(0) + for toRead > 0 { - if read, err = v.readInternal(buffer, offset, toRead); err != nil { - return 0, err - } + read := v.readInternal(buffer, offset, toRead) if read == 0 { break @@ -102,153 +118,149 @@ func (v *Stream) Read(buffer []byte, offset, count uint32) (readTotal uint32, er toRead -= read } - return readTotal, nil + return readTotal } -func (v *Stream) readInternalSingleUnit(buffer []byte, offset, count uint32) (uint32, error) { - if len(v.Data) == 0 { - if err := v.loadSingleUnit(); err != nil { - return 0, err - } +func (v *Stream) readInternalSingleUnit(buffer []byte, offset, count uint32) uint32 { + if len(v.CurrentData) == 0 { + v.loadSingleUnit() } - return v.copy(buffer, offset, v.Position, count) + bytesToCopy := d2math.Min(uint32(len(v.CurrentData))-v.CurrentPosition, count) + + copy(buffer[offset:offset+bytesToCopy], v.CurrentData[v.CurrentPosition:v.CurrentPosition+bytesToCopy]) + + v.CurrentPosition += bytesToCopy + + return bytesToCopy } -func (v *Stream) readInternal(buffer []byte, offset, count uint32) (uint32, error) { - if err := v.bufferData(); err != nil { - return 0, err - } +func (v *Stream) readInternal(buffer []byte, offset, count uint32) uint32 { + v.bufferData() - localPosition := v.Position % v.Size + localPosition := v.CurrentPosition % v.BlockSize + bytesToCopy := d2math.MinInt32(int32(len(v.CurrentData))-int32(localPosition), int32(count)) - return v.copy(buffer, offset, localPosition, count) -} - -func (v *Stream) copy(buffer []byte, offset, pos, count uint32) (uint32, error) { - bytesToCopy := d2math.Min(uint32(len(v.Data))-pos, count) if bytesToCopy <= 0 { - return 0, nil + return 0 } - copy(buffer[offset:offset+bytesToCopy], v.Data[pos:pos+bytesToCopy]) - v.Position += bytesToCopy + copy(buffer[offset:offset+uint32(bytesToCopy)], v.CurrentData[localPosition:localPosition+uint32(bytesToCopy)]) - return bytesToCopy, nil + v.CurrentPosition += uint32(bytesToCopy) + + return uint32(bytesToCopy) } -func (v *Stream) bufferData() (err error) { - blockIndex := v.Position / v.Size +func (v *Stream) bufferData() { + requiredBlock := v.CurrentPosition / v.BlockSize - if blockIndex == v.Index { - return nil + if requiredBlock == v.CurrentBlockIndex { + return } - expectedLength := d2math.Min(v.Block.UncompressedFileSize-(blockIndex*v.Size), v.Size) - if v.Data, err = v.loadBlock(blockIndex, expectedLength); err != nil { - return err - } - - v.Index = blockIndex - - return nil + expectedLength := d2math.Min(v.BlockTableEntry.UncompressedFileSize-(requiredBlock*v.BlockSize), v.BlockSize) + v.CurrentData = v.loadBlock(requiredBlock, expectedLength) + v.CurrentBlockIndex = requiredBlock } -func (v *Stream) loadSingleUnit() (err error) { - if _, err = v.MPQ.file.Seek(int64(v.MPQ.header.HeaderSize), io.SeekStart); err != nil { - return err +func (v *Stream) loadSingleUnit() { + fileData := make([]byte, v.BlockSize) + + _, err := v.MPQData.file.Seek(int64(v.MPQData.data.HeaderSize), 0) + if err != nil { + log.Print(err) } - fileData := make([]byte, v.Size) - - if _, err = v.MPQ.file.Read(fileData); err != nil { - return err + _, err = v.MPQData.file.Read(fileData) + if err != nil { + log.Print(err) } - if v.Size == v.Block.UncompressedFileSize { - v.Data = fileData - return nil + if v.BlockSize == v.BlockTableEntry.UncompressedFileSize { + v.CurrentData = fileData + return } - v.Data, err = decompressMulti(fileData, v.Block.UncompressedFileSize) - - return err + v.CurrentData = decompressMulti(fileData, v.BlockTableEntry.UncompressedFileSize) } -func (v *Stream) loadBlock(blockIndex, expectedLength uint32) ([]byte, error) { +func (v *Stream) loadBlock(blockIndex, expectedLength uint32) []byte { var ( offset uint32 toRead uint32 ) - if v.Block.HasFlag(FileCompress) || v.Block.HasFlag(FileImplode) { - offset = v.Positions[blockIndex] - toRead = v.Positions[blockIndex+1] - offset + if v.BlockTableEntry.HasFlag(FileCompress) || v.BlockTableEntry.HasFlag(FileImplode) { + offset = v.BlockPositions[blockIndex] + toRead = v.BlockPositions[blockIndex+1] - offset } else { - offset = blockIndex * v.Size + offset = blockIndex * v.BlockSize toRead = expectedLength } - offset += v.Block.FilePosition + offset += v.BlockTableEntry.FilePosition data := make([]byte, toRead) - if _, err := v.MPQ.file.Seek(int64(offset), io.SeekStart); err != nil { - return []byte{}, err + _, err := v.MPQData.file.Seek(int64(offset), 0) + if err != nil { + log.Print(err) } - if _, err := v.MPQ.file.Read(data); err != nil { - return []byte{}, err + _, err = v.MPQData.file.Read(data) + if err != nil { + log.Print(err) } - if v.Block.HasFlag(FileEncrypted) && v.Block.UncompressedFileSize > 3 { - if v.Block.EncryptionSeed == 0 { - return []byte{}, errors.New("unable to determine encryption key") + if v.BlockTableEntry.HasFlag(FileEncrypted) && v.BlockTableEntry.UncompressedFileSize > 3 { + if v.EncryptionSeed == 0 { + panic("Unable to determine encryption key") } - decryptBytes(data, blockIndex+v.Block.EncryptionSeed) + decryptBytes(data, blockIndex+v.EncryptionSeed) } - if v.Block.HasFlag(FileCompress) && (toRead != expectedLength) { - if !v.Block.HasFlag(FileSingleUnit) { - return decompressMulti(data, expectedLength) + if v.BlockTableEntry.HasFlag(FileCompress) && (toRead != expectedLength) { + if !v.BlockTableEntry.HasFlag(FileSingleUnit) { + data = decompressMulti(data, expectedLength) + } else { + data = pkDecompress(data) } - - return pkDecompress(data) } - if v.Block.HasFlag(FileImplode) && (toRead != expectedLength) { - return pkDecompress(data) + if v.BlockTableEntry.HasFlag(FileImplode) && (toRead != expectedLength) { + data = pkDecompress(data) } - return data, nil + return data } //nolint:gomnd // Will fix enum values later -func decompressMulti(data []byte /*expectedLength*/, _ uint32) ([]byte, error) { +func decompressMulti(data []byte /*expectedLength*/, _ uint32) []byte { compressionType := data[0] switch compressionType { case 1: // Huffman - return []byte{}, errors.New("huffman decompression not supported") + panic("huffman decompression not supported") case 2: // ZLib/Deflate return deflate(data[1:]) case 8: // PKLib/Impode return pkDecompress(data[1:]) case 0x10: // BZip2 - return []byte{}, errors.New("bzip2 decompression not supported") + panic("bzip2 decompression not supported") case 0x80: // IMA ADPCM Stereo - return d2compression.WavDecompress(data[1:], 2), nil + return d2compression.WavDecompress(data[1:], 2) case 0x40: // IMA ADPCM Mono - return d2compression.WavDecompress(data[1:], 1), nil + return d2compression.WavDecompress(data[1:], 1) case 0x12: - return []byte{}, errors.New("lzma decompression not supported") + panic("lzma decompression not supported") // Combos case 0x22: // sparse then zlib - return []byte{}, errors.New("sparse decompression + deflate decompression not supported") + panic("sparse decompression + deflate decompression not supported") case 0x30: // sparse then bzip2 - return []byte{}, errors.New("sparse decompression + bzip2 decompression not supported") + panic("sparse decompression + bzip2 decompression not supported") case 0x41: sinput := d2compression.HuffmanDecompress(data[1:]) sinput = d2compression.WavDecompress(sinput, 1) @@ -256,68 +268,69 @@ func decompressMulti(data []byte /*expectedLength*/, _ uint32) ([]byte, error) { copy(tmp, sinput) - return tmp, nil + return tmp case 0x48: // byte[] result = PKDecompress(sinput, outputLength); // return MpqWavCompression.Decompress(new MemoryStream(result), 1); - return []byte{}, errors.New("pk + mpqwav decompression not supported") + panic("pk + mpqwav decompression not supported") case 0x81: sinput := d2compression.HuffmanDecompress(data[1:]) sinput = d2compression.WavDecompress(sinput, 2) tmp := make([]byte, len(sinput)) copy(tmp, sinput) - return tmp, nil + return tmp case 0x88: // byte[] result = PKDecompress(sinput, outputLength); // return MpqWavCompression.Decompress(new MemoryStream(result), 2); - return []byte{}, errors.New("pk + wav decompression not supported") + panic("pk + wav decompression not supported") + default: + panic(fmt.Sprintf("decompression not supported for unknown compression type %X", compressionType)) } - - return []byte{}, fmt.Errorf("decompression not supported for unknown compression type %X", compressionType) } -func deflate(data []byte) ([]byte, error) { +func deflate(data []byte) []byte { b := bytes.NewReader(data) - r, err := zlib.NewReader(b) + if err != nil { - return []byte{}, err + panic(err) } buffer := new(bytes.Buffer) _, err = buffer.ReadFrom(r) if err != nil { - return []byte{}, err + log.Panic(err) } err = r.Close() if err != nil { - return []byte{}, err + log.Panic(err) } - return buffer.Bytes(), nil + return buffer.Bytes() } -func pkDecompress(data []byte) ([]byte, error) { +func pkDecompress(data []byte) []byte { b := bytes.NewReader(data) - r, err := blast.NewReader(b) + if err != nil { - return []byte{}, err + panic(err) } buffer := new(bytes.Buffer) - if _, err = buffer.ReadFrom(r); err != nil { - return []byte{}, err + _, err = buffer.ReadFrom(r) + if err != nil { + panic(err) } err = r.Close() if err != nil { - return []byte{}, err + panic(err) } - return buffer.Bytes(), nil + return buffer.Bytes() } diff --git a/d2common/d2interface/archive.go b/d2common/d2interface/archive.go index 9de0f9ea..5e9d60da 100644 --- a/d2common/d2interface/archive.go +++ b/d2common/d2interface/archive.go @@ -8,9 +8,10 @@ type Archive interface { Path() string Contains(string) bool Size() uint32 - Close() error + Close() + FileExists(fileName string) bool ReadFile(fileName string) ([]byte, error) ReadFileStream(fileName string) (DataStream, error) ReadTextFile(fileName string) (string, error) - Listfile() ([]string, error) + GetFileList() ([]string, error) } diff --git a/d2common/d2interface/renderer.go b/d2common/d2interface/renderer.go index b2fd2716..b5b23142 100644 --- a/d2common/d2interface/renderer.go +++ b/d2common/d2interface/renderer.go @@ -1,5 +1,7 @@ package d2interface +import "github.com/hajimehoshi/ebiten/v2" + type renderCallback = func(Surface) error type updateCallback = func() error @@ -19,7 +21,7 @@ type Renderer interface { GetCursorPos() (int, int) CurrentFPS() float64 ShowPanicScreen(message string) - Print(target interface{}, str string) error - PrintAt(target interface{}, str string, x, y int) + Print(target *ebiten.Image, str string) error + PrintAt(target *ebiten.Image, str string, x, y int) GetWindowSize() (int, int) } diff --git a/d2common/d2interface/scene.go b/d2common/d2interface/scene.go index facffcec..72f3e2f8 100644 --- a/d2common/d2interface/scene.go +++ b/d2common/d2interface/scene.go @@ -2,14 +2,11 @@ package d2interface import ( "github.com/gravestench/akara" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" ) // Scene is an extension of akara.System type Scene interface { akara.SystemInitializer - State() d2enum.SceneState Key() string Booted() bool Paused() bool diff --git a/d2common/d2interface/terminal.go b/d2common/d2interface/terminal.go index be3bd31c..c6496437 100644 --- a/d2common/d2interface/terminal.go +++ b/d2common/d2interface/terminal.go @@ -13,17 +13,17 @@ type Terminal interface { OnKeyChars(event KeyCharsEvent) bool Render(surface Surface) error Execute(command string) error - Rawf(category d2enum.TermCategory, format string, params ...interface{}) - Printf(format string, params ...interface{}) - Infof(format string, params ...interface{}) - Warningf(format string, params ...interface{}) - Errorf(format string, params ...interface{}) - Clear() - Visible() bool + OutputRaw(text string, category d2enum.TermCategory) + Outputf(format string, params ...interface{}) + OutputInfof(format string, params ...interface{}) + OutputWarningf(format string, params ...interface{}) + OutputErrorf(format string, params ...interface{}) + OutputClear() + IsVisible() bool Hide() Show() - Bind(name, description string, arguments []string, fn func(args []string) error) error - Unbind(name ...string) error + BindAction(name, description string, action interface{}) error + UnbindAction(name string) error } // TerminalLogger is used tomake the Terminal write out diff --git a/d2common/d2loader/asset/types/source_types.go b/d2common/d2loader/asset/types/source_types.go index 32a2b402..563c3a7b 100644 --- a/d2common/d2loader/asset/types/source_types.go +++ b/d2common/d2loader/asset/types/source_types.go @@ -37,8 +37,7 @@ func Ext2SourceType(ext string) SourceType { func CheckSourceType(path string) SourceType { // on MacOS, the MPQ's from blizzard don't have file extensions // so we just attempt to init the file as an mpq - if mpq, err := d2mpq.New(path); err == nil { - _ = mpq.Close() + if _, err := d2mpq.Load(path); err == nil { return AssetSourceMPQ } diff --git a/d2common/d2loader/mpq/source.go b/d2common/d2loader/mpq/source.go index d5b4236d..4a326a42 100644 --- a/d2common/d2loader/mpq/source.go +++ b/d2common/d2loader/mpq/source.go @@ -14,7 +14,7 @@ var _ asset.Source = &Source{} // NewSource creates a new MPQ Source func NewSource(sourcePath string) (asset.Source, error) { - loaded, err := d2mpq.FromFile(sourcePath) + loaded, err := d2mpq.Load(sourcePath) if err != nil { return nil, err } diff --git a/d2common/d2resource/languages_map.go b/d2common/d2resource/languages_map.go index 511058f8..44045a17 100644 --- a/d2common/d2resource/languages_map.go +++ b/d2common/d2resource/languages_map.go @@ -62,7 +62,7 @@ key | value key | value So, GetLabelModifier returns value of offset in locale languages table */ // some of values need to be set up. For now values with "checked" comment -// was tested and works fine. +// was tested and works fine in main menu. func GetLabelModifier(language string) int { modifiers := map[string]int{ "ENG": 0, // (English) // checked @@ -70,7 +70,7 @@ func GetLabelModifier(language string) int { "DEU": 0, // (German) // checked "FRA": 0, // (French) "POR": 0, // (Portuguese) - "ITA": 0, // (Italian) // checked + "ITA": 0, // (Italian) "JPN": 0, // (Japanese) "KOR": 0, // (Korean) "SIN": 0, // diff --git a/d2common/d2resource/resource_paths.go b/d2common/d2resource/resource_paths.go index bbd88dd7..a18a196f 100644 --- a/d2common/d2resource/resource_paths.go +++ b/d2common/d2resource/resource_paths.go @@ -193,7 +193,6 @@ const ( QuestLogQDescrBtn = "/data/global/ui/MENU/questlast.dc6" QuestLogSocket = "/data/global/ui/MENU/questsockets.dc6" QuestLogAQuestAnimation = "/data/global/ui/MENU/a%dq%d.dc6" - QuestLogDoneSfx = "cursor/questdone.wav" // --- Mouse Pointers --- @@ -244,18 +243,16 @@ const ( MinipanelSmall = "/data/global/ui/PANEL/minipanel_s.dc6" MinipanelButton = "/data/global/ui/PANEL/minipanelbtn.DC6" - Frame = "/data/global/ui/PANEL/800borderframe.dc6" - InventoryCharacterPanel = "/data/global/ui/PANEL/invchar6.DC6" - HeroStatsPanelStatsPoints = "/data/global/ui/PANEL/skillpoints.dc6" - HeroStatsPanelSocket = "/data/global/ui/PANEL/levelsocket.dc6" - InventoryWeaponsTab = "/data/global/ui/PANEL/invchar6Tab.DC6" - SkillsPanelAmazon = "/data/global/ui/SPELLS/skltree_a_back.DC6" - SkillsPanelBarbarian = "/data/global/ui/SPELLS/skltree_b_back.DC6" - SkillsPanelDruid = "/data/global/ui/SPELLS/skltree_d_back.DC6" - SkillsPanelAssassin = "/data/global/ui/SPELLS/skltree_i_back.DC6" - SkillsPanelNecromancer = "/data/global/ui/SPELLS/skltree_n_back.DC6" - SkillsPanelPaladin = "/data/global/ui/SPELLS/skltree_p_back.DC6" - SkillsPanelSorcerer = "/data/global/ui/SPELLS/skltree_s_back.DC6" + Frame = "/data/global/ui/PANEL/800borderframe.dc6" + InventoryCharacterPanel = "/data/global/ui/PANEL/invchar6.DC6" + InventoryWeaponsTab = "/data/global/ui/PANEL/invchar6Tab.DC6" + SkillsPanelAmazon = "/data/global/ui/SPELLS/skltree_a_back.DC6" + SkillsPanelBarbarian = "/data/global/ui/SPELLS/skltree_b_back.DC6" + SkillsPanelDruid = "/data/global/ui/SPELLS/skltree_d_back.DC6" + SkillsPanelAssassin = "/data/global/ui/SPELLS/skltree_i_back.DC6" + SkillsPanelNecromancer = "/data/global/ui/SPELLS/skltree_n_back.DC6" + SkillsPanelPaladin = "/data/global/ui/SPELLS/skltree_p_back.DC6" + SkillsPanelSorcerer = "/data/global/ui/SPELLS/skltree_s_back.DC6" GenericSkills = "/data/global/ui/SPELLS/Skillicon.DC6" AmazonSkills = "/data/global/ui/SPELLS/AmSkillicon.DC6" diff --git a/d2common/d2sprite/sprite.go b/d2common/d2sprite/sprite.go index 2ff8438f..4593923c 100644 --- a/d2common/d2sprite/sprite.go +++ b/d2common/d2sprite/sprite.go @@ -332,7 +332,7 @@ func (a *Sprite) GetDirection() int { // SetCurrentFrame sets sprite at a specific frame func (a *Sprite) SetCurrentFrame(frameIndex int) error { - if frameIndex >= a.GetFrameCount() || frameIndex < 0 { + if frameIndex >= a.GetFrameCount() { return errors.New("invalid frame index") } diff --git a/d2common/d2util/debug_print.go b/d2common/d2util/debug_print.go index 489f28ae..daa20386 100644 --- a/d2common/d2util/debug_print.go +++ b/d2common/d2util/debug_print.go @@ -37,16 +37,16 @@ type GlyphPrinter struct { // Basic Latin and C1 Controls and Latin-1 Supplement. // // DebugPrint always returns nil as of 1.5.0-alpha. -func (p *GlyphPrinter) Print(target interface{}, str string) error { - p.PrintAt(target.(*ebiten.Image), str, 0, 0) +func (p *GlyphPrinter) Print(target *ebiten.Image, str string) error { + p.PrintAt(target, str, 0, 0) return nil } // PrintAt draws the string str on the image at (x, y) position. // The available runes are in U+0000 to U+00FF, which is C0 Controls and // Basic Latin and C1 Controls and Latin-1 Supplement. -func (p *GlyphPrinter) PrintAt(target interface{}, str string, x, y int) { - p.drawDebugText(target.(*ebiten.Image), str, x, y, false) +func (p *GlyphPrinter) PrintAt(target *ebiten.Image, str string, x, y int) { + p.drawDebugText(target, str, x, y, false) } func (p *GlyphPrinter) drawDebugText(target *ebiten.Image, str string, ox, oy int, shadow bool) { diff --git a/d2common/d2util/logger.go b/d2common/d2util/logger.go index 00e60db0..0ed3fd21 100644 --- a/d2common/d2util/logger.go +++ b/d2common/d2util/logger.go @@ -6,7 +6,6 @@ import ( "log" "os" "runtime" - "sync" ) // LogLevel determines how verbose the logging is (higher is more verbose) @@ -52,7 +51,6 @@ func NewLogger() *Logger { l := &Logger{ level: LogLevelDefault, colorEnabled: true, - mutex: sync.Mutex{}, } l.Writer = log.Writer() @@ -66,7 +64,6 @@ type Logger struct { io.Writer level LogLevel colorEnabled bool - mutex sync.Mutex } // SetPrefix sets a prefix for the message. @@ -74,17 +71,11 @@ type Logger struct { // logger.SetPrefix("XYZ") // logger.Debug("ABC") will print "[XYZ] [DEBUG] ABC" func (l *Logger) SetPrefix(s string) { - l.mutex.Lock() - defer l.mutex.Unlock() - l.prefix = s } // SetLevel sets the log level func (l *Logger) SetLevel(level LogLevel) { - l.mutex.Lock() - defer l.mutex.Unlock() - if level == LogLevelUnspecified { level = LogLevelDefault } @@ -94,9 +85,6 @@ func (l *Logger) SetLevel(level LogLevel) { // SetColorEnabled adds color escape-sequences to the logging output func (l *Logger) SetColorEnabled(b bool) { - l.mutex.Lock() - defer l.mutex.Unlock() - if runtime.GOOS == "windows" { b = false } @@ -106,6 +94,10 @@ func (l *Logger) SetColorEnabled(b bool) { // Info logs an info message func (l *Logger) Info(msg string) { + if l == nil || l.level < LogLevelInfo { + return + } + go l.print(LogLevelInfo, msg) } @@ -116,6 +108,10 @@ func (l *Logger) Infof(fmtMsg string, args ...interface{}) { // Warning logs a warning message func (l *Logger) Warning(msg string) { + if l == nil || l.level < LogLevelWarning { + return + } + go l.print(LogLevelWarning, msg) } @@ -126,6 +122,10 @@ func (l *Logger) Warningf(fmtMsg string, args ...interface{}) { // Error logs an error message func (l *Logger) Error(msg string) { + if l == nil || l.level < LogLevelError { + return + } + go l.print(LogLevelError, msg) } @@ -136,6 +136,10 @@ func (l *Logger) Errorf(fmtMsg string, args ...interface{}) { // Fatal logs an fatal error message and exits programm func (l *Logger) Fatal(msg string) { + if l == nil || l.level < LogLevelFatal { + return + } + go l.print(LogLevelFatal, msg) os.Exit(1) } @@ -147,6 +151,10 @@ func (l *Logger) Fatalf(fmtMsg string, args ...interface{}) { // Debug logs a debug message func (l *Logger) Debug(msg string) { + if l == nil || l.level < LogLevelDebug { + return + } + go l.print(LogLevelDebug, msg) } @@ -156,10 +164,7 @@ func (l *Logger) Debugf(fmtMsg string, args ...interface{}) { } func (l *Logger) print(level LogLevel, msg string) { - l.mutex.Lock() - defer l.mutex.Unlock() - - if l.level < level { + if l == nil || l.level < level { return } diff --git a/d2core/d2asset/animation.go b/d2core/d2asset/animation.go index b03aae64..94fe94af 100644 --- a/d2core/d2asset/animation.go +++ b/d2core/d2asset/animation.go @@ -323,7 +323,7 @@ func (a *Animation) GetDirection() int { // SetCurrentFrame sets animation at a specific frame func (a *Animation) SetCurrentFrame(frameIndex int) error { - if frameIndex >= a.GetFrameCount() || frameIndex < 0 { + if frameIndex >= a.GetFrameCount() { return errors.New("invalid frame index") } diff --git a/d2core/d2asset/asset_manager.go b/d2core/d2asset/asset_manager.go index 359befa0..0146170f 100644 --- a/d2core/d2asset/asset_manager.go +++ b/d2core/d2asset/asset_manager.go @@ -3,7 +3,6 @@ package d2asset import ( "fmt" "image/color" - "strconv" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" @@ -410,70 +409,43 @@ func (am *AssetManager) loadDCC(path string, // BindTerminalCommands binds the in-game terminal comands for the asset manager. func (am *AssetManager) BindTerminalCommands(term d2interface.Terminal) error { - if err := term.Bind("assetspam", "display verbose asset manager logs", nil, am.commandAssetSpam(term)); err != nil { - return err - } - - if err := term.Bind("assetstat", "display asset manager cache statistics", nil, am.commandAssetStat(term)); err != nil { - return err - } - - if err := term.Bind("assetclear", "clear asset manager cache", nil, am.commandAssetClear); err != nil { - return err - } - - return nil -} - -// UnbindTerminalCommands unbinds commands from the terminal -func (am *AssetManager) UnbindTerminalCommands(term d2interface.Terminal) error { - return term.Unbind("assetspam", "assetstat", "assetclear") -} - -func (am *AssetManager) commandAssetSpam(term d2interface.Terminal) func([]string) error { - return func(args []string) error { - verbose, err := strconv.ParseBool(args[0]) - if err != nil { - term.Errorf("asset manager verbose invalid argument") - return nil - } - + if err := term.BindAction("assetspam", "display verbose asset manager logs", func(verbose bool) { if verbose { - term.Infof("asset manager verbose logging enabled") + term.OutputInfof("asset manager verbose logging enabled") } else { - term.Infof("asset manager verbose logging disabled") + term.OutputInfof("asset manager verbose logging disabled") } am.palettes.SetVerbose(verbose) am.fonts.SetVerbose(verbose) am.transforms.SetVerbose(verbose) am.animations.SetVerbose(verbose) - - return nil + }); err != nil { + return err } -} -func (am *AssetManager) commandAssetStat(term d2interface.Terminal) func([]string) error { - return func([]string) error { + if err := term.BindAction("assetstat", "display asset manager cache statistics", func() { var cacheStatistics = func(c d2interface.Cache) float64 { const percent = 100.0 return float64(c.GetWeight()) / float64(c.GetBudget()) * percent } - term.Infof("palette cache: %f", cacheStatistics(am.palettes)) - term.Infof("palette transform cache: %f", cacheStatistics(am.transforms)) - term.Infof("Animation cache: %f", cacheStatistics(am.animations)) - term.Infof("font cache: %f", cacheStatistics(am.fonts)) - - return nil + term.OutputInfof("palette cache: %f", cacheStatistics(am.palettes)) + term.OutputInfof("palette transform cache: %f", cacheStatistics(am.transforms)) + term.OutputInfof("Animation cache: %f", cacheStatistics(am.animations)) + term.OutputInfof("font cache: %f", cacheStatistics(am.fonts)) + }); err != nil { + return err } -} -func (am *AssetManager) commandAssetClear([]string) error { - am.palettes.Clear() - am.transforms.Clear() - am.animations.Clear() - am.fonts.Clear() + if err := term.BindAction("assetclear", "clear asset manager cache", func() { + am.palettes.Clear() + am.transforms.Clear() + am.animations.Clear() + am.fonts.Clear() + }); err != nil { + return err + } return nil } diff --git a/d2core/d2asset/d2asset.go b/d2core/d2asset/d2asset.go index 0e721554..9abd6605 100644 --- a/d2core/d2asset/d2asset.go +++ b/d2core/d2asset/d2asset.go @@ -9,23 +9,19 @@ import ( ) // NewAssetManager creates and assigns all necessary dependencies for the AssetManager top-level functions to work correctly -func NewAssetManager(logLevel d2util.LogLevel) (*AssetManager, error) { - loader, err := d2loader.NewLoader(logLevel) +func NewAssetManager() (*AssetManager, error) { + loader, err := d2loader.NewLoader(d2util.LogLevelDefault) if err != nil { return nil, err } - records, err := d2records.NewRecordManager(logLevel) + records, err := d2records.NewRecordManager(d2util.LogLevelDebug) if err != nil { return nil, err } - logger := d2util.NewLogger() - logger.SetPrefix(logPrefix) - logger.SetLevel(logLevel) - manager := &AssetManager{ - Logger: logger, + Logger: d2util.NewLogger(), Loader: loader, tables: make([]d2tbl.TextDictionary, 0), animations: d2cache.CreateCache(animationBudget), @@ -35,5 +31,7 @@ func NewAssetManager(logLevel d2util.LogLevel) (*AssetManager, error) { Records: records, } + manager.SetPrefix(logPrefix) + return manager, err } diff --git a/d2core/d2asset/dcc_animation.go b/d2core/d2asset/dcc_animation.go index 326f8124..9961b5ea 100644 --- a/d2core/d2asset/dcc_animation.go +++ b/d2core/d2asset/dcc_animation.go @@ -2,6 +2,9 @@ package d2asset import ( "errors" + "math" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" @@ -129,11 +132,24 @@ func (a *DCCAnimation) decodeDirection(directionIndex int) error { func (a *DCCAnimation) decodeFrame(directionIndex int) animationFrame { dccDirection := a.dcc.Directions[directionIndex] + minX, minY := math.MaxInt32, math.MaxInt32 + maxX, maxY := math.MinInt32, math.MinInt32 + + for _, dccFrame := range dccDirection.Frames { + minX = d2math.MinInt(minX, dccFrame.Box.Left) + minY = d2math.MinInt(minY, dccFrame.Box.Top) + maxX = d2math.MaxInt(maxX, dccFrame.Box.Right()) + maxY = d2math.MaxInt(maxY, dccFrame.Box.Bottom()) + } + + frameWidth := maxX - minX + frameHeight := maxY - minY + frame := animationFrame{ - width: dccDirection.Box.Width, - height: dccDirection.Box.Height, - offsetX: dccDirection.Box.Left, - offsetY: dccDirection.Box.Top, + width: frameWidth, + height: frameHeight, + offsetX: minX, + offsetY: minY, decoded: true, } diff --git a/d2core/d2audio/sound_engine.go b/d2core/d2audio/sound_engine.go index 4ef91f57..f811c1f8 100644 --- a/d2core/d2audio/sound_engine.go +++ b/d2core/d2audio/sound_engine.go @@ -3,7 +3,6 @@ package d2audio import ( "fmt" "math/rand" - "strconv" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" @@ -32,7 +31,7 @@ const originalFPS float64 = 25 // A Sound that can be started and stopped type Sound struct { effect d2interface.SoundEffect - entry *d2records.SoundDetailRecord + entry *d2records.SoundDetailsRecord volume float64 vTarget float64 vRate float64 @@ -74,7 +73,7 @@ func (s *Sound) SetPan(pan float64) { // Play the sound func (s *Sound) Play() { - s.Info("starting sound " + s.entry.Handle) + s.Info("starting sound" + s.entry.Handle) s.effect.Play() if s.entry.FadeIn != 0 { @@ -104,11 +103,6 @@ func (s *Sound) Stop() { } } -// String returns the sound filename -func (s *Sound) String() string { - return s.entry.Handle -} - // SoundEngine provides functions for playing sounds type SoundEngine struct { asset *d2asset.AssetManager @@ -134,25 +128,43 @@ func NewSoundEngine(provider d2interface.AudioProvider, r.Logger.SetPrefix(logPrefix) r.Logger.SetLevel(l) - if err := term.Bind("playsoundid", "plays the sound for a given id", []string{"id"}, r.commandPlaySoundID); err != nil { + err := term.BindAction("playsoundid", "plays the sound for a given id", func(id int) { + r.PlaySoundID(id) + }) + if err != nil { r.Error(err.Error()) return nil } - if err := term.Bind("playsound", "plays the sound for a given handle string", []string{"name"}, r.commandPlaySound); err != nil { + err = term.BindAction("playsound", "plays the sound for a given handle string", func(handle string) { + r.PlaySoundHandle(handle) + }) + if err != nil { r.Error(err.Error()) return nil } - if err := term.Bind("activesounds", "list currently active sounds", nil, r.commandActiveSounds); err != nil { - r.Error(err.Error()) - return nil - } + err = term.BindAction("activesounds", "list currently active sounds", func() { + for s := range r.sounds { + if err != nil { + r.Error(err.Error()) + return + } - if err := term.Bind("killsounds", "kill active sounds", nil, r.commandKillSounds); err != nil { - r.Error(err.Error()) - return nil - } + r.Info(fmt.Sprint(s)) + } + }) + + err = term.BindAction("killsounds", "kill active sounds", func() { + for s := range r.sounds { + if err != nil { + r.Error(err.Error()) + return + } + + s.Stop() + } + }) return &r } @@ -182,11 +194,6 @@ func (s *SoundEngine) Advance(elapsed float64) { } } -// UnbindTerminalCommands unbinds commands from the terminal -func (s *SoundEngine) UnbindTerminalCommands(term d2interface.Terminal) error { - return term.Unbind("playsoundid", "playsound", "activesounds", "killsounds") -} - // Reset stop all sounds and reset state func (s *SoundEngine) Reset() { for snd := range s.sounds { @@ -235,35 +242,3 @@ func (s *SoundEngine) PlaySoundHandle(handle string) *Sound { sound := s.asset.Records.Sound.Details[handle].Index return s.PlaySoundID(sound) } - -func (s *SoundEngine) commandPlaySoundID(args []string) error { - id, err := strconv.Atoi(args[0]) - if err != nil { - return fmt.Errorf("invalid argument") - } - - s.PlaySoundID(id) - - return nil -} - -func (s *SoundEngine) commandPlaySound(args []string) error { - s.PlaySoundHandle(args[0]) - - return nil -} - -func (s *SoundEngine) commandActiveSounds([]string) error { - for sound := range s.sounds { - s.Info(sound.String()) - } - - return nil -} -func (s *SoundEngine) commandKillSounds([]string) error { - for sound := range s.sounds { - sound.Stop() - } - - return nil -} diff --git a/d2core/d2components/command_registration.go b/d2core/d2components/command_registration.go index e3ee72f0..28016cf3 100644 --- a/d2core/d2components/command_registration.go +++ b/d2core/d2components/command_registration.go @@ -13,8 +13,7 @@ type CommandRegistration struct { Enabled bool Name string Description string - Args []string - Callback func(args []string) error + Callback interface{} } // New creates a new CommandRegistration. By default, IsCommandRegistration is false. diff --git a/d2core/d2config/d2config.go b/d2core/d2config/d2config.go index de17fc27..3d57e753 100644 --- a/d2core/d2config/d2config.go +++ b/d2core/d2config/d2config.go @@ -5,6 +5,8 @@ import ( "os" "path" "path/filepath" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" ) // Configuration defines the configuration for the engine, loaded from config.json @@ -19,6 +21,7 @@ type Configuration struct { RunInBackground bool VsyncEnabled bool Backend string + LogLevel d2util.LogLevel path string } diff --git a/d2core/d2config/defaults.go b/d2core/d2config/defaults.go index 7e8a539b..a717005d 100644 --- a/d2core/d2config/defaults.go +++ b/d2core/d2config/defaults.go @@ -4,6 +4,8 @@ import ( "os/user" "path" "runtime" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" ) // DefaultConfig creates and returns a default configuration @@ -35,7 +37,8 @@ func DefaultConfig() *Configuration { "d2video.mpq", "d2speech.mpq", }, - path: DefaultConfigPath(), + LogLevel: d2util.LogLevelDefault, + path: DefaultConfigPath(), } switch runtime.GOOS { diff --git a/d2core/d2gui/box.go b/d2core/d2gui/box.go index 63f42394..446d5e10 100644 --- a/d2core/d2gui/box.go +++ b/d2core/d2gui/box.go @@ -74,7 +74,8 @@ func NewBox( renderer d2interface.Renderer, ui *d2ui.UIManager, contentLayout *Layout, - width, height, x, y int, + width, height int, + x, y int, l d2util.LogLevel, title string, ) *Box { diff --git a/d2core/d2gui/common.go b/d2core/d2gui/common.go index 02dd2094..f81bb4e6 100644 --- a/d2core/d2gui/common.go +++ b/d2core/d2gui/common.go @@ -1,6 +1,8 @@ package d2gui import ( + "image/color" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math" ) @@ -35,3 +37,28 @@ func renderSegmented(animation d2interface.Animation, segmentsX, segmentsY, fram func half(n int) int { return n / 2 } + +func rgbaColor(rgba uint32) color.RGBA { + result := color.RGBA{} + a, b, g, r := 0, 1, 2, 3 + byteWidth := 8 + byteMask := 0xff + + for idx := 0; idx < 4; idx++ { + shift := idx * byteWidth + component := uint8(rgba>>shift) & uint8(byteMask) + + switch idx { + case a: + result.A = component + case b: + result.B = component + case g: + result.G = component + case r: + result.R = component + } + } + + return result +} diff --git a/d2core/d2gui/layout.go b/d2core/d2gui/layout.go index 44c7f7a0..43125acf 100644 --- a/d2core/d2gui/layout.go +++ b/d2core/d2gui/layout.go @@ -248,16 +248,16 @@ func (l *Layout) renderEntryDebug(entry *layoutEntry, target d2interface.Surface target.PushTranslation(entry.x, entry.y) defer target.Pop() - drawColor := d2util.Color(white) + drawColor := rgbaColor(white) switch entry.widget.(type) { case *Layout: - drawColor = d2util.Color(magenta) + drawColor = rgbaColor(magenta) case *SpacerStatic, *SpacerDynamic: - drawColor = d2util.Color(grey2) + drawColor = rgbaColor(grey2) case *Label: - drawColor = d2util.Color(green) + drawColor = rgbaColor(green) case *Button: - drawColor = d2util.Color(yellow) + drawColor = rgbaColor(yellow) } target.DrawLine(entry.width, 0, drawColor) @@ -487,7 +487,7 @@ func (l *Layout) createButton(renderer d2interface.Renderer, text string, return nil, loadErr } - textColor := d2util.Color(grey) + textColor := rgbaColor(grey) textWidth, textHeight := font.GetTextMetrics(text) textX := half(buttonWidth) - half(textWidth) textY := half(buttonHeight) - half(textHeight) + config.textOffset diff --git a/d2core/d2gui/layout_scrollbar.go b/d2core/d2gui/layout_scrollbar.go index 38bf2551..151a2a34 100644 --- a/d2core/d2gui/layout_scrollbar.go +++ b/d2core/d2gui/layout_scrollbar.go @@ -64,7 +64,10 @@ const ( ) // NewLayoutScrollbar attaches a scrollbar to the parentLayout to control the targetLayout -func NewLayoutScrollbar(parentLayout, targetLayout *Layout) *LayoutScrollbar { +func NewLayoutScrollbar( + parentLayout *Layout, + targetLayout *Layout, +) *LayoutScrollbar { parentW, parentH := parentLayout.GetSize() _, targetH := targetLayout.GetSize() gutterHeight := parentH - (2 * textSliderPartHeight) diff --git a/d2core/d2hero/hero_state_factory.go b/d2core/d2hero/hero_state_factory.go index 10ad1bd2..43ba461e 100644 --- a/d2core/d2hero/hero_state_factory.go +++ b/d2core/d2hero/hero_state_factory.go @@ -110,7 +110,7 @@ func (f *HeroStateFactory) GetAllHeroStates() ([]*HeroState, error) { } // CreateHeroSkillsState will assemble the hero skills from the class stats record. -func (f *HeroStateFactory) CreateHeroSkillsState(classStats *d2records.CharStatRecord, heroType d2enum.Hero) (map[int]*HeroSkill, error) { +func (f *HeroStateFactory) CreateHeroSkillsState(classStats *d2records.CharStatsRecord, heroType d2enum.Hero) (map[int]*HeroSkill, error) { baseSkills := map[int]*HeroSkill{} for idx := range classStats.BaseSkill { diff --git a/d2core/d2hero/hero_stats_state.go b/d2core/d2hero/hero_stats_state.go index 4a3f1e99..04ad52a4 100644 --- a/d2core/d2hero/hero_stats_state.go +++ b/d2core/d2hero/hero_stats_state.go @@ -10,27 +10,32 @@ type HeroStatsState struct { Level int `json:"level"` Experience int `json:"experience"` - Strength int `json:"strength"` - Energy int `json:"energy"` - Dexterity int `json:"dexterity"` Vitality int `json:"vitality"` - // there are stats and skills points remaining to add. - StatsPoints int `json:"statsPoints"` - SkillPoints int `json:"skillPoints"` + Energy int `json:"energy"` + Strength int `json:"strength"` + Dexterity int `json:"dexterity"` - Health int `json:"health"` - MaxHealth int `json:"maxHealth"` - Mana int `json:"mana"` - MaxMana int `json:"maxMana"` - Stamina float64 `json:"-"` // only MaxStamina is saved, Stamina gets reset on entering world - MaxStamina int `json:"maxStamina"` + AttackRating int `json:"attackRating"` + DefenseRating int `json:"defenseRating"` + + MaxStamina int `json:"maxStamina"` + Health int `json:"health"` + MaxHealth int `json:"maxHealth"` + Mana int `json:"mana"` + MaxMana int `json:"maxMana"` + + FireResistance int `json:"fireResistance"` + ColdResistance int `json:"coldResistance"` + LightningResistance int `json:"lightningResistance"` + PoisonResistance int `json:"poisonResistance"` // values which are not saved/loaded(computed) - NextLevelExp int `json:"-"` + Stamina float64 `json:"-"` // only MaxStamina is saved, Stamina gets reset on entering world + NextLevelExp int `json:"-"` } // CreateHeroStatsState generates a running state from a hero stats. -func (f *HeroStateFactory) CreateHeroStatsState(heroClass d2enum.Hero, classStats *d2records.CharStatRecord) *HeroStatsState { +func (f *HeroStateFactory) CreateHeroStatsState(heroClass d2enum.Hero, classStats *d2records.CharStatsRecord) *HeroStatsState { result := HeroStatsState{ Level: 1, Experience: 0, @@ -39,8 +44,6 @@ func (f *HeroStateFactory) CreateHeroStatsState(heroClass d2enum.Hero, classStat Dexterity: classStats.InitDex, Vitality: classStats.InitVit, Energy: classStats.InitEne, - StatsPoints: 0, - SkillPoints: 0, MaxHealth: classStats.InitVit * classStats.LifePerVit, MaxMana: classStats.InitEne * classStats.ManaPerEne, diff --git a/d2core/d2inventory/inventory_item_factory.go b/d2core/d2inventory/inventory_item_factory.go index 7cf1d273..fb081fcc 100644 --- a/d2core/d2inventory/inventory_item_factory.go +++ b/d2core/d2inventory/inventory_item_factory.go @@ -136,6 +136,7 @@ func (f *InventoryItemFactory) GetMiscItemByCode(code string) (*InventoryItemMis // GetWeaponItemByCode returns the weapon item for the given code func (f *InventoryItemFactory) GetWeaponItemByCode(code string) (*InventoryItemWeapon, error) { + // https://github.com/OpenDiablo2/OpenDiablo2/issues/796 result := f.asset.Records.Item.Weapons[code] if result == nil { return nil, fmt.Errorf("could not find weapon entry for code '%s'", code) diff --git a/d2core/d2item/diablo2item/item_property_test.go b/d2core/d2item/diablo2item/item_property_test.go index bb98f697..ec932ff5 100644 --- a/d2core/d2item/diablo2item/item_property_test.go +++ b/d2core/d2item/diablo2item/item_property_test.go @@ -277,7 +277,7 @@ var itemStatCosts = map[string]*d2records.ItemStatCostRecord{ } // nolint:gochecknoglobals // just a test -var charStats = map[d2enum.Hero]*d2records.CharStatRecord{ +var charStats = map[d2enum.Hero]*d2records.CharStatsRecord{ d2enum.HeroPaladin: { Class: d2enum.HeroPaladin, SkillStrAll: "to Paladin Skill Levels", @@ -297,7 +297,7 @@ var skillDetails = map[int]*d2records.SkillRecord{ } // nolint:gochecknoglobals // just a test -var monStats = map[string]*d2records.MonStatRecord{ +var monStats = map[string]*d2records.MonStatsRecord{ "Specter": {NameString: "Specter", ID: 40}, } diff --git a/d2core/d2map/d2mapengine/engine.go b/d2core/d2map/d2mapengine/engine.go index 97ae289d..4ab2e17f 100644 --- a/d2core/d2map/d2mapengine/engine.go +++ b/d2core/d2map/d2mapengine/engine.go @@ -256,7 +256,7 @@ func (m *MapEngine) RemoveEntity(entity d2interface.MapEntity) { // GetTiles returns a slice of all tiles matching the given style, // sequence and tileType. func (m *MapEngine) GetTiles(style, sequence int, tileType d2enum.TileType) []d2dt1.Tile { - tiles := make([]d2dt1.Tile, 0) + tiles := make([]d2dt1.Tile, 0, len(m.dt1TileData)) for idx := range m.dt1TileData { if m.dt1TileData[idx].Style != int32(style) || m.dt1TileData[idx].Sequence != int32(sequence) || diff --git a/d2core/d2map/d2mapentity/factory.go b/d2core/d2map/d2mapentity/factory.go index 181b05c2..3d42c753 100644 --- a/d2core/d2map/d2mapentity/factory.go +++ b/d2core/d2map/d2mapentity/factory.go @@ -64,7 +64,7 @@ func NewAnimatedEntity(x, y int, animation d2interface.Animation) *AnimatedEntit // NewPlayer creates a new player entity and returns a pointer to it. func (f *MapEntityFactory) NewPlayer(id, name string, x, y, direction int, heroType d2enum.Hero, stats *d2hero.HeroStatsState, skills map[int]*d2hero.HeroSkill, equipment *d2inventory.CharacterEquipment, - leftSkill, rightSkill, gold int) *Player { + leftSkill, rightSkill int, gold int) *Player { layerEquipment := &[d2enum.CompositeTypeMax]string{ d2enum.CompositeTypeHead: equipment.Head.GetArmorClass(), d2enum.CompositeTypeTorso: equipment.Torso.GetArmorClass(), @@ -180,7 +180,7 @@ func (f *MapEntityFactory) NewItem(x, y int, codes ...string) (*Item, error) { } // NewNPC creates a new NPC and returns a pointer to it. -func (f *MapEntityFactory) NewNPC(x, y int, monstat *d2records.MonStatRecord, direction int) (*NPC, error) { +func (f *MapEntityFactory) NewNPC(x, y int, monstat *d2records.MonStatsRecord, direction int) (*NPC, error) { // https://github.com/OpenDiablo2/OpenDiablo2/issues/803 result := &NPC{ mapEntity: newMapEntity(x, y), @@ -237,6 +237,7 @@ func (f *MapEntityFactory) NewCastOverlay(x, y int, overlayRecord *d2records.Ove return nil, err } + // https://github.com/OpenDiablo2/OpenDiablo2/issues/767 animation.Rewind() animation.ResetPlayedCount() @@ -262,7 +263,7 @@ func (f *MapEntityFactory) NewCastOverlay(x, y int, overlayRecord *d2records.Ove } // NewObject creates an instance of AnimatedComposite -func (f *MapEntityFactory) NewObject(x, y int, objectRec *d2records.ObjectDetailRecord, +func (f *MapEntityFactory) NewObject(x, y int, objectRec *d2records.ObjectDetailsRecord, palettePath string) (*Object, error) { locX, locY := float64(x), float64(y) entity := &Object{ diff --git a/d2core/d2map/d2mapentity/npc.go b/d2core/d2map/d2mapentity/npc.go index 66284092..663401e0 100644 --- a/d2core/d2map/d2mapentity/npc.go +++ b/d2core/d2map/d2mapentity/npc.go @@ -22,8 +22,8 @@ type NPC struct { action int path int repetitions int - monstatRecord *d2records.MonStatRecord - monstatEx *d2records.MonStat2Record + monstatRecord *d2records.MonStatsRecord + monstatEx *d2records.MonStats2Record HasPaths bool isDone bool } diff --git a/d2core/d2map/d2mapentity/object.go b/d2core/d2map/d2mapentity/object.go index 314e9e04..71dbaf1e 100644 --- a/d2core/d2map/d2mapentity/object.go +++ b/d2core/d2map/d2mapentity/object.go @@ -20,7 +20,7 @@ type Object struct { composite *d2asset.Composite highlight bool // nameLabel d2ui.Label - objectRecord *d2records.ObjectDetailRecord + objectRecord *d2records.ObjectDetailsRecord drawLayer int name string } diff --git a/d2core/d2map/d2maprenderer/renderer.go b/d2core/d2map/d2maprenderer/renderer.go index e4abd367..ab0be25a 100644 --- a/d2core/d2map/d2maprenderer/renderer.go +++ b/d2core/d2map/d2maprenderer/renderer.go @@ -2,10 +2,8 @@ package d2maprenderer import ( "errors" - "fmt" "image/color" "math" - "strconv" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1" @@ -88,11 +86,20 @@ func CreateMapRenderer(asset *d2asset.AssetManager, renderer d2interface.Rendere result.Camera.position = &startPosition result.viewport.SetCamera(&result.Camera) - if err := term.Bind("mapdebugvis", "set map debug visualization level", nil, result.commandMapDebugVis); err != nil { + var err error + err = term.BindAction("mapdebugvis", "set map debug visualization level", func(level int) { + result.mapDebugVisLevel = level + }) + + if err != nil { result.Errorf("could not bind the mapdebugvis action, err: %v", err) } - if err := term.Bind("entitydebugvis", "set entity debug visualization level", nil, result.commandEntityDebugVis); err != nil { + err = term.BindAction("entitydebugvis", "set entity debug visualization level", func(level int) { + result.entityDebugVisLevel = level + }) + + if err != nil { result.Errorf("could not bind the entitydebugvis action, err: %v", err) } @@ -103,33 +110,6 @@ func CreateMapRenderer(asset *d2asset.AssetManager, renderer d2interface.Rendere return result } -// UnbindTerminalCommands unbinds commands from the terminal -func (mr *MapRenderer) UnbindTerminalCommands(term d2interface.Terminal) error { - return term.Unbind("mapdebugvis", "entitydebugvis") -} - -func (mr *MapRenderer) commandMapDebugVis(args []string) error { - level, err := strconv.Atoi(args[0]) - if err != nil { - return fmt.Errorf("invalid argument supplied") - } - - mr.mapDebugVisLevel = level - - return nil -} - -func (mr *MapRenderer) commandEntityDebugVis(args []string) error { - level, err := strconv.Atoi(args[0]) - if err != nil { - return fmt.Errorf("invalid argument supplied") - } - - mr.entityDebugVisLevel = level - - return nil -} - // RegenerateTileCache calls MapRenderer.generateTileCache(). func (mr *MapRenderer) RegenerateTileCache() { mr.generateTileCache() diff --git a/d2core/d2records/armor_type_loader.go b/d2core/d2records/armor_type_loader.go index 30a2b0fc..612e0481 100644 --- a/d2core/d2records/armor_type_loader.go +++ b/d2core/d2records/armor_type_loader.go @@ -22,7 +22,7 @@ func armorTypesLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Animation.Token.Armor = records - r.Debugf("Loaded %d ArmorType records", len(records)) + r.Logger.Infof("Loaded %d ArmorType records", len(records)) return nil } diff --git a/d2core/d2records/automagic_loader.go b/d2core/d2records/automagic_loader.go index cb66cef8..b666c96a 100644 --- a/d2core/d2records/automagic_loader.go +++ b/d2core/d2records/automagic_loader.go @@ -79,7 +79,7 @@ func autoMagicLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d AutoMagic records", len(records)) + r.Logger.Infof("Loaded %d AutoMagic records", len(records)) r.Item.AutoMagic = records diff --git a/d2core/d2records/automap_loader.go b/d2core/d2records/automap_loader.go index 69f26ad5..fe33002c 100644 --- a/d2core/d2records/automap_loader.go +++ b/d2core/d2records/automap_loader.go @@ -37,7 +37,7 @@ func autoMapLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d AutoMap records", len(records)) + r.Logger.Infof("Loaded %d AutoMapRecord records", len(records)) r.Level.AutoMaps = records diff --git a/d2core/d2records/belts_loader.go b/d2core/d2records/belts_loader.go index 622f8dec..ba954718 100644 --- a/d2core/d2records/belts_loader.go +++ b/d2core/d2records/belts_loader.go @@ -102,7 +102,7 @@ func beltsLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d Belt records", len(records)) + r.Logger.Infof("Loaded %d belts", len(records)) r.Item.Belts = records diff --git a/d2core/d2records/body_locations_loader.go b/d2core/d2records/body_locations_loader.go index 6b660c74..70c9855e 100644 --- a/d2core/d2records/body_locations_loader.go +++ b/d2core/d2records/body_locations_loader.go @@ -19,7 +19,7 @@ func bodyLocationsLoader(r *RecordManager, d *d2txt.DataDictionary) error { panic(d.Err) } - r.Debugf("Loaded %d BodyLocation records", len(records)) + r.Logger.Infof("Loaded %d Body Location records", len(records)) r.BodyLocations = records diff --git a/d2core/d2records/books_loader.go b/d2core/d2records/books_loader.go index 866f3228..9b8c46fa 100644 --- a/d2core/d2records/books_loader.go +++ b/d2core/d2records/books_loader.go @@ -8,7 +8,7 @@ func booksLoader(r *RecordManager, d *d2txt.DataDictionary) error { records := make(Books) for d.Next() { - record := &BookRecord{ + record := &BooksRecord{ Name: d.String("Name"), Namco: d.String("Namco"), Completed: d.String("Completed"), @@ -28,7 +28,7 @@ func booksLoader(r *RecordManager, d *d2txt.DataDictionary) error { panic(d.Err) } - r.Debugf("Loaded %d Book records", len(records)) + r.Logger.Infof("Loaded %d book items", len(records)) r.Item.Books = records diff --git a/d2core/d2records/books_record.go b/d2core/d2records/books_record.go index 472179ea..119185d7 100644 --- a/d2core/d2records/books_record.go +++ b/d2core/d2records/books_record.go @@ -1,10 +1,10 @@ package d2records -// Books stores all of the BookRecords -type Books map[string]*BookRecord +// Books stores all of the BooksRecords +type Books map[string]*BooksRecord -// BookRecord is a representation of a row from books.txt -type BookRecord struct { +// BooksRecord is a representation of a row from books.txt +type BooksRecord struct { Name string Namco string // The displayed name, where the string prefix is "Tome" Completed string diff --git a/d2core/d2records/calculations_loader.go b/d2core/d2records/calculations_loader.go index 3a07677f..bc3330cb 100644 --- a/d2core/d2records/calculations_loader.go +++ b/d2core/d2records/calculations_loader.go @@ -5,28 +5,32 @@ import ( ) func skillCalcLoader(r *RecordManager, d *d2txt.DataDictionary) error { - records, err := loadCalculations(r, d, "Skill") + records, err := loadCalculations(r, d) if err != nil { return err } + r.Logger.Infof("Loaded %d Skill Calculation records", len(records)) + r.Calculation.Skills = records return nil } func missileCalcLoader(r *RecordManager, d *d2txt.DataDictionary) error { - records, err := loadCalculations(r, d, "Missile") + records, err := loadCalculations(r, d) if err != nil { return err } + r.Logger.Infof("Loaded %d Missile Calculation records", len(records)) + r.Calculation.Missiles = records return nil } -func loadCalculations(r *RecordManager, d *d2txt.DataDictionary, name string) (Calculations, error) { +func loadCalculations(r *RecordManager, d *d2txt.DataDictionary) (Calculations, error) { records := make(Calculations) for d.Next() { @@ -41,7 +45,7 @@ func loadCalculations(r *RecordManager, d *d2txt.DataDictionary, name string) (C return nil, d.Err } - r.Debugf("Loaded %d %s Calculation records", len(records), name) + r.Logger.Infof("Loaded %d Skill Calculation records", len(records)) return records, nil } diff --git a/d2core/d2records/charstats_loader.go b/d2core/d2records/charstats_loader.go index 86f1d30a..b5af365d 100644 --- a/d2core/d2records/charstats_loader.go +++ b/d2core/d2records/charstats_loader.go @@ -38,7 +38,7 @@ func charStatsLoader(r *RecordManager, d *d2txt.DataDictionary) error { } for d.Next() { - record := &CharStatRecord{ + record := &CharStatsRecord{ Class: stringMap[d.String("class")], InitStr: d.Number("str"), @@ -136,7 +136,7 @@ func charStatsLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d CharStat records", len(records)) + r.Logger.Infof("Loaded %d CharStats records", len(records)) r.Character.Stats = records diff --git a/d2core/d2records/charstats_record.go b/d2core/d2records/charstats_record.go index c7717ac1..5d7bd35d 100644 --- a/d2core/d2records/charstats_record.go +++ b/d2core/d2records/charstats_record.go @@ -2,11 +2,11 @@ package d2records import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" -// CharStats holds all of the CharStatRecords -type CharStats map[d2enum.Hero]*CharStatRecord +// CharStats holds all of the CharStatsRecords +type CharStats map[d2enum.Hero]*CharStatsRecord -// CharStatRecord is a struct that represents a single row from charstats.txt -type CharStatRecord struct { +// CharStatsRecord is a struct that represents a single row from charstats.txt +type CharStatsRecord struct { Class d2enum.Hero // the initial stats at character level 1 diff --git a/d2core/d2records/color_loader.go b/d2core/d2records/color_loader.go index 2adb67cc..1f2c502a 100644 --- a/d2core/d2records/color_loader.go +++ b/d2core/d2records/color_loader.go @@ -22,7 +22,7 @@ func colorsLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Colors = records - r.Debugf("Loaded %d Color records", len(records)) + r.Logger.Infof("Loaded %d Color records", len(records)) return nil } diff --git a/d2core/d2records/component_codes_loader.go b/d2core/d2records/component_codes_loader.go index af02f09d..6a840f4b 100644 --- a/d2core/d2records/component_codes_loader.go +++ b/d2core/d2records/component_codes_loader.go @@ -19,7 +19,7 @@ func componentCodesLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d ComponentCode records", len(records)) + r.Logger.Infof("Loaded %d ComponentCode records", len(records)) r.ComponentCodes = records diff --git a/d2core/d2records/composite_type_loader.go b/d2core/d2records/composite_type_loader.go index ecb5c4ea..c60ed58c 100644 --- a/d2core/d2records/composite_type_loader.go +++ b/d2core/d2records/composite_type_loader.go @@ -22,7 +22,7 @@ func compositeTypeLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Animation.Token.Composite = records - r.Debugf("Loaded %d CompositeType records", len(records)) + r.Logger.Infof("Loaded %d Composite Type records", len(records)) return nil } diff --git a/d2core/d2records/cube_modifier_loader.go b/d2core/d2records/cube_modifier_loader.go index 18e25847..f9ddc7fa 100644 --- a/d2core/d2records/cube_modifier_loader.go +++ b/d2core/d2records/cube_modifier_loader.go @@ -22,7 +22,7 @@ func cubeModifierLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Item.Cube.Modifiers = records - r.Debugf("Loaded %d CubeModifier records", len(records)) + r.Logger.Infof("Loaded %d Cube Modifier records", len(records)) return nil } diff --git a/d2core/d2records/cube_type_loader.go b/d2core/d2records/cube_type_loader.go index b79551f6..18bbf171 100644 --- a/d2core/d2records/cube_type_loader.go +++ b/d2core/d2records/cube_type_loader.go @@ -22,7 +22,7 @@ func cubeTypeLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Item.Cube.Types = records - r.Debugf("Loaded %d CubeType records", len(records)) + r.Logger.Infof("Loaded %d Cube Type records", len(records)) return nil } diff --git a/d2core/d2records/cubemain_loader.go b/d2core/d2records/cubemain_loader.go index 01d61e40..76692521 100644 --- a/d2core/d2records/cubemain_loader.go +++ b/d2core/d2records/cubemain_loader.go @@ -96,7 +96,7 @@ func cubeRecipeLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d CubeRecipe records", len(records)) + r.Logger.Infof("Loaded %d CubeMainRecord records", len(records)) r.Item.Cube.Recipes = records diff --git a/d2core/d2records/difficultylevels_loader.go b/d2core/d2records/difficultylevels_loader.go index c048b2cd..c880e1e5 100644 --- a/d2core/d2records/difficultylevels_loader.go +++ b/d2core/d2records/difficultylevels_loader.go @@ -42,7 +42,7 @@ func difficultyLevelsLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d DifficultyLevel records", len(records)) + r.Logger.Infof("Loaded %d DifficultyLevel records", len(records)) r.DifficultyLevels = records diff --git a/d2core/d2records/elemtype_loader.go b/d2core/d2records/elemtype_loader.go index 863dd712..7142a0ac 100644 --- a/d2core/d2records/elemtype_loader.go +++ b/d2core/d2records/elemtype_loader.go @@ -20,7 +20,7 @@ func elemTypesLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d ElemType records", len(records)) + r.Logger.Infof("Loaded %d ElemType records", len(records)) r.ElemTypes = records diff --git a/d2core/d2records/events_loader.go b/d2core/d2records/events_loader.go index 4af88bbc..08db2485 100644 --- a/d2core/d2records/events_loader.go +++ b/d2core/d2records/events_loader.go @@ -20,7 +20,7 @@ func eventsLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d Event records", len(records)) + r.Logger.Infof("Loaded %d Event records", len(records)) r.Character.Events = records diff --git a/d2core/d2records/experience_loader.go b/d2core/d2records/experience_loader.go index 5ef9b922..7380420f 100644 --- a/d2core/d2records/experience_loader.go +++ b/d2core/d2records/experience_loader.go @@ -48,7 +48,7 @@ func experienceLoader(r *RecordManager, d *d2txt.DataDictionary) error { } for d.Next() { - record := &ExperienceBreakpointRecord{ + record := &ExperienceBreakpointsRecord{ Level: d.Number("Level"), HeroBreakpoints: map[d2enum.Hero]int{ d2enum.HeroAmazon: d.Number("Amazon"), @@ -68,7 +68,7 @@ func experienceLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d ExperienceBreakpoint records", len(breakpoints)) + r.Logger.Infof("Loaded %d Experience Breakpoint records", len(breakpoints)) r.Character.MaxLevel = maxLevels r.Character.Experience = breakpoints diff --git a/d2core/d2records/experience_record.go b/d2core/d2records/experience_record.go index 821122a5..9b80b0e4 100644 --- a/d2core/d2records/experience_record.go +++ b/d2core/d2records/experience_record.go @@ -4,14 +4,14 @@ import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" // ExperienceBreakpoints describes the required experience // for each level for each character class -type ExperienceBreakpoints map[int]*ExperienceBreakpointRecord +type ExperienceBreakpoints map[int]*ExperienceBreakpointsRecord // ExperienceMaxLevels defines the max character levels type ExperienceMaxLevels map[d2enum.Hero]int -// ExperienceBreakpointRecord describes the experience points required to +// ExperienceBreakpointsRecord describes the experience points required to // gain a level for all character classes -type ExperienceBreakpointRecord struct { +type ExperienceBreakpointsRecord struct { Level int HeroBreakpoints map[d2enum.Hero]int Ratio int diff --git a/d2core/d2records/gamble_loader.go b/d2core/d2records/gamble_loader.go index 7fce2967..dda7ac08 100644 --- a/d2core/d2records/gamble_loader.go +++ b/d2core/d2records/gamble_loader.go @@ -19,7 +19,7 @@ func gambleLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d Gamble records", len(records)) + r.Logger.Infof("Loaded %d gamble records", len(records)) r.Gamble = records diff --git a/d2core/d2records/gems_loader.go b/d2core/d2records/gems_loader.go index 5db90827..bc71b23a 100644 --- a/d2core/d2records/gems_loader.go +++ b/d2core/d2records/gems_loader.go @@ -4,12 +4,12 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" ) -// LoadGems loads gem records into a map[string]*GemRecord +// LoadGems loads gem records into a map[string]*GemsRecord func gemsLoader(r *RecordManager, d *d2txt.DataDictionary) error { records := make(Gems) for d.Next() { - gem := &GemRecord{ + gem := &GemsRecord{ Name: d.String("name"), Letter: d.String("letter"), Transform: d.Number("transform"), @@ -60,7 +60,7 @@ func gemsLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d Gem records", len(records)) + r.Logger.Infof("Loaded %d Gems records", len(records)) r.Item.Gems = records diff --git a/d2core/d2records/gems_record.go b/d2core/d2records/gems_record.go index cbb69e76..62879a00 100644 --- a/d2core/d2records/gems_record.go +++ b/d2core/d2records/gems_record.go @@ -1,11 +1,11 @@ package d2records -// Gems stores all of the GemRecords -type Gems map[string]*GemRecord +// Gems stores all of the GemsRecords +type Gems map[string]*GemsRecord -// GemRecord is a representation of a single row of gems.txt +// GemsRecord is a representation of a single row of gems.txt // it describes the properties of socketable items -type GemRecord struct { +type GemsRecord struct { Name string Letter string Transform int diff --git a/d2core/d2records/hireling_description_loader.go b/d2core/d2records/hireling_description_loader.go index e289f9d2..fa6c7d0c 100644 --- a/d2core/d2records/hireling_description_loader.go +++ b/d2core/d2records/hireling_description_loader.go @@ -22,7 +22,7 @@ func hirelingDescriptionLoader(r *RecordManager, d *d2txt.DataDictionary) error r.Hireling.Descriptions = records - r.Debugf("Loaded %d HirelingDescription records", len(records)) + r.Logger.Infof("Loaded %d Hireling Descriptions records", len(records)) return nil } diff --git a/d2core/d2records/hireling_loader.go b/d2core/d2records/hireling_loader.go index 785d7f76..c1262988 100644 --- a/d2core/d2records/hireling_loader.go +++ b/d2core/d2records/hireling_loader.go @@ -90,7 +90,7 @@ func hirelingLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d Hireling records", len(records)) + r.Logger.Infof("Loaded %d Hireling records", len(records)) r.Hireling.Details = records diff --git a/d2core/d2records/hit_class_loader.go b/d2core/d2records/hit_class_loader.go index 68c98d47..0c265066 100644 --- a/d2core/d2records/hit_class_loader.go +++ b/d2core/d2records/hit_class_loader.go @@ -22,7 +22,7 @@ func hitClassLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Animation.Token.HitClass = records - r.Debugf("Loaded %d HitClass records", len(records)) + r.Logger.Infof("Loaded %d HitClass records", len(records)) return nil } diff --git a/d2core/d2records/inventory_loader.go b/d2core/d2records/inventory_loader.go index 931c81fe..cbe0974c 100644 --- a/d2core/d2records/inventory_loader.go +++ b/d2core/d2records/inventory_loader.go @@ -130,7 +130,7 @@ func inventoryLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d Inventory records", len(records)) + r.Logger.Infof("Loaded %d Inventory Panel records", len(records)) r.Layout.Inventory = records diff --git a/d2core/d2records/item_affix_loader.go b/d2core/d2records/item_affix_loader.go index 09f1608d..2b0f7999 100644 --- a/d2core/d2records/item_affix_loader.go +++ b/d2core/d2records/item_affix_loader.go @@ -70,7 +70,7 @@ func loadAffixDictionary( } name := getAffixString(superType, subType) - r.Debugf("Loaded %d %s records", len(records), name) + r.Logger.Infof("Loaded %d %s records", len(records), name) return records, groups, nil } diff --git a/d2core/d2records/item_armor_loader.go b/d2core/d2records/item_armor_loader.go index d910e964..2023f7fd 100644 --- a/d2core/d2records/item_armor_loader.go +++ b/d2core/d2records/item_armor_loader.go @@ -16,7 +16,7 @@ func armorLoader(r *RecordManager, d *d2txt.DataDictionary) error { return err } - r.Debugf("Loaded %d Armor Item records", len(records)) + r.Logger.Infof("Loaded %d armors", len(records)) r.Item.Armors = records diff --git a/d2core/d2records/item_low_quality_loader.go b/d2core/d2records/item_low_quality_loader.go index ec491780..eb8b99bf 100644 --- a/d2core/d2records/item_low_quality_loader.go +++ b/d2core/d2records/item_low_quality_loader.go @@ -21,7 +21,7 @@ func lowQualityLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Item.LowQualityPrefixes = records - r.Debugf("Loaded %d LowQuality records", len(records)) + r.Logger.Infof("Loaded %d Low Item Quality records", len(records)) return nil } diff --git a/d2core/d2records/item_misc_loader.go b/d2core/d2records/item_misc_loader.go index 957abeaa..0851fd08 100644 --- a/d2core/d2records/item_misc_loader.go +++ b/d2core/d2records/item_misc_loader.go @@ -13,7 +13,7 @@ func miscItemsLoader(r *RecordManager, d *d2txt.DataDictionary) error { return err } - r.Debugf("Loaded %d Misc Item records", len(records)) + r.Logger.Infof("Loaded %d misc items", len(records)) r.Item.Misc = records diff --git a/d2core/d2records/item_quality_loader.go b/d2core/d2records/item_quality_loader.go index e497376d..f3d92e74 100644 --- a/d2core/d2records/item_quality_loader.go +++ b/d2core/d2records/item_quality_loader.go @@ -45,7 +45,7 @@ func itemQualityLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Item.Quality = records - r.Debugf("Loaded %d ItemQuality records", len(records)) + r.Logger.Infof("Loaded %d ItemQualities records", len(records)) return nil } diff --git a/d2core/d2records/item_ratio_loader.go b/d2core/d2records/item_ratio_loader.go index bc47202f..06e460b7 100644 --- a/d2core/d2records/item_ratio_loader.go +++ b/d2core/d2records/item_ratio_loader.go @@ -55,7 +55,7 @@ func itemRatioLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d ItemRatio records", len(records)) + r.Logger.Infof("Loaded %d ItemRatio records", len(records)) r.Item.Ratios = records diff --git a/d2core/d2records/item_types_loader.go b/d2core/d2records/item_types_loader.go index e278e0b6..258791f3 100644 --- a/d2core/d2records/item_types_loader.go +++ b/d2core/d2records/item_types_loader.go @@ -76,7 +76,7 @@ func itemTypesLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d ItemType records", len(records)) + r.Logger.Infof("Loaded %d ItemType records", len(records)) r.Item.Types = records r.Item.Equivalency = equivMap diff --git a/d2core/d2records/item_weapons_loader.go b/d2core/d2records/item_weapons_loader.go index cbc12d56..dfd9e2cd 100644 --- a/d2core/d2records/item_weapons_loader.go +++ b/d2core/d2records/item_weapons_loader.go @@ -13,7 +13,7 @@ func weaponsLoader(r *RecordManager, d *d2txt.DataDictionary) error { return err } - r.Debugf("Loaded %d Weapon records", len(records)) + r.Logger.Infof("Loaded %d weapons", len(records)) r.Item.Weapons = records diff --git a/d2core/d2records/itemstatcost_loader.go b/d2core/d2records/itemstatcost_loader.go index e4f38f76..ed4c328a 100644 --- a/d2core/d2records/itemstatcost_loader.go +++ b/d2core/d2records/itemstatcost_loader.go @@ -95,7 +95,7 @@ func itemStatCostLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d ItemStatCost records", len(records)) + r.Logger.Infof("Loaded %d ItemStatCost records", len(records)) r.Item.Stats = records diff --git a/d2core/d2records/level_details_loader.go b/d2core/d2records/level_details_loader.go index 5591523b..e53722ff 100644 --- a/d2core/d2records/level_details_loader.go +++ b/d2core/d2records/level_details_loader.go @@ -11,7 +11,7 @@ func levelDetailsLoader(r *RecordManager, d *d2txt.DataDictionary) error { records := make(LevelDetails) for d.Next() { - record := &LevelDetailRecord{ + record := &LevelDetailsRecord{ Name: d.String("Name "), ID: d.Number("Id"), Palette: d.Number("Pal"), @@ -165,7 +165,7 @@ func levelDetailsLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d LevelDetail records", len(records)) + r.Logger.Infof("Loaded %d LevelDetails records", len(records)) r.Level.Details = records diff --git a/d2core/d2records/level_details_record.go b/d2core/d2records/level_details_record.go index 386882e5..db9430b2 100644 --- a/d2core/d2records/level_details_record.go +++ b/d2core/d2records/level_details_record.go @@ -2,13 +2,13 @@ package d2records import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" -// LevelDetails has all of the LevelDetailRecords -type LevelDetails map[int]*LevelDetailRecord +// LevelDetails has all of the LevelDetailsRecords +type LevelDetails map[int]*LevelDetailsRecord -// LevelDetailRecord is a representation of a row from levels.txt +// LevelDetailsRecord is a representation of a row from levels.txt // it describes lots of things about the levels, like where they are connected, // what kinds of monsters spawn, the level generator type, and lots of other stuff. -type LevelDetailRecord struct { +type LevelDetailsRecord struct { // Name // This column has no function, it only serves as a comment field to make it diff --git a/d2core/d2records/level_maze_loader.go b/d2core/d2records/level_maze_loader.go index 0b6edd6a..8c592922 100644 --- a/d2core/d2records/level_maze_loader.go +++ b/d2core/d2records/level_maze_loader.go @@ -8,7 +8,7 @@ func levelMazeDetailsLoader(r *RecordManager, d *d2txt.DataDictionary) error { records := make(LevelMazeDetails) for d.Next() { - record := &LevelMazeDetailRecord{ + record := &LevelMazeDetailsRecord{ Name: d.String("Name"), LevelID: d.Number("Level"), NumRoomsNormal: d.Number("Rooms"), @@ -24,7 +24,7 @@ func levelMazeDetailsLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d LevelMazeDetail records", len(records)) + r.Logger.Infof("Loaded %d LevelMazeDetails records", len(records)) r.Level.Maze = records diff --git a/d2core/d2records/level_maze_record.go b/d2core/d2records/level_maze_record.go index b47dfee3..61877e5b 100644 --- a/d2core/d2records/level_maze_record.go +++ b/d2core/d2records/level_maze_record.go @@ -1,11 +1,11 @@ package d2records -// LevelMazeDetails stores all of the LevelMazeDetailRecords -type LevelMazeDetails map[int]*LevelMazeDetailRecord +// LevelMazeDetails stores all of the LevelMazeDetailsRecords +type LevelMazeDetails map[int]*LevelMazeDetailsRecord -// LevelMazeDetailRecord is a representation of a row from lvlmaze.txt +// LevelMazeDetailsRecord is a representation of a row from lvlmaze.txt // these records define the parameters passed to the maze level generator -type LevelMazeDetailRecord struct { +type LevelMazeDetailsRecord struct { // descriptive, not loaded in game. Corresponds with Name field in // Levels.txt Name string // Name diff --git a/d2core/d2records/level_presets_loader.go b/d2core/d2records/level_presets_loader.go index 39847d6b..8cd32ea6 100644 --- a/d2core/d2records/level_presets_loader.go +++ b/d2core/d2records/level_presets_loader.go @@ -42,7 +42,7 @@ func levelPresetLoader(r *RecordManager, d *d2txt.DataDictionary) error { records[record.DefinitionID] = record } - r.Debugf("Loaded %d LevelPresets records", len(records)) + r.Logger.Infof("Loaded %d level presets", len(records)) if d.Err != nil { return d.Err diff --git a/d2core/d2records/level_substitutions_loader.go b/d2core/d2records/level_substitutions_loader.go index 0b0059a8..3eacde9c 100644 --- a/d2core/d2records/level_substitutions_loader.go +++ b/d2core/d2records/level_substitutions_loader.go @@ -40,7 +40,7 @@ func levelSubstitutionsLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d LevelSubstitution records", len(records)) + r.Logger.Infof("Loaded %d LevelSubstitution records", len(records)) r.Level.Sub = records diff --git a/d2core/d2records/level_types_loader.go b/d2core/d2records/level_types_loader.go index 8ff4cabd..160a621c 100644 --- a/d2core/d2records/level_types_loader.go +++ b/d2core/d2records/level_types_loader.go @@ -58,7 +58,7 @@ func levelTypesLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d LevelType records", len(records)) + r.Logger.Infof("Loaded %d LevelType records", len(records)) r.Level.Types = records diff --git a/d2core/d2records/level_warp_loader.go b/d2core/d2records/level_warp_loader.go index 84bc6964..f474f94a 100644 --- a/d2core/d2records/level_warp_loader.go +++ b/d2core/d2records/level_warp_loader.go @@ -30,7 +30,7 @@ func levelWarpsLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d LevelWarp records", len(records)) + r.Logger.Infof("Loaded %d level warps", len(records)) r.Level.Warp = records diff --git a/d2core/d2records/missiles_loader.go b/d2core/d2records/missiles_loader.go index 807e88e5..6183c2fb 100644 --- a/d2core/d2records/missiles_loader.go +++ b/d2core/d2records/missiles_loader.go @@ -304,7 +304,7 @@ func missilesLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d Missile records", len(records)) + r.Logger.Infof("Loaded %d Missile Records", len(records)) r.Missiles = records diff --git a/d2core/d2records/monster_ai_loader.go b/d2core/d2records/monster_ai_loader.go index 47841eea..5f89565e 100644 --- a/d2core/d2records/monster_ai_loader.go +++ b/d2core/d2records/monster_ai_loader.go @@ -19,7 +19,7 @@ func monsterAiLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d MonsterAI records", len(records)) + r.Logger.Infof("Loaded %d MonsterAI records", len(records)) r.Monster.AI = records diff --git a/d2core/d2records/monster_equipment_loader.go b/d2core/d2records/monster_equipment_loader.go index 0e4b800a..5639649a 100644 --- a/d2core/d2records/monster_equipment_loader.go +++ b/d2core/d2records/monster_equipment_loader.go @@ -49,7 +49,7 @@ func monsterEquipmentLoader(r *RecordManager, d *d2txt.DataDictionary) error { length += len(records[k]) } - r.Debugf("Loaded %d MonsterEquipment records", length) + r.Logger.Infof("Loaded %d MonsterEquipment records", length) r.Monster.Equipment = records diff --git a/d2core/d2records/monster_levels_record.go b/d2core/d2records/monster_levels_record.go index 6b71e601..89bb71f9 100644 --- a/d2core/d2records/monster_levels_record.go +++ b/d2core/d2records/monster_levels_record.go @@ -52,7 +52,7 @@ func monsterLevelsLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d MonsterLevel records", len(records)) + r.Logger.Infof("Loaded %d MonsterLevel records", len(records)) r.Monster.Levels = records diff --git a/d2core/d2records/monster_mode_loader.go b/d2core/d2records/monster_mode_loader.go index 41cb4adf..23e512fb 100644 --- a/d2core/d2records/monster_mode_loader.go +++ b/d2core/d2records/monster_mode_loader.go @@ -21,7 +21,7 @@ func monsterModeLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d MonMode records", len(records)) + r.Logger.Infof("Loaded %d MonMode records", len(records)) r.Monster.Modes = records diff --git a/d2core/d2records/monster_mode_record.go b/d2core/d2records/monster_mode_record.go index 9df79421..7a36ff05 100644 --- a/d2core/d2records/monster_mode_record.go +++ b/d2core/d2records/monster_mode_record.go @@ -1,6 +1,6 @@ package d2records -// MonModes stores all of the MonModeRecords +// MonModes stores all of the GemsRecords type MonModes map[string]*MonModeRecord // MonModeRecord is a representation of a single row of Monmode.txt diff --git a/d2core/d2records/monster_placement_loader.go b/d2core/d2records/monster_placement_loader.go index adfbb8e4..a1e56df0 100644 --- a/d2core/d2records/monster_placement_loader.go +++ b/d2core/d2records/monster_placement_loader.go @@ -18,7 +18,7 @@ func monsterPlacementsLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Monster.Placements = records - r.Debugf("Loaded %d MonsterPlacement records", len(records)) + r.Logger.Infof("Loaded %d MonsterPlacement records", len(records)) return nil } diff --git a/d2core/d2records/monster_preset_loader.go b/d2core/d2records/monster_preset_loader.go index d9380549..c2e84ea3 100644 --- a/d2core/d2records/monster_preset_loader.go +++ b/d2core/d2records/monster_preset_loader.go @@ -21,7 +21,7 @@ func monsterPresetLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d MonPreset records", len(records)) + r.Logger.Infof("Loaded %d MonPreset records", len(records)) r.Monster.Presets = records diff --git a/d2core/d2records/monster_property_loader.go b/d2core/d2records/monster_property_loader.go index b3e63c3c..0662c40d 100644 --- a/d2core/d2records/monster_property_loader.go +++ b/d2core/d2records/monster_property_loader.go @@ -57,7 +57,7 @@ func monsterPropertiesLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d MonProp records", len(records)) + r.Logger.Infof("Loaded %d MonProp records", len(records)) r.Monster.Props = records diff --git a/d2core/d2records/monster_sequence_loader.go b/d2core/d2records/monster_sequence_loader.go index e168fe1d..449c6b3e 100644 --- a/d2core/d2records/monster_sequence_loader.go +++ b/d2core/d2records/monster_sequence_loader.go @@ -31,7 +31,7 @@ func monsterSequencesLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d MonsterSequence records", len(records)) + r.Logger.Infof("Loaded %d MonsterSequence records", len(records)) r.Monster.Sequences = records diff --git a/d2core/d2records/monster_sound_loader.go b/d2core/d2records/monster_sound_loader.go index 3f8aa3f2..96b6909c 100644 --- a/d2core/d2records/monster_sound_loader.go +++ b/d2core/d2records/monster_sound_loader.go @@ -57,7 +57,7 @@ func monsterSoundsLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d MonsterSound records", len(records)) + r.Logger.Infof("Loaded %d Monster Sound records", len(records)) r.Monster.Sounds = records diff --git a/d2core/d2records/monster_stats2_loader.go b/d2core/d2records/monster_stats2_loader.go index 91fc7ca0..51981d27 100644 --- a/d2core/d2records/monster_stats2_loader.go +++ b/d2core/d2records/monster_stats2_loader.go @@ -7,7 +7,7 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" ) -// LoadMonStats2 loads MonStat2Records from monstats2.txt +// LoadMonStats2 loads MonStats2Records from monstats2.txt //nolint:funlen //just a big data loader func monsterStats2Loader(r *RecordManager, d *d2txt.DataDictionary) error { records := make(MonStats2) @@ -18,7 +18,7 @@ func monsterStats2Loader(r *RecordManager, d *d2txt.DataDictionary) error { return err } - record := &MonStat2Record{ + record := &MonStats2Record{ Key: d.String("Id"), Height: d.Number("Height"), OverlayHeight: d.Number("OverlayHeight"), @@ -161,7 +161,7 @@ func monsterStats2Loader(r *RecordManager, d *d2txt.DataDictionary) error { panic(d.Err) } - r.Debugf("Loaded %d MonStat2 records", len(records)) + r.Logger.Infof("Loaded %d MonStats2 records", len(records)) r.Monster.Stats2 = records diff --git a/d2core/d2records/monster_stats2_record.go b/d2core/d2records/monster_stats2_record.go index 914b1c61..306ea1e4 100644 --- a/d2core/d2records/monster_stats2_record.go +++ b/d2core/d2records/monster_stats2_record.go @@ -2,11 +2,11 @@ package d2records import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" -// MonStats2 stores all of the MonStat2Records -type MonStats2 map[string]*MonStat2Record +// MonStats2 stores all of the MonStats2Records +type MonStats2 map[string]*MonStats2Record -// MonStat2Record is a representation of a row from monstats2.txt -type MonStat2Record struct { +// MonStats2Record is a representation of a row from monstats2.txt +type MonStats2Record struct { // Available options for equipment // randomly selected from EquipmentOptions [16][]string diff --git a/d2core/d2records/monster_stats_loader.go b/d2core/d2records/monster_stats_loader.go index e9675623..a6b8c6a9 100644 --- a/d2core/d2records/monster_stats_loader.go +++ b/d2core/d2records/monster_stats_loader.go @@ -10,7 +10,7 @@ func monsterStatsLoader(r *RecordManager, d *d2txt.DataDictionary) error { records := make(MonStats) for d.Next() { - record := &MonStatRecord{ + record := &MonStatsRecord{ Key: d.String("Id"), ID: d.Number("hcIdx"), BaseKey: d.String("BaseId"), @@ -272,7 +272,7 @@ func monsterStatsLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d MonStat records", len(records)) + r.Logger.Infof("Loaded %d MonStats records", len(records)) r.Monster.Stats = records diff --git a/d2core/d2records/monster_stats_record.go b/d2core/d2records/monster_stats_record.go index 191bb62c..2469299a 100644 --- a/d2core/d2records/monster_stats_record.go +++ b/d2core/d2records/monster_stats_record.go @@ -4,13 +4,13 @@ import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" // https://d2mods.info/forum/kb/viewarticle?a=360 -// MonStats stores all of the MonStatRecords -type MonStats map[string]*MonStatRecord +// MonStats stores all of the MonStat Records +type MonStats map[string]*MonStatsRecord type ( - // MonStatRecord represents a single row from `data/global/excel/monstats.txt` in the MPQ files. + // MonStatsRecord represents a single row from `data/global/excel/monstats.txt` in the MPQ files. // These records are used for creating monsters. - MonStatRecord struct { + MonStatsRecord struct { // Key contains the pointer that will be used in other txt files // such as levels.txt and superuniques.txt. diff --git a/d2core/d2records/monster_super_unique_loader.go b/d2core/d2records/monster_super_unique_loader.go index 852c0f25..774bb77a 100644 --- a/d2core/d2records/monster_super_unique_loader.go +++ b/d2core/d2records/monster_super_unique_loader.go @@ -40,7 +40,7 @@ func monsterSuperUniqeLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Monster.Unique.Super = records - r.Debugf("Loaded %d SuperUnique records", len(records)) + r.Logger.Infof("Loaded %d SuperUnique records", len(records)) return nil } diff --git a/d2core/d2records/monster_type_loader.go b/d2core/d2records/monster_type_loader.go index d4635af9..75f2fd0d 100644 --- a/d2core/d2records/monster_type_loader.go +++ b/d2core/d2records/monster_type_loader.go @@ -23,7 +23,7 @@ func monsterTypesLoader(r *RecordManager, d *d2txt.DataDictionary) error { panic(d.Err) } - r.Debugf("Loaded %d MonType records", len(records)) + r.Logger.Infof("Loaded %d MonType records", len(records)) r.Monster.Types = records diff --git a/d2core/d2records/monster_unique_affix_loader.go b/d2core/d2records/monster_unique_affix_loader.go index 0061926a..cfe6f497 100644 --- a/d2core/d2records/monster_unique_affix_loader.go +++ b/d2core/d2records/monster_unique_affix_loader.go @@ -59,7 +59,7 @@ func uniqueMonsterPrefixLoader(r *RecordManager, d *d2txt.DataDictionary) error r.Monster.Name.Prefix = records - r.Debugf("Loaded %d UniqueMonsterPrefix records", len(records)) + r.Logger.Infof("Loaded %d unique monster prefix records", len(records)) return nil } @@ -72,7 +72,7 @@ func uniqueMonsterSuffixLoader(r *RecordManager, d *d2txt.DataDictionary) error r.Monster.Name.Suffix = records - r.Debugf("Loaded %d UniqueMonsterSuffix records", len(records)) + r.Logger.Infof("Loaded %d unique monster suffix records", len(records)) return nil } diff --git a/d2core/d2records/monster_unique_modifiers_loader.go b/d2core/d2records/monster_unique_modifiers_loader.go index 3da4aec3..949d0ef4 100644 --- a/d2core/d2records/monster_unique_modifiers_loader.go +++ b/d2core/d2records/monster_unique_modifiers_loader.go @@ -48,7 +48,7 @@ func monsterUniqModifiersLoader(r *RecordManager, d *d2txt.DataDictionary) error return d.Err } - r.Debugf("Loaded %d MonsterUniqueModifier records", len(records)) + r.Logger.Infof("Loaded %d MonsterUniqueModifier records", len(records)) r.Monster.Unique.Mods = records r.Monster.Unique.Constants = constants diff --git a/d2core/d2records/npc_loader.go b/d2core/d2records/npc_loader.go index ebf9a590..685a3a5c 100644 --- a/d2core/d2records/npc_loader.go +++ b/d2core/d2records/npc_loader.go @@ -64,7 +64,7 @@ func npcLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.NPCs = records - r.Debugf("Loaded %d NPC records", len(records)) + r.Logger.Infof("Loaded %d NPC records", len(records)) return nil } diff --git a/d2core/d2records/object_details_loader.go b/d2core/d2records/object_details_loader.go index 2dcbf43d..c7dca4c6 100644 --- a/d2core/d2records/object_details_loader.go +++ b/d2core/d2records/object_details_loader.go @@ -11,7 +11,7 @@ func objectDetailsLoader(r *RecordManager, d *d2txt.DataDictionary) error { i := 0 for d.Next() { - record := &ObjectDetailRecord{ + record := &ObjectDetailsRecord{ Index: i, Name: d.String("Name"), Description: d.String("description - not loaded"), @@ -225,7 +225,7 @@ func objectDetailsLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d ObjectDetail records", len(records)) + r.Logger.Infof("Loaded %d objects", len(records)) r.Object.Details = records diff --git a/d2core/d2records/object_details_record.go b/d2core/d2records/object_details_record.go index 7137cf63..ee56249a 100644 --- a/d2core/d2records/object_details_record.go +++ b/d2core/d2records/object_details_record.go @@ -1,10 +1,10 @@ package d2records // ObjectDetails stores all of the ObjectDetailRecords -type ObjectDetails map[int]*ObjectDetailRecord +type ObjectDetails map[int]*ObjectDetailsRecord -// ObjectDetailRecord represents the settings for one type of object from objects.txt -type ObjectDetailRecord struct { +// ObjectDetailsRecord represents the settings for one type of object from objects.txt +type ObjectDetailsRecord struct { Index int // Line number in file, this is the actual index used for objects FrameCount [8]int // how many frames does this mode have, 0 = skip FrameDelta [8]int // what rate is the animation played at (256 = 100% speed) diff --git a/d2core/d2records/object_groups_loader.go b/d2core/d2records/object_groups_loader.go index ed109cfd..8a4ee9c7 100644 --- a/d2core/d2records/object_groups_loader.go +++ b/d2core/d2records/object_groups_loader.go @@ -32,7 +32,7 @@ func objectGroupsLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d ObjectGroup records", len(records)) + r.Logger.Infof("Loaded %d ObjectGroup records", len(records)) return nil } diff --git a/d2core/d2records/object_mode_loader.go b/d2core/d2records/object_mode_loader.go index 39c40ae2..dc544360 100644 --- a/d2core/d2records/object_mode_loader.go +++ b/d2core/d2records/object_mode_loader.go @@ -22,7 +22,7 @@ func objectModesLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Object.Modes = records - r.Debugf("Loaded %d ObjectMode records", len(records)) + r.Logger.Infof("Loaded %d ObjectMode records", len(records)) return nil } diff --git a/d2core/d2records/object_types_loader.go b/d2core/d2records/object_types_loader.go index aa855a96..fdb9f7bd 100644 --- a/d2core/d2records/object_types_loader.go +++ b/d2core/d2records/object_types_loader.go @@ -23,7 +23,7 @@ func objectTypesLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d ObjectType records", len(records)) + r.Logger.Infof("Loaded %d object types", len(records)) r.Object.Types = records diff --git a/d2core/d2records/overlays_loader.go b/d2core/d2records/overlays_loader.go index a10344ab..aed7a028 100644 --- a/d2core/d2records/overlays_loader.go +++ b/d2core/d2records/overlays_loader.go @@ -35,7 +35,7 @@ func overlaysLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d Overlay records", len(records)) + r.Logger.Infof("Loaded %d Overlay records", len(records)) r.Layout.Overlays = records diff --git a/d2core/d2records/pet_type_loader.go b/d2core/d2records/pet_type_loader.go index b8000f1d..59d9f4a9 100644 --- a/d2core/d2records/pet_type_loader.go +++ b/d2core/d2records/pet_type_loader.go @@ -39,7 +39,7 @@ func petTypesLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d PetType records", len(records)) + r.Logger.Infof("Loaded %d PetType records", len(records)) r.PetTypes = records diff --git a/d2core/d2records/player_class_loader.go b/d2core/d2records/player_class_loader.go index 3eff68be..df6321ce 100644 --- a/d2core/d2records/player_class_loader.go +++ b/d2core/d2records/player_class_loader.go @@ -29,7 +29,7 @@ func playerClassLoader(r *RecordManager, d *d2txt.DataDictionary) error { return d.Err } - r.Debugf("Loaded %d PlayerClass records", len(records)) + r.Logger.Infof("Loaded %d PlayerClass records", len(records)) r.Character.Classes = records diff --git a/d2core/d2records/player_mode_loader.go b/d2core/d2records/player_mode_loader.go index 010876c1..9274759f 100644 --- a/d2core/d2records/player_mode_loader.go +++ b/d2core/d2records/player_mode_loader.go @@ -23,7 +23,7 @@ func playerModesLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Character.Modes = records - r.Debugf("Loaded %d PlayerMode records", len(records)) + r.Logger.Infof("Loaded %d PlayerMode records", len(records)) return nil } diff --git a/d2core/d2records/player_type_loader.go b/d2core/d2records/player_type_loader.go index 60b7e2c3..e383df9a 100644 --- a/d2core/d2records/player_type_loader.go +++ b/d2core/d2records/player_type_loader.go @@ -24,7 +24,7 @@ func playerTypeLoader(r *RecordManager, d *d2txt.DataDictionary) error { panic(d.Err) } - r.Debugf("Loaded %d PlayerType records", len(records)) + r.Logger.Infof("Loaded %d PlayerType records", len(records)) r.Animation.Token.Player = records diff --git a/d2core/d2records/property_loader.go b/d2core/d2records/property_loader.go index e68b1708..db718319 100644 --- a/d2core/d2records/property_loader.go +++ b/d2core/d2records/property_loader.go @@ -67,7 +67,7 @@ func propertyLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Properties = records - r.Debugf("Loaded %d Property records", len(records)) + r.Logger.Infof("Loaded %d Property records", len(records)) return nil } diff --git a/d2core/d2records/rare_prefix_loader.go b/d2core/d2records/rare_prefix_loader.go index 65512d0f..290eccf4 100644 --- a/d2core/d2records/rare_prefix_loader.go +++ b/d2core/d2records/rare_prefix_loader.go @@ -12,7 +12,7 @@ func rareItemPrefixLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Item.Rare.Prefix = records - r.Debugf("Loaded %d RarePrefix records", len(records)) + r.Logger.Infof("Loaded %d RarePrefix records", len(records)) return nil } diff --git a/d2core/d2records/rare_suffix_loader.go b/d2core/d2records/rare_suffix_loader.go index 94123695..fb158cb7 100644 --- a/d2core/d2records/rare_suffix_loader.go +++ b/d2core/d2records/rare_suffix_loader.go @@ -10,7 +10,7 @@ func rareItemSuffixLoader(r *RecordManager, d *d2txt.DataDictionary) error { return err } - r.Debugf("Loaded %d RareSuffix records", len(records)) + r.Logger.Infof("Loaded %d RareSuffix records", len(records)) r.Item.Rare.Suffix = records diff --git a/d2core/d2records/record_manager.go b/d2core/d2records/record_manager.go index 8afba41c..716a4f4d 100644 --- a/d2core/d2records/record_manager.go +++ b/d2core/d2records/record_manager.go @@ -334,8 +334,8 @@ func (r *RecordManager) GetExperienceBreakpoint(heroType d2enum.Hero, level int) return r.Character.Experience[level].HeroBreakpoints[heroType] } -// GetLevelDetails gets a LevelDetailRecord by the record Id -func (r *RecordManager) GetLevelDetails(id int) *LevelDetailRecord { +// GetLevelDetails gets a LevelDetailsRecord by the record Id +func (r *RecordManager) GetLevelDetails(id int) *LevelDetailsRecord { for i := 0; i < len(r.Level.Details); i++ { if r.Level.Details[i].ID == id { return r.Level.Details[i] @@ -433,7 +433,7 @@ func (r *RecordManager) lookupObject(act, typ, id int) *ObjectLookupRecord { } // SelectSoundByIndex selects a sound by its ID -func (r *RecordManager) SelectSoundByIndex(index int) *SoundDetailRecord { +func (r *RecordManager) SelectSoundByIndex(index int) *SoundDetailsRecord { for idx := range r.Sound.Details { if r.Sound.Details[idx].Index == index { return r.Sound.Details[idx] diff --git a/d2core/d2records/runeword_loader.go b/d2core/d2records/runeword_loader.go index a367d9d2..e498fe2d 100644 --- a/d2core/d2records/runeword_loader.go +++ b/d2core/d2records/runeword_loader.go @@ -24,12 +24,12 @@ const ( fmtRunewordPropMax = "T1Max%d" ) -// Loadrecords loads runes records into a map[string]*RuneRecord +// Loadrecords loads runes records into a map[string]*RunesRecord func runewordLoader(r *RecordManager, d *d2txt.DataDictionary) error { - records := make(map[string]*RuneRecord) + records := make(map[string]*RunesRecord) for d.Next() { - record := &RuneRecord{ + record := &RunesRecord{ Name: d.String("name"), RuneName: d.String("Rune Name"), Complete: d.Bool("complete"), @@ -89,7 +89,7 @@ func runewordLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Item.Runewords = records - r.Debugf("Loaded %d Rune records", len(records)) + r.Logger.Infof("Loaded %d records records", len(records)) return nil } diff --git a/d2core/d2records/runeword_record.go b/d2core/d2records/runeword_record.go index 7041a018..cfd63461 100644 --- a/d2core/d2records/runeword_record.go +++ b/d2core/d2records/runeword_record.go @@ -1,11 +1,11 @@ package d2records -// Runewords stores all of the RuneRecords -type Runewords map[string]*RuneRecord +// Runewords stores all of the RunesRecords +type Runewords map[string]*RunesRecord -// RuneRecord is a representation of a single row of runes.txt. It defines +// RunesRecord is a representation of a single row of runes.txt. It defines // runewords available in the game. -type RuneRecord struct { +type RunesRecord struct { Name string RuneName string // More of a note - the actual name should be read from the TBL files. Complete bool // An enabled/disabled flag. Only "Complete" runewords work in game. diff --git a/d2core/d2records/set_item_loader.go b/d2core/d2records/set_item_loader.go index 88021cbf..e1dfff03 100644 --- a/d2core/d2records/set_item_loader.go +++ b/d2core/d2records/set_item_loader.go @@ -94,7 +94,7 @@ func setItemLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Item.SetItems = records - r.Debugf("Loaded %d SetItem records", len(records)) + r.Logger.Infof("Loaded %d SetItem records", len(records)) return nil } diff --git a/d2core/d2records/set_loader.go b/d2core/d2records/set_loader.go index 5ac03637..8babd544 100644 --- a/d2core/d2records/set_loader.go +++ b/d2core/d2records/set_loader.go @@ -107,7 +107,7 @@ func setLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Item.Sets = records - r.Debugf("Loaded %d Set records", len(records)) + r.Logger.Infof("Loaded %d records records", len(records)) return nil } diff --git a/d2core/d2records/shrine_loader.go b/d2core/d2records/shrine_loader.go index 2aedf9bc..7d143032 100644 --- a/d2core/d2records/shrine_loader.go +++ b/d2core/d2records/shrine_loader.go @@ -31,7 +31,7 @@ func shrineLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Object.Shrines = records - r.Debugf("Loaded %d Shrine records", len(records)) + r.Logger.Infof("Loaded %d shrines", len(records)) return nil } diff --git a/d2core/d2records/skill_description_loader.go b/d2core/d2records/skill_description_loader.go index f579a8d5..5eb1b96e 100644 --- a/d2core/d2records/skill_description_loader.go +++ b/d2core/d2records/skill_description_loader.go @@ -139,7 +139,7 @@ func skillDescriptionLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Skill.Descriptions = records - r.Debugf("Loaded %d SkillDescription records", len(records)) + r.Logger.Infof("Loaded %d Skill Description records", len(records)) return nil } diff --git a/d2core/d2records/skill_details_loader.go b/d2core/d2records/skill_details_loader.go index 6067cc4c..da790eed 100644 --- a/d2core/d2records/skill_details_loader.go +++ b/d2core/d2records/skill_details_loader.go @@ -277,7 +277,7 @@ func skillDetailsLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Skill.Details = records - r.Debugf("Loaded %d Skill records", len(records)) + r.Logger.Infof("Loaded %d Skill records", len(records)) return nil } diff --git a/d2core/d2records/sound_details_loader.go b/d2core/d2records/sound_details_loader.go index b3436f0d..ff62077f 100644 --- a/d2core/d2records/sound_details_loader.go +++ b/d2core/d2records/sound_details_loader.go @@ -9,7 +9,7 @@ func soundDetailsLoader(r *RecordManager, d *d2txt.DataDictionary) error { records := make(SoundDetails) for d.Next() { - entry := &SoundDetailRecord{ + entry := &SoundDetailsRecord{ Handle: d.String("Sound"), Index: d.Number("Index"), FileName: d.String("FileName"), @@ -46,7 +46,7 @@ func soundDetailsLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Sound.Details = records - r.Debugf("Loaded %d SoundDetail records", len(records)) + r.Logger.Infof("Loaded %d sound definitions", len(records)) return nil } diff --git a/d2core/d2records/sound_details_record.go b/d2core/d2records/sound_details_record.go index 4365998a..56de22c5 100644 --- a/d2core/d2records/sound_details_record.go +++ b/d2core/d2records/sound_details_record.go @@ -1,10 +1,10 @@ package d2records // SoundDetails is a map of the SoundEntries -type SoundDetails map[string]*SoundDetailRecord +type SoundDetails map[string]*SoundDetailsRecord -// SoundDetailRecord represents a sound entry -type SoundDetailRecord struct { +// SoundDetailsRecord represents a sound entry +type SoundDetailsRecord struct { Handle string FileName string Index int diff --git a/d2core/d2records/sound_environment_loader.go b/d2core/d2records/sound_environment_loader.go index 68562ccc..98c6bdea 100644 --- a/d2core/d2records/sound_environment_loader.go +++ b/d2core/d2records/sound_environment_loader.go @@ -44,7 +44,7 @@ func soundEnvironmentLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Sound.Environment = records - r.Debugf("Loaded %d SoundEnviron records", len(records)) + r.Logger.Infof("Loaded %d SoundEnviron records", len(records)) return nil } diff --git a/d2core/d2records/states_loader.go b/d2core/d2records/states_loader.go index b7724b3a..21798f82 100644 --- a/d2core/d2records/states_loader.go +++ b/d2core/d2records/states_loader.go @@ -89,7 +89,7 @@ func statesLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.States = records - r.Debugf("Loaded %d State records", len(records)) + r.Logger.Infof("Loaded %d State records", len(records)) return nil } diff --git a/d2core/d2records/storepage_loader.go b/d2core/d2records/storepage_loader.go index f66a422b..67655354 100644 --- a/d2core/d2records/storepage_loader.go +++ b/d2core/d2records/storepage_loader.go @@ -21,7 +21,7 @@ func storePagesLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Item.StorePages = records - r.Debugf("Loaded %d StorePage records", len(records)) + r.Logger.Infof("Loaded %d StorePage records", len(records)) return nil } diff --git a/d2core/d2records/treasure_class_loader.go b/d2core/d2records/treasure_class_loader.go index e8fdbe7a..8f858a96 100644 --- a/d2core/d2records/treasure_class_loader.go +++ b/d2core/d2records/treasure_class_loader.go @@ -20,7 +20,7 @@ func treasureClassLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Item.Treasure.Normal = records - r.Debugf("Loaded %d TreasureClass (normal) records", len(records)) + r.Logger.Infof("Loaded %d treasure class (normal) records", len(records)) return nil } @@ -33,7 +33,7 @@ func treasureClassExLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Item.Treasure.Expansion = records - r.Debugf("Loaded %d TreasureClass (expansion) records", len(records)) + r.Logger.Infof("Loaded %d treasure class (expansion) records", len(records)) return nil } diff --git a/d2core/d2records/unique_appellation_loader.go b/d2core/d2records/unique_appellation_loader.go index c6d3c614..e551f901 100644 --- a/d2core/d2records/unique_appellation_loader.go +++ b/d2core/d2records/unique_appellation_loader.go @@ -21,7 +21,7 @@ func uniqueAppellationsLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Monster.Unique.Appellations = records - r.Debugf("Loaded %d UniqueAppellation records", len(records)) + r.Logger.Infof("Loaded %d UniqueAppellation records", len(records)) return nil } diff --git a/d2core/d2records/unique_items_loader.go b/d2core/d2records/unique_items_loader.go index 639d2dbd..a9ae79e6 100644 --- a/d2core/d2records/unique_items_loader.go +++ b/d2core/d2records/unique_items_loader.go @@ -128,7 +128,7 @@ func uniqueItemsLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Item.Unique = records - r.Debugf("Loaded %d UniqueItem records", len(records)) + r.Logger.Infof("Loaded %d unique items", len(records)) return nil } diff --git a/d2core/d2records/weapon_class_loader.go b/d2core/d2records/weapon_class_loader.go index 84c0b569..76154b1e 100644 --- a/d2core/d2records/weapon_class_loader.go +++ b/d2core/d2records/weapon_class_loader.go @@ -22,7 +22,7 @@ func weaponClassesLoader(r *RecordManager, d *d2txt.DataDictionary) error { r.Animation.Token.Weapon = records - r.Debugf("Loaded %d WeaponClass records", len(records)) + r.Logger.Infof("Loaded %d WeaponClass records", len(records)) return nil } diff --git a/d2core/d2stats/diablo2stats/stat_test.go b/d2core/d2stats/diablo2stats/stat_test.go index e6ae66e2..cd54927c 100644 --- a/d2core/d2stats/diablo2stats/stat_test.go +++ b/d2core/d2stats/diablo2stats/stat_test.go @@ -238,12 +238,12 @@ var skillDetails = map[int]*d2records.SkillRecord{ } // nolint:gochecknoglobals // just a test -var monStats = map[string]*d2records.MonStatRecord{ +var monStats = map[string]*d2records.MonStatsRecord{ "Specter": {NameString: "Specter", ID: 40}, } // nolint:gochecknoglobals // just a test -var charStats = map[d2enum.Hero]*d2records.CharStatRecord{ +var charStats = map[d2enum.Hero]*d2records.CharStatsRecord{ d2enum.HeroPaladin: { Class: d2enum.HeroPaladin, SkillStrAll: "to Paladin Skill Levels", diff --git a/d2core/d2systems/file_source_resolver.go b/d2core/d2systems/file_source_resolver.go index cf860cfd..f55b7fcc 100644 --- a/d2core/d2systems/file_source_resolver.go +++ b/d2core/d2systems/file_source_resolver.go @@ -144,7 +144,7 @@ func (s *fsSource) Path() string { // mpq source func (m *FileSourceResolver) makeMpqSource(path string) (d2components.AbstractSource, error) { - mpq, err := d2mpq.FromFile(path) + mpq, err := d2mpq.Load(path) if err != nil { return nil, err } diff --git a/d2core/d2systems/file_type_resolver.go b/d2core/d2systems/file_type_resolver.go index 7ff8cc27..95ba97ad 100644 --- a/d2core/d2systems/file_type_resolver.go +++ b/d2core/d2systems/file_type_resolver.go @@ -88,7 +88,7 @@ func (m *FileTypeResolver) determineFileType(id akara.EID) { ft := m.Components.FileType.Add(id) // try to immediately load as an mpq - if _, err := d2mpq.New(fp.Path); err == nil { + if _, err := d2mpq.Load(fp.Path); err == nil { ft.Type = d2enum.FileTypeMPQ return } diff --git a/d2core/d2systems/render.go b/d2core/d2systems/render.go index 964b1d9d..bc44518c 100644 --- a/d2core/d2systems/render.go +++ b/d2core/d2systems/render.go @@ -146,6 +146,7 @@ func (m *RenderSystem) createRenderer() { RunInBackground: config.RunInBackground, VsyncEnabled: config.VsyncEnabled, Backend: config.Backend, + LogLevel: config.LogLevel, } renderer, err := d2render.CreateRenderer(oldStyleConfig) diff --git a/d2core/d2systems/scene_base.go b/d2core/d2systems/scene_base.go index f98c9bb5..fb9258b8 100644 --- a/d2core/d2systems/scene_base.go +++ b/d2core/d2systems/scene_base.go @@ -5,8 +5,6 @@ import ( "image/color" "sort" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2geom/rectangle" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2scene" @@ -84,7 +82,7 @@ type BaseScene struct { } *d2util.Logger key string - state d2enum.SceneState + booted bool paused bool Add *sceneObjectFactory Viewports []akara.EID @@ -94,14 +92,9 @@ type BaseScene struct { gameConfigs *akara.Subscription } -// State returns the scene's current state -func (s *BaseScene) State() d2enum.SceneState { - return s.state -} - // Booted returns whether or not the scene has booted func (s *BaseScene) Booted() bool { - return s.state == d2enum.SceneStateBooted + return s.booted } // Paused returns whether or not the scene is paused @@ -120,20 +113,17 @@ func (s *BaseScene) Init(world *akara.World) { } func (s *BaseScene) boot() { - if s.state == d2enum.SceneStateUninitialized { - s.Debug("base scene booting ...") + s.Debug("base scene booting ...") - s.Add = &sceneObjectFactory{ - BaseScene: s, - Logger: d2util.NewLogger(), - } - - s.Add.SetPrefix(fmt.Sprintf("%s -> %s", s.key, "Object Factory")) - - s.bindRequiredSystems() - s.state = d2enum.SceneStateBooting + s.Add = &sceneObjectFactory{ + BaseScene: s, + Logger: d2util.NewLogger(), } + s.Add.SetPrefix(fmt.Sprintf("%s -> %s", s.key, "Object Factory")) + + s.bindRequiredSystems() + if !s.requiredSystemsPresent() { return } @@ -154,7 +144,7 @@ func (s *BaseScene) boot() { gameConfigs := s.NewComponentFilter().Require(&d2components.GameConfig{}).Build() s.gameConfigs = s.World.AddSubscription(gameConfigs) - s.state = d2enum.SceneStateBooted + s.booted = true } func (s *BaseScene) bindRequiredSystems() { @@ -215,23 +205,23 @@ func (s *BaseScene) setupSceneObjectFactories() { func (s *BaseScene) setupFactories() { s.Debug("setting up component factories") - s.InjectComponent(&d2components.SceneGraphNode{}, &s.Components.SceneGraphNode.ComponentFactory) - s.InjectComponent(&d2components.Viewport{}, &s.Components.Viewport.ComponentFactory) s.InjectComponent(&d2components.MainViewport{}, &s.Components.MainViewport.ComponentFactory) + s.InjectComponent(&d2components.Viewport{}, &s.Components.Viewport.ComponentFactory) s.InjectComponent(&d2components.ViewportFilter{}, &s.Components.ViewportFilter.ComponentFactory) - s.InjectComponent(&d2components.Priority{}, &s.Components.Priority.ComponentFactory) s.InjectComponent(&d2components.Camera{}, &s.Components.Camera.ComponentFactory) + s.InjectComponent(&d2components.Priority{}, &s.Components.Priority.ComponentFactory) s.InjectComponent(&d2components.Texture{}, &s.Components.Texture.ComponentFactory) s.InjectComponent(&d2components.Interactive{}, &s.Components.Interactive.ComponentFactory) s.InjectComponent(&d2components.Transform{}, &s.Components.Transform.ComponentFactory) - s.InjectComponent(&d2components.Sprite{}, &s.Components.Sprite.ComponentFactory) - s.InjectComponent(&d2components.SegmentedSprite{}, &s.Components.SegmentedSprite.ComponentFactory) s.InjectComponent(&d2components.Origin{}, &s.Components.Origin.ComponentFactory) s.InjectComponent(&d2components.Alpha{}, &s.Components.Alpha.ComponentFactory) + s.InjectComponent(&d2components.SceneGraphNode{}, &s.Components.SceneGraphNode.ComponentFactory) s.InjectComponent(&d2components.DrawEffect{}, &s.Components.DrawEffect.ComponentFactory) + s.InjectComponent(&d2components.Sprite{}, &s.Components.Sprite.ComponentFactory) + s.InjectComponent(&d2components.SegmentedSprite{}, &s.Components.SegmentedSprite.ComponentFactory) s.InjectComponent(&d2components.Rectangle{}, &s.Components.Rectangle.ComponentFactory) - s.InjectComponent(&d2components.Label{}, &s.Components.Label.ComponentFactory) s.InjectComponent(&d2components.Checkbox{}, &s.Components.Checkbox.ComponentFactory) + s.InjectComponent(&d2components.Label{}, &s.Components.Label.ComponentFactory) s.InjectComponent(&d2components.Color{}, &s.Components.Color.ComponentFactory) s.InjectComponent(&d2components.CommandRegistration{}, &s.Components.CommandRegistration.ComponentFactory) s.InjectComponent(&d2components.Dirty{}, &s.Components.Dirty.ComponentFactory) @@ -245,8 +235,11 @@ func (s *BaseScene) Key() string { // Update performs scene boot and renders the scene viewports func (s *BaseScene) Update() { - if !s.Booted() { + if !s.booted { s.boot() + } + + if !s.booted { return } @@ -460,13 +453,12 @@ func (s *BaseScene) renderViewportsToMainViewport() { } // RegisterTerminalCommand registers a command that can be executed from the terminal -func (s *BaseScene) RegisterTerminalCommand(name, desc string, args []string, fn func(args []string) error) { +func (s *BaseScene) RegisterTerminalCommand(name, desc string, fn interface{}) { regID := s.NewEntity() reg := s.Components.CommandRegistration.Add(regID) s.Components.Dirty.Add(regID) reg.Name = name reg.Description = desc - reg.Args = args reg.Callback = fn } diff --git a/d2core/d2systems/scene_ebiten_splash.go b/d2core/d2systems/scene_ebiten_splash.go index 09756cc3..ca6d742d 100644 --- a/d2core/d2systems/scene_ebiten_splash.go +++ b/d2core/d2systems/scene_ebiten_splash.go @@ -4,8 +4,6 @@ import ( "image/color" "math" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/gravestench/akara" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2input" @@ -41,7 +39,7 @@ func NewEbitenSplashScene() *EbitenSplashScene { // EbitenSplashScene represents the in-game terminal for typing commands type EbitenSplashScene struct { *BaseScene - state d2enum.SceneState + booted bool squares []akara.EID timeElapsed float64 delay float64 @@ -55,14 +53,14 @@ func (s *EbitenSplashScene) Init(world *akara.World) { } func (s *EbitenSplashScene) boot() { - if !s.BaseScene.Booted() { + if !s.BaseScene.booted { s.BaseScene.boot() return } s.createSplash() - s.state = d2enum.SceneStateBooted + s.booted = true } // Update and render the terminal to the terminal viewport @@ -75,11 +73,11 @@ func (s *EbitenSplashScene) Update() { return } - if s.state == d2enum.SceneStateUninitialized { + if !s.booted { s.boot() } - if s.state != d2enum.SceneStateBooted { + if !s.booted { return } diff --git a/d2core/d2systems/scene_loading_screen.go b/d2core/d2systems/scene_loading_screen.go index 0fdd031e..463402cb 100644 --- a/d2core/d2systems/scene_loading_screen.go +++ b/d2core/d2systems/scene_loading_screen.go @@ -4,8 +4,6 @@ import ( "image/color" "math" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/gravestench/akara" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" @@ -39,10 +37,9 @@ type LoadingScene struct { stage2 *akara.Subscription // has type, no handle stage3 *akara.Subscription // has handle, no asset stage4 *akara.Subscription // is loaded - total *akara.Subscription // total files } progress float64 - state d2enum.SceneState + booted bool } // Init the loading scene @@ -59,15 +56,6 @@ func (s *LoadingScene) Init(world *akara.World) { func (s *LoadingScene) setupSubscriptions() { s.Debug("setting up component subscriptions") - total := s.NewComponentFilter(). - Require( - &d2components.File{}, - ). - Forbid( - &d2components.FileSource{}, - ). - Build() - stage1 := s.NewComponentFilter(). Require( &d2components.File{}, @@ -114,7 +102,6 @@ func (s *LoadingScene) setupSubscriptions() { ). Build() - s.loadStages.total = s.World.AddSubscription(total) // total count of all files at all stages s.loadStages.stage1 = s.World.AddSubscription(stage1) // has path, no type s.loadStages.stage2 = s.World.AddSubscription(stage2) // has type, no handle s.loadStages.stage3 = s.World.AddSubscription(stage3) // has handle, no asset @@ -122,14 +109,14 @@ func (s *LoadingScene) setupSubscriptions() { } func (s *LoadingScene) boot() { - if !s.BaseScene.Booted() { + if !s.BaseScene.booted { s.BaseScene.boot() return } s.createLoadingScreen() - s.state = d2enum.SceneStateBooted + s.booted = true } func (s *LoadingScene) createLoadingScreen() { @@ -147,14 +134,10 @@ func (s *LoadingScene) Update() { return } - if s.state == d2enum.SceneStateUninitialized { + if !s.booted { s.boot() } - if s.state != d2enum.SceneStateBooted { - return - } - s.updateLoadProgress() s.updateViewportAlpha() s.updateLoadingSpritePosition() @@ -164,10 +147,12 @@ func (s *LoadingScene) Update() { } func (s *LoadingScene) updateLoadProgress() { - total := float64(len(s.loadStages.total.GetEntities())) + untyped := float64(len(s.loadStages.stage1.GetEntities())) + unhandled := float64(len(s.loadStages.stage2.GetEntities())) + unparsed := float64(len(s.loadStages.stage3.GetEntities())) loaded := float64(len(s.loadStages.stage4.GetEntities())) - s.progress = loaded / total + s.progress = 1 - ((untyped + unhandled + unparsed) / 3 / loaded) } //nolint:gomnd // arbitrary numbers for test scene diff --git a/d2core/d2systems/scene_main_menu.go b/d2core/d2systems/scene_main_menu.go index 26924b7b..a8a31e7b 100644 --- a/d2core/d2systems/scene_main_menu.go +++ b/d2core/d2systems/scene_main_menu.go @@ -39,8 +39,8 @@ var _ d2interface.Scene = &MainMenuScene{} // or start the map engine test. type MainMenuScene struct { *BaseScene + booted bool logoInit bool - state d2enum.SceneState sprites struct { trademark akara.EID logoFireLeft akara.EID @@ -59,20 +59,18 @@ func (s *MainMenuScene) Init(world *akara.World) { } func (s *MainMenuScene) boot() { - if !s.BaseScene.Booted() { + if !s.BaseScene.booted { s.BaseScene.boot() return } - s.state = d2enum.SceneStateBooting - s.setupViewports() s.createBackground() s.createButtons() s.createTrademarkScreen() s.createLogo() - s.state = d2enum.SceneStateBooted + s.booted = true } func (s *MainMenuScene) setupViewports() { @@ -172,14 +170,10 @@ func (s *MainMenuScene) Update() { return } - if s.state == d2enum.SceneStateUninitialized { + if !s.booted { s.boot() } - if s.state != d2enum.SceneStateBooted { - return - } - if !s.logoInit { s.Debug("attempting logo sprite init") s.initLogoSprites() diff --git a/d2core/d2systems/scene_mouse_cursor.go b/d2core/d2systems/scene_mouse_cursor.go index a36c891a..987e1119 100644 --- a/d2core/d2systems/scene_mouse_cursor.go +++ b/d2core/d2systems/scene_mouse_cursor.go @@ -1,13 +1,9 @@ package d2systems import ( - "fmt" "math" - "strconv" "time" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/gravestench/akara" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" @@ -39,7 +35,7 @@ type MouseCursorScene struct { lastTimeMoved time.Time *BaseScene cursor akara.EID - state d2enum.SceneState + booted bool debug struct { enabled bool } @@ -53,7 +49,7 @@ func (s *MouseCursorScene) Init(world *akara.World) { } func (s *MouseCursorScene) boot() { - if !s.BaseScene.Booted() { + if !s.BaseScene.booted { s.BaseScene.boot() return } @@ -62,7 +58,7 @@ func (s *MouseCursorScene) boot() { s.createMouseCursor() - s.state = d2enum.SceneStateBooted + s.booted = true } func (s *MouseCursorScene) createMouseCursor() { @@ -80,14 +76,10 @@ func (s *MouseCursorScene) Update() { return } - if s.state == d2enum.SceneStateUninitialized { + if !s.booted { s.boot() } - if s.state != d2enum.SceneStateBooted { - return - } - s.updateCursorTransform() s.handleCursorFade() @@ -149,15 +141,8 @@ func (s *MouseCursorScene) registerDebugCommand() { description = "show debug information about the mouse" ) - s.RegisterTerminalCommand(command, description, []string{"val"}, func(args []string) error { - val, err := strconv.ParseBool(args[0]) - if err != nil { - return fmt.Errorf("invalid argument") - } - + s.RegisterTerminalCommand(command, description, func(val bool) { s.setDebug(val) - - return nil }) } diff --git a/d2core/d2systems/scene_terminal.go b/d2core/d2systems/scene_terminal.go index ad995007..961a0c11 100644 --- a/d2core/d2systems/scene_terminal.go +++ b/d2core/d2systems/scene_terminal.go @@ -4,8 +4,6 @@ import ( "image/color" "time" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2components" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2input" @@ -46,7 +44,7 @@ type TerminalScene struct { d2interface.Terminal d2interface.InputManager commandsToRegister *akara.Subscription - state d2enum.SceneState + booted bool } // Init the terminal @@ -72,14 +70,14 @@ func (s *TerminalScene) setupSubscriptions() { } func (s *TerminalScene) boot() { - if !s.BaseScene.Booted() { + if !s.BaseScene.booted { s.BaseScene.boot() return } s.createTerminal() - s.state = d2enum.SceneStateBooted + s.booted = true } // Update and render the terminal to the terminal viewport @@ -92,11 +90,11 @@ func (s *TerminalScene) Update() { return } - if s.state == d2enum.SceneStateUninitialized { + if !s.booted { s.boot() } - if s.state != d2enum.SceneStateBooted { + if !s.booted { return } @@ -120,7 +118,7 @@ func (s *TerminalScene) processCommand(eid akara.EID) { s.Infof("Registering command `%s` - %s", reg.Name, reg.Description) - err := s.Terminal.Bind(reg.Name, reg.Description, nil, reg.Callback) + err := s.Terminal.BindAction(reg.Name, reg.Description, reg.Callback) if err != nil { s.Error(err.Error()) } diff --git a/d2core/d2systems/scene_test_button.go b/d2core/d2systems/scene_test_button.go index 0fa36b5f..99320a4e 100644 --- a/d2core/d2systems/scene_test_button.go +++ b/d2core/d2systems/scene_test_button.go @@ -3,8 +3,6 @@ package d2systems import ( "github.com/gravestench/akara" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2button" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" @@ -32,7 +30,7 @@ var _ d2interface.Scene = &ButtonTestScene{} // or start the map engine test. type ButtonTestScene struct { *BaseScene - state d2enum.SceneState + booted bool buttons *akara.Subscription } @@ -51,7 +49,7 @@ func (s *ButtonTestScene) Init(world *akara.World) { } func (s *ButtonTestScene) boot() { - if !s.BaseScene.Booted() { + if !s.BaseScene.booted { s.BaseScene.boot() return } @@ -60,7 +58,7 @@ func (s *ButtonTestScene) boot() { s.createButtons() - s.state = d2enum.SceneStateBooted + s.booted = true } func (s *ButtonTestScene) createButtons() { @@ -73,14 +71,10 @@ func (s *ButtonTestScene) Update() { return } - if s.state == d2enum.SceneStateUninitialized { + if !s.booted { s.boot() } - if s.state != d2enum.SceneStateBooted { - return - } - for _, eid := range s.buttons.GetEntities() { s.updateButtonPosition(eid) } diff --git a/d2core/d2systems/scene_test_checkbox.go b/d2core/d2systems/scene_test_checkbox.go index 6899b8fd..bee3b626 100644 --- a/d2core/d2systems/scene_test_checkbox.go +++ b/d2core/d2systems/scene_test_checkbox.go @@ -4,8 +4,6 @@ import ( "image/color" "log" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/gravestench/akara" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" @@ -33,7 +31,7 @@ var _ d2interface.Scene = &CheckboxTestScene{} // or start the map engine test. type CheckboxTestScene struct { *BaseScene - state d2enum.SceneState + booted bool checkboxes *akara.Subscription } @@ -52,7 +50,7 @@ func (s *CheckboxTestScene) Init(world *akara.World) { } func (s *CheckboxTestScene) boot() { - if !s.BaseScene.Booted() { + if !s.BaseScene.booted { s.BaseScene.boot() return } @@ -68,7 +66,7 @@ func (s *CheckboxTestScene) boot() { s.createCheckboxes() - s.state = d2enum.SceneStateBooted + s.booted = true } //nolint:gomnd // arbitrary example numbers for test @@ -86,14 +84,10 @@ func (s *CheckboxTestScene) Update() { return } - if s.state == d2enum.SceneStateUninitialized { + if !s.booted { s.boot() } - if s.state != d2enum.SceneStateBooted { - return - } - s.BaseScene.Update() } diff --git a/d2core/d2systems/scene_test_label.go b/d2core/d2systems/scene_test_label.go index 2d546b93..735d6413 100644 --- a/d2core/d2systems/scene_test_label.go +++ b/d2core/d2systems/scene_test_label.go @@ -4,8 +4,6 @@ import ( "image/color" "math/rand" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/gravestench/akara" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" @@ -34,7 +32,7 @@ var _ d2interface.Scene = &LabelTestScene{} // or start the map engine test. type LabelTestScene struct { *BaseScene - state d2enum.SceneState + booted bool labels *akara.Subscription velocity d2components.VelocityFactory } @@ -52,7 +50,7 @@ func (s *LabelTestScene) Init(world *akara.World) { } func (s *LabelTestScene) boot() { - if !s.BaseScene.Booted() { + if !s.BaseScene.booted { s.BaseScene.boot() return } @@ -61,7 +59,7 @@ func (s *LabelTestScene) boot() { s.createLabels() - s.state = d2enum.SceneStateBooted + s.booted = true } //nolint:gosec,gomnd // test scene, weak RNG is fine @@ -113,13 +111,9 @@ func (s *LabelTestScene) Update() { return } - if s.state == d2enum.SceneStateUninitialized { + if !s.booted { s.boot() } - if s.state != d2enum.SceneStateBooted { - return - } - s.BaseScene.Update() } diff --git a/d2core/d2systems/scene_widget_system.go b/d2core/d2systems/scene_widget_system.go index c6ff5d23..e9779557 100644 --- a/d2core/d2systems/scene_widget_system.go +++ b/d2core/d2systems/scene_widget_system.go @@ -5,8 +5,6 @@ import ( "image/color" "time" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/gravestench/akara" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2cache" @@ -77,11 +75,11 @@ type UIWidgetFactory struct { buttonLoadQueue checkboxLoadQueue labelLoadQueue - state d2enum.SceneState bitmapFontCache d2interface.Cache labelsToUpdate *akara.Subscription buttonsToUpdate *akara.Subscription checkboxesToUpdate *akara.Subscription + booted bool Components struct { File d2components.FileFactory Transform d2components.TransformFactory @@ -157,7 +155,7 @@ func (t *UIWidgetFactory) boot() { return } - t.state = d2enum.SceneStateBooted + t.booted = true } // Update processes the load queues and update the widgets. The load queues are necessary because @@ -165,7 +163,7 @@ func (t *UIWidgetFactory) boot() { func (t *UIWidgetFactory) Update() { start := time.Now() - if t.state != d2enum.SceneStateBooted { + if !t.booted { t.boot() return } diff --git a/d2core/d2systems/timescale.go b/d2core/d2systems/timescale.go index af878bce..a61b13c9 100644 --- a/d2core/d2systems/timescale.go +++ b/d2core/d2systems/timescale.go @@ -1,8 +1,6 @@ package d2systems import ( - "fmt" - "strconv" "time" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2components" @@ -64,21 +62,15 @@ func (t *TimeScaleSystem) Update() { func (t *TimeScaleSystem) registerCommands() { e := t.NewEntity() + reg := t.Components.CommandRegistration.Add(e) + t.Components.Dirty.Add(e) reg.Name = "timescale" reg.Description = "set the time scale of the game (default is 1.0)" - reg.Args = []string{"scale"} - reg.Callback = func(args []string) error { - val, err := strconv.ParseFloat(args[0], 64) - if err != nil { - return fmt.Errorf("invalid argument") - } - - t.Infof("setting time scale to %.1f", val) - t.scale = val - - return nil + reg.Callback = func(scale float64) { + t.Infof("setting time scale to %.1f", scale) + t.scale = scale } } diff --git a/d2core/d2term/commmand.go b/d2core/d2term/commmand.go deleted file mode 100644 index d72a121d..00000000 --- a/d2core/d2term/commmand.go +++ /dev/null @@ -1,33 +0,0 @@ -package d2term - -import ( - "sort" -) - -func (t *Terminal) commandList([]string) error { - names := make([]string, 0, len(t.commands)) - for name := range t.commands { - names = append(names, name) - } - - sort.Strings(names) - t.Infof("available actions (%d):", len(names)) - - for _, name := range names { - entry := t.commands[name] - if entry.arguments != nil { - t.Infof("%s: %s; %v", name, entry.description, entry.arguments) - continue - } - - t.Infof("%s: %s", name, entry.description) - } - - return nil -} - -func (t *Terminal) commandClear([]string) error { - t.Clear() - - return nil -} diff --git a/d2core/d2term/d2term.go b/d2core/d2term/d2term.go index 4e478784..561b144b 100644 --- a/d2core/d2term/d2term.go +++ b/d2core/d2term/d2term.go @@ -6,8 +6,8 @@ import ( ) // New creates and initializes the terminal -func New(inputManager d2interface.InputManager) (*Terminal, error) { - term, err := NewTerminal() +func New(inputManager d2interface.InputManager) (d2interface.Terminal, error) { + term, err := createTerminal() if err != nil { return nil, err } diff --git a/d2core/d2term/terminal.go b/d2core/d2term/terminal.go index 465905a0..f83a5089 100644 --- a/d2core/d2term/terminal.go +++ b/d2core/d2term/terminal.go @@ -6,22 +6,24 @@ import ( "image/color" "log" "math" + "reflect" + "sort" + "strconv" "strings" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" ) const ( - charWidth = 6 - charHeight = 16 - charDoubleWidth = charWidth * 2 - rowCount = 24 - rowCountMax = 32 - colCountMax = 128 - animLength = 0.5 + termCharWidth = 6 + termCharHeight = 16 + termCharDoubleWidth = termCharWidth * 2 + termRowCount = 24 + termRowCountMax = 32 + termColCountMax = 128 + termAnimLength = 0.5 ) const ( @@ -32,13 +34,13 @@ const ( red = 0xcc0000b0 ) -type visibility int +type termVis int const ( - visHidden visibility = iota - visShowing - visShown - visHiding + termVisHidden termVis = iota + termVisShowing + termVisShown + termVisHiding ) const ( @@ -46,20 +48,18 @@ const ( minVisAnim = 0.0 ) -type historyEntry struct { +type termHistoryEntry struct { text string category d2enum.TermCategory } -type commandEntry struct { +type termActionEntry struct { + action interface{} description string - arguments []string - fn func([]string) error } -// Terminal handles the in-game terminal -type Terminal struct { - outputHistory []historyEntry +type terminal struct { + outputHistory []termHistoryEntry outputIndex int command string @@ -67,7 +67,7 @@ type Terminal struct { commandIndex int lineCount int - visState visibility + visState termVis visAnim float64 bgColor color.RGBA @@ -76,88 +76,36 @@ type Terminal struct { warningColor color.RGBA errorColor color.RGBA - commands map[string]commandEntry + actions map[string]termActionEntry } -// NewTerminal creates and returns a terminal -func NewTerminal() (*Terminal, error) { - term := &Terminal{ - lineCount: rowCount, - bgColor: d2util.Color(darkGrey), - fgColor: d2util.Color(lightGrey), - infoColor: d2util.Color(lightBlue), - warningColor: d2util.Color(yellow), - errorColor: d2util.Color(red), - commands: make(map[string]commandEntry), - } - - term.Infof("::: OpenDiablo2 Terminal :::") - term.Infof("type \"ls\" for a list of commands") - - if err := term.Bind("ls", "list available commands", nil, term.commandList); err != nil { - return nil, err - } - - if err := term.Bind("clear", "clear terminal", nil, term.commandClear); err != nil { - return nil, err - } - - return term, nil -} - -// Bind binds commands to the terminal -func (t *Terminal) Bind(name, description string, arguments []string, fn func(args []string) error) error { - if name == "" || description == "" { - return fmt.Errorf("missing name or description") - } - - if _, ok := t.commands[name]; ok { - t.Warningf("rebinding command with name: %s", name) - } - - t.commands[name] = commandEntry{description, arguments, fn} - - return nil -} - -// Unbind unbinds commands from the terminal -func (t *Terminal) Unbind(names ...string) error { - for _, name := range names { - delete(t.commands, name) - } - - return nil -} - -// Advance advances the terminal animation -func (t *Terminal) Advance(elapsed float64) error { +func (t *terminal) Advance(elapsed float64) error { switch t.visState { - case visShowing: - t.visAnim = math.Min(maxVisAnim, t.visAnim+elapsed/animLength) + case termVisShowing: + t.visAnim = math.Min(maxVisAnim, t.visAnim+elapsed/termAnimLength) if t.visAnim == maxVisAnim { - t.visState = visShown + t.visState = termVisShown } - case visHiding: - t.visAnim = math.Max(minVisAnim, t.visAnim-elapsed/animLength) + case termVisHiding: + t.visAnim = math.Max(minVisAnim, t.visAnim-elapsed/termAnimLength) if t.visAnim == minVisAnim { - t.visState = visHidden + t.visState = termVisHidden } } - if !t.Visible() { + if !t.IsVisible() { return nil } return nil } -// OnKeyDown handles key down in the terminal -func (t *Terminal) OnKeyDown(event d2interface.KeyEvent) bool { +func (t *terminal) OnKeyDown(event d2interface.KeyEvent) bool { if event.Key() == d2enum.KeyGraveAccent { - t.toggle() + t.toggleTerminal() } - if !t.Visible() { + if !t.IsVisible() { return false } @@ -190,7 +138,7 @@ func (t *Terminal) OnKeyDown(event d2interface.KeyEvent) bool { return true } -func (t *Terminal) processCommand() { +func (t *terminal) processCommand() { if t.command == "" { return } @@ -207,17 +155,17 @@ func (t *Terminal) processCommand() { t.commandHistory = t.commandHistory[:n] t.commandHistory = append(t.commandHistory, t.command) - t.Printf(t.command) + t.Outputf(t.command) if err := t.Execute(t.command); err != nil { - t.Errorf(err.Error()) + t.OutputErrorf(err.Error()) } t.commandIndex = len(t.commandHistory) - 1 t.command = "" } -func (t *Terminal) handleControlKey(eventKey d2enum.Key, keyMod d2enum.KeyMod) { +func (t *terminal) handleControlKey(eventKey d2enum.Key, keyMod d2enum.KeyMod) { switch eventKey { case d2enum.KeyUp: if keyMod == d2enum.KeyModControl { @@ -232,14 +180,21 @@ func (t *Terminal) handleControlKey(eventKey d2enum.Key, keyMod d2enum.KeyMod) { } case d2enum.KeyDown: if keyMod == d2enum.KeyModControl { - t.lineCount = d2math.MinInt(t.lineCount+1, rowCountMax) + t.lineCount = d2math.MinInt(t.lineCount+1, termRowCountMax) } } } -// OnKeyChars handles char key in terminal -func (t *Terminal) OnKeyChars(event d2interface.KeyCharsEvent) bool { - if !t.Visible() { +func (t *terminal) toggleTerminal() { + if t.visState == termVisHiding || t.visState == termVisHidden { + t.Show() + } else { + t.Hide() + } +} + +func (t *terminal) OnKeyChars(event d2interface.KeyCharsEvent) bool { + if !t.IsVisible() { return false } @@ -255,15 +210,14 @@ func (t *Terminal) OnKeyChars(event d2interface.KeyCharsEvent) bool { return handled } -// Render renders the terminal -func (t *Terminal) Render(surface d2interface.Surface) error { - if !t.Visible() { +func (t *terminal) Render(surface d2interface.Surface) error { + if !t.IsVisible() { return nil } totalWidth, _ := surface.GetSize() - outputHeight := t.lineCount * charHeight - totalHeight := outputHeight + charHeight + outputHeight := t.lineCount * termCharHeight + totalHeight := outputHeight + termCharHeight offset := -int((1.0 - easeInOut(t.visAnim)) * float64(totalHeight)) surface.PushTranslation(0, offset) @@ -276,19 +230,19 @@ func (t *Terminal) Render(surface d2interface.Surface) error { break } - entry := t.outputHistory[historyIndex] + historyEntry := t.outputHistory[historyIndex] - surface.PushTranslation(charDoubleWidth, outputHeight-(i+1)*charHeight) - surface.DrawTextf(entry.text) - surface.PushTranslation(-charDoubleWidth, 0) + surface.PushTranslation(termCharDoubleWidth, outputHeight-(i+1)*termCharHeight) + surface.DrawTextf(historyEntry.text) + surface.PushTranslation(-termCharDoubleWidth, 0) - switch entry.category { + switch historyEntry.category { case d2enum.TermCategoryInfo: - surface.DrawRect(charWidth, charHeight, t.infoColor) + surface.DrawRect(termCharWidth, termCharHeight, t.infoColor) case d2enum.TermCategoryWarning: - surface.DrawRect(charWidth, charHeight, t.warningColor) + surface.DrawRect(termCharWidth, termCharHeight, t.warningColor) case d2enum.TermCategoryError: - surface.DrawRect(charWidth, charHeight, t.errorColor) + surface.DrawRect(termCharWidth, termCharHeight, t.errorColor) } surface.Pop() @@ -296,7 +250,7 @@ func (t *Terminal) Render(surface d2interface.Surface) error { } surface.PushTranslation(0, outputHeight) - surface.DrawRect(totalWidth, charHeight, t.fgColor) + surface.DrawRect(totalWidth, termCharHeight, t.fgColor) surface.DrawTextf("> " + t.command) surface.Pop() @@ -305,105 +259,184 @@ func (t *Terminal) Render(surface d2interface.Surface) error { return nil } -// Execute executes a command with arguments -func (t *Terminal) Execute(command string) error { +func (t *terminal) Execute(command string) error { params := parseCommand(command) if len(params) == 0 { return errors.New("invalid command") } - name := params[0] - args := params[1:] + actionName := params[0] + actionParams := params[1:] - entry, ok := t.commands[name] + actionEntry, ok := t.actions[actionName] if !ok { - return errors.New("command not found") + return errors.New("action not found") } - if len(args) != len(entry.arguments) { - return errors.New("command requires different argument count") + actionType := reflect.TypeOf(actionEntry.action) + if actionType.Kind() != reflect.Func { + return errors.New("action is not a function") } - if err := entry.fn(args); err != nil { + if len(actionParams) != actionType.NumIn() { + return errors.New("action requires different argument count") + } + + paramValues, err := parseActionParams(actionType, actionParams) + if err != nil { return err } + actionValue := reflect.ValueOf(actionEntry.action) + actionReturnValues := actionValue.Call(paramValues) + + if actionReturnValueCount := len(actionReturnValues); actionReturnValueCount > 0 { + t.OutputInfof("function returned %d values:", actionReturnValueCount) + + for _, actionReturnValue := range actionReturnValues { + t.OutputInfof("%v: %s", actionReturnValue.Interface(), actionReturnValue.String()) + } + } + return nil } -// Rawf writes a raw message to the terminal -func (t *Terminal) Rawf(category d2enum.TermCategory, format string, params ...interface{}) { - text := fmt.Sprintf(format, params...) - lines := d2util.SplitIntoLinesWithMaxWidth(text, colCountMax) +func parseActionParams(actionType reflect.Type, actionParams []string) ([]reflect.Value, error) { + var paramValues []reflect.Value - for _, line := range lines { - // removes color token (this token ends with [0m ) - l := strings.Split(line, "\033[0m") - line = l[len(l)-1] + for i := 0; i < actionType.NumIn(); i++ { + actionParam := actionParams[i] - t.outputHistory = append(t.outputHistory, historyEntry{line, category}) + switch actionType.In(i).Kind() { + case reflect.String: + paramValues = append(paramValues, reflect.ValueOf(actionParam)) + case reflect.Int: + value, err := strconv.ParseInt(actionParam, 10, 64) + if err != nil { + return nil, err + } + + paramValues = append(paramValues, reflect.ValueOf(int(value))) + case reflect.Uint: + value, err := strconv.ParseUint(actionParam, 10, 64) + if err != nil { + return nil, err + } + + paramValues = append(paramValues, reflect.ValueOf(uint(value))) + case reflect.Float64: + value, err := strconv.ParseFloat(actionParam, 64) + if err != nil { + return nil, err + } + + paramValues = append(paramValues, reflect.ValueOf(value)) + case reflect.Bool: + value, err := strconv.ParseBool(actionParam) + if err != nil { + return nil, err + } + + paramValues = append(paramValues, reflect.ValueOf(value)) + default: + return nil, errors.New("action has unsupported arguments") + } } + + return paramValues, nil } -// Printf writes a message to the terminal -func (t *Terminal) Printf(format string, params ...interface{}) { - t.Rawf(d2enum.TermCategoryNone, format, params...) +func (t *terminal) OutputRaw(text string, category d2enum.TermCategory) { + var line string + + for _, word := range strings.Split(text, " ") { + if len(line) > 0 { + line += " " + } + + lineLength := len(line) + wordLength := len(word) + + if lineLength+wordLength >= termColCountMax { + t.outputHistory = append(t.outputHistory, termHistoryEntry{line, category}) + line = word + } else { + line += word + } + } + + t.outputHistory = append(t.outputHistory, termHistoryEntry{line, category}) } -// Infof writes a warning message to the terminal -func (t *Terminal) Infof(format string, params ...interface{}) { - t.Rawf(d2enum.TermCategoryInfo, format, params...) +func (t *terminal) Outputf(format string, params ...interface{}) { + t.OutputRaw(fmt.Sprintf(format, params...), d2enum.TermCategoryNone) } -// Warningf writes a warning message to the terminal -func (t *Terminal) Warningf(format string, params ...interface{}) { - t.Rawf(d2enum.TermCategoryWarning, format, params...) +func (t *terminal) OutputInfof(format string, params ...interface{}) { + t.OutputRaw(fmt.Sprintf(format, params...), d2enum.TermCategoryInfo) } -// Errorf writes a error message to the terminal -func (t *Terminal) Errorf(format string, params ...interface{}) { - t.Rawf(d2enum.TermCategoryError, format, params...) +func (t *terminal) OutputWarningf(format string, params ...interface{}) { + t.OutputRaw(fmt.Sprintf(format, params...), d2enum.TermCategoryWarning) } -// Clear clears the terminal -func (t *Terminal) Clear() { +func (t *terminal) OutputErrorf(format string, params ...interface{}) { + t.OutputRaw(fmt.Sprintf(format, params...), d2enum.TermCategoryError) +} + +func (t *terminal) OutputClear() { t.outputHistory = nil t.outputIndex = 0 } -// Visible returns visible state -func (t *Terminal) Visible() bool { - return t.visState != visHidden +func (t *terminal) IsVisible() bool { + return t.visState != termVisHidden } -// Hide hides the terminal -func (t *Terminal) Hide() { - if t.visState != visHidden { - t.visState = visHiding +func (t *terminal) Hide() { + if t.visState != termVisHidden { + t.visState = termVisHiding } } -// Show shows the terminal -func (t *Terminal) Show() { - if t.visState != visShown { - t.visState = visShowing +func (t *terminal) Show() { + if t.visState != termVisShown { + t.visState = termVisShowing } } -func (t *Terminal) toggle() { - if t.visState == visHiding || t.visState == visHidden { - t.Show() - return +func (t *terminal) BindAction(name, description string, action interface{}) error { + actionType := reflect.TypeOf(action) + if actionType.Kind() != reflect.Func { + return errors.New("action is not a function") } - t.Hide() + for i := 0; i < actionType.NumIn(); i++ { + switch actionType.In(i).Kind() { + case reflect.String: + case reflect.Int: + case reflect.Uint: + case reflect.Float64: + case reflect.Bool: + default: + return errors.New("action has unsupported arguments") + } + } + + t.actions[name] = termActionEntry{action, description} + + return nil } -// BindLogger binds a log.Writer to the output -func (t *Terminal) BindLogger() { +func (t *terminal) BindLogger() { log.SetOutput(&terminalLogger{writer: log.Writer(), terminal: t}) } +func (t *terminal) UnbindAction(name string) error { + delete(t.actions, name) + return nil +} + func easeInOut(t float64) float64 { t *= 2 if t < 1 { @@ -457,3 +490,70 @@ func parseCommand(command string) []string { return params } + +func rgbaColor(rgba uint32) color.RGBA { + result := color.RGBA{} + a, b, g, r := 0, 1, 2, 3 + byteWidth := 8 + byteMask := 0xff + + for idx := 0; idx < 4; idx++ { + shift := idx * byteWidth + component := uint8(rgba>>shift) & uint8(byteMask) + + switch idx { + case a: + result.A = component + case b: + result.B = component + case g: + result.G = component + case r: + result.R = component + } + } + + return result +} + +func createTerminal() (*terminal, error) { + terminal := &terminal{ + lineCount: termRowCount, + bgColor: rgbaColor(darkGrey), + fgColor: rgbaColor(lightGrey), + infoColor: rgbaColor(lightBlue), + warningColor: rgbaColor(yellow), + errorColor: rgbaColor(red), + actions: make(map[string]termActionEntry), + } + + terminal.OutputInfof("::: OpenDiablo2 Terminal :::") + terminal.OutputInfof("type \"ls\" for a list of actions") + + err := terminal.BindAction("ls", "list available actions", func() { + var names []string + for name := range terminal.actions { + names = append(names, name) + } + + sort.Strings(names) + + terminal.OutputInfof("available actions (%d):", len(names)) + for _, name := range names { + entry := terminal.actions[name] + terminal.OutputInfof("%s: %s; %s", name, entry.description, reflect.TypeOf(entry.action).String()) + } + }) + if err != nil { + return nil, fmt.Errorf("failed to bind the '%s' action, err: %w", "ls", err) + } + + err = terminal.BindAction("clear", "clear terminal", func() { + terminal.OutputClear() + }) + if err != nil { + return nil, fmt.Errorf("failed to bind the '%s' action, err: %w", "clear", err) + } + + return terminal, nil +} diff --git a/d2core/d2term/terminal_logger.go b/d2core/d2term/terminal_logger.go index 5e0dbb08..62eb67c8 100644 --- a/d2core/d2term/terminal_logger.go +++ b/d2core/d2term/terminal_logger.go @@ -8,7 +8,7 @@ import ( ) type terminalLogger struct { - terminal *Terminal + terminal *terminal buffer bytes.Buffer writer io.Writer } @@ -31,16 +31,16 @@ func (tl *terminalLogger) Write(p []byte) (int, error) { switch { case strings.Index(lineLower, "error") > 0: - tl.terminal.Errorf(line) + tl.terminal.OutputErrorf(line) case strings.Index(lineLower, "warning") > 0: - tl.terminal.Warningf(line) + tl.terminal.OutputWarningf(line) default: - tl.terminal.Printf(line) + tl.terminal.Outputf(line) } return tl.writer.Write(p) } -func (tl *terminalLogger) BindToTerminal(t *Terminal) { +func (tl *terminalLogger) BindToTerminal(t *terminal) { tl.terminal = t } diff --git a/d2core/d2term/terminal_test.go b/d2core/d2term/terminal_test.go deleted file mode 100644 index a2332f01..00000000 --- a/d2core/d2term/terminal_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package d2term - -import ( - "fmt" - "testing" -) - -func TestTerminal(t *testing.T) { - term, err := NewTerminal() - if err != nil { - t.Fatal(err) - } - - lenOutput := len(term.outputHistory) - - const expected1 = 2 - if lenOutput != expected1 { - t.Fatalf("got %d expected %d", lenOutput, expected1) - } - - if err := term.Execute("clear"); err != nil { - t.Fatal(err) - } - - if err := term.Execute("ls"); err != nil { - t.Fatal(err) - } - - lenOutput = len(term.outputHistory) - - const expected2 = 3 - if lenOutput != expected2 { - t.Fatalf("got %d expected %d", lenOutput, expected2) - } -} - -func TestBind(t *testing.T) { - term, err := NewTerminal() - if err != nil { - t.Fatal(err) - } - - term.Clear() - - if err := term.Bind("hello", "world", []string{"world"}, func(args []string) error { - const expected = "world" - if args[0] != expected { - return fmt.Errorf("got %s expected %s", args[0], expected) - } - return nil - }); err != nil { - t.Fatal(err) - } - - if err := term.Execute("hello world"); err != nil { - t.Fatal(err) - } -} - -func TestUnbind(t *testing.T) { - term, err := NewTerminal() - if err != nil { - t.Fatal(err) - } - - if err := term.Unbind("clear"); err != nil { - t.Fatal(err) - } - - term.Clear() - - if err := term.Execute("ls"); err != nil { - t.Fatal(err) - } - - lenOutput := len(term.outputHistory) - - const expected = 2 - if lenOutput != expected { - t.Fatalf("got %d expected %d", lenOutput, expected) - } -} diff --git a/d2core/d2ui/button.go b/d2core/d2ui/button.go index aad9c1c4..cb61ebec 100644 --- a/d2core/d2ui/button.go +++ b/d2core/d2ui/button.go @@ -55,7 +55,6 @@ const ( ButtonTypeSquelchChat ButtonType = 35 ButtonTypeTabBlank ButtonType = 36 ButtonTypeBlankQuestBtn ButtonType = 37 - ButtonTypeAddSkill ButtonType = 38 ButtonNoFixedWidth int = -1 ButtonNoFixedHeight int = -1 @@ -181,7 +180,7 @@ const ( blankQuestButtonXSegments = 1 blankQuestButtonYSegments = 1 - blankQuestButtonDisabledFrames = 0 + blankQuestButtonDisabledFrames = -1 buttonMinipanelCharacterBaseFrame = 0 buttonMinipanelInventoryBaseFrame = 2 @@ -200,10 +199,6 @@ const ( buttonGoldCoinSegmentsY = 1 buttonGoldCoinDisabledFrame = -1 - buttonAddSkillSegmentsX = 1 - buttonAddSkillSegmentsY = 1 - buttonAddSkillDisabledFrame = 2 - pressedButtonOffset = 2 ) @@ -751,20 +746,6 @@ func getButtonLayouts() map[ButtonType]ButtonLayout { FixedHeight: ButtonNoFixedHeight, LabelColor: whiteAlpha100, }, - ButtonTypeAddSkill: { - XSegments: buttonAddSkillSegmentsX, - YSegments: buttonAddSkillSegmentsY, - DisabledFrame: buttonAddSkillDisabledFrame, - DisabledColor: whiteAlpha100, - ResourceName: d2resource.AddSkillButton, - PaletteName: d2resource.PaletteSky, - Toggleable: true, - FontPath: d2resource.Font16, - AllowFrameChange: true, - HasImage: true, - FixedWidth: ButtonNoFixedWidth, - FixedHeight: ButtonNoFixedHeight, - }, } } @@ -867,7 +848,8 @@ type buttonStateDescriptor struct { func (v *Button) createTooltip() { var t *Tooltip - + // this is also related with https://github.com/OpenDiablo2/OpenDiablo2/issues/944 + // all strings starting with "#" could be wrong translated to another locales switch v.buttonLayout.Tooltip { case buttonTooltipNone: return @@ -888,7 +870,7 @@ func (v *Button) createTooltip() { t.SetText(v.manager.asset.TranslateString("NPCRepairItems")) case buttonTooltipRepairAll: t = v.manager.NewTooltip(d2resource.Font16, d2resource.PaletteSky, TooltipXCenter, TooltipYBottom) - t.SetText(v.manager.asset.TranslateLabel(d2enum.RepairAll)) + t.SetText(v.manager.asset.TranslateString("#128")) case buttonTooltipLeftArrow: t = v.manager.NewTooltip(d2resource.Font16, d2resource.PaletteSky, TooltipXCenter, TooltipYBottom) t.SetText(v.manager.asset.TranslateString("KeyLeft")) @@ -923,7 +905,7 @@ func (v *Button) prerenderStates(btnSprite *Sprite, btnLayout *ButtonLayout, lab label.SetPosition(xOffset, textY) label.Render(v.normalSurface) - if !btnLayout.AllowFrameChange { + if !btnLayout.HasImage || !btnLayout.AllowFrameChange { return } @@ -1025,7 +1007,7 @@ func (v *Button) Render(target d2interface.Surface) { if v.toggled { target.Render(v.toggledSurface) - } else if v.buttonLayout.HasImage { // it allows to use SetEnabled(false) for non-image budons + } else { target.Render(v.disabledSurface) } case v.toggled && v.pressed: diff --git a/d2core/d2ui/checkbox.go b/d2core/d2ui/checkbox.go index 84984764..c91bccbe 100644 --- a/d2core/d2ui/checkbox.go +++ b/d2core/d2ui/checkbox.go @@ -6,8 +6,8 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" ) -// static check that Checkbox implements ClickableWidget -var _ ClickableWidget = &Checkbox{} +// static check that Checkbox implements Widget +var _ Widget = &Checkbox{} // Checkbox represents a checkbox UI element type Checkbox struct { diff --git a/d2core/d2ui/frame.go b/d2core/d2ui/frame.go index 4255d61e..4d66c1cd 100644 --- a/d2core/d2ui/frame.go +++ b/d2core/d2ui/frame.go @@ -3,7 +3,7 @@ package d2ui import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" ) // static check that UIFrame implements Widget @@ -21,9 +21,9 @@ const ( // when it is visible. type UIFrame struct { *BaseWidget + asset *d2asset.AssetManager frame *Sprite frameOrientation frameOrientation - *d2util.Logger } // frame indices into dc6 images for panels @@ -41,7 +41,11 @@ const ( ) // NewUIFrame creates a new Frame instance -func (ui *UIManager) NewUIFrame(frameOrientation frameOrientation) *UIFrame { +func NewUIFrame( + asset *d2asset.AssetManager, + uiManager *UIManager, + frameOrientation frameOrientation, +) *UIFrame { var originX, originY = 0, 0 switch frameOrientation { @@ -53,29 +57,32 @@ func (ui *UIManager) NewUIFrame(frameOrientation frameOrientation) *UIFrame { originY = 0 } - base := NewBaseWidget(ui) - base.SetVisible(false) + base := NewBaseWidget(uiManager) base.SetPosition(originX, originY) frame := &UIFrame{ BaseWidget: base, + asset: asset, frameOrientation: frameOrientation, - Logger: ui.Logger, } + frame.Load() - sprite, err := ui.NewSprite(d2resource.Frame, d2resource.PaletteSky) - if err != nil { - frame.Error(err.Error()) - } - - frame.frame = sprite - frame.calculateSize() - - ui.addWidget(frame) + frame.asset.Logger.SetPrefix(logPrefix) // workaround return frame } +// Load the necessary frame resources +func (u *UIFrame) Load() { + sprite, err := u.manager.NewSprite(d2resource.Frame, d2resource.PaletteSky) + if err != nil { + u.asset.Logger.Error(err.Error()) + } + + u.frame = sprite + u.calculateSize() +} + func (u *UIFrame) calculateSize() { var framesWidth, framesHeight []int @@ -104,7 +111,7 @@ func (u *UIFrame) calculateSize() { for i := range framesWidth { w, _, err := u.frame.GetFrameSize(framesWidth[i]) if err != nil { - u.Error(err.Error()) + u.asset.Logger.Error(err.Error()) } u.width += w @@ -113,7 +120,7 @@ func (u *UIFrame) calculateSize() { for i := range framesHeight { _, h, err := u.frame.GetFrameSize(framesHeight[i]) if err != nil { - u.Error(err.Error()) + u.asset.Logger.Error(err.Error()) } u.height += h @@ -125,11 +132,11 @@ func (u *UIFrame) Render(target d2interface.Surface) { switch u.frameOrientation { case FrameLeft: if err := u.renderLeft(target); err != nil { - u.Error("Render error" + err.Error()) + u.asset.Logger.Error("Render error" + err.Error()) } case FrameRight: if err := u.renderRight(target); err != nil { - u.Error("Render error" + err.Error()) + u.asset.Logger.Error("Render error" + err.Error()) } } } diff --git a/d2core/d2ui/label.go b/d2core/d2ui/label.go index d151507d..3582ed44 100644 --- a/d2core/d2ui/label.go +++ b/d2core/d2ui/label.go @@ -11,9 +11,6 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" ) -// static check if Label implemented Widget -var _ Widget = &Label{} - // Label represents a user interface label type Label struct { *BaseWidget @@ -46,10 +43,6 @@ func (ui *UIManager) NewLabel(fontPath, palettePath string) *Label { result.bindManager(ui) - result.SetVisible(false) - - ui.addWidget(result) - return result } @@ -100,6 +93,11 @@ func (v *Label) Render(target d2interface.Surface) { target.Pop() } +// GetSize returns the size of the label +func (v *Label) GetSize() (width, height int) { + return v.font.GetTextMetrics(v.text) +} + // GetTextMetrics returns the width and height of the enclosing rectangle in Pixels. func (v *Label) GetTextMetrics(text string) (width, height int) { return v.font.GetTextMetrics(text) @@ -108,12 +106,6 @@ func (v *Label) GetTextMetrics(text string) (width, height int) { // SetText sets the label's text func (v *Label) SetText(newText string) { v.text = v.processColorTokens(newText) - v.BaseWidget.width, v.BaseWidget.height = v.font.GetTextMetrics(v.text) -} - -// GetText returns label text -func (v *Label) GetText() string { - return v.text } // SetBackgroundColor sets the background highlight color diff --git a/d2core/d2ui/label_button.go b/d2core/d2ui/label_button.go deleted file mode 100644 index 0c8c1e38..00000000 --- a/d2core/d2ui/label_button.go +++ /dev/null @@ -1,121 +0,0 @@ -package d2ui - -import ( - "image/color" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" -) - -const ( - white = 0xffffffff -) - -// static checks to ensure LabelButton implemented Widget and ClickableWidget -var _ Widget = &LabelButton{} -var _ ClickableWidget = &LabelButton{} - -// LabelButton represents LabelButton -type LabelButton struct { - *BaseWidget - stdColor color.Color - hoverColor color.Color - onClick func() - label *Label - - *d2util.Logger -} - -// NewLabelButton creates a label-button -func (ui *UIManager) NewLabelButton(font, palette string) *LabelButton { - base := NewBaseWidget(ui) - base.SetVisible(true) - - result := &LabelButton{ - BaseWidget: base, - stdColor: d2util.Color(white), - } - - result.label = ui.NewLabel(font, palette) - result.label.Alignment = HorizontalAlignCenter - result.label.Color[0] = result.stdColor - - ui.addWidget(result) - - return result -} - -// SetText sets button's text -func (b *LabelButton) SetText(text string) { - b.label.SetText(text) - b.width, b.height = b.label.GetSize() -} - -// SetColors sets label-button colors (on normal and hovered state) -func (b *LabelButton) SetColors(normColor, hoverColor color.Color) { - b.stdColor = normColor - b.hoverColor = hoverColor -} - -// GetSize returns label's size -func (b *LabelButton) GetSize() (x, y int) { - return b.label.GetSize() -} - -// GetPosition returns real position (including offset for the alignment) -func (b *LabelButton) GetPosition() (x, y int) { - return b.x - b.label.getAlignOffset(b.width), b.y -} - -// OnActivated defines the callback handler for the activate event -func (b *LabelButton) OnActivated(cb func()) { - b.onClick = cb -} - -// Activate calls the on activated callback handler, if any -func (b *LabelButton) Activate() { - if b.onClick == nil { - return - } - - b.onClick() -} - -// SetEnabled sets the enabled state -func (b *LabelButton) SetEnabled(_ bool) { - // noop -} - -// GetEnabled returns the enabled state -func (b *LabelButton) GetEnabled() bool { - return true -} - -// SetPressed sets the pressed state of the button -func (b *LabelButton) SetPressed(_ bool) { - // noop -} - -// GetPressed returns the pressed state of the button -func (b *LabelButton) GetPressed() bool { - return false -} - -// Advance advances the label-button -func (b *LabelButton) Advance(_ float64) error { - return nil -} - -// Render renders label-button -func (b *LabelButton) Render(target d2interface.Surface) { - target.PushTranslation(b.GetPosition()) - defer target.Pop() - - b.label.Render(target) - - if b.isHovered() { - b.label.Color[0] = b.hoverColor - } else { - b.label.Color[0] = b.stdColor - } -} diff --git a/d2core/d2ui/scrollbar.go b/d2core/d2ui/scrollbar.go index ba832019..db41da5d 100644 --- a/d2core/d2ui/scrollbar.go +++ b/d2core/d2ui/scrollbar.go @@ -13,8 +13,8 @@ const ( scrollbarWidth = 10 ) -// static check that Scrollbar implements clickable widget -var _ ClickableWidget = &Scrollbar{} +// static check that Scrollbar implements widget +var _ Widget = &Scrollbar{} // Scrollbar is a vertical slider ui element type Scrollbar struct { diff --git a/d2core/d2ui/sprite.go b/d2core/d2ui/sprite.go index 4a904add..91988891 100644 --- a/d2core/d2ui/sprite.go +++ b/d2core/d2ui/sprite.go @@ -11,9 +11,6 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" ) -// static check if Sprite implemented Widget -var _ Widget = &Sprite{} - // Sprite is a positioned visual object. type Sprite struct { *BaseWidget @@ -77,7 +74,7 @@ func (s *Sprite) RenderSegmented(target d2interface.Surface, segmentsX, segments for x := 0; x < segmentsX; x++ { idx := x + y*segmentsX + frameOffset*segmentsX*segmentsY if err := s.animation.SetCurrentFrame(idx); err != nil { - s.Errorf("Error while setting frame (%d): %s", idx, err) + s.Error("SetCurrentFrame error" + err.Error()) } target.PushTranslation(s.x+currentX, s.y+currentY) diff --git a/d2core/d2ui/textbox.go b/d2core/d2ui/textbox.go index 20ab092b..9a874024 100644 --- a/d2core/d2ui/textbox.go +++ b/d2core/d2ui/textbox.go @@ -11,8 +11,8 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" ) -// static check that TextBox implements clickable widget -var _ ClickableWidget = &TextBox{} +// static check that TextBox implements widget +var _ Widget = &TextBox{} // TextBox represents a text input box type TextBox struct { diff --git a/d2core/d2ui/tooltip.go b/d2core/d2ui/tooltip.go index 732efc63..281d0765 100644 --- a/d2core/d2ui/tooltip.go +++ b/d2core/d2ui/tooltip.go @@ -69,8 +69,6 @@ func (ui *UIManager) NewTooltip(font, boxEnabled: true, } res.manager = ui - // cannot use ui.addWidget, because - // some tooltips could be covered by another widgets ui.addTooltip(res) return res diff --git a/d2game/d2gamescreen/character_select.go b/d2game/d2gamescreen/character_select.go index feecc61a..552ea523 100644 --- a/d2game/d2gamescreen/character_select.go +++ b/d2game/d2gamescreen/character_select.go @@ -1,6 +1,7 @@ package d2gamescreen import ( + "image/color" "math" "os" "strconv" @@ -189,7 +190,7 @@ func (v *CharacterSelect) OnLoad(loading d2screen.LoadingState) { v.characterNameLabel[i] = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits) v.characterNameLabel[i].SetPosition(offsetX, offsetY) - v.characterNameLabel[i].Color[0] = d2util.Color(lightBrown) + v.characterNameLabel[i].Color[0] = rgbaColor(lightBrown) offsetY += labelHeight @@ -200,7 +201,7 @@ func (v *CharacterSelect) OnLoad(loading d2screen.LoadingState) { v.characterExpLabel[i] = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteStatic) v.characterExpLabel[i].SetPosition(offsetX, offsetY) - v.characterExpLabel[i].Color[0] = d2util.Color(lightGreen) + v.characterExpLabel[i].Color[0] = rgbaColor(lightGreen) } v.refreshGameStates() @@ -266,6 +267,31 @@ func (v *CharacterSelect) loadCharScrollbar() { v.charScrollbar.OnActivated(func() { v.onScrollUpdate() }) } +func rgbaColor(rgba uint32) color.RGBA { + result := color.RGBA{} + a, b, g, r := 0, 1, 2, 3 + byteWidth := 8 + byteMask := 0xff + + for idx := 0; idx < 4; idx++ { + shift := idx * byteWidth + component := uint8(rgba>>shift) & uint8(byteMask) + + switch idx { + case a: + result.A = component + case b: + result.B = component + case g: + result.G = component + case r: + result.R = component + } + } + + return result +} + func (v *CharacterSelect) createButtons(loading d2screen.LoadingState) { v.newCharButton = v.uiManager.NewButton(d2ui.ButtonTypeTall, strings.Join( d2util.SplitIntoLinesWithMaxWidth(v.asset.TranslateString("#831"), 13), "\n")) @@ -385,7 +411,7 @@ func (v *CharacterSelect) Render(screen d2interface.Surface) { } if v.showDeleteConfirmation { - screen.DrawRect(screenWidth, screenHeight, d2util.Color(blackHalfOpacity)) + screen.DrawRect(screenWidth, screenHeight, rgbaColor(blackHalfOpacity)) v.okCancelBox.RenderSegmented(screen, 2, 1, 0) v.deleteCharConfirmLabel.Render(screen) } diff --git a/d2game/d2gamescreen/cinematics.go b/d2game/d2gamescreen/cinematics.go index ee3171e3..f1aa9a41 100644 --- a/d2game/d2gamescreen/cinematics.go +++ b/d2game/d2gamescreen/cinematics.go @@ -98,7 +98,7 @@ func (v *Cinematics) OnLoad(_ d2screen.LoadingState) { v.cinematicsLabel = v.uiManager.NewLabel(d2resource.Font30, d2resource.PaletteStatic) v.cinematicsLabel.Alignment = d2ui.HorizontalAlignCenter v.cinematicsLabel.SetText(v.asset.TranslateLabel(d2enum.SelectCinematicLabel)) - v.cinematicsLabel.Color[0] = d2util.Color(lightBrown) + v.cinematicsLabel.Color[0] = rgbaColor(lightBrown) v.cinematicsLabel.SetPosition(cinematicsLabelX, cinematicsLabelY) } diff --git a/d2game/d2gamescreen/game.go b/d2game/d2gamescreen/game.go index 4247ff36..07a826c1 100644 --- a/d2game/d2gamescreen/game.go +++ b/d2game/d2gamescreen/game.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "image/color" - "strconv" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui" @@ -131,36 +130,58 @@ type Game struct { func (v *Game) OnLoad(_ d2screen.LoadingState) { v.audioProvider.PlayBGM("") - commands := []struct { - name string - desc string - args []string - fn func([]string) error - }{ - {"spawnitem", "spawns an item at the local player position", - []string{"code1", "code2", "code3", "code4", "code5"}, v.commandSpawnItem}, - {"spawnitemat", "spawns an item at the x,y coordinates", - []string{"x", "y", "code1", "code2", "code3", "code4", "code5"}, v.commandSpawnItemAt}, - {"spawnmon", "spawn monster at the local player position", []string{"name"}, v.commandSpawnMon}, + err := v.terminal.BindAction( + "spawnitem", + "spawns an item at the local player position", + func(code1, code2, code3, code4, code5 string) { + codes := []string{code1, code2, code3, code4, code5} + v.debugSpawnItemAtPlayer(codes...) + }, + ) + if err != nil { + v.Errorf("failed to bind the '%s' action, err: %v\n", "spawnitem", err) } - for _, cmd := range commands { - if err := v.terminal.Bind(cmd.name, cmd.desc, cmd.args, cmd.fn); err != nil { - v.Errorf(err.Error()) - } + err = v.terminal.BindAction( + "spawnitemat", + "spawns an item at the x,y coordinates", + func(x, y int, code1, code2, code3, code4, code5 string) { + codes := []string{code1, code2, code3, code4, code5} + v.debugSpawnItemAtLocation(x, y, codes...) + }, + ) + if err != nil { + v.Errorf("failed to bind the '%s' action, err: %v\n", "spawnitemat", err) } - if err := v.asset.BindTerminalCommands(v.terminal); err != nil { - v.Errorf(err.Error()) + err = v.terminal.BindAction( + "spawnmon", + "spawn monster at the local player position", + func(name string) { + x := int(v.localPlayer.Position.X()) + y := int(v.localPlayer.Position.Y()) + monstat := v.asset.Records.Monster.Stats[name] + if monstat == nil { + v.terminal.OutputErrorf("no monstat entry for \"%s\"", name) + return + } + + monster, npcErr := v.gameClient.MapEngine.NewNPC(x, y, monstat, 0) + if npcErr != nil { + v.terminal.OutputErrorf("error generating monster \"%s\": %v", name, npcErr) + return + } + + v.gameClient.MapEngine.AddEntity(monster) + }, + ) + if err != nil { + v.Errorf("failed to bind the '%s' action, err: %v\n", "spawnmon", err) } } // OnUnload releases the resources of Gameplay screen func (v *Game) OnUnload() error { - if err := v.gameControls.UnbindTerminalCommands(v.terminal); err != nil { - return err - } - // https://github.com/OpenDiablo2/OpenDiablo2/issues/792 if err := v.inputManager.UnbindHandler(v.gameControls); err != nil { return err @@ -171,7 +192,11 @@ func (v *Game) OnUnload() error { return err } - if err := v.terminal.Unbind("spawnitemat", "spawnitem", "spawnmon"); err != nil { + if err := v.terminal.UnbindAction("spawnItemAt"); err != nil { + return err + } + + if err := v.terminal.UnbindAction("spawnItem"); err != nil { return err } @@ -183,18 +208,6 @@ func (v *Game) OnUnload() error { return err } - if err := v.asset.UnbindTerminalCommands(v.terminal); err != nil { - return err - } - - if err := v.mapRenderer.UnbindTerminalCommands(v.terminal); err != nil { - return err - } - - if err := v.soundEngine.UnbindTerminalCommands(v.terminal); err != nil { - return err - } - v.soundEngine.Reset() return nil @@ -291,8 +304,7 @@ func (v *Game) bindGameControls() error { var err error v.gameControls, err = d2player.NewGameControls(v.asset, v.renderer, player, v.gameClient.MapEngine, - v.escapeMenu, v.mapRenderer, v, v.terminal, v.uiManager, v.keyMap, v.audioProvider, v.logLevel, - v.gameClient.IsSinglePlayer()) + v.escapeMenu, v.mapRenderer, v, v.terminal, v.uiManager, v.keyMap, v.logLevel, v.gameClient.IsSinglePlayer()) if err != nil { return err @@ -382,47 +394,3 @@ func (v *Game) debugSpawnItemAtLocation(x, y int, codes ...string) { v.Errorf(spawnItemErrStr, x, y, codes) } } - -func (v *Game) commandSpawnItem(args []string) error { - v.debugSpawnItemAtPlayer(args...) - - return nil -} - -func (v *Game) commandSpawnItemAt(args []string) error { - x, err := strconv.Atoi(args[0]) - if err != nil { - return fmt.Errorf("invalid argument") - } - - y, err := strconv.Atoi(args[0]) - if err != nil { - return fmt.Errorf("invalid argument") - } - - v.debugSpawnItemAtLocation(x, y, args[2:]...) - - return nil -} - -func (v *Game) commandSpawnMon(args []string) error { - name := args[0] - x := int(v.localPlayer.Position.X()) - y := int(v.localPlayer.Position.Y()) - - monstat := v.asset.Records.Monster.Stats[name] - if monstat == nil { - v.terminal.Errorf("no monstat entry for \"%s\"", name) - return nil - } - - monster, npcErr := v.gameClient.MapEngine.NewNPC(x, y, monstat, 0) - if npcErr != nil { - v.terminal.Errorf("error generating monster \"%s\": %v", name, npcErr) - return nil - } - - v.gameClient.MapEngine.AddEntity(monster) - - return nil -} diff --git a/d2game/d2gamescreen/main_menu.go b/d2game/d2gamescreen/main_menu.go index c11f8e09..6a96355d 100644 --- a/d2game/d2gamescreen/main_menu.go +++ b/d2game/d2gamescreen/main_menu.go @@ -62,7 +62,6 @@ const ( tcpJoinBtnX, tcpJoinBtnY = 264, 240 errorLabelX, errorLabelY = 400, 250 machineIPX, machineIPY = 400, 90 - tipX, tipY = 400, 300 ) const ( @@ -160,8 +159,6 @@ type MainMenu struct { tcpJoinGameLabel *d2ui.Label machineIP *d2ui.Label errorLabel *d2ui.Label - joinTipLabel *d2ui.Label - hostTipLabel *d2ui.Label tcpJoinGameEntry *d2ui.TextBox screenMode mainMenuScreenMode leftButtonHeld bool @@ -185,12 +182,10 @@ func (v *MainMenu) OnLoad(loading d2screen.LoadingState) { v.audioProvider.PlayBGM(d2resource.BGMTitle) loading.Progress(twentyPercent) - v.createMainMenuLabels(loading) - v.createMultiplayerLabels() + v.createLabels(loading) v.loadBackgroundSprites() v.createLogos(loading) - v.createMainMenuButtons(loading) - v.createMultiplayerMenuButtons() + v.createButtons(loading) v.tcpJoinGameEntry = v.uiManager.NewTextbox() v.tcpJoinGameEntry.SetPosition(joinGameDialogX, joinGameDialogY) @@ -240,47 +235,39 @@ func (v *MainMenu) loadBackgroundSprites() { v.serverIPBackground.SetPosition(serverIPbackgroundX, serverIPbackgroundY) } -func (v *MainMenu) createMainMenuLabels(loading d2screen.LoadingState) { +func (v *MainMenu) createLabels(loading d2screen.LoadingState) { v.versionLabel = v.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteStatic) v.versionLabel.Alignment = d2ui.HorizontalAlignRight v.versionLabel.SetText("OpenDiablo2 - " + v.buildInfo.Branch) - v.versionLabel.Color[0] = d2util.Color(white) + v.versionLabel.Color[0] = rgbaColor(white) v.versionLabel.SetPosition(versionLabelX, versionLabelY) v.commitLabel = v.uiManager.NewLabel(d2resource.FontFormal10, d2resource.PaletteStatic) v.commitLabel.Alignment = d2ui.HorizontalAlignLeft v.commitLabel.SetText(v.buildInfo.Commit) - v.commitLabel.Color[0] = d2util.Color(white) + v.commitLabel.Color[0] = rgbaColor(white) v.commitLabel.SetPosition(commitLabelX, commitLabelY) v.copyrightLabel = v.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteStatic) v.copyrightLabel.Alignment = d2ui.HorizontalAlignCenter v.copyrightLabel.SetText(v.asset.TranslateLabel(d2enum.CopyrightLabel)) - v.copyrightLabel.Color[0] = d2util.Color(lightBrown) + v.copyrightLabel.Color[0] = rgbaColor(lightBrown) v.copyrightLabel.SetPosition(copyrightX, copyrightY) loading.Progress(thirtyPercent) v.copyrightLabel2 = v.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteStatic) v.copyrightLabel2.Alignment = d2ui.HorizontalAlignCenter v.copyrightLabel2.SetText(v.asset.TranslateLabel(d2enum.AllRightsReservedLabel)) - v.copyrightLabel2.Color[0] = d2util.Color(lightBrown) + v.copyrightLabel2.Color[0] = rgbaColor(lightBrown) v.copyrightLabel2.SetPosition(copyright2X, copyright2Y) v.openDiabloLabel = v.uiManager.NewLabel(d2resource.FontFormal10, d2resource.PaletteStatic) v.openDiabloLabel.Alignment = d2ui.HorizontalAlignCenter v.openDiabloLabel.SetText("OpenDiablo2 is neither developed by, nor endorsed by Blizzard or its parent company Activision") - v.openDiabloLabel.Color[0] = d2util.Color(lightYellow) + v.openDiabloLabel.Color[0] = rgbaColor(lightYellow) v.openDiabloLabel.SetPosition(od2LabelX, od2LabelY) loading.Progress(fiftyPercent) - if v.errorLabel != nil { - v.errorLabel.SetPosition(errorLabelX, errorLabelY) - v.errorLabel.Alignment = d2ui.HorizontalAlignCenter - v.errorLabel.Color[0] = d2util.Color(red) - } -} - -func (v *MainMenu) createMultiplayerLabels() { v.tcpIPOptionsLabel = v.uiManager.NewLabel(d2resource.Font42, d2resource.PaletteUnits) v.tcpIPOptionsLabel.SetPosition(tcpOptionsX, tcpOptionsY) v.tcpIPOptionsLabel.Alignment = d2ui.HorizontalAlignCenter @@ -289,30 +276,20 @@ func (v *MainMenu) createMultiplayerLabels() { v.tcpJoinGameLabel = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits) v.tcpJoinGameLabel.Alignment = d2ui.HorizontalAlignCenter v.tcpJoinGameLabel.SetText(strings.Join(d2util.SplitIntoLinesWithMaxWidth(v.asset.TranslateLabel(d2enum.TCPIPEnterHostIPLabel), 27), "\n")) - v.tcpJoinGameLabel.Color[0] = d2util.Color(gold) + v.tcpJoinGameLabel.Color[0] = rgbaColor(gold) v.tcpJoinGameLabel.SetPosition(joinGameX, joinGameY) v.machineIP = v.uiManager.NewLabel(d2resource.Font24, d2resource.PaletteUnits) v.machineIP.Alignment = d2ui.HorizontalAlignCenter v.machineIP.SetText(v.asset.TranslateLabel(d2enum.TCPIPYourIPLabel) + "\n" + v.getLocalIP()) - v.machineIP.Color[0] = d2util.Color(lightYellow) + v.machineIP.Color[0] = rgbaColor(lightYellow) v.machineIP.SetPosition(machineIPX, machineIPY) - v.hostTipLabel = v.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteUnits) - v.hostTipLabel.Alignment = d2ui.HorizontalAlignCenter - v.hostTipLabel.SetText(d2ui.ColorTokenize(strings.Join(d2util.SplitIntoLinesWithMaxWidth( - v.asset.TranslateLabel(d2enum.TipHostLabel), 36), - "\n"), d2ui.ColorTokenGold)) - v.hostTipLabel.SetPosition(tipX, tipY) - v.hostTipLabel.SetVisible(false) - - v.joinTipLabel = v.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteUnits) - v.joinTipLabel.Alignment = d2ui.HorizontalAlignCenter - v.joinTipLabel.SetText(d2ui.ColorTokenize(strings.Join(d2util.SplitIntoLinesWithMaxWidth( - v.asset.TranslateLabel(d2enum.TipJoinLabel), 36), - "\n"), d2ui.ColorTokenGold)) - v.joinTipLabel.SetPosition(tipX, tipY) - v.joinTipLabel.SetVisible(false) + if v.errorLabel != nil { + v.errorLabel.SetPosition(errorLabelX, errorLabelY) + v.errorLabel.Alignment = d2ui.HorizontalAlignCenter + v.errorLabel.Color[0] = rgbaColor(red) + } } func (v *MainMenu) createLogos(loading d2screen.LoadingState) { @@ -352,7 +329,7 @@ func (v *MainMenu) createLogos(loading d2screen.LoadingState) { v.diabloLogoRightBack.SetPosition(diabloLogoX, diabloLogoY) } -func (v *MainMenu) createMainMenuButtons(loading d2screen.LoadingState) { +func (v *MainMenu) createButtons(loading d2screen.LoadingState) { v.exitDiabloButton = v.uiManager.NewButton(d2ui.ButtonTypeWide, v.asset.TranslateLabel(d2enum.ExitGameLabel)) v.exitDiabloButton.SetPosition(exitDiabloBtnX, exitDiabloBtnY) v.exitDiabloButton.OnActivated(func() { v.onExitButtonClicked() }) @@ -390,6 +367,8 @@ func (v *MainMenu) createMainMenuButtons(loading d2screen.LoadingState) { v.btnServerIPOk = v.uiManager.NewButton(d2ui.ButtonTypeOkCancel, v.asset.TranslateString(d2enum.OKLabel)) v.btnServerIPOk.SetPosition(srvOkBtnX, srvOkBtnY) v.btnServerIPOk.OnActivated(func() { v.onBtnTCPIPOkClicked() }) + + v.createMultiplayerMenuButtons() loading.Progress(eightyPercent) } @@ -411,14 +390,10 @@ func (v *MainMenu) createMultiplayerMenuButtons() { v.btnTCPIPHostGame = v.uiManager.NewButton(d2ui.ButtonTypeWide, v.asset.TranslateLabel(d2enum.TCPIPHostGameLabel)) v.btnTCPIPHostGame.SetPosition(tcpHostBtnX, tcpHostBtnY) v.btnTCPIPHostGame.OnActivated(func() { v.onTCPIPHostGameClicked() }) - v.btnTCPIPHostGame.OnHoverStart(func() { v.hostTipLabel.SetVisible(true) }) - v.btnTCPIPHostGame.OnHoverEnd(func() { v.hostTipLabel.SetVisible(false) }) v.btnTCPIPJoinGame = v.uiManager.NewButton(d2ui.ButtonTypeWide, v.asset.TranslateLabel(d2enum.TCPIPJoinGameLabel)) v.btnTCPIPJoinGame.SetPosition(tcpJoinBtnX, tcpJoinBtnY) v.btnTCPIPJoinGame.OnActivated(func() { v.onTCPIPJoinGameClicked() }) - v.btnTCPIPJoinGame.OnHoverStart(func() { v.joinTipLabel.SetVisible(true) }) - v.btnTCPIPJoinGame.OnHoverEnd(func() { v.joinTipLabel.SetVisible(false) }) } func (v *MainMenu) onMapTestClicked() { diff --git a/d2game/d2player/escape_menu.go b/d2game/d2player/escape_menu.go index 0625add1..2609da1f 100644 --- a/d2game/d2player/escape_menu.go +++ b/d2game/d2player/escape_menu.go @@ -398,15 +398,25 @@ func (m *EscapeMenu) OnLoad() { // OnEscKey is called when the escape key is pressed func (m *EscapeMenu) OnEscKey() { - if m.currentLayout == configureControlsLayoutID { + // note: original D2 returns straight to the game from however deep in the menu we are + switch m.currentLayout { + case optionsLayoutID: + m.setLayout(mainLayoutID) + return + case soundOptionsLayoutID, + videoOptionsLayoutID, + automapOptionsLayoutID, + configureControlsLayoutID: m.setLayout(optionsLayoutID) if err := m.keyBindingMenu.Close(); err != nil { m.Errorf("unable to close the configure controls menu: %v", err.Error()) } - } else { - m.close() + + return } + + m.close() } // SetOnCloseCb sets the callback that is run when close() is called diff --git a/d2game/d2player/game_controls.go b/d2game/d2player/game_controls.go index 8a4eb155..c17f5281 100644 --- a/d2game/d2player/game_controls.go +++ b/d2game/d2player/game_controls.go @@ -2,7 +2,6 @@ package d2player import ( "fmt" - "strconv" "strings" "time" @@ -28,6 +27,7 @@ const ( // Panel represents the panel at the bottom of the game screen type Panel interface { IsOpen() bool + Toggle() Open() Close() } @@ -37,8 +37,11 @@ const mouseBtnActionsThreshold = 0.25 const ( // Since they require special handling, not considering (1) globes, (2) content of the mini panel, (3) belt leftSkill actionableType = iota + newStats xp + walkRun stamina + newSkills rightSkill hpGlobe manaGlobe @@ -50,16 +53,31 @@ const ( leftSkillWidth, leftSkillHeight = 117, 550, 50, 50 + newStatsX, + newStatsY, + newStatsWidth, + newStatsHeight = 206, 563, 30, 30 + xpX, xpY, xpWidth, xpHeight = 253, 560, 125, 5 + walkRunX, + walkRunY, + walkRunWidth, + walkRunHeight = 255, 573, 17, 20 + staminaX, staminaY, staminaWidth, staminaHeight = 273, 573, 105, 20 + newSkillsX, + newSkillsY, + newSkillsWidth, + newSkillsHeight = 562, 563, 30, 30 + rightSkillX, rightSkillY, rightSkillWidth, @@ -106,7 +124,6 @@ func NewGameControls( term d2interface.Terminal, ui *d2ui.UIManager, keyMap *KeyMap, - audioProvider d2interface.AudioProvider, l d2util.LogLevel, isSinglePlayer bool, ) (*GameControls, error) { @@ -138,18 +155,36 @@ func NewGameControls( Width: leftSkillWidth, Height: leftSkillHeight, }}, + {newStats, d2geom.Rectangle{ + Left: newStatsX, + Top: newStatsY, + Width: newStatsWidth, + Height: newStatsHeight, + }}, {xp, d2geom.Rectangle{ Left: xpX, Top: xpY, Width: xpWidth, Height: xpHeight, }}, + {walkRun, d2geom.Rectangle{ + Left: walkRunX, + Top: walkRunY, + Width: walkRunWidth, + Height: walkRunHeight, + }}, {stamina, d2geom.Rectangle{ Left: staminaX, Top: staminaY, Width: staminaWidth, Height: staminaHeight, }}, + {newSkills, d2geom.Rectangle{ + Left: newSkillsX, + Top: newSkillsY, + Width: newSkillsWidth, + Height: newSkillsHeight, + }}, {rightSkill, d2geom.Rectangle{ Left: rightSkillX, Top: rightSkillY, @@ -172,14 +207,14 @@ func NewGameControls( inventoryRecord := asset.Records.Layout.Inventory[inventoryRecordKey] heroStatsPanel := NewHeroStatsPanel(asset, ui, hero.Name(), hero.Class, l, hero.Stats) - questLog := NewQuestLog(asset, ui, l, audioProvider, hero.Act) + questLog := NewQuestLog(asset, ui, l, hero.Act) inventory, err := NewInventory(asset, ui, l, hero.Gold, inventoryRecord) if err != nil { return nil, err } - skilltree := newSkillTree(hero.Skills, hero.Class, hero.Stats, asset, l, ui) + skilltree := newSkillTree(hero.Skills, hero.Class, asset, l, ui) miniPanel := newMiniPanel(asset, ui, l, isSinglePlayer) @@ -189,9 +224,13 @@ func NewGameControls( } helpOverlay := NewHelpOverlay(asset, ui, l, keyMap) + hud := NewHUD(asset, ui, hero, miniPanel, actionableRegions, mapEngine, l, mapRenderer) const blackAlpha50percent = 0x0000007f + hoverLabel := hud.nameLabel + hoverLabel.SetBackgroundColor(d2util.Color(blackAlpha50percent)) + gc := &GameControls{ asset: asset, ui: ui, @@ -207,6 +246,7 @@ func NewGameControls( questLog: questLog, HelpOverlay: helpOverlay, keyMap: keyMap, + hud: hud, bottomMenuRect: &d2geom.Rectangle{ Left: menuBottomRectX, Top: menuBottomRectY, @@ -231,12 +271,6 @@ func NewGameControls( isSinglePlayer: isSinglePlayer, } - hud := NewHUD(asset, ui, hero, miniPanel, actionableRegions, mapEngine, l, gc, mapRenderer) - gc.hud = hud - - hoverLabel := hud.nameLabel - hoverLabel.SetBackgroundColor(d2util.Color(blackAlpha50percent)) - gc.heroStatsPanel.SetOnCloseCb(gc.onCloseHeroStatsPanel) gc.questLog.SetOnCloseCb(gc.onCloseQuestLog) gc.inventory.SetOnCloseCb(gc.onCloseInventory) @@ -355,7 +389,11 @@ func (g *GameControls) OnKeyDown(event d2interface.KeyEvent) bool { switch gameEvent { case d2enum.ClearScreen: - g.clearScreen() + g.inventory.Close() + g.skilltree.Close() + g.heroStatsPanel.Close() + g.questLog.Close() + g.HelpOverlay.Close() g.updateLayout() case d2enum.ToggleInventoryPanel: g.toggleInventoryPanel() @@ -382,8 +420,11 @@ func (g *GameControls) OnKeyDown(event d2interface.KeyEvent) bool { func (g *GameControls) OnKeyUp(event d2interface.KeyEvent) bool { gameEvent := g.keyMap.getGameEvent(event.Key()) - if gameEvent == d2enum.HoldRun { + switch gameEvent { + case d2enum.HoldRun: g.hud.onToggleRunButton(true) + default: + return false } return false @@ -396,18 +437,57 @@ func (g *GameControls) OnKeyUp(event d2interface.KeyEvent) bool { func (g *GameControls) onEscKey() { escHandled := false - escHandled = g.hasOpenPanels() || g.HelpOverlay.IsOpen() || g.hud.skillSelectMenu.IsOpen() - g.clearScreen() + if g.hud.skillSelectMenu.IsOpen() { + g.hud.skillSelectMenu.ClosePanels() - if escHandled { - g.updateLayout() - return + escHandled = true } - if g.escapeMenu.IsOpen() { - g.escapeMenu.OnEscKey() - } else { - g.openEscMenu() + if g.inventory.IsOpen() { + if g.inventory.moveGoldPanel.IsOpen() { + g.inventory.moveGoldPanel.Close() + + return + } + + g.inventory.Close() + + escHandled = true + } + + if g.skilltree.IsOpen() { + g.skilltree.Close() + + escHandled = true + } + + if g.heroStatsPanel.IsOpen() { + g.heroStatsPanel.Close() + + escHandled = true + } + + if g.questLog.IsOpen() { + g.questLog.Close() + + escHandled = true + } + + if g.HelpOverlay.IsOpen() { + g.HelpOverlay.Close() + + escHandled = true + } + + switch escHandled { + case true: + g.updateLayout() + case false: + if g.escapeMenu.IsOpen() { + g.escapeMenu.OnEscKey() + } else { + g.openEscMenu() + } } } @@ -549,89 +629,36 @@ func (g *GameControls) OnMouseButtonDown(event d2interface.MouseEvent) bool { return false } -func (g *GameControls) clearLeftScreenSide() { - g.heroStatsPanel.Close() - g.questLog.Close() - g.hud.skillSelectMenu.ClosePanels() - g.hud.miniPanel.SetMovedRight(false) - g.updateLayout() -} - -func (g *GameControls) clearRightScreenSide() { - g.inventory.Close() - g.skilltree.Close() - g.hud.skillSelectMenu.ClosePanels() - g.hud.miniPanel.SetMovedLeft(false) - g.updateLayout() -} - -func (g *GameControls) clearScreen() { - g.clearRightScreenSide() - g.clearLeftScreenSide() - g.hud.skillSelectMenu.ClosePanels() - g.HelpOverlay.Close() -} - -func (g *GameControls) openLeftPanel(panel Panel) { - if !g.HelpOverlay.IsOpen() { - isOpen := panel.IsOpen() - - g.clearLeftScreenSide() - - if !isOpen { - panel.Open() - g.hud.miniPanel.SetMovedRight(true) - g.updateLayout() - } - } -} - -func (g *GameControls) openRightPanel(panel Panel) { - if !g.HelpOverlay.IsOpen() { - isOpen := panel.IsOpen() - - g.clearRightScreenSide() - - if !isOpen { - panel.Open() - g.hud.miniPanel.SetMovedLeft(true) - g.updateLayout() - } - } -} - func (g *GameControls) toggleHeroStatsPanel() { - g.openLeftPanel(g.heroStatsPanel) + if !g.HelpOverlay.IsOpen() { + g.questLog.Close() + g.heroStatsPanel.Toggle() + g.hud.miniPanel.SetMovedRight(g.heroStatsPanel.IsOpen()) + g.updateLayout() + } } func (g *GameControls) onCloseHeroStatsPanel() { -} - -func (g *GameControls) toggleLeftSkillPanel() { - if !g.HelpOverlay.IsOpen() { - g.clearScreen() - g.hud.skillSelectMenu.ToggleLeftPanel() - } -} - -func (g *GameControls) toggleRightSkillPanel() { - if !g.HelpOverlay.IsOpen() { - g.clearScreen() - g.hud.skillSelectMenu.ToggleRightPanel() - } + g.hud.miniPanel.SetMovedRight(g.heroStatsPanel.IsOpen()) + g.updateLayout() } func (g *GameControls) toggleQuestLog() { - g.openLeftPanel(g.questLog) + if !g.HelpOverlay.IsOpen() { + g.heroStatsPanel.Close() + g.questLog.Toggle() + g.hud.miniPanel.SetMovedRight(g.questLog.IsOpen()) + g.updateLayout() + } } func (g *GameControls) onCloseQuestLog() { + g.hud.miniPanel.SetMovedRight(g.questLog.IsOpen()) + g.updateLayout() } func (g *GameControls) toggleHelpOverlay() { - if !g.isRightPanelOpen() || g.isLeftPanelOpen() { - g.HelpOverlay.updateKeyMap(g.keyMap) - g.hud.skillSelectMenu.ClosePanels() + if !g.inventory.IsOpen() && !g.skilltree.IsOpen() && !g.heroStatsPanel.IsOpen() && !g.questLog.IsOpen() { g.hud.miniPanel.openDisabled() g.HelpOverlay.Toggle() g.updateLayout() @@ -639,21 +666,38 @@ func (g *GameControls) toggleHelpOverlay() { } func (g *GameControls) toggleInventoryPanel() { - g.openRightPanel(g.inventory) + if !g.HelpOverlay.IsOpen() { + g.skilltree.Close() + g.inventory.Toggle() + g.hud.miniPanel.SetMovedLeft(g.inventory.IsOpen()) + g.updateLayout() + } } func (g *GameControls) onCloseInventory() { + g.hud.miniPanel.SetMovedLeft(g.inventory.IsOpen()) + g.updateLayout() } func (g *GameControls) toggleSkilltreePanel() { - g.openRightPanel(g.skilltree) + if !g.HelpOverlay.IsOpen() { + g.inventory.Close() + g.skilltree.Toggle() + g.hud.miniPanel.SetMovedLeft(g.skilltree.IsOpen()) + g.updateLayout() + } } func (g *GameControls) onCloseSkilltree() { + g.hud.miniPanel.SetMovedLeft(g.skilltree.IsOpen()) + g.updateLayout() } func (g *GameControls) openEscMenu() { - g.clearScreen() + g.inventory.Close() + g.skilltree.Close() + g.heroStatsPanel.Close() + g.questLog.Close() g.hud.miniPanel.closeDisabled() g.escapeMenu.open() g.updateLayout() @@ -668,9 +712,6 @@ func (g *GameControls) Load() { g.questLog.Load() g.HelpOverlay.Load() - g.loadAddButtons() - g.setAddButtons() - miniPanelActions := &miniPanelActions{ characterToggle: g.toggleHeroStatsPanel, inventoryToggle: g.toggleInventoryPanel, @@ -686,16 +727,11 @@ func (g *GameControls) Advance(elapsed float64) error { g.mapRenderer.Advance(elapsed) g.hud.Advance(elapsed) g.inventory.Advance(elapsed) - g.questLog.Advance(elapsed) if err := g.escapeMenu.Advance(elapsed); err != nil { return err } - if g.heroStatsPanel.IsOpen() || g.skilltree.IsOpen() { - g.setAddButtons() - } - return nil } @@ -708,12 +744,13 @@ func (g *GameControls) updateLayout() { g.mapRenderer.ViewportDefault() case isRightPanelOpen: g.mapRenderer.ViewportToLeft() - case isLeftPanelOpen: + default: g.mapRenderer.ViewportToRight() } } func (g *GameControls) isLeftPanelOpen() bool { + // https://github.com/OpenDiablo2/OpenDiablo2/issues/801 return g.heroStatsPanel.IsOpen() || g.questLog.IsOpen() || g.inventory.moveGoldPanel.IsOpen() } @@ -721,10 +758,6 @@ func (g *GameControls) isRightPanelOpen() bool { return g.inventory.IsOpen() || g.skilltree.IsOpen() } -func (g *GameControls) hasOpenPanels() bool { - return g.isRightPanelOpen() || g.isLeftPanelOpen() || g.hud.skillSelectMenu.IsOpen() -} - func (g *GameControls) isInActiveMenusRect(px, py int) bool { if g.bottomMenuRect.IsInRect(px, py) { return true @@ -759,11 +792,11 @@ func (g *GameControls) isInActiveMenusRect(px, py int) bool { // Render draws the GameControls onto the target func (g *GameControls) Render(target d2interface.Surface) error { - if err := g.hud.Render(target); err != nil { + if err := g.renderPanels(target); err != nil { return err } - if err := g.renderPanels(target); err != nil { + if err := g.hud.Render(target); err != nil { return err } @@ -821,8 +854,11 @@ func (g *GameControls) ToggleManaStats() { func (g *GameControls) onHoverActionable(item actionableType) { hoverMap := map[actionableType]func(){ leftSkill: func() {}, + newStats: func() {}, xp: func() {}, + walkRun: func() {}, stamina: func() {}, + newSkills: func() {}, rightSkill: func() {}, hpGlobe: func() {}, manaGlobe: func() {}, @@ -841,19 +877,31 @@ func (g *GameControls) onHoverActionable(item actionableType) { func (g *GameControls) onClickActionable(item actionableType) { actionMap := map[actionableType]func(){ leftSkill: func() { - g.toggleLeftSkillPanel() + g.hud.skillSelectMenu.ToggleLeftPanel() + }, + + newStats: func() { + g.Info("New Stats Selector Action Pressed") }, xp: func() { g.Info("XP Action Pressed") }, + walkRun: func() { + g.Info("Walk/Run Action Pressed") + }, + stamina: func() { g.Info("Stamina Action Pressed") }, + newSkills: func() { + g.Info("New Skills Selector Action Pressed") + }, + rightSkill: func() { - g.toggleRightSkillPanel() + g.hud.skillSelectMenu.ToggleRightPanel() }, hpGlobe: func() { @@ -877,135 +925,59 @@ func (g *GameControls) onClickActionable(item actionableType) { action() } -func (g *GameControls) bindTerminalCommands(term d2interface.Terminal) error { - if err := term.Bind("freecam", "toggle free camera movement", nil, g.commandFreeCam); err != nil { - return err - } - - if err := term.Bind("setleftskill", "set skill to fire on left click", []string{"id"}, g.commandSetLeftSkill(term)); err != nil { - return err - } - - if err := term.Bind("setrightskill", "set skill to fire on right click", []string{"id"}, g.commandSetRightSkill(term)); err != nil { - return err - } - - if err := term.Bind("learnskills", "learn all skills for the a given class", []string{"token"}, g.commandLearnSkills(term)); err != nil { - return err - } - - if err := term.Bind("learnskillid", "learn a skill by a given ID", []string{"id"}, g.commandLearnSkillID(term)); err != nil { - return err - } - - return nil +func (g *GameControls) bindFreeCamCommand(term d2interface.Terminal) error { + return term.BindAction("freecam", "toggle free camera movement", func() { + g.FreeCam = !g.FreeCam + }) } -// UnbindTerminalCommands unbinds commands from the terminal -func (g *GameControls) UnbindTerminalCommands(term d2interface.Terminal) error { - return term.Unbind("freecam", "setleftskill", "setrightskill", "learnskills", "learnskillid") -} +func (g *GameControls) bindSetLeftSkillCommand(term d2interface.Terminal) error { + setLeftSkill := func(id int) { + skillRecord := g.asset.Records.Skill.Details[id] + skill, err := g.heroState.CreateHeroSkill(1, skillRecord.Skill) -func (g *GameControls) setAddButtons() { - g.hud.addStatsButton.SetEnabled(g.hero.Stats.StatsPoints > 0) - g.hud.addSkillButton.SetEnabled(g.hero.Stats.SkillPoints > 0) -} - -func (g *GameControls) loadAddButtons() { - g.hud.addStatsButton.OnActivated(func() { g.toggleHeroStatsPanel() }) - g.hud.addSkillButton.OnActivated(func() { g.toggleSkilltreePanel() }) -} - -func (g *GameControls) commandFreeCam([]string) error { - g.FreeCam = !g.FreeCam - - return nil -} - -func (g *GameControls) commandSetLeftSkill(term d2interface.Terminal) func(args []string) error { - return func(args []string) error { - id, err := strconv.Atoi(args[0]) if err != nil { - term.Errorf("invalid argument") - return nil - } - - skill, err := g.heroSkillByID(id) - if err != nil { - term.Errorf(err.Error()) - return nil + term.OutputErrorf("cannot create skill with ID of %d, error: %s", id, err) + return } g.hero.LeftSkill = skill - - return nil } + + return term.BindAction( + "setleftskill", + "set skill to fire on left click", + setLeftSkill, + ) } -func (g *GameControls) commandSetRightSkill(term d2interface.Terminal) func(args []string) error { - return func(args []string) error { - id, err := strconv.Atoi(args[0]) - if err != nil { - term.Errorf("invalid argument") - return nil - } +func (g *GameControls) bindSetRightSkillCommand(term d2interface.Terminal) error { + setRightSkill := func(id int) { + skillRecord := g.asset.Records.Skill.Details[id] + skill, err := g.heroState.CreateHeroSkill(0, skillRecord.Skill) - skill, err := g.heroSkillByID(id) if err != nil { - term.Errorf(err.Error()) - return nil + term.OutputErrorf("cannot create skill with ID of %d, error: %s", id, err) + return } g.hero.RightSkill = skill - - return nil } + + return term.BindAction( + "setrightskill", + "set skill to fire on right click", + setRightSkill, + ) } -func (g *GameControls) commandLearnSkillID(term d2interface.Terminal) func(args []string) error { - return func(args []string) error { - id, err := strconv.Atoi(args[0]) - if err != nil { - term.Errorf("invalid argument") - return nil - } +const classTokenLength = 3 - skill, err := g.heroSkillByID(id) - if err != nil { - term.Errorf(err.Error()) - return nil - } - - g.hero.Skills[skill.ID] = skill - g.hud.skillSelectMenu.RegenerateImageCache() - g.Infof("Learned skill: " + skill.Skill) - - return nil - } -} - -func (g *GameControls) heroSkillByID(id int) (*d2hero.HeroSkill, error) { - skillRecord := g.asset.Records.Skill.Details[id] - if skillRecord == nil { - return nil, fmt.Errorf("cannot find a skill record for ID: %d", id) - } - - skill, err := g.heroState.CreateHeroSkill(1, skillRecord.Skill) - if err != nil { - return nil, fmt.Errorf("cannot create skill with ID of %d", id) - } - - return skill, nil -} - -func (g *GameControls) commandLearnSkills(term d2interface.Terminal) func(args []string) error { - const classTokenLength = 3 - - return func(args []string) error { - token := args[0] +func (g *GameControls) bindLearnSkillsCommand(term d2interface.Terminal) error { + learnSkills := func(token string) { if len(token) < classTokenLength { - term.Errorf("The given class token should be at least 3 characters") - return nil + term.OutputErrorf("The given class token should be at least 3 characters") + return } validPrefixes := []string{"ama", "ass", "nec", "bar", "sor", "dru", "pal"} @@ -1021,9 +993,9 @@ func (g *GameControls) commandLearnSkills(term d2interface.Terminal) func(args [ if !isValidToken { fmtInvalid := "Invalid class, must be a value starting with(case insensitive): %s" - term.Errorf(fmtInvalid, strings.Join(validPrefixes, ", ")) + term.OutputErrorf(fmtInvalid, strings.Join(validPrefixes, ", ")) - return nil + return } var err error @@ -1059,10 +1031,70 @@ func (g *GameControls) commandLearnSkills(term d2interface.Terminal) func(args [ g.Infof("Learned %d skills", learnedSkillsCount) if err != nil { - term.Errorf("cannot learn skill for class, error: %s", err) - return nil + term.OutputErrorf("cannot learn skill for class, error: %s", err) + return + } + } + + return term.BindAction( + "learnskills", + "learn all skills for the a given class", + learnSkills, + ) +} + +func (g *GameControls) bindLearnSkillByIDCommand(term d2interface.Terminal) error { + learnByID := func(id int) { + skillRecord := g.asset.Records.Skill.Details[id] + if skillRecord == nil { + term.OutputErrorf("cannot find a skill record for ID: %d", id) + return } - return nil + skill, err := g.heroState.CreateHeroSkill(1, skillRecord.Skill) + if skill == nil { + term.OutputErrorf("cannot create skill: %s", skillRecord.Skill) + return + } + + g.hero.Skills[skill.ID] = skill + + if err != nil { + term.OutputErrorf("cannot learn skill for class, error: %s", err) + return + } + + g.hud.skillSelectMenu.RegenerateImageCache() + g.Info("Learned skill: " + skill.Skill) } + + return term.BindAction( + "learnskillid", + "learn a skill by a given ID", + learnByID, + ) +} + +func (g *GameControls) bindTerminalCommands(term d2interface.Terminal) error { + if err := g.bindFreeCamCommand(term); err != nil { + return err + } + + if err := g.bindSetLeftSkillCommand(term); err != nil { + return err + } + + if err := g.bindSetRightSkillCommand(term); err != nil { + return err + } + + if err := g.bindLearnSkillsCommand(term); err != nil { + return err + } + + if err := g.bindLearnSkillByIDCommand(term); err != nil { + return err + } + + return nil } diff --git a/d2game/d2player/globeWidget.go b/d2game/d2player/globeWidget.go index 6a8da540..f2bccb84 100644 --- a/d2game/d2player/globeWidget.go +++ b/d2game/d2player/globeWidget.go @@ -68,7 +68,7 @@ func newGlobeWidget(ui *d2ui.UIManager, asset *d2asset.AssetManager, x, y int, gtype globeType, - value, valueMax *int, + value *int, valueMax *int, l d2util.LogLevel) *globeWidget { var globe, overlap *globeFrame diff --git a/d2game/d2player/help_overlay.go b/d2game/d2player/help_overlay.go index 07af8aff..54ab30b6 100644 --- a/d2game/d2player/help_overlay.go +++ b/d2game/d2player/help_overlay.go @@ -155,8 +155,6 @@ const ( beltDotY = 568 ) -const bullets = 8 - // NewHelpOverlay creates a new HelpOverlay instance func NewHelpOverlay( asset *d2asset.AssetManager, @@ -184,7 +182,6 @@ type HelpOverlay struct { frames []*d2ui.Sprite text []*d2ui.Label lines []line - bullets [bullets]*d2ui.Label uiManager *d2ui.UIManager closeButton *d2ui.Button keyMap *KeyMap @@ -335,8 +332,11 @@ func (h *HelpOverlay) setupTitleAndButton() { h.text = append(h.text, newLabel) } -func (h *HelpOverlay) updateBulletText() { +func (h *HelpOverlay) setupBulletedList() { // Bullets + // the hotkeys displayed here should be pulled from a mapping of input events to game events + // https://github.com/OpenDiablo2/OpenDiablo2/issues/793 + // https://github.com/OpenDiablo2/OpenDiablo2/issues/794 callouts := []struct{ text string }{ // "Ctrl" should be hotkey // "Hold Down <%s> to Run" {text: fmt.Sprintf( @@ -378,35 +378,17 @@ func (h *HelpOverlay) updateBulletText() { )}, } - for i := 0; i < bullets; i++ { - h.bullets[i].SetText(callouts[i].text) - } -} - -func (h *HelpOverlay) setupBulletedList() { - for idx := 0; idx < bullets; idx++ { + for idx := range callouts { listItemOffsetY := idx * listItemVerticalOffset - label := h.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteSky) - label.SetPosition(listRootX, listRootY+listItemOffsetY) - h.bullets[idx] = label - h.panelGroup.AddWidget(h.bullets[idx]) - - newDot, err := h.uiManager.NewSprite(d2resource.HelpYellowBullet, d2resource.PaletteSky) - if err != nil { - h.Error(err.Error()) - } - - err = newDot.SetCurrentFrame(0) - if err != nil { - h.Error(err.Error()) - } - - newDot.SetPosition(listBulletX, listBulletRootY+listItemOffsetY+bulletOffsetY) - - h.frames = append(h.frames, newDot) + h.createBullet(callout{ + LabelText: callouts[idx].text, + LabelX: listRootX, + LabelY: listRootY + listItemOffsetY, + DotX: listBulletX, + DotY: listBulletRootY + listItemOffsetY, + }) } - h.updateBulletText() } // nolint:funlen // can't reduce @@ -427,7 +409,7 @@ func (h *HelpOverlay) setupLabelsWithLines() { DotY: newSkillDotY, }) - // Some of the help fonts require multiple lines. + // Some of the help fonts require mulktiple lines. h.createLabel(callout{ LabelText: h.asset.TranslateString("StrHelp10"), // "Left Mouse-" LabelX: leftMouseLabelX, @@ -571,6 +553,26 @@ type callout struct { DotY int } +func (h *HelpOverlay) createBullet(c callout) { + newLabel := h.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteSky) + newLabel.SetText(c.LabelText) + newLabel.SetPosition(c.LabelX, c.LabelY) + h.text = append(h.text, newLabel) + + newDot, err := h.uiManager.NewSprite(d2resource.HelpYellowBullet, d2resource.PaletteSky) + if err != nil { + h.Error(err.Error()) + } + + err = newDot.SetCurrentFrame(0) + if err != nil { + h.Error(err.Error()) + } + + newDot.SetPosition(c.DotX, c.DotY+bulletOffsetY) + h.frames = append(h.frames, newDot) +} + func (h *HelpOverlay) createLabel(c callout) { newLabel := h.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteSky) newLabel.SetText(c.LabelText) @@ -629,8 +631,3 @@ func (h *HelpOverlay) Render(target d2interface.Surface) { target.Pop() } } - -func (h *HelpOverlay) updateKeyMap(km *KeyMap) { - h.keyMap = km - h.updateBulletText() -} diff --git a/d2game/d2player/hero_stats_panel.go b/d2game/d2player/hero_stats_panel.go index 759e2451..9f9cb5c0 100644 --- a/d2game/d2player/hero_stats_panel.go +++ b/d2game/d2player/hero_stats_panel.go @@ -9,7 +9,6 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" ) @@ -56,15 +55,6 @@ const ( const ( heroStatsCloseButtonX, heroStatsCloseButtonY = 208, 453 - addStatSocketOffsetX, addStatSocketOffsetY = -3, 34 -) - -const ( - newStatsRemainingPointsFieldX, newStatsRemainingPointsFieldY = 83, 430 - newStatsRemainingPointsLabelX = 92 - newStatsRemainingPointsLabel1Y = 411 - newStatsRemainingPointsLabel2Y = 418 - newStatsRemainingPointsValueX, newStatsRemainingPointsValueY = 188, 411 ) // PanelText represents text on the panel @@ -123,17 +113,15 @@ func NewHeroStatsPanel(asset *d2asset.AssetManager, // HeroStatsPanel represents the hero status panel type HeroStatsPanel struct { - asset *d2asset.AssetManager - uiManager *d2ui.UIManager - panel *d2ui.Sprite - heroState *d2hero.HeroStatsState - heroName string - heroClass d2enum.Hero - labels *StatsPanelLabels - onCloseCb func() - panelGroup *d2ui.WidgetGroup - newStatPoints *d2ui.WidgetGroup - remainingPoints *d2ui.Label + asset *d2asset.AssetManager + uiManager *d2ui.UIManager + panel *d2ui.Sprite + heroState *d2hero.HeroStatsState + heroName string + heroClass d2enum.Hero + labels *StatsPanelLabels + onCloseCb func() + panelGroup *d2ui.WidgetGroup originX int originY int @@ -147,9 +135,8 @@ func (s *HeroStatsPanel) Load() { var err error s.panelGroup = s.uiManager.NewWidgetGroup(d2ui.RenderPriorityHeroStatsPanel) - s.newStatPoints = s.uiManager.NewWidgetGroup(d2ui.RenderPriorityHeroStatsPanel) - frame := s.uiManager.NewUIFrame(d2ui.FrameLeft) + frame := d2ui.NewUIFrame(s.asset, s.uiManager, d2ui.FrameLeft) s.panelGroup.AddWidget(frame) s.panel, err = s.uiManager.NewSprite(d2resource.InventoryCharacterPanel, d2resource.PaletteSky) @@ -167,91 +154,10 @@ func (s *HeroStatsPanel) Load() { closeButton.OnActivated(func() { s.Close() }) s.panelGroup.AddWidget(closeButton) - s.loadNewStatPoints() - s.setLayout() - s.initStatValueLabels() s.panelGroup.SetVisible(false) } -func (s *HeroStatsPanel) loadNewStatPoints() { - field, err := s.uiManager.NewSprite(d2resource.HeroStatsPanelStatsPoints, d2resource.PaletteSky) - if err != nil { - s.Error(err.Error()) - } - - field.SetPosition(newStatsRemainingPointsFieldX, newStatsRemainingPointsFieldY) - s.newStatPoints.AddWidget(field) - - label1 := s.uiManager.NewLabel(d2resource.Font6, d2resource.PaletteSky) - label1.SetPosition(newStatsRemainingPointsLabelX, newStatsRemainingPointsLabel1Y) - label1.SetText(s.asset.TranslateString("strchrstat")) - label1.Color[0] = d2util.Color(d2gui.ColorRed) - s.newStatPoints.AddWidget(label1) - - label2 := s.uiManager.NewLabel(d2resource.Font6, d2resource.PaletteSky) - label2.SetPosition(newStatsRemainingPointsLabelX, newStatsRemainingPointsLabel2Y) - label2.SetText(s.asset.TranslateString("strchrrema")) - label2.Color[0] = d2util.Color(d2gui.ColorRed) - s.newStatPoints.AddWidget(label2) - - s.remainingPoints = s.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky) - s.remainingPoints.SetText(strconv.Itoa(s.heroState.StatsPoints)) - s.remainingPoints.SetPosition(newStatsRemainingPointsValueX, newStatsRemainingPointsValueY) - s.remainingPoints.Alignment = d2ui.HorizontalAlignCenter - s.newStatPoints.AddWidget(s.remainingPoints) - - buttons := []struct { - x int - y int - cb func() - }{ - {205, 140, func() { - s.heroState.Strength++ - }}, - {205, 201, func() { - s.heroState.Dexterity++ - }}, - {205, 286, func() { - s.heroState.Vitality++ - }}, - {205, 347, func() { - s.heroState.Energy++ - }}, - } - - var socket *d2ui.Sprite - - var button *d2ui.Button - - for _, i := range buttons { - currentValue := i - - socket, err = s.uiManager.NewSprite(d2resource.HeroStatsPanelSocket, d2resource.PaletteSky) - if err != nil { - s.Error(err.Error()) - } - - socket.SetPosition(i.x+addStatSocketOffsetX, i.y+addStatSocketOffsetY) - s.newStatPoints.AddWidget(socket) - - button = s.uiManager.NewButton(d2ui.ButtonTypeAddSkill, d2resource.PaletteSky) - button.SetPosition(i.x, i.y) - button.OnActivated(func() { - currentValue.cb() - s.heroState.StatsPoints-- - s.remainingPoints.SetText(strconv.Itoa(s.heroState.StatsPoints)) - s.setStatValues() - s.setLayout() - }) - s.newStatPoints.AddWidget(button) - } -} - -func (s *HeroStatsPanel) setLayout() { - s.newStatPoints.SetVisible(s.heroState.StatsPoints > 0 && s.IsOpen()) -} - // IsOpen returns true if the hero status panel is open func (s *HeroStatsPanel) IsOpen() bool { return s.isOpen @@ -270,14 +176,12 @@ func (s *HeroStatsPanel) Toggle() { func (s *HeroStatsPanel) Open() { s.isOpen = true s.panelGroup.SetVisible(true) - s.setLayout() } // Close closed the hero status panel func (s *HeroStatsPanel) Close() { s.isOpen = false s.panelGroup.SetVisible(false) - s.setLayout() s.onCloseCb() } diff --git a/d2game/d2player/hud.go b/d2game/d2player/hud.go index e68608c7..be455a3f 100644 --- a/d2game/d2player/hud.go +++ b/d2game/d2player/hud.go @@ -70,11 +70,6 @@ const ( whiteAlpha100 = 0xffffffff ) -const ( - addStatsButtonX, addStatsButtonY = 206, 561 - addSkillButtonX, addSkillButtonY = 563, 561 -) - // HUD represents the always visible user interface of the game type HUD struct { actionableRegions []actionableRegion @@ -108,11 +103,7 @@ type HUD struct { widgetLeftSkill *d2ui.CustomWidget widgetRightSkill *d2ui.CustomWidget panelBackground *d2ui.CustomWidget - addStatsButton *d2ui.Button - addSkillButton *d2ui.Button panelGroup *d2ui.WidgetGroup - gameControls *GameControls - *d2util.Logger } @@ -125,7 +116,6 @@ func NewHUD( actionableRegions []actionableRegion, mapEngine *d2mapengine.MapEngine, l d2util.LogLevel, - gameControls *GameControls, mapRenderer *d2maprenderer.MapRenderer, ) *HUD { nameLabel := ui.NewLabel(d2resource.Font16, d2resource.PaletteStatic) @@ -159,7 +149,6 @@ func NewHUD( zoneChangeText: zoneLabel, healthGlobe: healthGlobe, manaGlobe: manaGlobe, - gameControls: gameControls, } hud.Logger = d2util.NewLogger() @@ -188,27 +177,6 @@ func (h *HUD) Load() { h.loadCustomWidgets() h.loadUIButtons() - // nolint:gomnd // dividing by 2 (const) - h.addStatsButton = h.uiManager.NewButton(d2ui.ButtonTypeAddSkill, "") - h.addStatsButton.SetPosition(addStatsButtonX, addStatsButtonY) - h.addStatsButton.SetVisible(false) - bw, bh := h.addStatsButton.GetSize() - statsTooltip := h.uiManager.NewTooltip(d2resource.Font16, d2resource.PaletteSky, d2ui.TooltipXCenter, d2ui.TooltipYTop) - statsTooltip.SetPosition(addStatsButtonX+bw/2, addStatsButtonY-bh/2) - statsTooltip.SetText(h.asset.TranslateString("strlvlup")) - h.addStatsButton.SetTooltip(statsTooltip) - h.panelGroup.AddWidget(h.addStatsButton) - - h.addSkillButton = h.uiManager.NewButton(d2ui.ButtonTypeAddSkill, "") - h.addSkillButton.SetPosition(addSkillButtonX, addSkillButtonY) - h.addSkillButton.SetVisible(false) - bw, bh = h.addSkillButton.GetSize() - skillTooltip := h.uiManager.NewTooltip(d2resource.Font16, d2resource.PaletteSky, d2ui.TooltipXCenter, d2ui.TooltipYTop) - skillTooltip.SetPosition(addSkillButtonX+bw/2, addSkillButtonY-bh/2) - skillTooltip.SetText(h.asset.TranslateString("strnewskl")) - h.addSkillButton.SetTooltip(skillTooltip) - h.panelGroup.AddWidget(h.addSkillButton) - h.panelGroup.SetVisible(true) } @@ -258,6 +226,7 @@ func (h *HUD) loadCustomWidgets() { } func (h *HUD) loadSkillResources() { + // https://github.com/OpenDiablo2/OpenDiablo2/issues/799 genericSkillsSprite, err := h.uiManager.NewSprite(d2resource.GenericSkills, d2resource.PaletteSky) if err != nil { h.Error(err.Error()) @@ -312,6 +281,21 @@ func (h *HUD) loadTooltips() { labelY := staminaExperienceY - halfLabelHeight h.staminaTooltip.SetPosition(labelX, labelY) + // runwalk tooltip + h.runWalkTooltip = h.uiManager.NewTooltip(d2resource.Font16, d2resource.PaletteSky, d2ui.TooltipXCenter, d2ui.TooltipYBottom) + rect = &h.actionableRegions[walkRun].rect + + halfButtonWidth = rect.Width >> 1 + halfButtonHeight := rect.Height >> 1 + + centerX = rect.Left + halfButtonWidth + centerY := rect.Top + halfButtonHeight + + _, labelHeight = h.runWalkTooltip.GetSize() + labelX = centerX + labelY = centerY - halfButtonHeight - labelHeight + h.runWalkTooltip.SetPosition(labelX, labelY) + // experience tooltip h.experienceTooltip = h.uiManager.NewTooltip(d2resource.Font16, d2resource.PaletteSky, d2ui.TooltipXCenter, d2ui.TooltipYTop) rect = &h.actionableRegions[stamina].rect @@ -332,21 +316,8 @@ func (h *HUD) loadUIButtons() { h.runButton = h.uiManager.NewButton(d2ui.ButtonTypeRun, "") h.runButton.SetPosition(runButtonX, runButtonY) h.runButton.OnActivated(func() { h.onToggleRunButton(false) }) - - h.runWalkTooltip = h.uiManager.NewTooltip(d2resource.Font16, d2resource.PaletteSky, d2ui.TooltipXCenter, d2ui.TooltipYTop) - // we must set text first, and then we're getting its height - h.updateRunTooltipText() - - bw, bh := h.runButton.GetSize() - _, lh := h.runWalkTooltip.GetSize() - // nolint:gomnd // dividing by 2 (const) - labelX := runButtonX + bw/2 - // nolint:gomnd // dividing by 2 (const) - labelY := runButtonY - bh/2 - lh/2 - - h.runWalkTooltip.SetPosition(labelX, labelY) h.runButton.SetTooltip(h.runWalkTooltip) - + h.updateRunTooltipText() h.panelGroup.AddWidget(h.runButton) if h.hero.IsRunToggled() { @@ -373,6 +344,7 @@ func (h *HUD) onToggleRunButton(noButton bool) { h.hero.ToggleRunWalk() h.updateRunTooltipText() + // https://github.com/OpenDiablo2/OpenDiablo2/issues/800 h.hero.SetIsRunning(h.hero.IsRunToggled()) } diff --git a/d2game/d2player/inventory.go b/d2game/d2player/inventory.go index d28a4c29..4b0875d3 100644 --- a/d2game/d2player/inventory.go +++ b/d2game/d2player/inventory.go @@ -104,7 +104,7 @@ func (g *Inventory) Load() { g.panelGroup = g.uiManager.NewWidgetGroup(d2ui.RenderPriorityInventory) - frame := g.uiManager.NewUIFrame(d2ui.FrameRight) + frame := d2ui.NewUIFrame(g.asset, g.uiManager, d2ui.FrameRight) g.panelGroup.AddWidget(frame) g.panel, err = g.uiManager.NewSprite(d2resource.InventoryCharacterPanel, d2resource.PaletteSky) diff --git a/d2game/d2player/key_binding_menu.go b/d2game/d2player/key_binding_menu.go index b5c7dd61..fa0da7b7 100644 --- a/d2game/d2player/key_binding_menu.go +++ b/d2game/d2player/key_binding_menu.go @@ -666,7 +666,7 @@ func (menu *KeyBindingMenu) onDefaultClicked() error { func (menu *KeyBindingMenu) onAcceptClicked() error { for gameEvent, change := range menu.changesToBeSaved { menu.keyMap.SetPrimaryBinding(gameEvent, change.primary) - menu.keyMap.SetSecondaryBinding(gameEvent, change.secondary) + menu.keyMap.SetSecondaryBinding(gameEvent, change.primary) } menu.changesToBeSaved = make(map[d2enum.GameEvent]*bindingChange) diff --git a/d2game/d2player/quest_log.go b/d2game/d2player/quest_log.go index 4de2b90f..c35d866a 100644 --- a/d2game/d2player/quest_log.go +++ b/d2game/d2player/quest_log.go @@ -2,6 +2,7 @@ package d2player import ( "fmt" + "image/color" "strings" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" @@ -51,8 +52,6 @@ const ( questTabXOffset = 61 ) -const questCompleteAnimationDuration = 3 - func (s *QuestLog) getPositionForSocket(number int) (x, y int) { pos := []struct { x int @@ -73,7 +72,6 @@ func (s *QuestLog) getPositionForSocket(number int) (x, y int) { func NewQuestLog(asset *d2asset.AssetManager, ui *d2ui.UIManager, l d2util.LogLevel, - audioProvider d2interface.AudioProvider, act int) *QuestLog { originX := 0 originY := 0 @@ -82,12 +80,12 @@ func NewQuestLog(asset *d2asset.AssetManager, qs := map[int]int{ 0: -2, 1: -2, - 2: -2, + 2: -1, 3: 0, 4: 1, - 5: 4, + 5: 2, 6: 3, - 7: -1, + 7: 0, 8: 0, 9: 0, 10: 0, @@ -109,9 +107,9 @@ func NewQuestLog(asset *d2asset.AssetManager, 26: 0, } - var quests [d2enum.ActsNumber]*questEntire + var quests [d2enum.ActsNumber]*d2ui.WidgetGroup for i := 0; i < d2enum.ActsNumber; i++ { - quests[i] = &questEntire{WidgetGroup: ui.NewWidgetGroup(d2ui.RenderPriorityQuestLog)} + quests[i] = ui.NewWidgetGroup(d2ui.RenderPriorityQuestLog) } var tabs [d2enum.ActsNumber]questLogTab @@ -132,7 +130,6 @@ func NewQuestLog(asset *d2asset.AssetManager, quests: quests, questStatus: qs, maxPlayersAct: mpa, - audioProvider: audioProvider, } ql.Logger = d2util.NewLogger() @@ -153,12 +150,10 @@ type QuestLog struct { selectedQuest int act int tab [d2enum.ActsNumber]questLogTab - audioProvider d2interface.AudioProvider - completeSound d2interface.SoundEffect questName *d2ui.Label questDescr *d2ui.Label - quests [d2enum.ActsNumber]*questEntire + quests [d2enum.ActsNumber]*d2ui.WidgetGroup questStatus map[int]int maxPlayersAct int @@ -169,13 +164,6 @@ type QuestLog struct { *d2util.Logger } -type questEntire struct { - *d2ui.WidgetGroup - icons []*d2ui.Sprite - buttons []*d2ui.Button - sockets []*d2ui.Sprite -} - /* questIconTab returns path to quest animation using its act and number. From d2resource: QuestLogAQuestAnimation = "/data/global/ui/MENU/a%dq%d.dc6"*/ @@ -189,11 +177,6 @@ const ( notStartedFrame = 26 ) -const ( - socketNormalFrame = 0 - socketHighlightedFrame = 1 -) - const questDescriptionLenght = 30 type questLogTab struct { @@ -207,13 +190,7 @@ func (s *QuestLog) Load() { s.panelGroup = s.uiManager.NewWidgetGroup(d2ui.RenderPriorityQuestLog) - // quest completion sound. - s.completeSound, err = s.audioProvider.LoadSound(d2resource.QuestLogDoneSfx, false, false) - if err != nil { - s.Error(err.Error()) - } - - frame := s.uiManager.NewUIFrame(d2ui.FrameLeft) + frame := d2ui.NewUIFrame(s.asset, s.uiManager, d2ui.FrameLeft) s.panelGroup.AddWidget(frame) s.panel, err = s.uiManager.NewSprite(d2resource.QuestLogBg, d2resource.PaletteSky) @@ -222,7 +199,7 @@ func (s *QuestLog) Load() { } w, h := frame.GetSize() - staticPanel := s.uiManager.NewCustomWidgetCached(s.renderStaticPanelFrames, w, h) + staticPanel := s.uiManager.NewCustomWidgetCached(s.renderStaticMenu, w, h) s.panelGroup.AddWidget(staticPanel) closeButton := s.uiManager.NewButton(d2ui.ButtonTypeSquareClose, "") @@ -239,34 +216,30 @@ func (s *QuestLog) Load() { s.questName = s.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteStatic) s.questName.Alignment = d2ui.HorizontalAlignCenter - s.questName.Color[0] = d2util.Color(white) + s.questName.Color[0] = rgbaColor(white) s.questName.SetPosition(questNameLabelX, questNameLabelY) s.panelGroup.AddWidget(s.questName) s.questDescr = s.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteStatic) s.questDescr.Alignment = d2ui.HorizontalAlignLeft - s.questDescr.Color[0] = d2util.Color(white) + s.questDescr.Color[0] = rgbaColor(white) s.questDescr.SetPosition(questDescrLabelX, questDescrLabelY) s.panelGroup.AddWidget(s.questDescr) s.loadTabs() - // creates quest boards for each act for i := 0; i < d2enum.ActsNumber; i++ { - item, icons, buttons, sockets := s.loadQuestBoard(i + 1) - s.quests[i] = &questEntire{item, icons, buttons, sockets} + s.quests[i] = s.loadQuestIconsForAct(i + 1) } s.panelGroup.SetVisible(false) } -// loadTabs loads quest log tabs func (s *QuestLog) loadTabs() { var err error tabsResource := d2resource.WPTabs - // create tabs only for 'discovered' acts for i := 0; i < s.maxPlayersAct; i++ { currentValue := i @@ -275,8 +248,7 @@ func (s *QuestLog) loadTabs() { s.Error(err.Error()) } - // nolint:gomnd // it's constant. - // each tab has two frames (active / inactive) + // nolint:gomnd // it's constant frame := 2 * currentValue err := s.tab[i].sprite.SetCurrentFrame(frame) @@ -294,15 +266,12 @@ func (s *QuestLog) loadTabs() { s.panelGroup.AddWidget(s.tab[i].invisibleButton) } - // sets tab to current player's act. s.setTab(s.act - 1) } -// loadQuestBoard creates quest fields (socket, button, icon) for specified act -func (s *QuestLog) loadQuestBoard(act int) (wg *d2ui.WidgetGroup, icons []*d2ui.Sprite, buttons []*d2ui.Button, sockets []*d2ui.Sprite) { - wg = s.uiManager.NewWidgetGroup(d2ui.RenderPriorityQuestLog) +func (s *QuestLog) loadQuestIconsForAct(act int) *d2ui.WidgetGroup { + wg := s.uiManager.NewWidgetGroup(d2ui.RenderPriorityQuestLog) - // sets number of quests in act (for act 4 it's only 3, else 6) var questsInAct int if act == d2enum.Act4 { questsInAct = d2enum.HalfQuestsNumber @@ -310,8 +279,13 @@ func (s *QuestLog) loadQuestBoard(act int) (wg *d2ui.WidgetGroup, icons []*d2ui. questsInAct = d2enum.NormalActQuestsNumber } + var sockets []*d2ui.Sprite + + var buttons []*d2ui.Button + + var icon *d2ui.Sprite + for n := 0; n < questsInAct; n++ { - cw := n x, y := s.getPositionForSocket(n) socket, err := s.uiManager.NewSprite(d2resource.QuestLogSocket, d2resource.PaletteSky) @@ -319,69 +293,56 @@ func (s *QuestLog) loadQuestBoard(act int) (wg *d2ui.WidgetGroup, icons []*d2ui. s.Error(err.Error()) } - socket.SetPosition(x, y+iconOffsetY+questOffsetY) + socket.SetPosition(x+questOffsetX, y+iconOffsetY+2*questOffsetY) sockets = append(sockets, socket) - icon, err := s.makeQuestIconForAct(act, n, x, y) + button := s.uiManager.NewButton(d2ui.ButtonTypeBlankQuestBtn, "") + button.SetPosition(x+questOffsetX, y+questOffsetY) + buttons = append(buttons, button) + + icon, err = s.makeQuestIconForAct(act, n) if err != nil { s.Error(err.Error()) } - icons = append(icons, icon) - - button := s.uiManager.NewButton(d2ui.ButtonTypeBlankQuestBtn, "") - button.SetPosition(x+questOffsetX, y+questOffsetY) - button.SetEnabled(s.questStatus[s.cordsToQuestID(act, cw)] != d2enum.QuestStatusNotStarted) - buttons = append(buttons, button) + icon.SetPosition(x+questOffsetX, y+questOffsetY+iconOffsetY) + wg.AddWidget(icon) } for i := 0; i < questsInAct; i++ { currentQuest := i - - // creates callback for quest button buttons[i].OnActivated(func() { var err error - - // set normal (not-highlighted) frame for each quest socket for j := 0; j < questsInAct; j++ { - err = sockets[j].SetCurrentFrame(socketNormalFrame) + err = sockets[j].SetCurrentFrame(0) if err != nil { s.Error(err.Error()) } } - - // highlights appropriate socket - err = sockets[currentQuest].SetCurrentFrame(socketHighlightedFrame) - if err != nil { - s.Error(err.Error()) + if act-1 == s.selectedTab { + err = sockets[currentQuest].SetCurrentFrame(1) + if err != nil { + s.Error(err.Error()) + } } - - // sets quest labels s.onQuestClicked(currentQuest + 1) }) } - // adds sockets to widget group for _, s := range sockets { wg.AddWidget(s) } - // adds buttons to widget group for _, b := range buttons { wg.AddWidget(b) } - // adds icons to widget group - for _, i := range icons { - wg.AddWidget(i) - } - wg.SetVisible(false) - return wg, icons, buttons, sockets + return wg } -func (s *QuestLog) makeQuestIconForAct(act, n, x, y int) (*d2ui.Sprite, error) { +func (s *QuestLog) makeQuestIconForAct(act, n int) (*d2ui.Sprite, error) { iconResource := s.questIconsTable(act, n) icon, err := s.uiManager.NewSprite(iconResource, d2resource.PaletteSky) @@ -393,56 +354,27 @@ func (s *QuestLog) makeQuestIconForAct(act, n, x, y int) (*d2ui.Sprite, error) { case d2enum.QuestStatusCompleted: err = icon.SetCurrentFrame(completedFrame) case d2enum.QuestStatusCompleting: - // animation will be played after quest-log panel is opened (see s.playQuestAnimation) + // that's not complet now err = icon.SetCurrentFrame(0) + if err != nil { + s.Error(err.Error()) + } + + icon.PlayForward() + icon.SetPlayLoop(false) + err = icon.SetCurrentFrame(completedFrame) + s.questStatus[s.cordsToQuestID(act, n)] = d2enum.QuestStatusCompleted case d2enum.QuestStatusNotStarted: err = icon.SetCurrentFrame(notStartedFrame) default: err = icon.SetCurrentFrame(inProgresFrame) } - icon.SetPosition(x+questOffsetX, y+questOffsetY+iconOffsetY) - return icon, err } -// playQuestAnimations plays animations for quests (when status=questStatusCompleting) -func (s *QuestLog) playQuestAnimations() { - for j, i := range s.quests[s.selectedTab].icons { - questID := s.cordsToQuestID(s.selectedTab+1, j) - if s.questStatus[questID] == d2enum.QuestStatusCompleting { - s.completeSound.Play() - - // quest should be highlighted and it's label should be displayed - s.quests[s.selectedTab].buttons[j].Activate() - - i.SetPlayLength(questCompleteAnimationDuration) - i.PlayForward() - i.SetPlayLoop(false) - } - } -} - -// stopPlayedAnimation stops currently played animations and sets quests in -// completing state to completed (should be used, when quest log is closing) -func (s *QuestLog) stopPlayedAnimations() { - // stops all played animations - for j, i := range s.quests[s.selectedTab].icons { - questID := s.cordsToQuestID(s.selectedTab+1, j) - if s.questStatus[questID] == d2enum.QuestStatusCompleting { - s.questStatus[questID] = d2enum.QuestStatusCompleted - - err := i.SetCurrentFrame(completedFrame) - if err != nil { - s.Error(err.Error()) - } - } - } -} - -// setQuestLabel loads quest labels text (title and description) func (s *QuestLog) setQuestLabel() { - if s.selectedQuest == d2enum.QuestNone { + if s.selectedQuest == 0 { s.questName.SetText("") s.questDescr.SetText("") @@ -451,9 +383,9 @@ func (s *QuestLog) setQuestLabel() { s.questName.SetText(s.asset.TranslateString(fmt.Sprintf("qstsa%dq%d", s.selectedTab+1, s.selectedQuest))) - status := s.questStatus[s.cordsToQuestID(s.selectedTab+1, s.selectedQuest)-1] + status := s.questStatus[s.cordsToQuestID(s.selectedTab+1, s.selectedQuest)] switch status { - case d2enum.QuestStatusCompleted, d2enum.QuestStatusCompleting: + case d2enum.QuestStatusCompleted: s.questDescr.SetText( strings.Join( d2util.SplitIntoLinesWithMaxWidth( @@ -464,61 +396,37 @@ func (s *QuestLog) setQuestLabel() { case d2enum.QuestStatusNotStarted: s.questDescr.SetText("") default: - str := fmt.Sprintf("qstsa%dq%d%d", s.selectedTab+1, s.selectedQuest, status) - descr := s.asset.TranslateString(str) - - // if description not found - if str == descr { - s.questDescr.SetText("") - } else { - s.questDescr.SetText(strings.Join( - d2util.SplitIntoLinesWithMaxWidth( - descr, questDescriptionLenght), - "\n"), - ) - } - } -} - -// switch all socket (in current tab) to normal state -func (s *QuestLog) clearHighlightment() { - for _, i := range s.quests[s.selectedTab].sockets { - err := i.SetCurrentFrame(socketNormalFrame) - if err != nil { - s.Error(err.Error()) - } + s.questDescr.SetText(strings.Join( + d2util.SplitIntoLinesWithMaxWidth( + s.asset.TranslateString( + fmt.Sprintf("qstsa%dq%d%d", s.selectedTab+1, s.selectedQuest, status), + ), + questDescriptionLenght), + "\n"), + ) } } func (s *QuestLog) setTab(tab int) { var mod int - // before we leafe current tab, we need to switch highlighted - // quest socket to normal frame - s.clearHighlightment() - s.selectedTab = tab s.selectedQuest = d2enum.QuestNone s.setQuestLabel() - s.playQuestAnimations() - // displays appropriate quests board for i := 0; i < s.maxPlayersAct; i++ { s.quests[i].SetVisible(tab == i) } - // "highlights" appropriate tab for i := 0; i < s.maxPlayersAct; i++ { cv := i - // converts bool to 1/0 if cv == s.selectedTab { mod = 0 } else { mod = 1 } - // sets tab sprite to highlighted/non-highlighted err := s.tab[cv].sprite.SetCurrentFrame(2*cv + mod) if err != nil { s.Error(err.Error()) @@ -532,9 +440,8 @@ func (s *QuestLog) onQuestClicked(number int) { s.Infof("Quest number %d in tab %d clicked", number, s.selectedTab) } -// func (s *QuestLog) onDescrClicked() { - s.Info("Quest description button clicked") + // } // IsOpen returns true if the hero status panel is open @@ -556,7 +463,6 @@ func (s *QuestLog) Open() { s.isOpen = true s.panelGroup.SetVisible(true) s.setTab(s.selectedTab) - s.playQuestAnimations() } // Close closed the hero status panel @@ -568,8 +474,6 @@ func (s *QuestLog) Close() { s.quests[i].SetVisible(false) } - s.stopPlayedAnimations() - s.onCloseCb() } @@ -580,22 +484,11 @@ func (s *QuestLog) SetOnCloseCb(cb func()) { // Advance updates labels on the panel func (s *QuestLog) Advance(elapsed float64) { - if !s.IsOpen() { - return - } + // +} - for j, i := range s.quests[s.selectedTab].icons { - questID := s.cordsToQuestID(s.selectedTab+1, j) - if s.questStatus[questID] == d2enum.QuestStatusCompleting { - if err := i.Advance(elapsed); err != nil { - s.Error(err.Error()) - } - - if i.GetCurrentFrame() == completedFrame { - s.questStatus[questID] = d2enum.QuestStatusCompleted - } - } - } +func (s *QuestLog) renderStaticMenu(target d2interface.Surface) { + s.renderStaticPanelFrames(target) } // nolint:dupl // I think it is OK, to duplicate this function @@ -634,6 +527,32 @@ func (s *QuestLog) renderStaticPanelFrames(target d2interface.Surface) { } } +// copy from character select (github.com/OpenDiablo2/OpenDiablo2/d2game/d2gamescreen/character_select.go) +func rgbaColor(rgba uint32) color.RGBA { + result := color.RGBA{} + a, b, g, r := 0, 1, 2, 3 + byteWidth := 8 + byteMask := 0xff + + for idx := 0; idx < 4; idx++ { + shift := idx * byteWidth + component := uint8(rgba>>shift) & uint8(byteMask) + + switch idx { + case a: + result.A = component + case b: + result.B = component + case g: + result.G = component + case r: + result.R = component + } + } + + return result +} + func (s *QuestLog) cordsToQuestID(act, number int) int { key := (act-1)*d2enum.NormalActQuestsNumber + number if act > d2enum.Act4 { @@ -642,3 +561,25 @@ func (s *QuestLog) cordsToQuestID(act, number int) int { return key } + +//nolint:varcheck,unused // I think, it will be used, if not, we can just remove it +func (s *QuestLog) questIDToCords(id int) (act, number int) { + act = 1 + + for i := 0; i < d2enum.ActsNumber; i++ { + if id < d2enum.NormalActQuestsNumber { + break + } + + act++ + + id -= d2enum.NormalActQuestsNumber + } + + number = id + if act > d2enum.Act4 { + number -= d2enum.HalfQuestsNumber + } + + return act, number +} diff --git a/d2game/d2player/skilltree.go b/d2game/d2player/skilltree.go index b60c6509..3f5ddae0 100644 --- a/d2game/d2player/skilltree.go +++ b/d2game/d2player/skilltree.go @@ -3,7 +3,6 @@ package d2player import ( "errors" "fmt" - "strconv" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" @@ -57,10 +56,6 @@ const ( frameSelectedTab3Full = 13 ) -const ( - remainingPointsLabelX, remainingPointsLabelY = 677, 128 -) - const ( skillTreePanelX = 401 skillTreePanelY = 64 @@ -92,7 +87,6 @@ type skillTreeHeroTypeResources struct { func newSkillTree( skills map[int]*d2hero.HeroSkill, heroClass d2enum.Hero, - hero *d2hero.HeroStatsState, asset *d2asset.AssetManager, l d2util.LogLevel, ui *d2ui.UIManager, @@ -104,7 +98,6 @@ func newSkillTree( uiManager: ui, originX: skillTreePanelX, originY: skillTreePanelY, - stats: hero, tab: [numTabs]*skillTreeTab{ {}, {}, @@ -121,25 +114,24 @@ func newSkillTree( } type skillTree struct { - resources *skillTreeHeroTypeResources - asset *d2asset.AssetManager - uiManager *d2ui.UIManager - skills map[int]*d2hero.HeroSkill - skillIcons []*skillIcon - heroClass d2enum.Hero - availSPLabel *d2ui.Label - closeButton *d2ui.Button - tab [numTabs]*skillTreeTab - remainingPoints *d2ui.Label - isOpen bool - originX int - originY int - selectedTab int - onCloseCb func() - panelGroup *d2ui.WidgetGroup - iconGroup *d2ui.WidgetGroup - panel *d2ui.CustomWidget - stats *d2hero.HeroStatsState + resources *skillTreeHeroTypeResources + asset *d2asset.AssetManager + uiManager *d2ui.UIManager + skills map[int]*d2hero.HeroSkill + skillIcons []*skillIcon + heroClass d2enum.Hero + frame *d2ui.UIFrame + availSPLabel *d2ui.Label + closeButton *d2ui.Button + tab [numTabs]*skillTreeTab + isOpen bool + originX int + originY int + selectedTab int + onCloseCb func() + panelGroup *d2ui.WidgetGroup + iconGroup *d2ui.WidgetGroup + panel *d2ui.CustomWidget *d2util.Logger l d2util.LogLevel @@ -152,20 +144,14 @@ func (s *skillTree) load() { s.panel = s.uiManager.NewCustomWidget(s.Render, 400, 600) s.panelGroup.AddWidget(s.panel) - frame := s.uiManager.NewUIFrame(d2ui.FrameRight) - s.panelGroup.AddWidget(frame) + s.frame = d2ui.NewUIFrame(s.asset, s.uiManager, d2ui.FrameRight) + s.panelGroup.AddWidget(s.frame) s.closeButton = s.uiManager.NewButton(d2ui.ButtonTypeSquareClose, "") s.closeButton.SetVisible(false) s.closeButton.OnActivated(func() { s.Close() }) s.panelGroup.AddWidget(s.closeButton) - s.remainingPoints = s.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky) - s.remainingPoints.SetPosition(remainingPointsLabelX, remainingPointsLabelY) - s.remainingPoints.Alignment = d2ui.HorizontalAlignCenter - s.remainingPoints.SetText(strconv.Itoa(s.stats.SkillPoints)) - s.panelGroup.AddWidget(s.remainingPoints) - if err := s.setHeroTypeResourcePath(); err != nil { s.Error(err.Error()) } diff --git a/d2networking/d2client/d2localclient/local_client_connection.go b/d2networking/d2client/d2localclient/local_client_connection.go index 3923d42b..391160b7 100644 --- a/d2networking/d2client/d2localclient/local_client_connection.go +++ b/d2networking/d2client/d2localclient/local_client_connection.go @@ -87,16 +87,19 @@ func (l *LocalClientConnection) Open(_, saveFilePath string) error { // Close disconnects from the server and destroys it. func (l *LocalClientConnection) Close() error { - disconnectRequest, err := d2netpacket.CreatePlayerDisconnectRequestPacket(l.uniqueID) + sc, err := d2netpacket.CreateServerClosedPacket() if err != nil { return err } - err = l.SendPacketToServer(disconnectRequest) + err = l.SendPacketToServer(sc) if err != nil { return err } + l.gameServer.OnClientDisconnected(l) + l.gameServer.Stop() + return nil } diff --git a/d2networking/d2client/d2remoteclient/remote_client_connection.go b/d2networking/d2client/d2remoteclient/remote_client_connection.go index 57df11ef..9f47744e 100644 --- a/d2networking/d2client/d2remoteclient/remote_client_connection.go +++ b/d2networking/d2client/d2remoteclient/remote_client_connection.go @@ -3,7 +3,6 @@ package d2remoteclient import ( "encoding/json" "fmt" - "io" "net" "strings" @@ -132,34 +131,30 @@ func (r *RemoteClientConnection) SetClientListener(listener d2networking.ClientL // SendPacketToServer compresses the JSON encoding of a NetPacket and // sends it to the server. func (r *RemoteClientConnection) SendPacketToServer(packet d2netpacket.NetPacket) error { - encoder := json.NewEncoder(r.tcpConnection) - - err := encoder.Encode(packet) + data, err := json.Marshal(packet) if err != nil { return err } + if _, err = r.tcpConnection.Write(data); err != nil { + return err + } + return nil } // serverListener runs a while loop, reading from the GameServer's TCP // connection. func (r *RemoteClientConnection) serverListener() { + var packet d2netpacket.NetPacket + decoder := json.NewDecoder(r.tcpConnection) for { - var packet d2netpacket.NetPacket - err := decoder.Decode(&packet) if err != nil { - switch err { - case io.EOF: - break // the other side closed the connection - default: - r.Errorf("failed to decode the packet, err: %v\n", err) - } - - return // allow the connection to close + r.Errorf("failed to decode the packet, err: %v\n", err) + return } p, err := r.decodeToPacket(packet.PacketType, string(packet.PacketData)) @@ -191,29 +186,102 @@ func (r *RemoteClientConnection) bytesToJSON(buffer []byte) (string, d2netpacket func (r *RemoteClientConnection) decodeToPacket( t d2netpackettype.NetPacketType, data string) (d2netpacket.NetPacket, error) { - var ( - np = d2netpacket.NetPacket{} - err error - p interface{} - ) + var np = d2netpacket.NetPacket{} + + var err error switch t { case d2netpackettype.GenerateMap: - p, err = d2netpacket.UnmarshalGenerateMap([]byte(data)) + var p d2netpacket.GenerateMapPacket + if err = json.Unmarshal([]byte(data), &p); err != nil { + break + } + + mp, marshalErr := d2netpacket.MarshalPacket(p) + if marshalErr != nil { + r.Errorf("MarshalPacket: %v", marshalErr) + } + + np = d2netpacket.NetPacket{PacketType: t, PacketData: mp} + case d2netpackettype.MovePlayer: - p, err = d2netpacket.UnmarshalMovePlayer([]byte(data)) + var p d2netpacket.MovePlayerPacket + if err = json.Unmarshal([]byte(data), &p); err != nil { + break + } + + mp, marshalErr := d2netpacket.MarshalPacket(p) + if marshalErr != nil { + r.Errorf("MarshalPacket: %v", marshalErr) + } + + np = d2netpacket.NetPacket{PacketType: t, PacketData: mp} + case d2netpackettype.UpdateServerInfo: - p, err = d2netpacket.UnmarshalUpdateServerInfo([]byte(data)) + var p d2netpacket.UpdateServerInfoPacket + if err = json.Unmarshal([]byte(data), &p); err != nil { + break + } + + mp, marshalErr := d2netpacket.MarshalPacket(p) + if marshalErr != nil { + r.Errorf("MarshalPacket: %v", marshalErr) + } + + np = d2netpacket.NetPacket{PacketType: t, PacketData: mp} + case d2netpackettype.AddPlayer: - p, err = d2netpacket.UnmarshalAddPlayer([]byte(data)) + var p d2netpacket.AddPlayerPacket + if err = json.Unmarshal([]byte(data), &p); err != nil { + break + } + + mp, marshalErr := d2netpacket.MarshalPacket(p) + if marshalErr != nil { + r.Errorf("MarshalPacket: %v", marshalErr) + } + + np = d2netpacket.NetPacket{PacketType: t, PacketData: mp} + case d2netpackettype.CastSkill: - p, err = d2netpacket.UnmarshalCast([]byte(data)) + var p d2netpacket.CastPacket + if err = json.Unmarshal([]byte(data), &p); err != nil { + break + } + + mp, marshalErr := d2netpacket.MarshalPacket(p) + if marshalErr != nil { + r.Errorf("MarshalPacket: %v", marshalErr) + } + + np = d2netpacket.NetPacket{PacketType: t, PacketData: mp} + case d2netpackettype.Ping: - p, err = d2netpacket.UnmarshalPing([]byte(data)) + var p d2netpacket.PingPacket + if err = json.Unmarshal([]byte(data), &p); err != nil { + break + } + + mp, marshalErr := d2netpacket.MarshalPacket(p) + if marshalErr != nil { + r.Errorf("MarshalPacket: %v", marshalErr) + } + + np = d2netpacket.NetPacket{PacketType: t, PacketData: mp} + case d2netpackettype.PlayerDisconnectionNotification: - p, err = d2netpacket.UnmarshalPlayerDisconnectionRequest([]byte(data)) - case d2netpackettype.ServerClosed: - p, err = d2netpacket.UnmarshalServerClosed([]byte(data)) + var p d2netpacket.PlayerDisconnectRequestPacket + if err = json.Unmarshal([]byte(data), &p); err != nil { + break + } + + mp, marshalErr := d2netpacket.MarshalPacket(p) + if marshalErr != nil { + r.Errorf("MarshalPacket: %v", marshalErr) + } + + np = d2netpacket.NetPacket{PacketType: t, PacketData: mp} + default: err = fmt.Errorf("RemoteClientConnection: unrecognized packet type: %v", t) } @@ -222,12 +290,5 @@ func (r *RemoteClientConnection) decodeToPacket( return np, err } - mp, marshalErr := d2netpacket.MarshalPacket(p) - if marshalErr != nil { - r.Errorf("MarshalPacket: %v", marshalErr) - } - - np = d2netpacket.NetPacket{PacketType: t, PacketData: mp} - return np, nil } diff --git a/d2networking/d2client/game_client.go b/d2networking/d2client/game_client.go index 0737223d..d3b197aa 100644 --- a/d2networking/d2client/game_client.go +++ b/d2networking/d2client/game_client.go @@ -160,9 +160,8 @@ func (g *GameClient) OnPacketReceived(packet d2netpacket.NetPacket) error { g.Errorf("GameClient: error responding to server ping: %s", err) } case d2netpackettype.PlayerDisconnectionNotification: - if err := g.handlePlayerDisconnectionPacket(packet); err != nil { - return err - } + // Not implemented + g.Infof("RemoteClientConnection: received disconnect: %s", packet.PacketData) case d2netpackettype.ServerClosed: // https://github.com/OpenDiablo2/OpenDiablo2/issues/802 g.Infof("Server has been closed") @@ -447,19 +446,6 @@ func (g *GameClient) handlePingPacket() error { return nil } -func (g *GameClient) handlePlayerDisconnectionPacket(packet d2netpacket.NetPacket) error { - disconnectPacket, err := d2netpacket.UnmarshalPlayerDisconnectionRequest(packet.PacketData) - if err != nil { - return err - } - - player := g.Players[disconnectPacket.ID] - g.MapEngine.RemoveEntity(player) - delete(g.Players, disconnectPacket.ID) - - return nil -} - // IsSinglePlayer returns a bool for whether the game is a single-player game func (g *GameClient) IsSinglePlayer() bool { return g.connectionType == d2clientconnectiontype.Local diff --git a/d2networking/d2netpacket/packet_add_player.go b/d2networking/d2netpacket/packet_add_player.go index 795879a6..a503a075 100644 --- a/d2networking/d2netpacket/packet_add_player.go +++ b/d2networking/d2netpacket/packet_add_player.go @@ -35,7 +35,8 @@ func CreateAddPlayerPacket( stats *d2hero.HeroStatsState, skills map[int]*d2hero.HeroSkill, equipment d2inventory.CharacterEquipment, - leftSkill, rightSkill, gold int) (NetPacket, error) { + leftSkill, rightSkill int, + gold int) (NetPacket, error) { addPlayerPacket := AddPlayerPacket{ ID: id, Name: name, diff --git a/d2networking/d2netpacket/packet_ping.go b/d2networking/d2netpacket/packet_ping.go index 5ce7b61a..edad91f0 100644 --- a/d2networking/d2netpacket/packet_ping.go +++ b/d2networking/d2netpacket/packet_ping.go @@ -1,4 +1,4 @@ -package d2netpacket //nolint:dupl // ServerClosed and Ping just happen to be very similar packets +package d2netpacket import ( "encoding/json" @@ -30,13 +30,3 @@ func CreatePingPacket() (NetPacket, error) { PacketData: b, }, nil } - -// UnmarshalPing unmarshals the given data to a PingPacket struct -func UnmarshalPing(packet []byte) (PingPacket, error) { - var p PingPacket - if err := json.Unmarshal(packet, &p); err != nil { - return p, err - } - - return p, nil -} diff --git a/d2networking/d2netpacket/packet_server_closed.go b/d2networking/d2netpacket/packet_server_closed.go index 79c05833..eb7fcb19 100644 --- a/d2networking/d2netpacket/packet_server_closed.go +++ b/d2networking/d2netpacket/packet_server_closed.go @@ -1,4 +1,4 @@ -package d2netpacket //nolint:dupl // ServerClosed and Ping just happen to be very similar packets +package d2netpacket import ( "encoding/json" diff --git a/d2networking/d2server/d2tcpclientconnection/tcp_client_connection.go b/d2networking/d2server/d2tcpclientconnection/tcp_client_connection.go index 7a457feb..36bf953c 100644 --- a/d2networking/d2server/d2tcpclientconnection/tcp_client_connection.go +++ b/d2networking/d2server/d2tcpclientconnection/tcp_client_connection.go @@ -33,9 +33,12 @@ func (t TCPClientConnection) GetUniqueID() string { // SendPacketToClient marshals and sends (writes) NetPackets func (t *TCPClientConnection) SendPacketToClient(p d2netpacket.NetPacket) error { - encoder := json.NewEncoder(t.tcpConnection) + packet, err := json.Marshal(p) + if err != nil { + return err + } - err := encoder.Encode(p) + _, err = t.tcpConnection.Write(packet) if err != nil { return err } diff --git a/d2networking/d2server/game_server.go b/d2networking/d2server/game_server.go index 8e349281..de20b456 100644 --- a/d2networking/d2server/game_server.go +++ b/d2networking/d2server/game_server.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "errors" - "io" "net" "sync" "time" @@ -17,7 +16,6 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapgen" - "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2server/d2tcpclientconnection" @@ -52,19 +50,12 @@ type GameServer struct { scriptEngine *d2script.ScriptEngine seed int64 maxConnections int - packetManagerChan chan ReceivedPacket + packetManagerChan chan []byte heroStateFactory *d2hero.HeroStateFactory *d2util.Logger } -// ReceivedPacket encapsulates the data necessary for the packet manager goroutine to process data from clients. -// The packet manager needs to know who sent the data, in addition to the data itself. -type ReceivedPacket struct { - Client ClientConnection - Packet d2netpacket.NetPacket -} - // NewGameServer builds a new GameServer that can be started // // ctx: required context item @@ -93,7 +84,7 @@ func NewGameServer(asset *d2asset.AssetManager, connections: make(map[string]ClientConnection), networkServer: networkServer, maxConnections: maxConnections[0], - packetManagerChan: make(chan ReceivedPacket), + packetManagerChan: make(chan []byte), mapEngines: make([]*d2mapengine.MapEngine, 0), scriptEngine: d2script.CreateScriptEngine(), seed: time.Now().UnixNano(), @@ -151,13 +142,7 @@ func (g *GameServer) Start() error { for { c, err := g.listener.Accept() if err != nil { - select { - case <-g.ctx.Done(): - // this error was just a result of the server closing, don't worry about it - default: - g.Errorf("Unable to accept connection: %s", err) - } - + g.Errorf("Unable to accept connection: %s", err) return } @@ -172,7 +157,6 @@ func (g *GameServer) Start() error { func (g *GameServer) Stop() { g.Lock() g.cancel() - g.connections = make(map[string]ClientConnection) if err := g.listener.Close(); err != nil { g.Errorf("failed to close the listener %s, err: %v\n", g.listener.Addr(), err) @@ -189,9 +173,45 @@ func (g *GameServer) packetManager() { case <-g.ctx.Done(): return case p := <-g.packetManagerChan: - err := g.OnPacketReceived(p.Client, p.Packet) + ipt, err := d2netpacket.InspectPacketType(p) if err != nil { - g.Errorf("failed to handle packet received from client %s: %v", p.Client.GetUniqueID(), err) + g.Errorf("InspectPacketType: %v", err) + } + + switch ipt { + case d2netpackettype.PlayerConnectionRequest: + player, err := d2netpacket.UnmarshalNetPacket(p) + if err != nil { + g.Errorf("Unable to unmarshal PlayerConnectionRequestPacket: %s\n", err) + } + + g.sendPacketToClients(player) + case d2netpackettype.MovePlayer: + move, err := d2netpacket.UnmarshalNetPacket(p) + if err != nil { + g.Error(err.Error()) + continue + } + + g.sendPacketToClients(move) + case d2netpackettype.CastSkill: + castSkill, err := d2netpacket.UnmarshalNetPacket(p) + if err != nil { + g.Error(err.Error()) + continue + } + + g.sendPacketToClients(castSkill) + case d2netpackettype.SpawnItem: + item, err := d2netpacket.UnmarshalNetPacket(p) + if err != nil { + g.Error(err.Error()) + continue + } + + g.sendPacketToClients(item) + case d2netpackettype.ServerClosed: + g.Stop() } } } @@ -208,10 +228,9 @@ func (g *GameServer) sendPacketToClients(packet d2netpacket.NetPacket) { // handleConnection accepts an individual connection and starts pooling for new packets. It is recommended this is called // via Go Routine. Context should be a property of the GameServer Struct. func (g *GameServer) handleConnection(conn net.Conn) { - var ( - connected int - client ClientConnection - ) + var connected int + + var packet d2netpacket.NetPacket g.Infof("Accepting connection: %s\n", conn.RemoteAddr().String()) @@ -224,18 +243,10 @@ func (g *GameServer) handleConnection(conn net.Conn) { decoder := json.NewDecoder(conn) for { - var packet d2netpacket.NetPacket - err := decoder.Decode(&packet) if err != nil { - switch err { - case io.EOF: - break // the other side closed the connection - default: - g.Error(err.Error()) - } - - return // allow the connection to close + g.Error(err.Error()) + return // exit this connection as we could not read the first packet } // If this is the first packet we are seeing from this specific connection we first need to see if the client @@ -246,7 +257,25 @@ func (g *GameServer) handleConnection(conn net.Conn) { g.Infof("Closing connection with %s: did not receive new player connection request...", conn.RemoteAddr().String()) } - if client, err = g.registerConnection(packet.PacketData, conn); err != nil { + if err := g.registerConnection(packet.PacketData, conn); err != nil { + switch err { + case errServerFull: // Server is currently full and not accepting new connections. + sf, serverFullErr := d2netpacket.CreateServerFullPacket() + if serverFullErr != nil { + g.Errorf("ServerFullPacket: %v", serverFullErr) + } + + msf, marshalServerFullErr := d2netpacket.MarshalPacket(sf) + if marshalServerFullErr != nil { + g.Errorf("MarshalPacket: %v", marshalServerFullErr) + } + + _, errServerFullPacket := conn.Write(msf) + g.Warningf("%v", errServerFullPacket) + case errPlayerAlreadyExists: // Player is already registered and did not disconnection correctly. + g.Errorf("%v", err) + } + return } @@ -257,10 +286,7 @@ func (g *GameServer) handleConnection(conn net.Conn) { case <-g.ctx.Done(): return default: - g.packetManagerChan <- ReceivedPacket{ - Client: client, - Packet: packet, - } + g.packetManagerChan <- packet.PacketData } } } @@ -270,28 +296,12 @@ func (g *GameServer) handleConnection(conn net.Conn) { // Errors: // - errServerFull // - errPlayerAlreadyExists -func (g *GameServer) registerConnection(b []byte, conn net.Conn) (ClientConnection, error) { - var client ClientConnection - +func (g *GameServer) registerConnection(b []byte, conn net.Conn) error { g.Lock() - defer g.Unlock() // check to see if the server is full if len(g.connections) >= g.maxConnections { - sf, serverFullErr := d2netpacket.CreateServerFullPacket() - if serverFullErr != nil { - g.Errorf("ServerFullPacket: %v", serverFullErr) - } - - msf, marshalServerFullErr := d2netpacket.MarshalPacket(sf) - if marshalServerFullErr != nil { - g.Errorf("MarshalPacket: %v", marshalServerFullErr) - } - - _, errServerFullPacket := conn.Write(msf) - g.Warningf("%v", errServerFullPacket) - - return client, errServerFull + return errServerFull } // if it is not full, unmarshal the playerConnectionRequest @@ -302,17 +312,29 @@ func (g *GameServer) registerConnection(b []byte, conn net.Conn) (ClientConnecti // check to see if the player is already registered if _, ok := g.connections[packet.ID]; ok { - g.Errorf("%v", errPlayerAlreadyExists) - return client, errPlayerAlreadyExists + return errPlayerAlreadyExists } // Client a new TCP Client Connection and add it to the connections map - client = d2tcpclientconnection.CreateTCPClientConnection(conn, packet.ID) + client := d2tcpclientconnection.CreateTCPClientConnection(conn, packet.ID) client.SetPlayerState(packet.PlayerState) + g.Infof("Client connected with an id of %s", client.GetUniqueID()) + g.connections[client.GetUniqueID()] = client - g.OnClientConnected(client) + // Temporary position hack -------------------------------------------- + // https://github.com/OpenDiablo2/OpenDiablo2/issues/829 + sx, sy := g.mapEngines[0].GetStartPosition() + clientPlayerState := client.GetPlayerState() + clientPlayerState.X = sx + clientPlayerState.Y = sy + // --------- - return client, nil + // This really should be deferred however to much time will be spend holding a lock when we attempt to send a packet + g.Unlock() + + g.handleClientConnection(client, sx, sy) + + return nil } // OnClientConnected initializes the given ClientConnection. It sends the @@ -425,27 +447,12 @@ func (g *GameServer) handleClientConnection(client ClientConnection, x, y float6 // OnClientDisconnected removes the given client from the list // of client connections. -// If this client was the host, disconnects all clients and kills GameServer. func (g *GameServer) OnClientDisconnected(client ClientConnection) { g.Infof("Client disconnected with an id of %s", client.GetUniqueID()) delete(g.connections, client.GetUniqueID()) - - if client.GetConnectionType() == d2clientconnectiontype.Local { - g.Info("Host disconnected, game server shuting down") - - serverClosed, err := d2netpacket.CreateServerClosedPacket() - if err != nil { - g.Errorf("failed to generate ServerClosed packet after host disconnected: %s", err) - } else { - g.sendPacketToClients(serverClosed) - } - - g.Stop() - } } -// OnPacketReceived is called when a packet has been received from a remote client, -// and by the local client to 'send' a packet to the server, +// OnPacketReceived is called by the local client to 'send' a packet to the server. // nolint:gocyclo // switch statement on packet type makes sense, no need to change func (g *GameServer) OnPacketReceived(client ClientConnection, packet d2netpacket.NetPacket) error { if g == nil { @@ -483,13 +490,8 @@ func (g *GameServer) OnPacketReceived(client ClientConnection, packet d2netpacke if err != nil { g.Errorf("GameServer: error saving saving Player: %s", err) } - case d2netpackettype.PlayerConnectionRequest: - break // prevent log message. these are handled by handleConnection - case d2netpackettype.PlayerDisconnectionNotification: - g.sendPacketToClients(packet) - g.OnClientDisconnected(client) default: - g.Warningf("GameServer: received unknown packet %s", packet.PacketType) + g.Warningf("GameServer: received unknown packet %T", packet) } return nil diff --git a/docs/game_panels.png b/docs/game_panels.png deleted file mode 100644 index 15bf933d..00000000 Binary files a/docs/game_panels.png and /dev/null differ diff --git a/go.mod b/go.mod index 27504538..3b9a57d1 100644 --- a/go.mod +++ b/go.mod @@ -10,14 +10,11 @@ require ( github.com/google/uuid v1.1.2 github.com/gravestench/akara v0.0.0-20201208183338-ab0934060133 github.com/gravestench/pho v0.0.0-20201029002250-f9afbd637e4d - github.com/hajimehoshi/ebiten/v2 v2.0.2 - github.com/pkg/errors v0.9.1 // indirect + github.com/hajimehoshi/ebiten/v2 v2.0.1 github.com/pkg/profile v1.5.0 github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac github.com/stretchr/testify v1.4.0 - golang.org/x/exp v0.0.0-20201008143054-e3b2a7f2fdc7 // indirect golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 - golang.org/x/sys v0.0.0-20201028215240-c5abc1b1d397 // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/sourcemap.v1 v1.0.5 // indirect ) diff --git a/go.sum b/go.sum index 7b52f021..480b63b3 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,3 @@ -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/JoshVarga/blast v0.0.0-20180421040937-681c804fb9f0 h1:tDnuU0igiBiQFjsvq1Bi7DpoUjqI76VVvW045vpeFeM= github.com/JoshVarga/blast v0.0.0-20180421040937-681c804fb9f0/go.mod h1:h/5OEGj4G+fpYxluLjSMZbFY011ZxAntO98nCl8mrCs= @@ -8,7 +7,6 @@ github.com/alecthomas/units v0.0.0-20201120081800-1786d5ef83d4 h1:EBTWhcAX7rNQ80 github.com/alecthomas/units v0.0.0-20201120081800-1786d5ef83d4/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200707082815-5321531c36a2 h1:Ac1OEHHkbAZ6EUnJahF0GKcU0FjPc/V8F1DvjhKngFE= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200707082815-5321531c36a2/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-restruct/restruct v1.2.0-alpha h1:2Lp474S/9660+SJjpVxoKuWX09JsXHSrdV7Nv3/gkvc= @@ -32,8 +30,6 @@ github.com/gravestench/pho v0.0.0-20201029002250-f9afbd637e4d/go.mod h1:yi5GHMLL github.com/hajimehoshi/bitmapfont/v2 v2.1.0/go.mod h1:2BnYrkTQGThpr/CY6LorYtt/zEPNzvE/ND69CRTaHMs= github.com/hajimehoshi/ebiten/v2 v2.0.1 h1:94ucoKKoqiJOZxDod8gdMrroCDy0CO6Ct+Nc9kjsW98= github.com/hajimehoshi/ebiten/v2 v2.0.1/go.mod h1:AbHP/SS226aFTex/izULVwW0D2AuGyqC4AVwilmRjOg= -github.com/hajimehoshi/ebiten/v2 v2.0.2 h1:t8HXO9hJfKlS9tNhht8Ov6xecag0gRl7AkfKgC9hcLE= -github.com/hajimehoshi/ebiten/v2 v2.0.2/go.mod h1:AbHP/SS226aFTex/izULVwW0D2AuGyqC4AVwilmRjOg= github.com/hajimehoshi/file2byteslice v0.0.0-20200812174855-0e5e8a80490e/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE= github.com/hajimehoshi/go-mp3 v0.3.1/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= @@ -50,8 +46,6 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug= github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -69,7 +63,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= -golang.org/x/exp v0.0.0-20201008143054-e3b2a7f2fdc7/go.mod h1:1phAWC201xIgDyaFpmDeZkgf70Q4Pd/CNqfRtVPtxNw= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -77,14 +70,12 @@ golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckH golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mobile v0.0.0-20200801112145-973feb4309de h1:OVJ6QQUBAesB8CZijKDSsXX7xYVtUhrkY0gwMfbi4p4= golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -95,16 +86,13 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 h1:bNEHhJCnrwMKNMmOx3yAynp5vs5/gRy+XWFtZFu7NBM= golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201028215240-c5abc1b1d397/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20201009162240-fcf82128ed91/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/utils/extract-mpq/extract-mpq.go b/utils/extract-mpq/extract-mpq.go index 2d4d42c0..6c556b77 100644 --- a/utils/extract-mpq/extract-mpq.go +++ b/utils/extract-mpq/extract-mpq.go @@ -33,13 +33,13 @@ func main() { } filename := flag.Arg(0) + mpq, err := d2mpq.Load(filename) - mpq, err := d2mpq.FromFile(filename) if err != nil { log.Fatal(err) } - list, err := mpq.Listfile() + list, err := mpq.GetFileList() if err != nil { log.Fatal(err) }