1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-11-02 09:17:19 -04:00
OpenDiablo2/d2core/engine.go

276 lines
8.7 KiB
Go
Raw Normal View History

2019-11-10 03:36:53 -05:00
package d2core
2019-10-24 09:31:59 -04:00
import (
"log"
2019-10-25 20:28:14 -04:00
"math"
2019-10-24 09:31:59 -04:00
"path"
2019-11-09 01:13:49 -05:00
"runtime"
"strconv"
2019-10-24 09:31:59 -04:00
"strings"
"sync"
"time"
2019-10-24 09:31:59 -04:00
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
2019-11-10 08:51:02 -05:00
"github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
2019-10-25 20:28:14 -04:00
2019-11-10 03:36:53 -05:00
"github.com/OpenDiablo2/OpenDiablo2/d2render"
"github.com/OpenDiablo2/D2Shared/d2data"
2019-11-10 03:36:53 -05:00
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
2019-11-10 03:36:53 -05:00
"github.com/OpenDiablo2/D2Shared/d2data/d2mpq"
2019-11-10 03:36:53 -05:00
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
"github.com/OpenDiablo2/D2Shared/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon"
2019-11-10 08:51:02 -05:00
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
2019-10-24 09:31:59 -04:00
"github.com/hajimehoshi/ebiten"
"github.com/hajimehoshi/ebiten/ebitenutil"
"github.com/hajimehoshi/ebiten/inpututil"
2019-10-24 09:31:59 -04:00
)
// Engine is the core OpenDiablo2 engine
2019-10-24 09:31:59 -04:00
type Engine struct {
Settings *d2corecommon.Configuration // Engine configuration settings from json file
Files map[string]string // Map that defines which files are in which MPQs
CheckedPatch map[string]bool // First time we check a file, we'll check if it's in the patch. This notes that we've already checked that.
LoadingSprite d2render.Sprite // The sprite shown when loading stuff
loadingProgress float64 // LoadingProcess is a range between 0.0 and 1.0. If set, loading screen displays.
loadingIndex int // Determines which load function is currently being called
thingsToLoad []func() // The load functions for the next scene
stepLoadingSize float64 // The size for each loading step
CurrentScene d2coreinterface.Scene // The current scene being rendered
UIManager *d2ui.Manager // The UI manager
SoundManager *d2audio.Manager // The sound manager
nextScene d2coreinterface.Scene // The next scene to be loaded at the end of the game loop
fullscreenKey bool // When true, the fullscreen toggle is still being pressed
lastTime float64 // Last time we updated the scene
showFPS bool
2019-10-24 09:31:59 -04:00
}
// CreateEngine creates and instance of the OpenDiablo2 engine
2019-11-10 10:44:13 -05:00
func CreateEngine() Engine {
result := Engine{
2019-10-25 20:28:14 -04:00
CurrentScene: nil,
nextScene: nil,
2019-10-24 09:31:59 -04:00
}
result.loadConfigurationFile()
2019-11-10 08:51:02 -05:00
d2resource.LanguageCode = result.Settings.Language
2019-10-24 09:31:59 -04:00
result.mapMpqFiles()
2019-11-10 10:44:13 -05:00
d2datadict.LoadPalettes(result.Files, &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)
2019-11-10 10:44:13 -05:00
d2datadict.LoadUniqueItems(&result)
d2datadict.LoadMissiles(&result)
d2datadict.LoadSounds(&result)
d2data.LoadAnimationData(&result)
d2datadict.LoadMonStats(&result)
2019-11-14 22:20:01 -05:00
LoadHeroObjects()
2019-11-10 10:44:13 -05:00
result.SoundManager = d2audio.CreateManager(&result)
2019-10-26 20:09:33 -04:00
result.SoundManager.SetVolumes(result.Settings.BgmVolume, result.Settings.SfxVolume)
2019-11-10 10:44:13 -05:00
result.UIManager = d2ui.CreateManager(&result, *result.SoundManager)
2019-11-10 08:51:02 -05:00
result.LoadingSprite = result.LoadSprite(d2resource.LoadingScreen, d2enum.Loading)
2019-10-24 09:31:59 -04:00
loadingSpriteSizeX, loadingSpriteSizeY := result.LoadingSprite.GetSize()
result.LoadingSprite.MoveTo(int(400-(loadingSpriteSizeX/2)), int(300+(loadingSpriteSizeY/2)))
//result.SetNextScene(Scenes.CreateBlizzardIntro(result, result))
2019-10-24 09:31:59 -04:00
return result
}
func (v *Engine) loadConfigurationFile() {
log.Println("Loading configuration file")
v.Settings = d2corecommon.LoadConfiguration()
2019-10-24 09:31:59 -04:00
}
func (v *Engine) mapMpqFiles() {
v.Files = make(map[string]string)
}
var mutex sync.Mutex
func (v *Engine) LoadFile(fileName string) []byte {
2019-11-10 08:51:02 -05:00
fileName = strings.ReplaceAll(fileName, "{LANG}", d2resource.LanguageCode)
// todo: separate CJK and latin characters from LanguageCode
if "CHI" == strings.ToUpper(d2resource.LanguageCode) {
fileName = strings.ReplaceAll(fileName, "{LANG_FONT}", d2resource.LanguageCode)
} else {
fileName = strings.ReplaceAll(fileName, "{LANG_FONT}", "latin")
}
fileName = strings.ToLower(fileName)
fileName = strings.ReplaceAll(fileName, `/`, "\\")
if fileName[0] == '\\' {
fileName = fileName[1:]
}
mutex.Lock()
defer mutex.Unlock()
// TODO: May want to cache some things if performance becomes an issue
cachedMpqFile, cacheExists := v.Files[fileName]
if cacheExists {
2019-11-10 08:51:02 -05:00
archive, _ := d2mpq.Load(cachedMpqFile)
result, _ := archive.ReadFile(fileName)
return result
}
for _, mpqFile := range v.Settings.MpqLoadOrder {
2019-11-10 08:51:02 -05:00
archive, _ := d2mpq.Load(path.Join(v.Settings.MpqPath, mpqFile))
if archive == nil {
log.Fatalf("Failed to load specified MPQ file: %s", mpqFile)
}
if !archive.FileExists(fileName) {
continue
}
result, _ := archive.ReadFile(fileName)
if len(result) == 0 {
continue
}
v.Files[fileName] = path.Join(v.Settings.MpqPath, mpqFile)
2019-11-13 14:26:42 -05:00
// log.Printf("%v in %v", fileName, mpqFile)
return result
}
2019-11-13 14:26:42 -05:00
log.Printf("Could not load %s from MPQs\n", fileName)
return []byte{}
}
2019-10-24 09:31:59 -04:00
// IsLoading returns true if the engine is currently in a loading state
2019-11-10 10:44:13 -05:00
func (v Engine) IsLoading() bool {
2019-10-25 20:28:14 -04:00
return v.loadingProgress < 1.0
2019-10-24 09:31:59 -04:00
}
// LoadSprite loads a sprite from the game's data files
2019-11-10 10:44:13 -05:00
func (v Engine) LoadSprite(fileName string, palette d2enum.PaletteType) d2render.Sprite {
2019-10-25 19:37:04 -04:00
data := v.LoadFile(fileName)
2019-11-10 08:51:02 -05:00
sprite := d2render.CreateSprite(data, d2datadict.Palettes[palette])
2019-10-24 09:31:59 -04:00
return sprite
}
// updateScene handles the scene maintenance for the engine
func (v *Engine) updateScene() {
if v.nextScene == nil {
if v.thingsToLoad != nil {
if v.loadingIndex < len(v.thingsToLoad) {
v.thingsToLoad[v.loadingIndex]()
v.loadingIndex++
if v.loadingIndex < len(v.thingsToLoad) {
v.StepLoading()
} else {
v.FinishLoading()
v.thingsToLoad = nil
}
return
}
}
2019-10-24 09:31:59 -04:00
return
}
if v.CurrentScene != nil {
v.CurrentScene.Unload()
2019-11-09 01:13:49 -05:00
runtime.GC()
2019-10-24 09:31:59 -04:00
}
v.CurrentScene = v.nextScene
v.nextScene = nil
2019-10-25 19:12:42 -04:00
v.UIManager.Reset()
v.thingsToLoad = v.CurrentScene.Load()
v.loadingIndex = 0
v.SetLoadingStepSize(1.0 / float64(len(v.thingsToLoad)))
2019-10-25 20:28:14 -04:00
v.ResetLoading()
2019-10-25 15:06:47 -04:00
}
2019-10-24 09:31:59 -04:00
// Update updates the internal state of the engine
func (v *Engine) Update() {
2019-10-25 17:15:44 -04:00
if ebiten.IsKeyPressed(ebiten.KeyAlt) && ebiten.IsKeyPressed(ebiten.KeyEnter) {
if !v.fullscreenKey {
ebiten.SetFullscreen(!ebiten.IsFullscreen())
}
v.fullscreenKey = true
} else {
v.fullscreenKey = false
}
if inpututil.IsKeyJustPressed(ebiten.KeyF6) {
v.showFPS = !v.showFPS
}
if inpututil.IsKeyJustPressed(ebiten.KeyF8) {
ebiten.SetVsyncEnabled(!ebiten.IsVsyncEnabled())
}
2019-10-24 09:31:59 -04:00
v.updateScene()
if v.CurrentScene == nil {
2019-10-25 15:06:47 -04:00
log.Fatal("no scene loaded")
}
2019-10-25 19:12:42 -04:00
if v.IsLoading() {
return
}
2019-10-25 20:28:14 -04:00
currentTime := float64(time.Now().UnixNano()) / float64(time.Second)
deltaTime := math.Min((currentTime - v.lastTime), 0.1)
v.lastTime = currentTime
v.CurrentScene.Update(deltaTime)
2019-10-25 19:12:42 -04:00
v.UIManager.Update()
2019-10-24 09:31:59 -04:00
}
// Draw draws the game
2019-11-10 10:44:13 -05:00
func (v Engine) Draw(screen *ebiten.Image) {
2019-10-25 20:28:14 -04:00
if v.loadingProgress < 1.0 {
2019-11-13 00:31:52 -05:00
v.LoadingSprite.Frame = int16(d2helper.Max(0, d2helper.Min(uint32(len(v.LoadingSprite.Frames)-1), uint32(float64(len(v.LoadingSprite.Frames)-1)*v.loadingProgress))))
2019-10-24 09:31:59 -04:00
v.LoadingSprite.Draw(screen)
} else {
if v.CurrentScene == nil {
2019-10-25 15:06:47 -04:00
log.Fatal("no scene loaded")
2019-10-24 09:31:59 -04:00
}
v.CurrentScene.Render(screen)
2019-10-25 19:12:42 -04:00
v.UIManager.Draw(screen)
2019-10-24 09:31:59 -04:00
}
if v.showFPS {
ebitenutil.DebugPrintAt(screen, "vsync:"+strconv.FormatBool(ebiten.IsVsyncEnabled())+"\nFPS:"+strconv.Itoa(int(ebiten.CurrentFPS())), 5, 565)
var m runtime.MemStats
runtime.ReadMemStats(&m)
ebitenutil.DebugPrintAt(screen, "Alloc "+strconv.FormatInt(int64(m.Alloc)/1024/1024, 10), 700, 0)
ebitenutil.DebugPrintAt(screen, "Pause "+strconv.FormatInt(int64(m.PauseTotalNs/1024/1024), 10), 700, 10)
ebitenutil.DebugPrintAt(screen, "HeapSys "+strconv.FormatInt(int64(m.HeapSys/1024/1024), 10), 700, 20)
ebitenutil.DebugPrintAt(screen, "NumGC "+strconv.FormatInt(int64(m.NumGC), 10), 700, 30)
}
2019-10-24 09:31:59 -04:00
}
// SetNextScene tells the engine what scene to load on the next update cycle
func (v *Engine) SetNextScene(nextScene d2coreinterface.Scene) {
2019-10-24 09:31:59 -04:00
v.nextScene = nextScene
}
2019-10-25 20:28:14 -04:00
// SetLoadingStepSize sets the size of the loading step
func (v *Engine) SetLoadingStepSize(size float64) {
v.stepLoadingSize = size
}
2019-10-25 15:06:47 -04:00
2019-10-25 20:28:14 -04:00
// ResetLoading resets the loading progress
func (v *Engine) ResetLoading() {
v.loadingProgress = 0.0
}
// StepLoading increments the loading progress
func (v *Engine) StepLoading() {
v.loadingProgress = math.Min(1.0, v.loadingProgress+v.stepLoadingSize)
}
// FinishLoading terminates the loading phase
func (v *Engine) FinishLoading() {
v.loadingProgress = 1.0
2019-10-25 15:06:47 -04:00
}