mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2025-01-14 21:36:40 -05: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:
parent
0ee937f01b
commit
b7e50bf098
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
75
d2render/d2surface/surface.go
Normal file
75
d2render/d2surface/surface.go
Normal 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
566
d2term/terminal.go
Normal 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
12
main.go
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user