mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2025-02-02 22:57:04 -05:00
This reverts commit 9121209f86
.
This commit is contained in:
parent
9121209f86
commit
99c7e2e754
5
.github/workflows/pullRequest.yml
vendored
5
.github/workflows/pullRequest.yml
vendored
@ -1,9 +1,10 @@
|
|||||||
|
---
|
||||||
name: pull_request
|
name: pull_request
|
||||||
"on": [pull_request]
|
"on": [pull_request]
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: ''
|
name: Build
|
||||||
runs-on: self-hosted
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Go 1.14
|
- name: Set up Go 1.14
|
||||||
|
39
.github/workflows/pushToMaster.yml
vendored
Normal file
39
.github/workflows/pushToMaster.yml
vendored
Normal file
@ -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 .
|
@ -25,8 +25,7 @@ ALL OTHER TRADEMARKS ARE THE PROPERTY OF THEIR RESPECTIVE OWNERS.
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
At the moment (december 2020) the game starts, you can select any character and run around Act1 town.
|
At the moment (october 2020) the game starts, you can select any character and run around Act1 town.
|
||||||
You can also open any of the game's panels.
|
|
||||||
|
|
||||||
Much work has been made in the background, but a lot of work still has to be done for the game to be playable.
|
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)
|
![Inventory Window](docs/Inventory.png)
|
||||||
|
|
||||||
![Game Panels](docs/game_panels.png)
|
|
||||||
|
|
||||||
## Additional Credits
|
## Additional Credits
|
||||||
|
|
||||||
- Diablo2 Logo
|
- Diablo2 Logo
|
||||||
|
296
d2app/app.go
296
d2app/app.go
@ -6,7 +6,6 @@ import (
|
|||||||
"container/ring"
|
"container/ring"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/gif"
|
"image/gif"
|
||||||
@ -25,6 +24,7 @@ import (
|
|||||||
|
|
||||||
"github.com/pkg/profile"
|
"github.com/pkg/profile"
|
||||||
"golang.org/x/image/colornames"
|
"golang.org/x/image/colornames"
|
||||||
|
"gopkg.in/alecthomas/kingpin.v2"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
||||||
@ -85,12 +85,19 @@ type App struct {
|
|||||||
|
|
||||||
// Options is used to store all of the app options that can be set with arguments
|
// Options is used to store all of the app options that can be set with arguments
|
||||||
type Options struct {
|
type Options struct {
|
||||||
|
printVersion *bool
|
||||||
Debug *bool
|
Debug *bool
|
||||||
profiler *string
|
profiler *string
|
||||||
Server *d2networking.ServerOptions
|
Server *d2networking.ServerOptions
|
||||||
LogLevel *d2util.LogLevel
|
LogLevel *d2util.LogLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type bindTerminalEntry struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
action interface{}
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
bytesToMegabyte = 1024 * 1024
|
bytesToMegabyte = 1024 * 1024
|
||||||
nSamplesTAlloc = 100
|
nSamplesTAlloc = 100
|
||||||
@ -103,24 +110,21 @@ const (
|
|||||||
|
|
||||||
// Create creates a new instance of the application
|
// Create creates a new instance of the application
|
||||||
func Create(gitBranch, gitCommit string) *App {
|
func Create(gitBranch, gitCommit string) *App {
|
||||||
logger := d2util.NewLogger()
|
assetManager, assetError := d2asset.NewAssetManager()
|
||||||
logger.SetPrefix(appLoggerPrefix)
|
|
||||||
|
|
||||||
app := &App{
|
app := &App{
|
||||||
Logger: logger,
|
|
||||||
gitBranch: gitBranch,
|
gitBranch: gitBranch,
|
||||||
gitCommit: gitCommit,
|
gitCommit: gitCommit,
|
||||||
|
asset: assetManager,
|
||||||
Options: &Options{
|
Options: &Options{
|
||||||
Server: &d2networking.ServerOptions{},
|
Server: &d2networking.ServerOptions{},
|
||||||
},
|
},
|
||||||
|
errorMessage: assetError,
|
||||||
}
|
}
|
||||||
app.Infof("OpenDiablo2 - Open source Diablo 2 engine")
|
|
||||||
|
|
||||||
app.parseArguments()
|
app.Logger = d2util.NewLogger()
|
||||||
|
app.Logger.SetPrefix(appLoggerPrefix)
|
||||||
app.SetLevel(*app.Options.LogLevel)
|
app.Logger.SetLevel(d2util.LogLevelNone)
|
||||||
|
|
||||||
app.asset, app.errorMessage = d2asset.NewAssetManager(*app.Options.LogLevel)
|
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
@ -136,7 +140,7 @@ func (a *App) startDedicatedServer() error {
|
|||||||
srvChanIn := make(chan int)
|
srvChanIn := make(chan int)
|
||||||
srvChanLog := make(chan string)
|
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 {
|
if srvErr != nil {
|
||||||
return srvErr
|
return srvErr
|
||||||
}
|
}
|
||||||
@ -169,7 +173,15 @@ func (a *App) loadEngine() error {
|
|||||||
return a.renderer.Run(a.updateInitError, updateNOOP, 800, 600, "OpenDiablo2")
|
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()
|
inputManager := d2input.NewInputManager()
|
||||||
|
|
||||||
@ -178,9 +190,14 @@ func (a *App) loadEngine() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = a.asset.BindTerminalCommands(term)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
scriptEngine := d2script.CreateScriptEngine()
|
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.inputManager = inputManager
|
||||||
a.terminal = term
|
a.terminal = term
|
||||||
@ -189,48 +206,50 @@ func (a *App) loadEngine() error {
|
|||||||
a.ui = uiManager
|
a.ui = uiManager
|
||||||
a.tAllocSamples = createZeroedRing(nSamplesTAlloc)
|
a.tAllocSamples = createZeroedRing(nSamplesTAlloc)
|
||||||
|
|
||||||
|
if a.gitBranch == "" {
|
||||||
|
a.gitBranch = "Local Build"
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) parseArguments() {
|
func (a *App) parseArguments() {
|
||||||
const (
|
const (
|
||||||
descProfile = "Profiles the program,\none of (cpu, mem, block, goroutine, trace, thread, mutex)"
|
versionArg = "version"
|
||||||
descPlayers = "Sets the number of max players for the dedicated server"
|
versionShort = 'v'
|
||||||
descLogging = "Enables verbose logging. Log levels will include those below it.\n" +
|
versionDesc = "Prints the version of the app"
|
||||||
" 0 disables log messages\n" +
|
|
||||||
" 1 shows fatal\n" +
|
profilerArg = "profile"
|
||||||
" 2 shows error\n" +
|
profilerDesc = "Profiles the program, one of (cpu, mem, block, goroutine, trace, thread, mutex)"
|
||||||
" 3 shows warning\n" +
|
|
||||||
" 4 shows info\n" +
|
serverArg = "dedicated"
|
||||||
" 5 shows debug\n"
|
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.profiler = kingpin.Flag(profilerArg, profilerDesc).String()
|
||||||
a.Options.Server.Dedicated = flag.Bool("dedicated", false, "Starts a dedicated server")
|
a.Options.Server.Dedicated = kingpin.Flag(serverArg, serverDesc).Short(serverShort).Bool()
|
||||||
a.Options.Server.MaxPlayers = flag.Int("players", 0, descPlayers)
|
a.Options.printVersion = kingpin.Flag(versionArg, versionDesc).Short(versionShort).Bool()
|
||||||
a.Options.LogLevel = flag.Int("l", d2util.LogLevelDefault, descLogging)
|
a.Options.Server.MaxPlayers = kingpin.Flag(playersArg, playersDesc).Int()
|
||||||
showVersion := flag.Bool("v", false, "Show version")
|
a.Options.LogLevel = kingpin.Flag(loggingArg, loggingDesc).
|
||||||
showHelp := flag.Bool("h", false, "Show help")
|
Short(loggingShort).
|
||||||
|
Default(strconv.Itoa(d2util.LogLevelUnspecified)).
|
||||||
|
Int()
|
||||||
|
|
||||||
flag.Usage = func() {
|
kingpin.Parse()
|
||||||
fmt.Printf("usage: %s [<flags>]\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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadConfig loads the OpenDiablo2 config file
|
// 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
|
// 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
|
// add our possible config directories
|
||||||
_, _ = a.asset.AddSource(filepath.Dir(d2config.LocalConfigPath()))
|
_, _ = a.asset.AddSource(filepath.Dir(d2config.LocalConfigPath()))
|
||||||
_, _ = a.asset.AddSource(filepath.Dir(d2config.DefaultConfigPath()))
|
_, _ = a.asset.AddSource(filepath.Dir(d2config.DefaultConfigPath()))
|
||||||
|
|
||||||
if a.config, err = a.LoadConfig(); err != nil {
|
config, err := a.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
return err
|
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
|
// start profiler if argument was supplied
|
||||||
if len(*a.Options.profiler) > 0 {
|
if len(*a.Options.profiler) > 0 {
|
||||||
profiler := enableProfiler(*a.Options.profiler, a)
|
profiler := enableProfiler(*a.Options.profiler, a)
|
||||||
@ -340,39 +389,36 @@ func (a *App) initialize() error {
|
|||||||
a.renderer.SetWindowIcon("d2logo.png")
|
a.renderer.SetWindowIcon("d2logo.png")
|
||||||
a.terminal.BindLogger()
|
a.terminal.BindLogger()
|
||||||
|
|
||||||
terminalCommands := []struct {
|
terminalActions := [...]bindTerminalEntry{
|
||||||
name string
|
{"dumpheap", "dumps the heap to pprof/heap.pprof", a.dumpHeap},
|
||||||
desc string
|
{"fullscreen", "toggles fullscreen", a.toggleFullScreen},
|
||||||
args []string
|
{"capframe", "captures a still frame", a.setupCaptureFrame},
|
||||||
fn func(args []string) error
|
{"capgifstart", "captures an animation (start)", a.startAnimationCapture},
|
||||||
}{
|
{"capgifstop", "captures an animation (stop)", a.stopAnimationCapture},
|
||||||
{"dumpheap", "dumps the heap to pprof/heap.pprof", nil, a.dumpHeap},
|
{"vsync", "toggles vsync", a.toggleVsync},
|
||||||
{"fullscreen", "toggles fullscreen", nil, a.toggleFullScreen},
|
{"fps", "toggle fps counter", a.toggleFpsCounter},
|
||||||
{"capframe", "captures a still frame", []string{"filename"}, a.setupCaptureFrame},
|
{"timescale", "set scalar for elapsed time", a.setTimeScale},
|
||||||
{"capgifstart", "captures an animation (start)", []string{"filename"}, a.startAnimationCapture},
|
{"quit", "exits the game", a.quitGame},
|
||||||
{"capgifstop", "captures an animation (stop)", nil, a.stopAnimationCapture},
|
{"screen-gui", "enters the gui playground screen", a.enterGuiPlayground},
|
||||||
{"vsync", "toggles vsync", nil, a.toggleVsync},
|
{"js", "eval JS scripts", a.evalJS},
|
||||||
{"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},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, cmd := range terminalCommands {
|
for idx := range terminalActions {
|
||||||
if err := a.terminal.Bind(cmd.name, cmd.desc, cmd.args, cmd.fn); err != nil {
|
action := &terminalActions[idx]
|
||||||
a.Fatalf("failed to bind action %q: %v", cmd.name, err.Error())
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
a.guiManager = gui
|
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)
|
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
|
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.Stat("./pprof/"); os.IsNotExist(err) {
|
||||||
if err := os.Mkdir("./pprof/", 0750); err != nil {
|
if err := os.Mkdir("./pprof/", 0750); err != nil {
|
||||||
a.Fatal(err.Error())
|
a.Fatal(err.Error())
|
||||||
@ -655,56 +701,48 @@ func (a *App) dumpHeap([]string) error {
|
|||||||
if err := fileOut.Close(); err != nil {
|
if err := fileOut.Close(); err != nil {
|
||||||
a.Fatal(err.Error())
|
a.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) evalJS(args []string) error {
|
func (a *App) evalJS(code string) {
|
||||||
val, err := a.scriptEngine.Eval(args[0])
|
val, err := a.scriptEngine.Eval(code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.terminal.Errorf(err.Error())
|
a.terminal.OutputErrorf("%s", err)
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
a.Info("%s" + val)
|
a.Info("%s" + val)
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) toggleFullScreen([]string) error {
|
func (a *App) toggleFullScreen() {
|
||||||
fullscreen := !a.renderer.IsFullScreen()
|
fullscreen := !a.renderer.IsFullScreen()
|
||||||
a.renderer.SetFullScreen(fullscreen)
|
a.renderer.SetFullScreen(fullscreen)
|
||||||
a.terminal.Infof("fullscreen is now: %v", fullscreen)
|
a.terminal.OutputInfof("fullscreen is now: %v", fullscreen)
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) setupCaptureFrame(args []string) error {
|
func (a *App) setupCaptureFrame(path string) {
|
||||||
a.captureState = captureStateFrame
|
a.captureState = captureStateFrame
|
||||||
a.capturePath = args[0]
|
a.capturePath = path
|
||||||
a.captureFrames = nil
|
a.captureFrames = nil
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) doCaptureFrame(target d2interface.Surface) error {
|
func (a *App) doCaptureFrame(target d2interface.Surface) error {
|
||||||
fp, err := os.Create(a.capturePath)
|
fp, err := os.Create(a.capturePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.terminal.Errorf("failed to create %q", a.capturePath)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := fp.Close(); err != nil {
|
||||||
|
a.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
screenshot := target.Screenshot()
|
screenshot := target.Screenshot()
|
||||||
if err := png.Encode(fp, screenshot); err != nil {
|
if err := png.Encode(fp, screenshot); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := fp.Close(); err != nil {
|
a.Info(fmt.Sprintf("saved frame to %s", a.capturePath))
|
||||||
a.terminal.Errorf("failed to create %q", a.capturePath)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
a.terminal.Infof("saved frame to %s", a.capturePath)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -764,61 +802,47 @@ func (a *App) convertFramesToGif() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
a.Infof("saved animation to %s", a.capturePath)
|
a.Info(fmt.Sprintf("saved animation to %s", a.capturePath))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) startAnimationCapture(args []string) error {
|
func (a *App) startAnimationCapture(path string) {
|
||||||
a.captureState = captureStateGif
|
a.captureState = captureStateGif
|
||||||
a.capturePath = args[0]
|
a.capturePath = path
|
||||||
a.captureFrames = nil
|
a.captureFrames = nil
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) stopAnimationCapture([]string) error {
|
func (a *App) stopAnimationCapture() {
|
||||||
a.captureState = captureStateNone
|
a.captureState = captureStateNone
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) toggleVsync([]string) error {
|
func (a *App) toggleVsync() {
|
||||||
vsync := !a.renderer.GetVSyncEnabled()
|
vsync := !a.renderer.GetVSyncEnabled()
|
||||||
a.renderer.SetVSyncEnabled(vsync)
|
a.renderer.SetVSyncEnabled(vsync)
|
||||||
a.terminal.Infof("vsync is now: %v", vsync)
|
a.terminal.OutputInfof("vsync is now: %v", vsync)
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) toggleFpsCounter([]string) error {
|
func (a *App) toggleFpsCounter() {
|
||||||
a.showFPS = !a.showFPS
|
a.showFPS = !a.showFPS
|
||||||
a.terminal.Infof("fps counter is now: %v", a.showFPS)
|
a.terminal.OutputInfof("fps counter is now: %v", a.showFPS)
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) setTimeScale(args []string) error {
|
func (a *App) setTimeScale(timeScale float64) {
|
||||||
timeScale, err := strconv.ParseFloat(args[0], 64)
|
if timeScale <= 0 {
|
||||||
if err != nil || timeScale <= 0 {
|
a.terminal.OutputErrorf("invalid time scale value")
|
||||||
a.terminal.Errorf("invalid time scale value")
|
} else {
|
||||||
return nil
|
a.terminal.OutputInfof("timescale changed from %f to %f", a.timeScale, timeScale)
|
||||||
}
|
|
||||||
|
|
||||||
a.terminal.Infof("timescale changed from %f to %f", a.timeScale, timeScale)
|
|
||||||
a.timeScale = timeScale
|
a.timeScale = timeScale
|
||||||
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) quitGame([]string) error {
|
func (a *App) quitGame() {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) enterGuiPlayground([]string) error {
|
func (a *App) enterGuiPlayground() {
|
||||||
a.screen.SetNextScreen(d2gamescreen.CreateGuiTestMain(a.renderer, a.guiManager, *a.Options.LogLevel, a.asset))
|
a.screen.SetNextScreen(d2gamescreen.CreateGuiTestMain(a.renderer, a.guiManager, a.config.LogLevel, a.asset))
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createZeroedRing(n int) *ring.Ring {
|
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}
|
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,
|
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 {
|
if err != nil {
|
||||||
a.Error(err.Error())
|
a.Error(err.Error())
|
||||||
return
|
return
|
||||||
@ -898,7 +922,7 @@ func (a *App) ToMainMenu(errorMessageOptional ...string) {
|
|||||||
|
|
||||||
// ToSelectHero forces the game to transition to the Select Hero (create character) screen
|
// ToSelectHero forces the game to transition to the Select Hero (create character) screen
|
||||||
func (a *App) ToSelectHero(connType d2clientconnectiontype.ClientConnectionType, host string) {
|
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 {
|
if err != nil {
|
||||||
a.Error(err.Error())
|
a.Error(err.Error())
|
||||||
return
|
return
|
||||||
@ -909,18 +933,18 @@ func (a *App) ToSelectHero(connType d2clientconnectiontype.ClientConnectionType,
|
|||||||
|
|
||||||
// ToCreateGame forces the game to transition to the Create Game screen
|
// ToCreateGame forces the game to transition to the Create Game screen
|
||||||
func (a *App) ToCreateGame(filePath string, connType d2clientconnectiontype.ClientConnectionType, host string) {
|
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 {
|
if err != nil {
|
||||||
a.Error(err.Error())
|
a.Error(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = gameClient.Open(host, filePath); err != nil {
|
if err = gameClient.Open(host, filePath); err != nil {
|
||||||
errorMessage := fmt.Sprintf("can not connect to the host: %s", host)
|
errorMessage := fmt.Sprintf("can not connect to the host: %s", host)
|
||||||
a.Error(errorMessage)
|
fmt.Println(errorMessage)
|
||||||
a.ToMainMenu(errorMessage)
|
a.ToMainMenu(errorMessage)
|
||||||
} else {
|
} else {
|
||||||
game, err := d2gamescreen.CreateGame(
|
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 {
|
if err != nil {
|
||||||
a.Error(err.Error())
|
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
|
// ToCharacterSelect forces the game to transition to the Character Select (load character) screen
|
||||||
func (a *App) ToCharacterSelect(connType d2clientconnectiontype.ClientConnectionType, connHost string) {
|
func (a *App) ToCharacterSelect(connType d2clientconnectiontype.ClientConnectionType, connHost string) {
|
||||||
characterSelect, err := d2gamescreen.CreateCharacterSelect(a, a.asset, a.renderer, a.inputManager,
|
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 {
|
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)
|
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
|
// ToMapEngineTest forces the game to transition to the map engine test screen
|
||||||
func (a *App) ToMapEngineTest(region, level int) {
|
func (a *App) ToMapEngineTest(region, level int) {
|
||||||
met, err := d2gamescreen.CreateMapEngineTest(region, level, a.asset, a.terminal, a.renderer, a.inputManager, a.audio,
|
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 {
|
if err != nil {
|
||||||
a.Error(err.Error())
|
a.Error(err.Error())
|
||||||
return
|
return
|
||||||
@ -955,10 +979,10 @@ func (a *App) ToMapEngineTest(region, level int) {
|
|||||||
|
|
||||||
// ToCredits forces the game to transition to the credits screen
|
// ToCredits forces the game to transition to the credits screen
|
||||||
func (a *App) ToCredits() {
|
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
|
// ToCinematics forces the game to transition to the cinematics menu
|
||||||
func (a *App) ToCinematics() {
|
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))
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,12 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
bytesPerInt16 = 2
|
||||||
|
bytesPerInt32 = 4
|
||||||
|
bytesPerInt64 = 8
|
||||||
|
)
|
||||||
|
|
||||||
// StreamReader allows you to read data from a byte array in various formats
|
// StreamReader allows you to read data from a byte array in various formats
|
||||||
type StreamReader struct {
|
type StreamReader struct {
|
||||||
data []byte
|
data []byte
|
||||||
@ -20,6 +26,16 @@ func CreateStreamReader(source []byte) *StreamReader {
|
|||||||
return result
|
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
|
// GetByte returns a byte from the stream
|
||||||
func (v *StreamReader) GetByte() byte {
|
func (v *StreamReader) GetByte() byte {
|
||||||
result := v.data[v.position]
|
result := v.data[v.position]
|
||||||
@ -28,46 +44,32 @@ func (v *StreamReader) GetByte() byte {
|
|||||||
return result
|
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
|
// GetInt16 returns a int16 word from the stream
|
||||||
func (v *StreamReader) GetInt16() int16 {
|
func (v *StreamReader) GetInt16() int16 {
|
||||||
return int16(v.GetUInt16())
|
var result int16
|
||||||
|
|
||||||
|
for offset := uint64(0); offset < bytesPerInt16; offset++ {
|
||||||
|
shift := uint8(bitsPerByte * offset)
|
||||||
|
result += int16(v.data[v.position+offset]) << shift
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUInt16 returns a uint16 word from the stream
|
v.position += bytesPerInt16
|
||||||
//nolint
|
|
||||||
func (v *StreamReader) GetUInt16() uint16 {
|
|
||||||
b := v.ReadBytes(2)
|
|
||||||
return uint16(b[0]) | uint16(b[1])<<8
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInt32 returns an int32 dword from the stream
|
return result
|
||||||
func (v *StreamReader) GetInt32() int32 {
|
|
||||||
return int32(v.GetUInt32())
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPosition sets the stream position with the given position
|
// SetPosition sets the stream position with the given position
|
||||||
@ -75,9 +77,51 @@ func (v *StreamReader) SetPosition(newPosition uint64) {
|
|||||||
v.position = newPosition
|
v.position = newPosition
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSize returns the total size of the stream in bytes
|
// GetUInt32 returns a uint32 dword from the stream
|
||||||
func (v *StreamReader) GetSize() uint64 {
|
func (v *StreamReader) GetUInt32() uint32 {
|
||||||
return uint64(len(v.data))
|
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
|
// ReadByte implements io.ByteReader
|
||||||
|
@ -2,6 +2,10 @@ package d2datautils
|
|||||||
|
|
||||||
import "bytes"
|
import "bytes"
|
||||||
|
|
||||||
|
const (
|
||||||
|
byteMask = 0xFF
|
||||||
|
)
|
||||||
|
|
||||||
// StreamWriter allows you to create a byte array by streaming in writes of various sizes
|
// StreamWriter allows you to create a byte array by streaming in writes of various sizes
|
||||||
type StreamWriter struct {
|
type StreamWriter struct {
|
||||||
data *bytes.Buffer
|
data *bytes.Buffer
|
||||||
@ -16,40 +20,41 @@ func CreateStreamWriter() *StreamWriter {
|
|||||||
return result
|
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
|
// PushByte writes a byte to the stream
|
||||||
func (v *StreamWriter) PushByte(val byte) {
|
func (v *StreamWriter) PushByte(val byte) {
|
||||||
v.data.WriteByte(val)
|
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
|
// PushInt16 writes a int16 word to the stream
|
||||||
func (v *StreamWriter) PushInt16(val int16) {
|
func (v *StreamWriter) PushInt16(val int16) {
|
||||||
v.PushUint16(uint16(val))
|
for count := 0; count < bytesPerInt16; count++ {
|
||||||
|
shift := count * bitsPerByte
|
||||||
|
v.data.WriteByte(byte(val>>shift) & byteMask)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushUint32 writes a uint32 dword to the stream
|
// PushUint32 writes a uint32 dword to the stream
|
||||||
//nolint
|
|
||||||
func (v *StreamWriter) PushUint32(val uint32) {
|
func (v *StreamWriter) PushUint32(val uint32) {
|
||||||
v.data.WriteByte(byte(val))
|
for count := 0; count < bytesPerInt32; count++ {
|
||||||
v.data.WriteByte(byte(val >> 8))
|
shift := count * bitsPerByte
|
||||||
v.data.WriteByte(byte(val >> 16))
|
v.data.WriteByte(byte(val>>shift) & byteMask)
|
||||||
v.data.WriteByte(byte(val >> 24))
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// PushInt64 writes a uint64 qword to the stream
|
||||||
@ -57,15 +62,7 @@ func (v *StreamWriter) PushInt64(val int64) {
|
|||||||
v.PushUint64(uint64(val))
|
v.PushUint64(uint64(val))
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushUint64 writes a uint64 qword to the stream
|
// GetBytes returns the the byte slice of the underlying data
|
||||||
//nolint
|
func (v *StreamWriter) GetBytes() []byte {
|
||||||
func (v *StreamWriter) PushUint64(val uint64) {
|
return v.data.Bytes()
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,7 @@ package d2enum
|
|||||||
|
|
||||||
// there are labels for "numeric labels (see AssetManager.TranslateLabel)
|
// there are labels for "numeric labels (see AssetManager.TranslateLabel)
|
||||||
const (
|
const (
|
||||||
RepairAll = iota
|
CancelLabel = iota
|
||||||
_
|
|
||||||
CancelLabel
|
|
||||||
CopyrightLabel
|
CopyrightLabel
|
||||||
AllRightsReservedLabel
|
AllRightsReservedLabel
|
||||||
SinglePlayerLabel
|
SinglePlayerLabel
|
||||||
@ -64,8 +62,6 @@ const (
|
|||||||
// BaseLabelNumbers returns base label value (#n in english string table table)
|
// BaseLabelNumbers returns base label value (#n in english string table table)
|
||||||
func BaseLabelNumbers(idx int) int {
|
func BaseLabelNumbers(idx int) int {
|
||||||
baseLabelNumbers := []int{
|
baseLabelNumbers := []int{
|
||||||
128, // repairAll
|
|
||||||
127,
|
|
||||||
// main menu labels
|
// main menu labels
|
||||||
1612, // CANCEL
|
1612, // CANCEL
|
||||||
1613, // (c) 2000 Blizzard Entertainment
|
1613, // (c) 2000 Blizzard Entertainment
|
||||||
|
@ -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
|
|
||||||
)
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
32
d2common/d2fileformats/d2mpq/crypto_buff.go
Normal file
32
d2common/d2fileformats/d2mpq/crypto_buff.go
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
d2common/d2fileformats/d2mpq/hash_entry_map.go
Normal file
35
d2common/d2fileformats/d2mpq/hash_entry_map.go
Normal file
@ -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
|
||||||
|
}
|
@ -2,9 +2,10 @@ package d2mpq
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -20,9 +21,31 @@ var _ d2interface.Archive = &MPQ{} // Static check to confirm struct conforms to
|
|||||||
type MPQ struct {
|
type MPQ struct {
|
||||||
filePath string
|
filePath string
|
||||||
file *os.File
|
file *os.File
|
||||||
hashes map[uint64]*Hash
|
hashEntryMap HashEntryMap
|
||||||
blocks []*Block
|
blockTableEntries []BlockTableEntry
|
||||||
header Header
|
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.
|
// PatchInfo represents patch info for the MPQ.
|
||||||
@ -30,153 +53,71 @@ type PatchInfo struct {
|
|||||||
Length uint32 // Length of patch info header, in bytes
|
Length uint32 // Length of patch info header, in bytes
|
||||||
Flags uint32 // Flags. 0x80000000 = MD5 (?)
|
Flags uint32 // Flags. 0x80000000 = MD5 (?)
|
||||||
DataSize uint32 // Uncompressed size of the patch file
|
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
|
// FileFlag represents flags for a file record in the MPQ archive
|
||||||
func New(fileName string) (*MPQ, error) {
|
type FileFlag uint32
|
||||||
mpq := &MPQ{filePath: fileName}
|
|
||||||
|
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
|
var err error
|
||||||
if runtime.GOOS == "linux" {
|
if runtime.GOOS == "linux" {
|
||||||
mpq.file, err = openIgnoreCase(fileName)
|
result.file, err = openIgnoreCase(fileName)
|
||||||
} else {
|
} 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := mpq.readHeader(); err != nil {
|
if err := result.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 {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := mpq.readHashTable(); err != nil {
|
return result, 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func openIgnoreCase(mpqPath string) (*os.File, error) {
|
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
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -11,14 +11,14 @@ type MpqDataStream struct {
|
|||||||
|
|
||||||
// Read reads data from the data stream
|
// Read reads data from the data stream
|
||||||
func (m *MpqDataStream) Read(p []byte) (n int, err error) {
|
func (m *MpqDataStream) Read(p []byte) (n int, err error) {
|
||||||
totalRead, err := m.stream.Read(p, 0, uint32(len(p)))
|
totalRead := m.stream.Read(p, 0, uint32(len(p)))
|
||||||
return int(totalRead), err
|
return int(totalRead), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seek sets the position of the data stream
|
// Seek sets the position of the data stream
|
||||||
func (m *MpqDataStream) Seek(offset int64, whence int) (int64, error) {
|
func (m *MpqDataStream) Seek(offset int64, whence int) (int64, error) {
|
||||||
m.stream.Position = uint32(offset + int64(whence))
|
m.stream.CurrentPosition = uint32(offset + int64(whence))
|
||||||
return int64(m.stream.Position), nil
|
return int64(m.stream.CurrentPosition), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the data stream
|
// Close closes the data stream
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -6,7 +6,8 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/JoshVarga/blast"
|
"github.com/JoshVarga/blast"
|
||||||
|
|
||||||
@ -16,63 +17,80 @@ import (
|
|||||||
|
|
||||||
// Stream represents a stream of data in an MPQ archive
|
// Stream represents a stream of data in an MPQ archive
|
||||||
type Stream struct {
|
type Stream struct {
|
||||||
Data []byte
|
BlockTableEntry BlockTableEntry
|
||||||
Positions []uint32
|
BlockPositions []uint32
|
||||||
MPQ *MPQ
|
CurrentData []byte
|
||||||
Block *Block
|
FileName string
|
||||||
Index uint32
|
MPQData *MPQ
|
||||||
Size uint32
|
EncryptionSeed uint32
|
||||||
Position uint32
|
CurrentPosition uint32
|
||||||
|
CurrentBlockIndex uint32
|
||||||
|
BlockSize uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateStream creates an MPQ stream
|
// CreateStream creates an MPQ stream
|
||||||
func CreateStream(mpq *MPQ, block *Block, fileName string) (*Stream, error) {
|
func CreateStream(mpq *MPQ, blockTableEntry BlockTableEntry, fileName string) (*Stream, error) {
|
||||||
s := &Stream{
|
result := &Stream{
|
||||||
MPQ: mpq,
|
MPQData: mpq,
|
||||||
Block: block,
|
BlockTableEntry: blockTableEntry,
|
||||||
Index: 0xFFFFFFFF, //nolint:gomnd // MPQ magic
|
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) {
|
result.BlockSize = 0x200 << result.MPQData.data.BlockSize //nolint:gomnd // MPQ magic
|
||||||
s.Block.calculateEncryptionSeed(fileName)
|
|
||||||
|
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) {
|
if (result.BlockTableEntry.HasFlag(FileCompress) || result.BlockTableEntry.HasFlag(FileImplode)) &&
|
||||||
return nil, errors.New("patching is not supported")
|
!result.BlockTableEntry.HasFlag(FileSingleUnit) {
|
||||||
|
err = result.loadBlockOffsets()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s.Block.HasFlag(FileCompress) || s.Block.HasFlag(FileImplode)) && !s.Block.HasFlag(FileSingleUnit) {
|
return result, err
|
||||||
if err := s.loadBlockOffsets(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return s, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Stream) loadBlockOffsets() error {
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
blockPositionCount := ((v.Block.UncompressedFileSize + v.Size - 1) / v.Size) + 1
|
mpqBytes := make([]byte, blockPositionCount*4) //nolint:gomnd // MPQ magic
|
||||||
v.Positions = make([]uint32, blockPositionCount)
|
|
||||||
|
|
||||||
if err := binary.Read(v.MPQ.file, binary.LittleEndian, &v.Positions); err != nil {
|
_, err = v.MPQData.file.Read(mpqBytes)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.Block.HasFlag(FileEncrypted) {
|
for i := range v.BlockPositions {
|
||||||
decrypt(v.Positions, v.Block.EncryptionSeed-1)
|
idx := i * 4 //nolint:gomnd // MPQ magic
|
||||||
|
v.BlockPositions[i] = binary.LittleEndian.Uint32(mpqBytes[idx : idx+4])
|
||||||
|
}
|
||||||
|
|
||||||
blockPosSize := blockPositionCount << 2 //nolint:gomnd // MPQ magic
|
blockPosSize := blockPositionCount << 2 //nolint:gomnd // MPQ magic
|
||||||
if v.Positions[0] != blockPosSize {
|
|
||||||
|
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")
|
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")
|
return errors.New("decryption of MPQ failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,18 +98,16 @@ func (v *Stream) loadBlockOffsets() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Stream) Read(buffer []byte, offset, count uint32) (readTotal uint32, err error) {
|
func (v *Stream) Read(buffer []byte, offset, count uint32) uint32 {
|
||||||
if v.Block.HasFlag(FileSingleUnit) {
|
if v.BlockTableEntry.HasFlag(FileSingleUnit) {
|
||||||
return v.readInternalSingleUnit(buffer, offset, count)
|
return v.readInternalSingleUnit(buffer, offset, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
var read uint32
|
|
||||||
|
|
||||||
toRead := count
|
toRead := count
|
||||||
|
readTotal := uint32(0)
|
||||||
|
|
||||||
for toRead > 0 {
|
for toRead > 0 {
|
||||||
if read, err = v.readInternal(buffer, offset, toRead); err != nil {
|
read := v.readInternal(buffer, offset, toRead)
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if read == 0 {
|
if read == 0 {
|
||||||
break
|
break
|
||||||
@ -102,153 +118,149 @@ func (v *Stream) Read(buffer []byte, offset, count uint32) (readTotal uint32, er
|
|||||||
toRead -= read
|
toRead -= read
|
||||||
}
|
}
|
||||||
|
|
||||||
return readTotal, nil
|
return readTotal
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Stream) readInternalSingleUnit(buffer []byte, offset, count uint32) (uint32, error) {
|
func (v *Stream) readInternalSingleUnit(buffer []byte, offset, count uint32) uint32 {
|
||||||
if len(v.Data) == 0 {
|
if len(v.CurrentData) == 0 {
|
||||||
if err := v.loadSingleUnit(); err != nil {
|
v.loadSingleUnit()
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
func (v *Stream) readInternal(buffer []byte, offset, count uint32) uint32 {
|
||||||
if err := v.bufferData(); err != nil {
|
v.bufferData()
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
if bytesToCopy <= 0 {
|
||||||
return 0, nil
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
copy(buffer[offset:offset+bytesToCopy], v.Data[pos:pos+bytesToCopy])
|
copy(buffer[offset:offset+uint32(bytesToCopy)], v.CurrentData[localPosition:localPosition+uint32(bytesToCopy)])
|
||||||
v.Position += bytesToCopy
|
|
||||||
|
|
||||||
return bytesToCopy, nil
|
v.CurrentPosition += uint32(bytesToCopy)
|
||||||
|
|
||||||
|
return uint32(bytesToCopy)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Stream) bufferData() (err error) {
|
func (v *Stream) bufferData() {
|
||||||
blockIndex := v.Position / v.Size
|
requiredBlock := v.CurrentPosition / v.BlockSize
|
||||||
|
|
||||||
if blockIndex == v.Index {
|
if requiredBlock == v.CurrentBlockIndex {
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedLength := d2math.Min(v.Block.UncompressedFileSize-(blockIndex*v.Size), v.Size)
|
expectedLength := d2math.Min(v.BlockTableEntry.UncompressedFileSize-(requiredBlock*v.BlockSize), v.BlockSize)
|
||||||
if v.Data, err = v.loadBlock(blockIndex, expectedLength); err != nil {
|
v.CurrentData = v.loadBlock(requiredBlock, expectedLength)
|
||||||
return err
|
v.CurrentBlockIndex = requiredBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
v.Index = blockIndex
|
func (v *Stream) loadSingleUnit() {
|
||||||
|
fileData := make([]byte, v.BlockSize)
|
||||||
|
|
||||||
return nil
|
_, err := v.MPQData.file.Seek(int64(v.MPQData.data.HeaderSize), 0)
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Stream) loadSingleUnit() (err error) {
|
_, err = v.MPQData.file.Read(fileData)
|
||||||
if _, err = v.MPQ.file.Seek(int64(v.MPQ.header.HeaderSize), io.SeekStart); err != nil {
|
if err != nil {
|
||||||
return err
|
log.Print(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileData := make([]byte, v.Size)
|
if v.BlockSize == v.BlockTableEntry.UncompressedFileSize {
|
||||||
|
v.CurrentData = fileData
|
||||||
if _, err = v.MPQ.file.Read(fileData); err != nil {
|
return
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.Size == v.Block.UncompressedFileSize {
|
v.CurrentData = decompressMulti(fileData, v.BlockTableEntry.UncompressedFileSize)
|
||||||
v.Data = fileData
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
v.Data, err = decompressMulti(fileData, v.Block.UncompressedFileSize)
|
func (v *Stream) loadBlock(blockIndex, expectedLength uint32) []byte {
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Stream) loadBlock(blockIndex, expectedLength uint32) ([]byte, error) {
|
|
||||||
var (
|
var (
|
||||||
offset uint32
|
offset uint32
|
||||||
toRead uint32
|
toRead uint32
|
||||||
)
|
)
|
||||||
|
|
||||||
if v.Block.HasFlag(FileCompress) || v.Block.HasFlag(FileImplode) {
|
if v.BlockTableEntry.HasFlag(FileCompress) || v.BlockTableEntry.HasFlag(FileImplode) {
|
||||||
offset = v.Positions[blockIndex]
|
offset = v.BlockPositions[blockIndex]
|
||||||
toRead = v.Positions[blockIndex+1] - offset
|
toRead = v.BlockPositions[blockIndex+1] - offset
|
||||||
} else {
|
} else {
|
||||||
offset = blockIndex * v.Size
|
offset = blockIndex * v.BlockSize
|
||||||
toRead = expectedLength
|
toRead = expectedLength
|
||||||
}
|
}
|
||||||
|
|
||||||
offset += v.Block.FilePosition
|
offset += v.BlockTableEntry.FilePosition
|
||||||
data := make([]byte, toRead)
|
data := make([]byte, toRead)
|
||||||
|
|
||||||
if _, err := v.MPQ.file.Seek(int64(offset), io.SeekStart); err != nil {
|
_, err := v.MPQData.file.Seek(int64(offset), 0)
|
||||||
return []byte{}, err
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := v.MPQ.file.Read(data); err != nil {
|
_, err = v.MPQData.file.Read(data)
|
||||||
return []byte{}, err
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.Block.HasFlag(FileEncrypted) && v.Block.UncompressedFileSize > 3 {
|
if v.BlockTableEntry.HasFlag(FileEncrypted) && v.BlockTableEntry.UncompressedFileSize > 3 {
|
||||||
if v.Block.EncryptionSeed == 0 {
|
if v.EncryptionSeed == 0 {
|
||||||
return []byte{}, errors.New("unable to determine encryption key")
|
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.BlockTableEntry.HasFlag(FileCompress) && (toRead != expectedLength) {
|
||||||
if !v.Block.HasFlag(FileSingleUnit) {
|
if !v.BlockTableEntry.HasFlag(FileSingleUnit) {
|
||||||
return decompressMulti(data, expectedLength)
|
data = decompressMulti(data, expectedLength)
|
||||||
|
} else {
|
||||||
|
data = pkDecompress(data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pkDecompress(data)
|
if v.BlockTableEntry.HasFlag(FileImplode) && (toRead != expectedLength) {
|
||||||
|
data = pkDecompress(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.Block.HasFlag(FileImplode) && (toRead != expectedLength) {
|
return data
|
||||||
return pkDecompress(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gomnd // Will fix enum values later
|
//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]
|
compressionType := data[0]
|
||||||
|
|
||||||
switch compressionType {
|
switch compressionType {
|
||||||
case 1: // Huffman
|
case 1: // Huffman
|
||||||
return []byte{}, errors.New("huffman decompression not supported")
|
panic("huffman decompression not supported")
|
||||||
case 2: // ZLib/Deflate
|
case 2: // ZLib/Deflate
|
||||||
return deflate(data[1:])
|
return deflate(data[1:])
|
||||||
case 8: // PKLib/Impode
|
case 8: // PKLib/Impode
|
||||||
return pkDecompress(data[1:])
|
return pkDecompress(data[1:])
|
||||||
case 0x10: // BZip2
|
case 0x10: // BZip2
|
||||||
return []byte{}, errors.New("bzip2 decompression not supported")
|
panic("bzip2 decompression not supported")
|
||||||
case 0x80: // IMA ADPCM Stereo
|
case 0x80: // IMA ADPCM Stereo
|
||||||
return d2compression.WavDecompress(data[1:], 2), nil
|
return d2compression.WavDecompress(data[1:], 2)
|
||||||
case 0x40: // IMA ADPCM Mono
|
case 0x40: // IMA ADPCM Mono
|
||||||
return d2compression.WavDecompress(data[1:], 1), nil
|
return d2compression.WavDecompress(data[1:], 1)
|
||||||
case 0x12:
|
case 0x12:
|
||||||
return []byte{}, errors.New("lzma decompression not supported")
|
panic("lzma decompression not supported")
|
||||||
// Combos
|
// Combos
|
||||||
case 0x22:
|
case 0x22:
|
||||||
// sparse then zlib
|
// sparse then zlib
|
||||||
return []byte{}, errors.New("sparse decompression + deflate decompression not supported")
|
panic("sparse decompression + deflate decompression not supported")
|
||||||
case 0x30:
|
case 0x30:
|
||||||
// sparse then bzip2
|
// sparse then bzip2
|
||||||
return []byte{}, errors.New("sparse decompression + bzip2 decompression not supported")
|
panic("sparse decompression + bzip2 decompression not supported")
|
||||||
case 0x41:
|
case 0x41:
|
||||||
sinput := d2compression.HuffmanDecompress(data[1:])
|
sinput := d2compression.HuffmanDecompress(data[1:])
|
||||||
sinput = d2compression.WavDecompress(sinput, 1)
|
sinput = d2compression.WavDecompress(sinput, 1)
|
||||||
@ -256,68 +268,69 @@ func decompressMulti(data []byte /*expectedLength*/, _ uint32) ([]byte, error) {
|
|||||||
|
|
||||||
copy(tmp, sinput)
|
copy(tmp, sinput)
|
||||||
|
|
||||||
return tmp, nil
|
return tmp
|
||||||
case 0x48:
|
case 0x48:
|
||||||
// byte[] result = PKDecompress(sinput, outputLength);
|
// byte[] result = PKDecompress(sinput, outputLength);
|
||||||
// return MpqWavCompression.Decompress(new MemoryStream(result), 1);
|
// return MpqWavCompression.Decompress(new MemoryStream(result), 1);
|
||||||
return []byte{}, errors.New("pk + mpqwav decompression not supported")
|
panic("pk + mpqwav decompression not supported")
|
||||||
case 0x81:
|
case 0x81:
|
||||||
sinput := d2compression.HuffmanDecompress(data[1:])
|
sinput := d2compression.HuffmanDecompress(data[1:])
|
||||||
sinput = d2compression.WavDecompress(sinput, 2)
|
sinput = d2compression.WavDecompress(sinput, 2)
|
||||||
tmp := make([]byte, len(sinput))
|
tmp := make([]byte, len(sinput))
|
||||||
copy(tmp, sinput)
|
copy(tmp, sinput)
|
||||||
|
|
||||||
return tmp, nil
|
return tmp
|
||||||
case 0x88:
|
case 0x88:
|
||||||
// byte[] result = PKDecompress(sinput, outputLength);
|
// byte[] result = PKDecompress(sinput, outputLength);
|
||||||
// return MpqWavCompression.Decompress(new MemoryStream(result), 2);
|
// 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 {
|
||||||
}
|
|
||||||
|
|
||||||
func deflate(data []byte) ([]byte, error) {
|
|
||||||
b := bytes.NewReader(data)
|
b := bytes.NewReader(data)
|
||||||
|
|
||||||
r, err := zlib.NewReader(b)
|
r, err := zlib.NewReader(b)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte{}, err
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
|
|
||||||
_, err = buffer.ReadFrom(r)
|
_, err = buffer.ReadFrom(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte{}, err
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = r.Close()
|
err = r.Close()
|
||||||
if err != nil {
|
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)
|
b := bytes.NewReader(data)
|
||||||
|
|
||||||
r, err := blast.NewReader(b)
|
r, err := blast.NewReader(b)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte{}, err
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
|
|
||||||
if _, err = buffer.ReadFrom(r); err != nil {
|
_, err = buffer.ReadFrom(r)
|
||||||
return []byte{}, err
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = r.Close()
|
err = r.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte{}, err
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer.Bytes(), nil
|
return buffer.Bytes()
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,10 @@ type Archive interface {
|
|||||||
Path() string
|
Path() string
|
||||||
Contains(string) bool
|
Contains(string) bool
|
||||||
Size() uint32
|
Size() uint32
|
||||||
Close() error
|
Close()
|
||||||
|
FileExists(fileName string) bool
|
||||||
ReadFile(fileName string) ([]byte, error)
|
ReadFile(fileName string) ([]byte, error)
|
||||||
ReadFileStream(fileName string) (DataStream, error)
|
ReadFileStream(fileName string) (DataStream, error)
|
||||||
ReadTextFile(fileName string) (string, error)
|
ReadTextFile(fileName string) (string, error)
|
||||||
Listfile() ([]string, error)
|
GetFileList() ([]string, error)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package d2interface
|
package d2interface
|
||||||
|
|
||||||
|
import "github.com/hajimehoshi/ebiten/v2"
|
||||||
|
|
||||||
type renderCallback = func(Surface) error
|
type renderCallback = func(Surface) error
|
||||||
|
|
||||||
type updateCallback = func() error
|
type updateCallback = func() error
|
||||||
@ -19,7 +21,7 @@ type Renderer interface {
|
|||||||
GetCursorPos() (int, int)
|
GetCursorPos() (int, int)
|
||||||
CurrentFPS() float64
|
CurrentFPS() float64
|
||||||
ShowPanicScreen(message string)
|
ShowPanicScreen(message string)
|
||||||
Print(target interface{}, str string) error
|
Print(target *ebiten.Image, str string) error
|
||||||
PrintAt(target interface{}, str string, x, y int)
|
PrintAt(target *ebiten.Image, str string, x, y int)
|
||||||
GetWindowSize() (int, int)
|
GetWindowSize() (int, int)
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,11 @@ package d2interface
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gravestench/akara"
|
"github.com/gravestench/akara"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Scene is an extension of akara.System
|
// Scene is an extension of akara.System
|
||||||
type Scene interface {
|
type Scene interface {
|
||||||
akara.SystemInitializer
|
akara.SystemInitializer
|
||||||
State() d2enum.SceneState
|
|
||||||
Key() string
|
Key() string
|
||||||
Booted() bool
|
Booted() bool
|
||||||
Paused() bool
|
Paused() bool
|
||||||
|
@ -13,17 +13,17 @@ type Terminal interface {
|
|||||||
OnKeyChars(event KeyCharsEvent) bool
|
OnKeyChars(event KeyCharsEvent) bool
|
||||||
Render(surface Surface) error
|
Render(surface Surface) error
|
||||||
Execute(command string) error
|
Execute(command string) error
|
||||||
Rawf(category d2enum.TermCategory, format string, params ...interface{})
|
OutputRaw(text string, category d2enum.TermCategory)
|
||||||
Printf(format string, params ...interface{})
|
Outputf(format string, params ...interface{})
|
||||||
Infof(format string, params ...interface{})
|
OutputInfof(format string, params ...interface{})
|
||||||
Warningf(format string, params ...interface{})
|
OutputWarningf(format string, params ...interface{})
|
||||||
Errorf(format string, params ...interface{})
|
OutputErrorf(format string, params ...interface{})
|
||||||
Clear()
|
OutputClear()
|
||||||
Visible() bool
|
IsVisible() bool
|
||||||
Hide()
|
Hide()
|
||||||
Show()
|
Show()
|
||||||
Bind(name, description string, arguments []string, fn func(args []string) error) error
|
BindAction(name, description string, action interface{}) error
|
||||||
Unbind(name ...string) error
|
UnbindAction(name string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// TerminalLogger is used tomake the Terminal write out
|
// TerminalLogger is used tomake the Terminal write out
|
||||||
|
@ -37,8 +37,7 @@ func Ext2SourceType(ext string) SourceType {
|
|||||||
func CheckSourceType(path string) SourceType {
|
func CheckSourceType(path string) SourceType {
|
||||||
// on MacOS, the MPQ's from blizzard don't have file extensions
|
// on MacOS, the MPQ's from blizzard don't have file extensions
|
||||||
// so we just attempt to init the file as an mpq
|
// so we just attempt to init the file as an mpq
|
||||||
if mpq, err := d2mpq.New(path); err == nil {
|
if _, err := d2mpq.Load(path); err == nil {
|
||||||
_ = mpq.Close()
|
|
||||||
return AssetSourceMPQ
|
return AssetSourceMPQ
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ var _ asset.Source = &Source{}
|
|||||||
|
|
||||||
// NewSource creates a new MPQ Source
|
// NewSource creates a new MPQ Source
|
||||||
func NewSource(sourcePath string) (asset.Source, error) {
|
func NewSource(sourcePath string) (asset.Source, error) {
|
||||||
loaded, err := d2mpq.FromFile(sourcePath)
|
loaded, err := d2mpq.Load(sourcePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ key | value key | value
|
|||||||
So, GetLabelModifier returns value of offset in locale languages table
|
So, GetLabelModifier returns value of offset in locale languages table
|
||||||
*/
|
*/
|
||||||
// some of values need to be set up. For now values with "checked" comment
|
// 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 {
|
func GetLabelModifier(language string) int {
|
||||||
modifiers := map[string]int{
|
modifiers := map[string]int{
|
||||||
"ENG": 0, // (English) // checked
|
"ENG": 0, // (English) // checked
|
||||||
@ -70,7 +70,7 @@ func GetLabelModifier(language string) int {
|
|||||||
"DEU": 0, // (German) // checked
|
"DEU": 0, // (German) // checked
|
||||||
"FRA": 0, // (French)
|
"FRA": 0, // (French)
|
||||||
"POR": 0, // (Portuguese)
|
"POR": 0, // (Portuguese)
|
||||||
"ITA": 0, // (Italian) // checked
|
"ITA": 0, // (Italian)
|
||||||
"JPN": 0, // (Japanese)
|
"JPN": 0, // (Japanese)
|
||||||
"KOR": 0, // (Korean)
|
"KOR": 0, // (Korean)
|
||||||
"SIN": 0, //
|
"SIN": 0, //
|
||||||
|
@ -193,7 +193,6 @@ const (
|
|||||||
QuestLogQDescrBtn = "/data/global/ui/MENU/questlast.dc6"
|
QuestLogQDescrBtn = "/data/global/ui/MENU/questlast.dc6"
|
||||||
QuestLogSocket = "/data/global/ui/MENU/questsockets.dc6"
|
QuestLogSocket = "/data/global/ui/MENU/questsockets.dc6"
|
||||||
QuestLogAQuestAnimation = "/data/global/ui/MENU/a%dq%d.dc6"
|
QuestLogAQuestAnimation = "/data/global/ui/MENU/a%dq%d.dc6"
|
||||||
QuestLogDoneSfx = "cursor/questdone.wav"
|
|
||||||
|
|
||||||
// --- Mouse Pointers ---
|
// --- Mouse Pointers ---
|
||||||
|
|
||||||
@ -246,8 +245,6 @@ const (
|
|||||||
|
|
||||||
Frame = "/data/global/ui/PANEL/800borderframe.dc6"
|
Frame = "/data/global/ui/PANEL/800borderframe.dc6"
|
||||||
InventoryCharacterPanel = "/data/global/ui/PANEL/invchar6.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"
|
InventoryWeaponsTab = "/data/global/ui/PANEL/invchar6Tab.DC6"
|
||||||
SkillsPanelAmazon = "/data/global/ui/SPELLS/skltree_a_back.DC6"
|
SkillsPanelAmazon = "/data/global/ui/SPELLS/skltree_a_back.DC6"
|
||||||
SkillsPanelBarbarian = "/data/global/ui/SPELLS/skltree_b_back.DC6"
|
SkillsPanelBarbarian = "/data/global/ui/SPELLS/skltree_b_back.DC6"
|
||||||
|
@ -332,7 +332,7 @@ func (a *Sprite) GetDirection() int {
|
|||||||
|
|
||||||
// SetCurrentFrame sets sprite at a specific frame
|
// SetCurrentFrame sets sprite at a specific frame
|
||||||
func (a *Sprite) SetCurrentFrame(frameIndex int) error {
|
func (a *Sprite) SetCurrentFrame(frameIndex int) error {
|
||||||
if frameIndex >= a.GetFrameCount() || frameIndex < 0 {
|
if frameIndex >= a.GetFrameCount() {
|
||||||
return errors.New("invalid frame index")
|
return errors.New("invalid frame index")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,16 +37,16 @@ type GlyphPrinter struct {
|
|||||||
// Basic Latin and C1 Controls and Latin-1 Supplement.
|
// Basic Latin and C1 Controls and Latin-1 Supplement.
|
||||||
//
|
//
|
||||||
// DebugPrint always returns nil as of 1.5.0-alpha.
|
// DebugPrint always returns nil as of 1.5.0-alpha.
|
||||||
func (p *GlyphPrinter) Print(target interface{}, str string) error {
|
func (p *GlyphPrinter) Print(target *ebiten.Image, str string) error {
|
||||||
p.PrintAt(target.(*ebiten.Image), str, 0, 0)
|
p.PrintAt(target, str, 0, 0)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrintAt draws the string str on the image at (x, y) position.
|
// 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
|
// The available runes are in U+0000 to U+00FF, which is C0 Controls and
|
||||||
// Basic Latin and C1 Controls and Latin-1 Supplement.
|
// Basic Latin and C1 Controls and Latin-1 Supplement.
|
||||||
func (p *GlyphPrinter) PrintAt(target interface{}, str string, x, y int) {
|
func (p *GlyphPrinter) PrintAt(target *ebiten.Image, str string, x, y int) {
|
||||||
p.drawDebugText(target.(*ebiten.Image), str, x, y, false)
|
p.drawDebugText(target, str, x, y, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *GlyphPrinter) drawDebugText(target *ebiten.Image, str string, ox, oy int, shadow bool) {
|
func (p *GlyphPrinter) drawDebugText(target *ebiten.Image, str string, ox, oy int, shadow bool) {
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// LogLevel determines how verbose the logging is (higher is more verbose)
|
// LogLevel determines how verbose the logging is (higher is more verbose)
|
||||||
@ -52,7 +51,6 @@ func NewLogger() *Logger {
|
|||||||
l := &Logger{
|
l := &Logger{
|
||||||
level: LogLevelDefault,
|
level: LogLevelDefault,
|
||||||
colorEnabled: true,
|
colorEnabled: true,
|
||||||
mutex: sync.Mutex{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Writer = log.Writer()
|
l.Writer = log.Writer()
|
||||||
@ -66,7 +64,6 @@ type Logger struct {
|
|||||||
io.Writer
|
io.Writer
|
||||||
level LogLevel
|
level LogLevel
|
||||||
colorEnabled bool
|
colorEnabled bool
|
||||||
mutex sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPrefix sets a prefix for the message.
|
// SetPrefix sets a prefix for the message.
|
||||||
@ -74,17 +71,11 @@ type Logger struct {
|
|||||||
// logger.SetPrefix("XYZ")
|
// logger.SetPrefix("XYZ")
|
||||||
// logger.Debug("ABC") will print "[XYZ] [DEBUG] ABC"
|
// logger.Debug("ABC") will print "[XYZ] [DEBUG] ABC"
|
||||||
func (l *Logger) SetPrefix(s string) {
|
func (l *Logger) SetPrefix(s string) {
|
||||||
l.mutex.Lock()
|
|
||||||
defer l.mutex.Unlock()
|
|
||||||
|
|
||||||
l.prefix = s
|
l.prefix = s
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLevel sets the log level
|
// SetLevel sets the log level
|
||||||
func (l *Logger) SetLevel(level LogLevel) {
|
func (l *Logger) SetLevel(level LogLevel) {
|
||||||
l.mutex.Lock()
|
|
||||||
defer l.mutex.Unlock()
|
|
||||||
|
|
||||||
if level == LogLevelUnspecified {
|
if level == LogLevelUnspecified {
|
||||||
level = LogLevelDefault
|
level = LogLevelDefault
|
||||||
}
|
}
|
||||||
@ -94,9 +85,6 @@ func (l *Logger) SetLevel(level LogLevel) {
|
|||||||
|
|
||||||
// SetColorEnabled adds color escape-sequences to the logging output
|
// SetColorEnabled adds color escape-sequences to the logging output
|
||||||
func (l *Logger) SetColorEnabled(b bool) {
|
func (l *Logger) SetColorEnabled(b bool) {
|
||||||
l.mutex.Lock()
|
|
||||||
defer l.mutex.Unlock()
|
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
b = false
|
b = false
|
||||||
}
|
}
|
||||||
@ -106,6 +94,10 @@ func (l *Logger) SetColorEnabled(b bool) {
|
|||||||
|
|
||||||
// Info logs an info message
|
// Info logs an info message
|
||||||
func (l *Logger) Info(msg string) {
|
func (l *Logger) Info(msg string) {
|
||||||
|
if l == nil || l.level < LogLevelInfo {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
go l.print(LogLevelInfo, msg)
|
go l.print(LogLevelInfo, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,6 +108,10 @@ func (l *Logger) Infof(fmtMsg string, args ...interface{}) {
|
|||||||
|
|
||||||
// Warning logs a warning message
|
// Warning logs a warning message
|
||||||
func (l *Logger) Warning(msg string) {
|
func (l *Logger) Warning(msg string) {
|
||||||
|
if l == nil || l.level < LogLevelWarning {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
go l.print(LogLevelWarning, msg)
|
go l.print(LogLevelWarning, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,6 +122,10 @@ func (l *Logger) Warningf(fmtMsg string, args ...interface{}) {
|
|||||||
|
|
||||||
// Error logs an error message
|
// Error logs an error message
|
||||||
func (l *Logger) Error(msg string) {
|
func (l *Logger) Error(msg string) {
|
||||||
|
if l == nil || l.level < LogLevelError {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
go l.print(LogLevelError, msg)
|
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
|
// Fatal logs an fatal error message and exits programm
|
||||||
func (l *Logger) Fatal(msg string) {
|
func (l *Logger) Fatal(msg string) {
|
||||||
|
if l == nil || l.level < LogLevelFatal {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
go l.print(LogLevelFatal, msg)
|
go l.print(LogLevelFatal, msg)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
@ -147,6 +151,10 @@ func (l *Logger) Fatalf(fmtMsg string, args ...interface{}) {
|
|||||||
|
|
||||||
// Debug logs a debug message
|
// Debug logs a debug message
|
||||||
func (l *Logger) Debug(msg string) {
|
func (l *Logger) Debug(msg string) {
|
||||||
|
if l == nil || l.level < LogLevelDebug {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
go l.print(LogLevelDebug, msg)
|
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) {
|
func (l *Logger) print(level LogLevel, msg string) {
|
||||||
l.mutex.Lock()
|
if l == nil || l.level < level {
|
||||||
defer l.mutex.Unlock()
|
|
||||||
|
|
||||||
if l.level < level {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,7 +323,7 @@ func (a *Animation) GetDirection() int {
|
|||||||
|
|
||||||
// SetCurrentFrame sets animation at a specific frame
|
// SetCurrentFrame sets animation at a specific frame
|
||||||
func (a *Animation) SetCurrentFrame(frameIndex int) error {
|
func (a *Animation) SetCurrentFrame(frameIndex int) error {
|
||||||
if frameIndex >= a.GetFrameCount() || frameIndex < 0 {
|
if frameIndex >= a.GetFrameCount() {
|
||||||
return errors.New("invalid frame index")
|
return errors.New("invalid frame index")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ package d2asset
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image/color"
|
"image/color"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
"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.
|
// BindTerminalCommands binds the in-game terminal comands for the asset manager.
|
||||||
func (am *AssetManager) BindTerminalCommands(term d2interface.Terminal) error {
|
func (am *AssetManager) BindTerminalCommands(term d2interface.Terminal) error {
|
||||||
if err := term.Bind("assetspam", "display verbose asset manager logs", nil, am.commandAssetSpam(term)); err != nil {
|
if err := term.BindAction("assetspam", "display verbose asset manager logs", func(verbose bool) {
|
||||||
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 verbose {
|
if verbose {
|
||||||
term.Infof("asset manager verbose logging enabled")
|
term.OutputInfof("asset manager verbose logging enabled")
|
||||||
} else {
|
} else {
|
||||||
term.Infof("asset manager verbose logging disabled")
|
term.OutputInfof("asset manager verbose logging disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
am.palettes.SetVerbose(verbose)
|
am.palettes.SetVerbose(verbose)
|
||||||
am.fonts.SetVerbose(verbose)
|
am.fonts.SetVerbose(verbose)
|
||||||
am.transforms.SetVerbose(verbose)
|
am.transforms.SetVerbose(verbose)
|
||||||
am.animations.SetVerbose(verbose)
|
am.animations.SetVerbose(verbose)
|
||||||
|
}); err != nil {
|
||||||
return nil
|
return err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *AssetManager) commandAssetStat(term d2interface.Terminal) func([]string) error {
|
if err := term.BindAction("assetstat", "display asset manager cache statistics", func() {
|
||||||
return func([]string) error {
|
|
||||||
var cacheStatistics = func(c d2interface.Cache) float64 {
|
var cacheStatistics = func(c d2interface.Cache) float64 {
|
||||||
const percent = 100.0
|
const percent = 100.0
|
||||||
return float64(c.GetWeight()) / float64(c.GetBudget()) * percent
|
return float64(c.GetWeight()) / float64(c.GetBudget()) * percent
|
||||||
}
|
}
|
||||||
|
|
||||||
term.Infof("palette cache: %f", cacheStatistics(am.palettes))
|
term.OutputInfof("palette cache: %f", cacheStatistics(am.palettes))
|
||||||
term.Infof("palette transform cache: %f", cacheStatistics(am.transforms))
|
term.OutputInfof("palette transform cache: %f", cacheStatistics(am.transforms))
|
||||||
term.Infof("Animation cache: %f", cacheStatistics(am.animations))
|
term.OutputInfof("Animation cache: %f", cacheStatistics(am.animations))
|
||||||
term.Infof("font cache: %f", cacheStatistics(am.fonts))
|
term.OutputInfof("font cache: %f", cacheStatistics(am.fonts))
|
||||||
|
}); err != nil {
|
||||||
return nil
|
return err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *AssetManager) commandAssetClear([]string) error {
|
if err := term.BindAction("assetclear", "clear asset manager cache", func() {
|
||||||
am.palettes.Clear()
|
am.palettes.Clear()
|
||||||
am.transforms.Clear()
|
am.transforms.Clear()
|
||||||
am.animations.Clear()
|
am.animations.Clear()
|
||||||
am.fonts.Clear()
|
am.fonts.Clear()
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -9,23 +9,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewAssetManager creates and assigns all necessary dependencies for the AssetManager top-level functions to work correctly
|
// NewAssetManager creates and assigns all necessary dependencies for the AssetManager top-level functions to work correctly
|
||||||
func NewAssetManager(logLevel d2util.LogLevel) (*AssetManager, error) {
|
func NewAssetManager() (*AssetManager, error) {
|
||||||
loader, err := d2loader.NewLoader(logLevel)
|
loader, err := d2loader.NewLoader(d2util.LogLevelDefault)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
records, err := d2records.NewRecordManager(logLevel)
|
records, err := d2records.NewRecordManager(d2util.LogLevelDebug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
logger := d2util.NewLogger()
|
|
||||||
logger.SetPrefix(logPrefix)
|
|
||||||
logger.SetLevel(logLevel)
|
|
||||||
|
|
||||||
manager := &AssetManager{
|
manager := &AssetManager{
|
||||||
Logger: logger,
|
Logger: d2util.NewLogger(),
|
||||||
Loader: loader,
|
Loader: loader,
|
||||||
tables: make([]d2tbl.TextDictionary, 0),
|
tables: make([]d2tbl.TextDictionary, 0),
|
||||||
animations: d2cache.CreateCache(animationBudget),
|
animations: d2cache.CreateCache(animationBudget),
|
||||||
@ -35,5 +31,7 @@ func NewAssetManager(logLevel d2util.LogLevel) (*AssetManager, error) {
|
|||||||
Records: records,
|
Records: records,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
manager.SetPrefix(logPrefix)
|
||||||
|
|
||||||
return manager, err
|
return manager, err
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,9 @@ package d2asset
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||||
|
|
||||||
@ -129,11 +132,24 @@ func (a *DCCAnimation) decodeDirection(directionIndex int) error {
|
|||||||
func (a *DCCAnimation) decodeFrame(directionIndex int) animationFrame {
|
func (a *DCCAnimation) decodeFrame(directionIndex int) animationFrame {
|
||||||
dccDirection := a.dcc.Directions[directionIndex]
|
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{
|
frame := animationFrame{
|
||||||
width: dccDirection.Box.Width,
|
width: frameWidth,
|
||||||
height: dccDirection.Box.Height,
|
height: frameHeight,
|
||||||
offsetX: dccDirection.Box.Left,
|
offsetX: minX,
|
||||||
offsetY: dccDirection.Box.Top,
|
offsetY: minY,
|
||||||
decoded: true,
|
decoded: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ package d2audio
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
||||||
|
|
||||||
@ -32,7 +31,7 @@ const originalFPS float64 = 25
|
|||||||
// A Sound that can be started and stopped
|
// A Sound that can be started and stopped
|
||||||
type Sound struct {
|
type Sound struct {
|
||||||
effect d2interface.SoundEffect
|
effect d2interface.SoundEffect
|
||||||
entry *d2records.SoundDetailRecord
|
entry *d2records.SoundDetailsRecord
|
||||||
volume float64
|
volume float64
|
||||||
vTarget float64
|
vTarget float64
|
||||||
vRate float64
|
vRate float64
|
||||||
@ -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
|
// SoundEngine provides functions for playing sounds
|
||||||
type SoundEngine struct {
|
type SoundEngine struct {
|
||||||
asset *d2asset.AssetManager
|
asset *d2asset.AssetManager
|
||||||
@ -134,25 +128,43 @@ func NewSoundEngine(provider d2interface.AudioProvider,
|
|||||||
r.Logger.SetPrefix(logPrefix)
|
r.Logger.SetPrefix(logPrefix)
|
||||||
r.Logger.SetLevel(l)
|
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())
|
r.Error(err.Error())
|
||||||
return nil
|
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())
|
r.Error(err.Error())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := term.Bind("activesounds", "list currently active sounds", nil, r.commandActiveSounds); err != nil {
|
err = term.BindAction("activesounds", "list currently active sounds", func() {
|
||||||
|
for s := range r.sounds {
|
||||||
|
if err != nil {
|
||||||
r.Error(err.Error())
|
r.Error(err.Error())
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := term.Bind("killsounds", "kill active sounds", nil, r.commandKillSounds); err != nil {
|
r.Info(fmt.Sprint(s))
|
||||||
r.Error(err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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
|
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
|
// Reset stop all sounds and reset state
|
||||||
func (s *SoundEngine) Reset() {
|
func (s *SoundEngine) Reset() {
|
||||||
for snd := range s.sounds {
|
for snd := range s.sounds {
|
||||||
@ -235,35 +242,3 @@ func (s *SoundEngine) PlaySoundHandle(handle string) *Sound {
|
|||||||
sound := s.asset.Records.Sound.Details[handle].Index
|
sound := s.asset.Records.Sound.Details[handle].Index
|
||||||
return s.PlaySoundID(sound)
|
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
|
|
||||||
}
|
|
||||||
|
@ -13,8 +13,7 @@ type CommandRegistration struct {
|
|||||||
Enabled bool
|
Enabled bool
|
||||||
Name string
|
Name string
|
||||||
Description string
|
Description string
|
||||||
Args []string
|
Callback interface{}
|
||||||
Callback func(args []string) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new CommandRegistration. By default, IsCommandRegistration is false.
|
// New creates a new CommandRegistration. By default, IsCommandRegistration is false.
|
||||||
|
@ -5,6 +5,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Configuration defines the configuration for the engine, loaded from config.json
|
// Configuration defines the configuration for the engine, loaded from config.json
|
||||||
@ -19,6 +21,7 @@ type Configuration struct {
|
|||||||
RunInBackground bool
|
RunInBackground bool
|
||||||
VsyncEnabled bool
|
VsyncEnabled bool
|
||||||
Backend string
|
Backend string
|
||||||
|
LogLevel d2util.LogLevel
|
||||||
path string
|
path string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@ import (
|
|||||||
"os/user"
|
"os/user"
|
||||||
"path"
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultConfig creates and returns a default configuration
|
// DefaultConfig creates and returns a default configuration
|
||||||
@ -35,6 +37,7 @@ func DefaultConfig() *Configuration {
|
|||||||
"d2video.mpq",
|
"d2video.mpq",
|
||||||
"d2speech.mpq",
|
"d2speech.mpq",
|
||||||
},
|
},
|
||||||
|
LogLevel: d2util.LogLevelDefault,
|
||||||
path: DefaultConfigPath(),
|
path: DefaultConfigPath(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +74,8 @@ func NewBox(
|
|||||||
renderer d2interface.Renderer,
|
renderer d2interface.Renderer,
|
||||||
ui *d2ui.UIManager,
|
ui *d2ui.UIManager,
|
||||||
contentLayout *Layout,
|
contentLayout *Layout,
|
||||||
width, height, x, y int,
|
width, height int,
|
||||||
|
x, y int,
|
||||||
l d2util.LogLevel,
|
l d2util.LogLevel,
|
||||||
title string,
|
title string,
|
||||||
) *Box {
|
) *Box {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package d2gui
|
package d2gui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"image/color"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
||||||
)
|
)
|
||||||
@ -35,3 +37,28 @@ func renderSegmented(animation d2interface.Animation, segmentsX, segmentsY, fram
|
|||||||
func half(n int) int {
|
func half(n int) int {
|
||||||
return n / 2
|
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
|
||||||
|
}
|
||||||
|
@ -248,16 +248,16 @@ func (l *Layout) renderEntryDebug(entry *layoutEntry, target d2interface.Surface
|
|||||||
target.PushTranslation(entry.x, entry.y)
|
target.PushTranslation(entry.x, entry.y)
|
||||||
defer target.Pop()
|
defer target.Pop()
|
||||||
|
|
||||||
drawColor := d2util.Color(white)
|
drawColor := rgbaColor(white)
|
||||||
switch entry.widget.(type) {
|
switch entry.widget.(type) {
|
||||||
case *Layout:
|
case *Layout:
|
||||||
drawColor = d2util.Color(magenta)
|
drawColor = rgbaColor(magenta)
|
||||||
case *SpacerStatic, *SpacerDynamic:
|
case *SpacerStatic, *SpacerDynamic:
|
||||||
drawColor = d2util.Color(grey2)
|
drawColor = rgbaColor(grey2)
|
||||||
case *Label:
|
case *Label:
|
||||||
drawColor = d2util.Color(green)
|
drawColor = rgbaColor(green)
|
||||||
case *Button:
|
case *Button:
|
||||||
drawColor = d2util.Color(yellow)
|
drawColor = rgbaColor(yellow)
|
||||||
}
|
}
|
||||||
|
|
||||||
target.DrawLine(entry.width, 0, drawColor)
|
target.DrawLine(entry.width, 0, drawColor)
|
||||||
@ -487,7 +487,7 @@ func (l *Layout) createButton(renderer d2interface.Renderer, text string,
|
|||||||
return nil, loadErr
|
return nil, loadErr
|
||||||
}
|
}
|
||||||
|
|
||||||
textColor := d2util.Color(grey)
|
textColor := rgbaColor(grey)
|
||||||
textWidth, textHeight := font.GetTextMetrics(text)
|
textWidth, textHeight := font.GetTextMetrics(text)
|
||||||
textX := half(buttonWidth) - half(textWidth)
|
textX := half(buttonWidth) - half(textWidth)
|
||||||
textY := half(buttonHeight) - half(textHeight) + config.textOffset
|
textY := half(buttonHeight) - half(textHeight) + config.textOffset
|
||||||
|
@ -64,7 +64,10 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewLayoutScrollbar attaches a scrollbar to the parentLayout to control the targetLayout
|
// 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()
|
parentW, parentH := parentLayout.GetSize()
|
||||||
_, targetH := targetLayout.GetSize()
|
_, targetH := targetLayout.GetSize()
|
||||||
gutterHeight := parentH - (2 * textSliderPartHeight)
|
gutterHeight := parentH - (2 * textSliderPartHeight)
|
||||||
|
@ -110,7 +110,7 @@ func (f *HeroStateFactory) GetAllHeroStates() ([]*HeroState, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateHeroSkillsState will assemble the hero skills from the class stats record.
|
// 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{}
|
baseSkills := map[int]*HeroSkill{}
|
||||||
|
|
||||||
for idx := range classStats.BaseSkill {
|
for idx := range classStats.BaseSkill {
|
||||||
|
@ -10,27 +10,32 @@ type HeroStatsState struct {
|
|||||||
Level int `json:"level"`
|
Level int `json:"level"`
|
||||||
Experience int `json:"experience"`
|
Experience int `json:"experience"`
|
||||||
|
|
||||||
Strength int `json:"strength"`
|
|
||||||
Energy int `json:"energy"`
|
|
||||||
Dexterity int `json:"dexterity"`
|
|
||||||
Vitality int `json:"vitality"`
|
Vitality int `json:"vitality"`
|
||||||
// there are stats and skills points remaining to add.
|
Energy int `json:"energy"`
|
||||||
StatsPoints int `json:"statsPoints"`
|
Strength int `json:"strength"`
|
||||||
SkillPoints int `json:"skillPoints"`
|
Dexterity int `json:"dexterity"`
|
||||||
|
|
||||||
|
AttackRating int `json:"attackRating"`
|
||||||
|
DefenseRating int `json:"defenseRating"`
|
||||||
|
|
||||||
|
MaxStamina int `json:"maxStamina"`
|
||||||
Health int `json:"health"`
|
Health int `json:"health"`
|
||||||
MaxHealth int `json:"maxHealth"`
|
MaxHealth int `json:"maxHealth"`
|
||||||
Mana int `json:"mana"`
|
Mana int `json:"mana"`
|
||||||
MaxMana int `json:"maxMana"`
|
MaxMana int `json:"maxMana"`
|
||||||
Stamina float64 `json:"-"` // only MaxStamina is saved, Stamina gets reset on entering world
|
|
||||||
MaxStamina int `json:"maxStamina"`
|
FireResistance int `json:"fireResistance"`
|
||||||
|
ColdResistance int `json:"coldResistance"`
|
||||||
|
LightningResistance int `json:"lightningResistance"`
|
||||||
|
PoisonResistance int `json:"poisonResistance"`
|
||||||
|
|
||||||
// values which are not saved/loaded(computed)
|
// values which are not saved/loaded(computed)
|
||||||
|
Stamina float64 `json:"-"` // only MaxStamina is saved, Stamina gets reset on entering world
|
||||||
NextLevelExp int `json:"-"`
|
NextLevelExp int `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateHeroStatsState generates a running state from a hero stats.
|
// 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{
|
result := HeroStatsState{
|
||||||
Level: 1,
|
Level: 1,
|
||||||
Experience: 0,
|
Experience: 0,
|
||||||
@ -39,8 +44,6 @@ func (f *HeroStateFactory) CreateHeroStatsState(heroClass d2enum.Hero, classStat
|
|||||||
Dexterity: classStats.InitDex,
|
Dexterity: classStats.InitDex,
|
||||||
Vitality: classStats.InitVit,
|
Vitality: classStats.InitVit,
|
||||||
Energy: classStats.InitEne,
|
Energy: classStats.InitEne,
|
||||||
StatsPoints: 0,
|
|
||||||
SkillPoints: 0,
|
|
||||||
|
|
||||||
MaxHealth: classStats.InitVit * classStats.LifePerVit,
|
MaxHealth: classStats.InitVit * classStats.LifePerVit,
|
||||||
MaxMana: classStats.InitEne * classStats.ManaPerEne,
|
MaxMana: classStats.InitEne * classStats.ManaPerEne,
|
||||||
|
@ -136,6 +136,7 @@ func (f *InventoryItemFactory) GetMiscItemByCode(code string) (*InventoryItemMis
|
|||||||
|
|
||||||
// GetWeaponItemByCode returns the weapon item for the given code
|
// GetWeaponItemByCode returns the weapon item for the given code
|
||||||
func (f *InventoryItemFactory) GetWeaponItemByCode(code string) (*InventoryItemWeapon, error) {
|
func (f *InventoryItemFactory) GetWeaponItemByCode(code string) (*InventoryItemWeapon, error) {
|
||||||
|
// https://github.com/OpenDiablo2/OpenDiablo2/issues/796
|
||||||
result := f.asset.Records.Item.Weapons[code]
|
result := f.asset.Records.Item.Weapons[code]
|
||||||
if result == nil {
|
if result == nil {
|
||||||
return nil, fmt.Errorf("could not find weapon entry for code '%s'", code)
|
return nil, fmt.Errorf("could not find weapon entry for code '%s'", code)
|
||||||
|
@ -277,7 +277,7 @@ var itemStatCosts = map[string]*d2records.ItemStatCostRecord{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// nolint:gochecknoglobals // just a test
|
// nolint:gochecknoglobals // just a test
|
||||||
var charStats = map[d2enum.Hero]*d2records.CharStatRecord{
|
var charStats = map[d2enum.Hero]*d2records.CharStatsRecord{
|
||||||
d2enum.HeroPaladin: {
|
d2enum.HeroPaladin: {
|
||||||
Class: d2enum.HeroPaladin,
|
Class: d2enum.HeroPaladin,
|
||||||
SkillStrAll: "to Paladin Skill Levels",
|
SkillStrAll: "to Paladin Skill Levels",
|
||||||
@ -297,7 +297,7 @@ var skillDetails = map[int]*d2records.SkillRecord{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// nolint:gochecknoglobals // just a test
|
// nolint:gochecknoglobals // just a test
|
||||||
var monStats = map[string]*d2records.MonStatRecord{
|
var monStats = map[string]*d2records.MonStatsRecord{
|
||||||
"Specter": {NameString: "Specter", ID: 40},
|
"Specter": {NameString: "Specter", ID: 40},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,7 +256,7 @@ func (m *MapEngine) RemoveEntity(entity d2interface.MapEntity) {
|
|||||||
// GetTiles returns a slice of all tiles matching the given style,
|
// GetTiles returns a slice of all tiles matching the given style,
|
||||||
// sequence and tileType.
|
// sequence and tileType.
|
||||||
func (m *MapEngine) GetTiles(style, sequence int, tileType d2enum.TileType) []d2dt1.Tile {
|
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 {
|
for idx := range m.dt1TileData {
|
||||||
if m.dt1TileData[idx].Style != int32(style) || m.dt1TileData[idx].Sequence != int32(sequence) ||
|
if m.dt1TileData[idx].Style != int32(style) || m.dt1TileData[idx].Sequence != int32(sequence) ||
|
||||||
|
@ -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.
|
// 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,
|
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,
|
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{
|
layerEquipment := &[d2enum.CompositeTypeMax]string{
|
||||||
d2enum.CompositeTypeHead: equipment.Head.GetArmorClass(),
|
d2enum.CompositeTypeHead: equipment.Head.GetArmorClass(),
|
||||||
d2enum.CompositeTypeTorso: equipment.Torso.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.
|
// 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
|
// https://github.com/OpenDiablo2/OpenDiablo2/issues/803
|
||||||
result := &NPC{
|
result := &NPC{
|
||||||
mapEntity: newMapEntity(x, y),
|
mapEntity: newMapEntity(x, y),
|
||||||
@ -237,6 +237,7 @@ func (f *MapEntityFactory) NewCastOverlay(x, y int, overlayRecord *d2records.Ove
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/OpenDiablo2/OpenDiablo2/issues/767
|
||||||
animation.Rewind()
|
animation.Rewind()
|
||||||
animation.ResetPlayedCount()
|
animation.ResetPlayedCount()
|
||||||
|
|
||||||
@ -262,7 +263,7 @@ func (f *MapEntityFactory) NewCastOverlay(x, y int, overlayRecord *d2records.Ove
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewObject creates an instance of AnimatedComposite
|
// 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) {
|
palettePath string) (*Object, error) {
|
||||||
locX, locY := float64(x), float64(y)
|
locX, locY := float64(x), float64(y)
|
||||||
entity := &Object{
|
entity := &Object{
|
||||||
|
@ -22,8 +22,8 @@ type NPC struct {
|
|||||||
action int
|
action int
|
||||||
path int
|
path int
|
||||||
repetitions int
|
repetitions int
|
||||||
monstatRecord *d2records.MonStatRecord
|
monstatRecord *d2records.MonStatsRecord
|
||||||
monstatEx *d2records.MonStat2Record
|
monstatEx *d2records.MonStats2Record
|
||||||
HasPaths bool
|
HasPaths bool
|
||||||
isDone bool
|
isDone bool
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ type Object struct {
|
|||||||
composite *d2asset.Composite
|
composite *d2asset.Composite
|
||||||
highlight bool
|
highlight bool
|
||||||
// nameLabel d2ui.Label
|
// nameLabel d2ui.Label
|
||||||
objectRecord *d2records.ObjectDetailRecord
|
objectRecord *d2records.ObjectDetailsRecord
|
||||||
drawLayer int
|
drawLayer int
|
||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,8 @@ package d2maprenderer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
|
||||||
@ -88,11 +86,20 @@ func CreateMapRenderer(asset *d2asset.AssetManager, renderer d2interface.Rendere
|
|||||||
result.Camera.position = &startPosition
|
result.Camera.position = &startPosition
|
||||||
result.viewport.SetCamera(&result.Camera)
|
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)
|
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)
|
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
|
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().
|
// RegenerateTileCache calls MapRenderer.generateTileCache().
|
||||||
func (mr *MapRenderer) RegenerateTileCache() {
|
func (mr *MapRenderer) RegenerateTileCache() {
|
||||||
mr.generateTileCache()
|
mr.generateTileCache()
|
||||||
|
@ -22,7 +22,7 @@ func armorTypesLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
|
|
||||||
r.Animation.Token.Armor = records
|
r.Animation.Token.Armor = records
|
||||||
|
|
||||||
r.Debugf("Loaded %d ArmorType records", len(records))
|
r.Logger.Infof("Loaded %d ArmorType records", len(records))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@ func autoMagicLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
return d.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d AutoMagic records", len(records))
|
r.Logger.Infof("Loaded %d AutoMagic records", len(records))
|
||||||
|
|
||||||
r.Item.AutoMagic = records
|
r.Item.AutoMagic = records
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ func autoMapLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
return d.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d AutoMap records", len(records))
|
r.Logger.Infof("Loaded %d AutoMapRecord records", len(records))
|
||||||
|
|
||||||
r.Level.AutoMaps = records
|
r.Level.AutoMaps = records
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ func beltsLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
return d.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d Belt records", len(records))
|
r.Logger.Infof("Loaded %d belts", len(records))
|
||||||
|
|
||||||
r.Item.Belts = records
|
r.Item.Belts = records
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ func bodyLocationsLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
panic(d.Err)
|
panic(d.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d BodyLocation records", len(records))
|
r.Logger.Infof("Loaded %d Body Location records", len(records))
|
||||||
|
|
||||||
r.BodyLocations = records
|
r.BodyLocations = records
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ func booksLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
records := make(Books)
|
records := make(Books)
|
||||||
|
|
||||||
for d.Next() {
|
for d.Next() {
|
||||||
record := &BookRecord{
|
record := &BooksRecord{
|
||||||
Name: d.String("Name"),
|
Name: d.String("Name"),
|
||||||
Namco: d.String("Namco"),
|
Namco: d.String("Namco"),
|
||||||
Completed: d.String("Completed"),
|
Completed: d.String("Completed"),
|
||||||
@ -28,7 +28,7 @@ func booksLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
panic(d.Err)
|
panic(d.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d Book records", len(records))
|
r.Logger.Infof("Loaded %d book items", len(records))
|
||||||
|
|
||||||
r.Item.Books = records
|
r.Item.Books = records
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package d2records
|
package d2records
|
||||||
|
|
||||||
// Books stores all of the BookRecords
|
// Books stores all of the BooksRecords
|
||||||
type Books map[string]*BookRecord
|
type Books map[string]*BooksRecord
|
||||||
|
|
||||||
// BookRecord is a representation of a row from books.txt
|
// BooksRecord is a representation of a row from books.txt
|
||||||
type BookRecord struct {
|
type BooksRecord struct {
|
||||||
Name string
|
Name string
|
||||||
Namco string // The displayed name, where the string prefix is "Tome"
|
Namco string // The displayed name, where the string prefix is "Tome"
|
||||||
Completed string
|
Completed string
|
||||||
|
@ -5,28 +5,32 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func skillCalcLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
func skillCalcLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
||||||
records, err := loadCalculations(r, d, "Skill")
|
records, err := loadCalculations(r, d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.Logger.Infof("Loaded %d Skill Calculation records", len(records))
|
||||||
|
|
||||||
r.Calculation.Skills = records
|
r.Calculation.Skills = records
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func missileCalcLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
func missileCalcLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
||||||
records, err := loadCalculations(r, d, "Missile")
|
records, err := loadCalculations(r, d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.Logger.Infof("Loaded %d Missile Calculation records", len(records))
|
||||||
|
|
||||||
r.Calculation.Missiles = records
|
r.Calculation.Missiles = records
|
||||||
|
|
||||||
return nil
|
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)
|
records := make(Calculations)
|
||||||
|
|
||||||
for d.Next() {
|
for d.Next() {
|
||||||
@ -41,7 +45,7 @@ func loadCalculations(r *RecordManager, d *d2txt.DataDictionary, name string) (C
|
|||||||
return nil, d.Err
|
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
|
return records, nil
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ func charStatsLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for d.Next() {
|
for d.Next() {
|
||||||
record := &CharStatRecord{
|
record := &CharStatsRecord{
|
||||||
Class: stringMap[d.String("class")],
|
Class: stringMap[d.String("class")],
|
||||||
|
|
||||||
InitStr: d.Number("str"),
|
InitStr: d.Number("str"),
|
||||||
@ -136,7 +136,7 @@ func charStatsLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
return d.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d CharStat records", len(records))
|
r.Logger.Infof("Loaded %d CharStats records", len(records))
|
||||||
|
|
||||||
r.Character.Stats = records
|
r.Character.Stats = records
|
||||||
|
|
||||||
|
@ -2,11 +2,11 @@ package d2records
|
|||||||
|
|
||||||
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||||
|
|
||||||
// CharStats holds all of the CharStatRecords
|
// CharStats holds all of the CharStatsRecords
|
||||||
type CharStats map[d2enum.Hero]*CharStatRecord
|
type CharStats map[d2enum.Hero]*CharStatsRecord
|
||||||
|
|
||||||
// CharStatRecord is a struct that represents a single row from charstats.txt
|
// CharStatsRecord is a struct that represents a single row from charstats.txt
|
||||||
type CharStatRecord struct {
|
type CharStatsRecord struct {
|
||||||
Class d2enum.Hero
|
Class d2enum.Hero
|
||||||
|
|
||||||
// the initial stats at character level 1
|
// the initial stats at character level 1
|
||||||
|
@ -22,7 +22,7 @@ func colorsLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
|
|
||||||
r.Colors = records
|
r.Colors = records
|
||||||
|
|
||||||
r.Debugf("Loaded %d Color records", len(records))
|
r.Logger.Infof("Loaded %d Color records", len(records))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ func componentCodesLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
return d.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d ComponentCode records", len(records))
|
r.Logger.Infof("Loaded %d ComponentCode records", len(records))
|
||||||
|
|
||||||
r.ComponentCodes = records
|
r.ComponentCodes = records
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ func compositeTypeLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
|
|
||||||
r.Animation.Token.Composite = records
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ func cubeModifierLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
|
|
||||||
r.Item.Cube.Modifiers = records
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ func cubeTypeLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
|
|
||||||
r.Item.Cube.Types = records
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ func cubeRecipeLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
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
|
r.Item.Cube.Recipes = records
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ func difficultyLevelsLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
return d.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d DifficultyLevel records", len(records))
|
r.Logger.Infof("Loaded %d DifficultyLevel records", len(records))
|
||||||
|
|
||||||
r.DifficultyLevels = records
|
r.DifficultyLevels = records
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ func elemTypesLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
return d.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d ElemType records", len(records))
|
r.Logger.Infof("Loaded %d ElemType records", len(records))
|
||||||
|
|
||||||
r.ElemTypes = records
|
r.ElemTypes = records
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ func eventsLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
return d.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d Event records", len(records))
|
r.Logger.Infof("Loaded %d Event records", len(records))
|
||||||
|
|
||||||
r.Character.Events = records
|
r.Character.Events = records
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ func experienceLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for d.Next() {
|
for d.Next() {
|
||||||
record := &ExperienceBreakpointRecord{
|
record := &ExperienceBreakpointsRecord{
|
||||||
Level: d.Number("Level"),
|
Level: d.Number("Level"),
|
||||||
HeroBreakpoints: map[d2enum.Hero]int{
|
HeroBreakpoints: map[d2enum.Hero]int{
|
||||||
d2enum.HeroAmazon: d.Number("Amazon"),
|
d2enum.HeroAmazon: d.Number("Amazon"),
|
||||||
@ -68,7 +68,7 @@ func experienceLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
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.MaxLevel = maxLevels
|
||||||
r.Character.Experience = breakpoints
|
r.Character.Experience = breakpoints
|
||||||
|
@ -4,14 +4,14 @@ import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
|||||||
|
|
||||||
// ExperienceBreakpoints describes the required experience
|
// ExperienceBreakpoints describes the required experience
|
||||||
// for each level for each character class
|
// for each level for each character class
|
||||||
type ExperienceBreakpoints map[int]*ExperienceBreakpointRecord
|
type ExperienceBreakpoints map[int]*ExperienceBreakpointsRecord
|
||||||
|
|
||||||
// ExperienceMaxLevels defines the max character levels
|
// ExperienceMaxLevels defines the max character levels
|
||||||
type ExperienceMaxLevels map[d2enum.Hero]int
|
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
|
// gain a level for all character classes
|
||||||
type ExperienceBreakpointRecord struct {
|
type ExperienceBreakpointsRecord struct {
|
||||||
Level int
|
Level int
|
||||||
HeroBreakpoints map[d2enum.Hero]int
|
HeroBreakpoints map[d2enum.Hero]int
|
||||||
Ratio int
|
Ratio int
|
||||||
|
@ -19,7 +19,7 @@ func gambleLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
return d.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d Gamble records", len(records))
|
r.Logger.Infof("Loaded %d gamble records", len(records))
|
||||||
|
|
||||||
r.Gamble = records
|
r.Gamble = records
|
||||||
|
|
||||||
|
@ -4,12 +4,12 @@ import (
|
|||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
|
"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 {
|
func gemsLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
||||||
records := make(Gems)
|
records := make(Gems)
|
||||||
|
|
||||||
for d.Next() {
|
for d.Next() {
|
||||||
gem := &GemRecord{
|
gem := &GemsRecord{
|
||||||
Name: d.String("name"),
|
Name: d.String("name"),
|
||||||
Letter: d.String("letter"),
|
Letter: d.String("letter"),
|
||||||
Transform: d.Number("transform"),
|
Transform: d.Number("transform"),
|
||||||
@ -60,7 +60,7 @@ func gemsLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
return d.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d Gem records", len(records))
|
r.Logger.Infof("Loaded %d Gems records", len(records))
|
||||||
|
|
||||||
r.Item.Gems = records
|
r.Item.Gems = records
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package d2records
|
package d2records
|
||||||
|
|
||||||
// Gems stores all of the GemRecords
|
// Gems stores all of the GemsRecords
|
||||||
type Gems map[string]*GemRecord
|
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
|
// it describes the properties of socketable items
|
||||||
type GemRecord struct {
|
type GemsRecord struct {
|
||||||
Name string
|
Name string
|
||||||
Letter string
|
Letter string
|
||||||
Transform int
|
Transform int
|
||||||
|
@ -22,7 +22,7 @@ func hirelingDescriptionLoader(r *RecordManager, d *d2txt.DataDictionary) error
|
|||||||
|
|
||||||
r.Hireling.Descriptions = records
|
r.Hireling.Descriptions = records
|
||||||
|
|
||||||
r.Debugf("Loaded %d HirelingDescription records", len(records))
|
r.Logger.Infof("Loaded %d Hireling Descriptions records", len(records))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ func hirelingLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
return d.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d Hireling records", len(records))
|
r.Logger.Infof("Loaded %d Hireling records", len(records))
|
||||||
|
|
||||||
r.Hireling.Details = records
|
r.Hireling.Details = records
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ func hitClassLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
|
|
||||||
r.Animation.Token.HitClass = records
|
r.Animation.Token.HitClass = records
|
||||||
|
|
||||||
r.Debugf("Loaded %d HitClass records", len(records))
|
r.Logger.Infof("Loaded %d HitClass records", len(records))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -130,7 +130,7 @@ func inventoryLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
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
|
r.Layout.Inventory = records
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ func loadAffixDictionary(
|
|||||||
}
|
}
|
||||||
|
|
||||||
name := getAffixString(superType, subType)
|
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
|
return records, groups, nil
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ func armorLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d Armor Item records", len(records))
|
r.Logger.Infof("Loaded %d armors", len(records))
|
||||||
|
|
||||||
r.Item.Armors = records
|
r.Item.Armors = records
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ func lowQualityLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
|
|
||||||
r.Item.LowQualityPrefixes = records
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ func miscItemsLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d Misc Item records", len(records))
|
r.Logger.Infof("Loaded %d misc items", len(records))
|
||||||
|
|
||||||
r.Item.Misc = records
|
r.Item.Misc = records
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ func itemQualityLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
|
|
||||||
r.Item.Quality = records
|
r.Item.Quality = records
|
||||||
|
|
||||||
r.Debugf("Loaded %d ItemQuality records", len(records))
|
r.Logger.Infof("Loaded %d ItemQualities records", len(records))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ func itemRatioLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
return d.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d ItemRatio records", len(records))
|
r.Logger.Infof("Loaded %d ItemRatio records", len(records))
|
||||||
|
|
||||||
r.Item.Ratios = records
|
r.Item.Ratios = records
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ func itemTypesLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
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.Types = records
|
||||||
r.Item.Equivalency = equivMap
|
r.Item.Equivalency = equivMap
|
||||||
|
@ -13,7 +13,7 @@ func weaponsLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d Weapon records", len(records))
|
r.Logger.Infof("Loaded %d weapons", len(records))
|
||||||
|
|
||||||
r.Item.Weapons = records
|
r.Item.Weapons = records
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ func itemStatCostLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
return d.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d ItemStatCost records", len(records))
|
r.Logger.Infof("Loaded %d ItemStatCost records", len(records))
|
||||||
|
|
||||||
r.Item.Stats = records
|
r.Item.Stats = records
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ func levelDetailsLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
records := make(LevelDetails)
|
records := make(LevelDetails)
|
||||||
|
|
||||||
for d.Next() {
|
for d.Next() {
|
||||||
record := &LevelDetailRecord{
|
record := &LevelDetailsRecord{
|
||||||
Name: d.String("Name "),
|
Name: d.String("Name "),
|
||||||
ID: d.Number("Id"),
|
ID: d.Number("Id"),
|
||||||
Palette: d.Number("Pal"),
|
Palette: d.Number("Pal"),
|
||||||
@ -165,7 +165,7 @@ func levelDetailsLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
return d.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d LevelDetail records", len(records))
|
r.Logger.Infof("Loaded %d LevelDetails records", len(records))
|
||||||
|
|
||||||
r.Level.Details = records
|
r.Level.Details = records
|
||||||
|
|
||||||
|
@ -2,13 +2,13 @@ package d2records
|
|||||||
|
|
||||||
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||||
|
|
||||||
// LevelDetails has all of the LevelDetailRecords
|
// LevelDetails has all of the LevelDetailsRecords
|
||||||
type LevelDetails map[int]*LevelDetailRecord
|
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,
|
// 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.
|
// what kinds of monsters spawn, the level generator type, and lots of other stuff.
|
||||||
type LevelDetailRecord struct {
|
type LevelDetailsRecord struct {
|
||||||
|
|
||||||
// Name
|
// Name
|
||||||
// This column has no function, it only serves as a comment field to make it
|
// This column has no function, it only serves as a comment field to make it
|
||||||
|
@ -8,7 +8,7 @@ func levelMazeDetailsLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
records := make(LevelMazeDetails)
|
records := make(LevelMazeDetails)
|
||||||
|
|
||||||
for d.Next() {
|
for d.Next() {
|
||||||
record := &LevelMazeDetailRecord{
|
record := &LevelMazeDetailsRecord{
|
||||||
Name: d.String("Name"),
|
Name: d.String("Name"),
|
||||||
LevelID: d.Number("Level"),
|
LevelID: d.Number("Level"),
|
||||||
NumRoomsNormal: d.Number("Rooms"),
|
NumRoomsNormal: d.Number("Rooms"),
|
||||||
@ -24,7 +24,7 @@ func levelMazeDetailsLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
return d.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d LevelMazeDetail records", len(records))
|
r.Logger.Infof("Loaded %d LevelMazeDetails records", len(records))
|
||||||
|
|
||||||
r.Level.Maze = records
|
r.Level.Maze = records
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package d2records
|
package d2records
|
||||||
|
|
||||||
// LevelMazeDetails stores all of the LevelMazeDetailRecords
|
// LevelMazeDetails stores all of the LevelMazeDetailsRecords
|
||||||
type LevelMazeDetails map[int]*LevelMazeDetailRecord
|
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
|
// 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
|
// descriptive, not loaded in game. Corresponds with Name field in
|
||||||
// Levels.txt
|
// Levels.txt
|
||||||
Name string // Name
|
Name string // Name
|
||||||
|
@ -42,7 +42,7 @@ func levelPresetLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
records[record.DefinitionID] = record
|
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 {
|
if d.Err != nil {
|
||||||
return d.Err
|
return d.Err
|
||||||
|
@ -40,7 +40,7 @@ func levelSubstitutionsLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
return d.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d LevelSubstitution records", len(records))
|
r.Logger.Infof("Loaded %d LevelSubstitution records", len(records))
|
||||||
|
|
||||||
r.Level.Sub = records
|
r.Level.Sub = records
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ func levelTypesLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
return d.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d LevelType records", len(records))
|
r.Logger.Infof("Loaded %d LevelType records", len(records))
|
||||||
|
|
||||||
r.Level.Types = records
|
r.Level.Types = records
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ func levelWarpsLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
return d.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d LevelWarp records", len(records))
|
r.Logger.Infof("Loaded %d level warps", len(records))
|
||||||
|
|
||||||
r.Level.Warp = records
|
r.Level.Warp = records
|
||||||
|
|
||||||
|
@ -304,7 +304,7 @@ func missilesLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
return d.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d Missile records", len(records))
|
r.Logger.Infof("Loaded %d Missile Records", len(records))
|
||||||
|
|
||||||
r.Missiles = records
|
r.Missiles = records
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ func monsterAiLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
return d.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d MonsterAI records", len(records))
|
r.Logger.Infof("Loaded %d MonsterAI records", len(records))
|
||||||
|
|
||||||
r.Monster.AI = records
|
r.Monster.AI = records
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ func monsterEquipmentLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
length += len(records[k])
|
length += len(records[k])
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d MonsterEquipment records", length)
|
r.Logger.Infof("Loaded %d MonsterEquipment records", length)
|
||||||
|
|
||||||
r.Monster.Equipment = records
|
r.Monster.Equipment = records
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ func monsterLevelsLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
return d.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d MonsterLevel records", len(records))
|
r.Logger.Infof("Loaded %d MonsterLevel records", len(records))
|
||||||
|
|
||||||
r.Monster.Levels = records
|
r.Monster.Levels = records
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ func monsterModeLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
|||||||
return d.Err
|
return d.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Debugf("Loaded %d MonMode records", len(records))
|
r.Logger.Infof("Loaded %d MonMode records", len(records))
|
||||||
|
|
||||||
r.Monster.Modes = records
|
r.Monster.Modes = records
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package d2records
|
package d2records
|
||||||
|
|
||||||
// MonModes stores all of the MonModeRecords
|
// MonModes stores all of the GemsRecords
|
||||||
type MonModes map[string]*MonModeRecord
|
type MonModes map[string]*MonModeRecord
|
||||||
|
|
||||||
// MonModeRecord is a representation of a single row of Monmode.txt
|
// MonModeRecord is a representation of a single row of Monmode.txt
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user