1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-05 15:30:42 +00:00

Add terminal, surface, assetmanager commands (#266)

* Add terminal, surface, assetmanager commands

* echo command

* add verbose logging

* more logging, word wrap

* add timescale command
This commit is contained in:
Alex Yatskov 2019-12-26 08:13:05 -08:00 committed by Tim Sarbin
parent 0ee937f01b
commit b7e50bf098
6 changed files with 745 additions and 32 deletions

View File

@ -9,6 +9,7 @@ import (
"github.com/OpenDiablo2/D2Shared/d2data/d2dcc"
"github.com/OpenDiablo2/D2Shared/d2data/d2mpq"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon"
"github.com/OpenDiablo2/OpenDiablo2/d2term"
)
const (
@ -54,9 +55,40 @@ func Initialize(config *d2corecommon.Configuration) error {
animationManager,
}
d2term.BindAction("assetspam", "display verbose asset manager logs", func(verbose bool) {
if verbose {
d2term.OutputInfo("asset manager verbose logging enabled")
} else {
d2term.OutputInfo("asset manager verbose logging disabled")
}
archiveManager.cache.verbose = verbose
fileManager.cache.verbose = verbose
paletteManager.cache.verbose = verbose
animationManager.cache.verbose = verbose
})
d2term.BindAction("assetstat", "display asset manager cache statistics", func() {
d2term.OutputInfo("archive cache: %f%%", float64(archiveManager.cache.weight)/float64(archiveManager.cache.budget)*100.0)
d2term.OutputInfo("file cache: %f%%", float64(fileManager.cache.weight)/float64(fileManager.cache.budget)*100.0)
d2term.OutputInfo("palette cache: %f%%", float64(paletteManager.cache.weight)/float64(paletteManager.cache.budget)*100.0)
d2term.OutputInfo("animation cache: %f%%", float64(animationManager.cache.weight)/float64(animationManager.cache.budget)*100.0)
})
d2term.BindAction("assetclear", "clear asset manager cache", func() {
archiveManager.cache.clear()
fileManager.cache.clear()
paletteManager.cache.clear()
animationManager.cache.clear()
})
return nil
}
func Shutdown() {
singleton = nil
}
func LoadArchive(archivePath string) (*d2mpq.MPQ, error) {
if singleton == nil {
return nil, ErrNoInit

View File

@ -2,6 +2,7 @@ package d2asset
import (
"errors"
"log"
"sync"
)
@ -14,12 +15,13 @@ type cacheNode struct {
}
type cache struct {
head *cacheNode
tail *cacheNode
lookup map[string]*cacheNode
weight int
budget int
mutex sync.Mutex
head *cacheNode
tail *cacheNode
lookup map[string]*cacheNode
weight int
budget int
verbose bool
mutex sync.Mutex
}
func createCache(budget int) *cache {
@ -56,6 +58,18 @@ func (c *cache) insert(key string, value interface{}, weight int) error {
for ; c.tail != nil && c.tail != c.head && c.weight > c.budget; c.tail = c.tail.prev {
c.weight -= c.tail.weight
c.tail.prev.next = nil
if c.verbose {
log.Printf(
"warning -- cache is evicting %s (%d) for %s (%d); spare weight is now %d",
c.tail.key,
c.tail.weight,
key,
weight,
c.budget-c.weight,
)
}
delete(c.lookup, c.tail.key)
}
@ -95,3 +109,13 @@ func (c *cache) retrieve(key string) (interface{}, bool) {
return node.value, true
}
func (c *cache) clear() {
c.mutex.Lock()
defer c.mutex.Unlock()
c.head = nil
c.tail = nil
c.lookup = make(map[string]*cacheNode)
c.weight = 0
}

View File

@ -5,13 +5,13 @@ import (
"math"
"runtime"
"strconv"
"time"
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
"github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
"github.com/OpenDiablo2/OpenDiablo2/d2term"
"github.com/OpenDiablo2/OpenDiablo2/d2render"
@ -24,6 +24,7 @@ import (
"github.com/OpenDiablo2/D2Shared/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
"github.com/hajimehoshi/ebiten"
@ -47,11 +48,12 @@ type Engine struct {
fullscreenKey bool // When true, the fullscreen toggle is still being pressed
lastTime float64 // Last time we updated the scene
showFPS bool
timeScale float64
}
// CreateEngine creates and instance of the OpenDiablo2 engine
func CreateEngine() Engine {
var result Engine
func CreateEngine() *Engine {
result := &Engine{timeScale: 1.0}
result.Settings = d2corecommon.LoadConfiguration()
if err := result.Settings.Save(); err != nil {
@ -59,23 +61,22 @@ func CreateEngine() Engine {
}
d2asset.Initialize(result.Settings)
d2resource.LanguageCode = result.Settings.Language
d2datadict.LoadPalettes(nil, &result)
d2common.LoadTextDictionary(&result)
d2datadict.LoadLevelTypes(&result)
d2datadict.LoadLevelPresets(&result)
d2datadict.LoadLevelWarps(&result)
d2datadict.LoadObjectTypes(&result)
d2datadict.LoadObjects(&result)
d2datadict.LoadWeapons(&result)
d2datadict.LoadArmors(&result)
d2datadict.LoadMiscItems(&result)
d2datadict.LoadUniqueItems(&result)
d2datadict.LoadMissiles(&result)
d2datadict.LoadSounds(&result)
d2data.LoadAnimationData(&result)
d2datadict.LoadMonStats(&result)
d2datadict.LoadPalettes(nil, result)
d2common.LoadTextDictionary(result)
d2datadict.LoadLevelTypes(result)
d2datadict.LoadLevelPresets(result)
d2datadict.LoadLevelWarps(result)
d2datadict.LoadObjectTypes(result)
d2datadict.LoadObjects(result)
d2datadict.LoadWeapons(result)
d2datadict.LoadArmors(result)
d2datadict.LoadMiscItems(result)
d2datadict.LoadUniqueItems(result)
d2datadict.LoadMissiles(result)
d2datadict.LoadSounds(result)
d2data.LoadAnimationData(result)
d2datadict.LoadMonStats(result)
LoadHeroObjects()
result.SoundManager = d2audio.CreateManager()
result.SoundManager.SetVolumes(result.Settings.BgmVolume, result.Settings.SfxVolume)
@ -83,6 +84,16 @@ func CreateEngine() Engine {
result.LoadingSprite, _ = d2render.LoadSprite(d2resource.LoadingScreen, d2resource.PaletteLoading)
loadingSpriteSizeX, loadingSpriteSizeY := result.LoadingSprite.GetCurrentFrameSize()
result.LoadingSprite.SetPosition(int(400-(loadingSpriteSizeX/2)), int(300+(loadingSpriteSizeY/2)))
d2term.BindAction("timescale", "set scalar for elapsed time", func(scale float64) {
if scale <= 0 {
d2term.OutputError("invalid time scale value")
} else {
d2term.OutputInfo("timescale changed from %f to %f", result.timeScale, scale)
result.timeScale = scale
}
})
return result
}
@ -155,13 +166,13 @@ func (v *Engine) Update() {
return
}
currentTime := float64(time.Now().UnixNano()) / float64(time.Second)
deltaTime := math.Min((currentTime - v.lastTime), 0.1)
currentTime := d2helper.Now()
deltaTime := (currentTime - v.lastTime) * v.timeScale
v.lastTime = currentTime
v.CurrentScene.Update(deltaTime)
v.UIManager.Update()
d2term.Advance(deltaTime)
}
// Draw draws the game
@ -188,6 +199,7 @@ func (v Engine) Draw(screen *ebiten.Image) {
ebitenutil.DebugPrintAt(screen, "Coords "+strconv.FormatInt(int64(cx), 10)+","+strconv.FormatInt(int64(cy), 10), 680, 40)
}
d2term.Render(d2surface.CreateSurface(screen))
}
// SetNextScene tells the engine what scene to load on the next update cycle

View File

@ -0,0 +1,75 @@
package d2surface
import (
"image/color"
"github.com/hajimehoshi/ebiten"
"github.com/hajimehoshi/ebiten/ebitenutil"
)
type surfaceState struct {
x int
y int
mode ebiten.CompositeMode
}
type Surface struct {
stateStack []surfaceState
stateCurrent surfaceState
image *ebiten.Image
}
func CreateSurface(image *ebiten.Image) *Surface {
return &Surface{
image: image,
stateCurrent: surfaceState{
mode: ebiten.CompositeModeSourceOver,
},
}
}
func (s *Surface) PushTranslation(x, y int) {
s.stateStack = append(s.stateStack, s.stateCurrent)
s.stateCurrent.x += x
s.stateCurrent.y += y
}
func (s *Surface) PushCompositeMode(mode ebiten.CompositeMode) {
s.stateStack = append(s.stateStack, s.stateCurrent)
s.stateCurrent.mode = mode
}
func (s *Surface) Pop() {
count := len(s.stateStack)
if count == 0 {
panic("empty stack")
}
s.stateCurrent = s.stateStack[count-1]
s.stateStack = s.stateStack[:count-1]
}
func (s *Surface) Render(image *ebiten.Image) error {
opts := &ebiten.DrawImageOptions{CompositeMode: s.stateCurrent.mode}
opts.GeoM.Translate(float64(s.stateCurrent.x), float64(s.stateCurrent.y))
return s.image.DrawImage(image, opts)
}
func (s *Surface) DrawText(text string) {
ebitenutil.DebugPrintAt(s.image, text, s.stateCurrent.x, s.stateCurrent.y)
}
func (s *Surface) DrawRect(width, height int, color color.Color) {
ebitenutil.DrawRect(
s.image,
float64(s.stateCurrent.x),
float64(s.stateCurrent.y),
float64(width),
float64(height),
color,
)
}
func (s *Surface) GetSize() (int, int) {
return s.image.Size()
}

566
d2term/terminal.go Normal file
View File

@ -0,0 +1,566 @@
package d2term
import (
"bufio"
"bytes"
"errors"
"fmt"
"image/color"
"io"
"log"
"math"
"reflect"
"sort"
"strconv"
"strings"
"github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
"github.com/hajimehoshi/ebiten"
"github.com/hajimehoshi/ebiten/inpututil"
)
const (
termCharWidth = 6
termCharHeight = 16
termRowCount = 24
termRowCountMax = 32
termColCountMax = 128
termAnimLength = 0.5
)
type termCategory int
const (
termCategoryNone termCategory = iota
termCategoryInfo
termCategoryWarning
termCategoryError
)
type termVis int
const (
termVisHidden termVis = iota
termVisShowing
termVisShown
termVisHiding
)
var (
termBgColor = color.RGBA{0x2e, 0x34, 0x36, 0xb0}
termFgColor = color.RGBA{0x55, 0x57, 0x53, 0xb0}
termInfoColor = color.RGBA{0x34, 0x65, 0xa4, 0xb0}
termWarningColor = color.RGBA{0xfc, 0xe9, 0x4f, 0xb0}
termErrorColor = color.RGBA{0xcc, 0x00, 0x00, 0xb0}
)
type termHistroyEntry struct {
text string
category termCategory
}
type termActionEntry struct {
action interface{}
description string
}
type terminal struct {
outputHistory []termHistroyEntry
outputIndex int
command string
commandHistory []string
commandIndex int
lineCount int
visState termVis
visAnim float64
actions map[string]termActionEntry
}
func createTerminal() (*terminal, error) {
terminal := &terminal{
lineCount: termRowCount,
actions: make(map[string]termActionEntry),
}
terminal.outputInfo("::: OpenDiablo2 Terminal :::")
terminal.outputInfo("type \"ls\" for a list of actions")
terminal.bindAction("ls", "list available actions", func() {
var names []string
for name, _ := range terminal.actions {
names = append(names, name)
}
sort.Strings(names)
terminal.outputInfo("available actions (%d):", len(names))
for _, name := range names {
entry := terminal.actions[name]
terminal.outputInfo("%s: %s; %s", name, entry.description, reflect.TypeOf(entry.action).String())
}
})
terminal.bindAction("clear", "clear terminal", func() {
terminal.outputClear()
})
return terminal, nil
}
func (t *terminal) advance(elapsed float64) error {
if inpututil.IsKeyJustPressed(ebiten.KeyGraveAccent) {
switch t.visState {
case termVisShowing, termVisShown:
t.hide()
case termVisHiding, termVisHidden:
t.show()
}
}
switch t.visState {
case termVisShowing:
t.visAnim = math.Min(1.0, t.visAnim+elapsed/termAnimLength)
if t.visAnim == 1.0 {
t.visState = termVisShown
}
case termVisHiding:
t.visAnim = math.Max(0.0, t.visAnim-elapsed/termAnimLength)
if t.visAnim == 0.0 {
t.visState = termVisHidden
}
}
if !t.isVisible() {
return nil
}
maxOutputIndex := d2helper.MaxInt(0, len(t.outputHistory)-t.lineCount)
if inpututil.IsKeyJustPressed(ebiten.KeyHome) {
t.outputIndex = maxOutputIndex
}
if inpututil.IsKeyJustPressed(ebiten.KeyEnd) {
t.outputIndex = 0
}
if inpututil.IsKeyJustPressed(ebiten.KeyPageUp) {
if t.outputIndex += t.lineCount; t.outputIndex >= maxOutputIndex {
t.outputIndex = maxOutputIndex
}
}
if inpututil.IsKeyJustPressed(ebiten.KeyPageDown) {
if t.outputIndex -= t.lineCount; t.outputIndex < 0 {
t.outputIndex = 0
}
}
if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
t.command = ""
}
if inpututil.IsKeyJustPressed(ebiten.KeyUp) {
if ebiten.IsKeyPressed(ebiten.KeyControl) {
t.lineCount = d2helper.MaxInt(0, t.lineCount-1)
} else if len(t.commandHistory) > 0 {
t.command = t.commandHistory[t.commandIndex]
if t.commandIndex == 0 {
t.commandIndex = len(t.commandHistory) - 1
} else {
t.commandIndex--
}
}
}
if inpututil.IsKeyJustPressed(ebiten.KeyDown) && ebiten.IsKeyPressed(ebiten.KeyControl) {
t.lineCount = d2helper.MinInt(t.lineCount+1, termRowCountMax)
}
if inpututil.IsKeyJustPressed(ebiten.KeyBackspace) && len(t.command) > 0 {
t.command = t.command[:len(t.command)-1]
}
if inpututil.IsKeyJustPressed(ebiten.KeyEnter) && len(t.command) > 0 {
var commandHistory []string
for _, command := range t.commandHistory {
if command != t.command {
commandHistory = append(commandHistory, command)
}
}
t.commandHistory = append(commandHistory, t.command)
t.output(t.command)
if err := t.execute(t.command); err != nil {
t.outputError(err.Error())
}
t.commandIndex = len(t.commandHistory) - 1
t.command = ""
}
for _, c := range ebiten.InputChars() {
if c != '`' {
t.command += string(c)
}
}
return nil
}
func (t *terminal) render(surface *d2surface.Surface) error {
if !t.isVisible() {
return nil
}
totalWidth, _ := surface.GetSize()
outputHeight := t.lineCount * termCharHeight
totalHeight := outputHeight + termCharHeight
offset := -int((1.0 - easeInOut(t.visAnim)) * float64(totalHeight))
surface.PushTranslation(0, offset)
surface.DrawRect(totalWidth, outputHeight, termBgColor)
for i := 0; i < t.lineCount; i++ {
historyIndex := len(t.outputHistory) - i - t.outputIndex - 1
if historyIndex < 0 {
break
}
historyEntry := t.outputHistory[historyIndex]
surface.PushTranslation(termCharWidth*2, outputHeight-(i+1)*termCharHeight)
surface.DrawText(historyEntry.text)
surface.PushTranslation(-termCharWidth*2, 0)
switch historyEntry.category {
case termCategoryInfo:
surface.DrawRect(termCharWidth, termCharHeight, termInfoColor)
case termCategoryWarning:
surface.DrawRect(termCharWidth, termCharHeight, termWarningColor)
case termCategoryError:
surface.DrawRect(termCharWidth, termCharHeight, termErrorColor)
}
surface.Pop()
surface.Pop()
}
surface.PushTranslation(0, outputHeight)
surface.DrawRect(totalWidth, termCharHeight, termFgColor)
surface.DrawText("> " + t.command)
surface.Pop()
surface.Pop()
return nil
}
func (t *terminal) execute(command string) error {
params := parseCommand(command)
if len(params) == 0 {
return errors.New("invalid command")
}
actionName := params[0]
actionParams := params[1:]
actionEntry, ok := t.actions[actionName]
if !ok {
return errors.New("action not found")
}
actionType := reflect.TypeOf(actionEntry.action)
if actionType.Kind() != reflect.Func {
return errors.New("action is not a function")
}
if len(actionParams) != actionType.NumIn() {
return errors.New("action requires different argument count")
}
var paramValues []reflect.Value
for i := 0; i < actionType.NumIn(); i++ {
actionParam := actionParams[i]
switch actionType.In(i).Kind() {
case reflect.String:
paramValues = append(paramValues, reflect.ValueOf(actionParam))
case reflect.Int:
value, err := strconv.ParseInt(actionParam, 10, 64)
if err != nil {
return err
}
paramValues = append(paramValues, reflect.ValueOf(int(value)))
case reflect.Uint:
value, err := strconv.ParseUint(actionParam, 10, 64)
if err != nil {
return err
}
paramValues = append(paramValues, reflect.ValueOf(uint(value)))
case reflect.Float64:
value, err := strconv.ParseFloat(actionParam, 64)
if err != nil {
return err
}
paramValues = append(paramValues, reflect.ValueOf(value))
case reflect.Bool:
value, err := strconv.ParseBool(actionParam)
if err != nil {
return err
}
paramValues = append(paramValues, reflect.ValueOf(value))
default:
return errors.New("action has unsupported arguments")
}
}
actionValue := reflect.ValueOf(actionEntry.action)
actionReturnValues := actionValue.Call(paramValues)
if actionReturnValueCount := len(actionReturnValues); actionReturnValueCount > 0 {
t.outputInfo("function returned %d values:", actionReturnValueCount)
for _, actionReturnValue := range actionReturnValues {
t.outputInfo("%v: %s", actionReturnValue.Interface(), actionReturnValue.String())
}
}
return nil
}
func (t *terminal) outputRaw(text string, category termCategory) {
var line string
for _, word := range strings.Split(text, " ") {
if len(line) > 0 {
line += " "
}
lineLength := len(line)
wordLength := len(word)
if lineLength+wordLength >= termColCountMax {
t.outputHistory = append(t.outputHistory, termHistroyEntry{line, category})
line = word
} else {
line += word
}
}
t.outputHistory = append(t.outputHistory, termHistroyEntry{line, category})
}
func (t *terminal) output(format string, params ...interface{}) {
t.outputRaw(fmt.Sprintf(format, params...), termCategoryNone)
}
func (t *terminal) outputInfo(format string, params ...interface{}) {
t.outputRaw(fmt.Sprintf(format, params...), termCategoryInfo)
}
func (t *terminal) outputWarning(format string, params ...interface{}) {
t.outputRaw(fmt.Sprintf(format, params...), termCategoryWarning)
}
func (t *terminal) outputError(format string, params ...interface{}) {
t.outputRaw(fmt.Sprintf(format, params...), termCategoryError)
}
func (t *terminal) outputClear() {
t.outputHistory = nil
t.outputIndex = 0
}
func (t *terminal) isVisible() bool {
return t.visState != termVisHidden
}
func (t *terminal) hide() {
if t.visState != termVisHidden {
t.visState = termVisHiding
}
}
func (t *terminal) show() {
if t.visState != termVisShown {
t.visState = termVisShowing
}
}
func (t *terminal) bindAction(name, description string, action interface{}) error {
actionType := reflect.TypeOf(action)
if actionType.Kind() != reflect.Func {
return errors.New("action is not a function")
}
for i := 0; i < actionType.NumIn(); i++ {
switch actionType.In(i).Kind() {
case reflect.String:
case reflect.Int:
case reflect.Uint:
case reflect.Float64:
case reflect.Bool:
break
default:
return errors.New("action has unsupported arguments")
}
}
t.actions[name] = termActionEntry{action, description}
return nil
}
func (t *terminal) unbindAction(name string) {
delete(t.actions, name)
}
var singleton *terminal
func Initialize() error {
if singleton != nil {
return errors.New("terminal system is already initialized")
}
var err error
singleton, err = createTerminal()
return err
}
func Advance(elapsed float64) error {
if singleton != nil {
return singleton.advance(elapsed)
}
return nil
}
func Output(format string, params ...interface{}) {
if singleton != nil {
singleton.output(format, params...)
}
}
func OutputInfo(format string, params ...interface{}) {
if singleton != nil {
singleton.outputInfo(format, params...)
}
}
func OutputWarning(format string, params ...interface{}) {
if singleton != nil {
singleton.outputWarning(format, params...)
}
}
func OutputError(format string, params ...interface{}) {
if singleton != nil {
singleton.outputError(format, params...)
}
}
func BindAction(name, description string, action interface{}) {
if singleton != nil {
singleton.bindAction(name, description, action)
}
}
func UnbindAction(name string) {
if singleton != nil {
singleton.unbindAction(name)
}
}
func Render(surface *d2surface.Surface) error {
if singleton != nil {
return singleton.render(surface)
}
return nil
}
type terminalLogger struct {
buffer bytes.Buffer
writer io.Writer
}
func (t *terminalLogger) Write(p []byte) (int, error) {
n, err := t.buffer.Write(p)
if err != nil {
return n, err
}
reader := bufio.NewReader(&t.buffer)
bytes, _, err := reader.ReadLine()
if err != nil {
return n, err
}
line := string(bytes[:])
lineLower := strings.ToLower(line)
if strings.Index(lineLower, "error") > 0 {
OutputError(line)
} else if strings.Index(lineLower, "warning") > 0 {
OutputWarning(line)
} else {
Output(line)
}
return t.writer.Write(p)
}
func BindLogger() {
log.SetOutput(&terminalLogger{writer: log.Writer()})
}
func easeInOut(t float64) float64 {
t *= 2
if t < 1 {
return 0.5 * t * t * t * t
} else {
t -= 2
return -0.5 * (t*t*t*t - 2)
}
}
func parseCommand(command string) []string {
var (
quoted bool
escape bool
param string
params []string
)
for _, c := range command {
switch c {
case '"':
if escape {
param += string(c)
escape = false
} else {
quoted = !quoted
}
case ' ':
if quoted {
param += string(c)
} else if len(param) > 0 {
params = append(params, param)
param = ""
}
case '\\':
if escape {
param += string(c)
escape = false
} else {
escape = true
}
default:
param += string(c)
}
}
if len(param) > 0 {
params = append(params, param)
}
return params
}

12
main.go
View File

@ -5,6 +5,7 @@ import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2scene"
"github.com/OpenDiablo2/OpenDiablo2/d2term"
"github.com/hajimehoshi/ebiten/ebitenutil"
@ -21,12 +22,15 @@ var GitBranch string
// GitCommit is set by the CI build process to the commit hash
var GitCommit string
var d2Engine d2core.Engine
var d2Engine *d2core.Engine
var region = kingpin.Arg("region", "Region type id").Int()
var preset = kingpin.Arg("preset", "Level preset").Int()
func main() {
d2term.Initialize()
d2term.BindLogger()
//procs := runtime.GOMAXPROCS(16)
//log.Printf("Setting gomaxprocs to 16, it was previously set to %d", procs)
//runtime.LockOSThread()
@ -37,7 +41,7 @@ func main() {
GitCommit = ""
}
d2common.SetBuildInfo(GitBranch, GitCommit)
log.SetFlags(log.Ldate | log.LUTC | log.Lmicroseconds | log.Llongfile)
log.SetFlags(log.Lshortfile)
log.Println("OpenDiablo2 - Open source Diablo 2 engine")
_, iconImage, err := ebitenutil.NewImageFromFile("d2logo.png", ebiten.FilterLinear)
if err == nil {
@ -47,9 +51,9 @@ func main() {
d2Engine = d2core.CreateEngine()
kingpin.Parse()
if *region == 0 {
d2Engine.SetNextScene(d2scene.CreateMainMenu(&d2Engine, d2Engine.UIManager, d2Engine.SoundManager))
d2Engine.SetNextScene(d2scene.CreateMainMenu(d2Engine, d2Engine.UIManager, d2Engine.SoundManager))
} else {
d2Engine.SetNextScene(d2scene.CreateMapEngineTest(&d2Engine, d2Engine.UIManager, d2Engine.SoundManager, *region, *preset))
d2Engine.SetNextScene(d2scene.CreateMapEngineTest(d2Engine, d2Engine.UIManager, d2Engine.SoundManager, *region, *preset))
}
ebiten.SetCursorVisible(false)
ebiten.SetFullscreen(d2Engine.Settings.FullScreen)