410 lines
13 KiB
Go
410 lines
13 KiB
Go
package levels
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/inkyblackness/imgui-go/v3"
|
|
|
|
"github.com/inkyblackness/hacked/editor/graphics"
|
|
"github.com/inkyblackness/hacked/editor/render"
|
|
"github.com/inkyblackness/hacked/ss1/content/archive"
|
|
"github.com/inkyblackness/hacked/ss1/content/archive/level"
|
|
"github.com/inkyblackness/hacked/ss1/content/text"
|
|
"github.com/inkyblackness/hacked/ss1/edit"
|
|
"github.com/inkyblackness/hacked/ss1/edit/undoable/cmd"
|
|
"github.com/inkyblackness/hacked/ss1/resource"
|
|
"github.com/inkyblackness/hacked/ss1/world"
|
|
"github.com/inkyblackness/hacked/ss1/world/ids"
|
|
"github.com/inkyblackness/hacked/ui/gui"
|
|
)
|
|
|
|
// ControlView is the core view for level editing.
|
|
type ControlView struct {
|
|
levels *edit.EditableLevels
|
|
levelSelection *edit.LevelSelectionService
|
|
editor *edit.LevelEditorService
|
|
|
|
guiScale float32
|
|
registry cmd.Registry
|
|
|
|
textCache *text.Cache
|
|
textureCache *graphics.TextureCache
|
|
|
|
model controlViewModel
|
|
}
|
|
|
|
// NewControlView returns a new instance.
|
|
func NewControlView(levels *edit.EditableLevels, levelSelection *edit.LevelSelectionService, editor *edit.LevelEditorService,
|
|
guiScale float32, textCache *text.Cache, textureCache *graphics.TextureCache,
|
|
registry cmd.Registry) *ControlView {
|
|
view := &ControlView{
|
|
levels: levels,
|
|
levelSelection: levelSelection,
|
|
editor: editor,
|
|
|
|
guiScale: guiScale,
|
|
registry: registry,
|
|
textCache: textCache,
|
|
textureCache: textureCache,
|
|
model: freshControlViewModel(),
|
|
}
|
|
return view
|
|
}
|
|
|
|
// WindowOpen returns the flag address, to be used with the main menu.
|
|
func (view *ControlView) WindowOpen() *bool {
|
|
return &view.model.windowOpen
|
|
}
|
|
|
|
// Render renders the view.
|
|
func (view *ControlView) Render() {
|
|
if view.model.restoreFocus {
|
|
imgui.SetNextWindowFocus()
|
|
view.model.restoreFocus = false
|
|
view.model.windowOpen = true
|
|
}
|
|
if view.model.windowOpen {
|
|
imgui.SetNextWindowSizeV(imgui.Vec2{X: 400 * view.guiScale, Y: 300 * view.guiScale}, imgui.ConditionFirstUseEver)
|
|
title := "Level Control"
|
|
readOnly := view.editor.IsReadOnly()
|
|
if readOnly {
|
|
title += hintReadOnly
|
|
}
|
|
if imgui.BeginV(title+"###Level Control", view.WindowOpen(), imgui.WindowFlagsNoCollapse) {
|
|
view.renderContent(view.editor.Level(), readOnly)
|
|
}
|
|
imgui.End()
|
|
}
|
|
}
|
|
|
|
var levelHeights = []string{
|
|
"32 Tiles",
|
|
"16 Tiles",
|
|
"8 Tiles",
|
|
"4 Tiles",
|
|
"2 Tiles",
|
|
"1 Tile",
|
|
"1/2 Tile",
|
|
"1/4 Tile",
|
|
}
|
|
|
|
func (view *ControlView) renderContent(lvl *level.Level, readOnly bool) {
|
|
imgui.PushItemWidth(-200 * view.guiScale)
|
|
selectedLevel := view.levelSelection.CurrentLevelID()
|
|
if gui.StepSliderInt("Active Level", &selectedLevel, 0, archive.MaxLevels-1) {
|
|
view.levelSelection.SetCurrentLevelID(selectedLevel)
|
|
}
|
|
imgui.Separator()
|
|
levelType := "Real World"
|
|
if lvl.IsCyberspace() {
|
|
levelType = "Cyberspace"
|
|
}
|
|
imgui.LabelText("Type", levelType)
|
|
view.renderLevelHeight(lvl, readOnly)
|
|
|
|
if !lvl.IsCyberspace() {
|
|
view.renderTextureAtlas(lvl, readOnly)
|
|
view.renderSurveillanceObjects(lvl, readOnly)
|
|
view.renderHazards(lvl, readOnly)
|
|
view.renderTextureAnimations(lvl, readOnly)
|
|
}
|
|
|
|
imgui.PopItemWidth()
|
|
}
|
|
|
|
func (view *ControlView) renderLevelHeight(lvl *level.Level, readOnly bool) {
|
|
_, _, currentShift := lvl.Size()
|
|
if readOnly {
|
|
imgui.LabelText("Level Height", levelHeights[currentShift])
|
|
} else if imgui.BeginCombo("Level Height", levelHeights[currentShift]) {
|
|
for shift, height := range levelHeights {
|
|
if imgui.SelectableV(height, shift == int(currentShift), 0, imgui.Vec2{}) {
|
|
view.requestSetZShift(lvl, shift)
|
|
}
|
|
}
|
|
imgui.EndCombo()
|
|
}
|
|
}
|
|
|
|
func (view *ControlView) textureName(index int) string {
|
|
key := resource.KeyOf(ids.TextureNames, resource.LangDefault, index)
|
|
name, err := view.textCache.Text(key)
|
|
suffix := ""
|
|
if err == nil {
|
|
suffix = ": " + name
|
|
}
|
|
return fmt.Sprintf("%3d", index) + suffix
|
|
}
|
|
|
|
func (view *ControlView) renderTextureAtlas(lvl *level.Level, readOnly bool) {
|
|
imgui.Separator()
|
|
|
|
atlas := lvl.TextureAtlas()
|
|
{
|
|
render.TextureSelector("Level Textures", -200, view.guiScale,
|
|
len(atlas), view.model.selectedAtlasIndex,
|
|
view.textureCache,
|
|
func(index int) resource.Key {
|
|
return resource.KeyOf(ids.LargeTextures.Plus(int(atlas[index])), resource.LangAny, 0)
|
|
},
|
|
func(index int) string {
|
|
textureType := "(F/C/W)"
|
|
if index >= level.FloorCeilingTextureLimit {
|
|
textureType = "(walls only)"
|
|
}
|
|
return fmt.Sprintf("Atlas Index %2d ", index) + textureType + "\n" + view.textureName(int(atlas[index]))
|
|
}, func(newIndex int) {
|
|
view.model.selectedAtlasIndex = newIndex
|
|
})
|
|
imgui.SameLine()
|
|
imgui.Text("Level Textures")
|
|
}
|
|
gameTextureIndex := -1
|
|
if (view.model.selectedAtlasIndex >= 0) && (view.model.selectedAtlasIndex < len(atlas)) {
|
|
gameTextureIndex = int(atlas[view.model.selectedAtlasIndex])
|
|
}
|
|
if !readOnly {
|
|
render.TextureSelector("Game Textures", -200, view.guiScale,
|
|
world.MaxWorldTextures, gameTextureIndex,
|
|
view.textureCache,
|
|
func(index int) resource.Key {
|
|
return resource.KeyOf(ids.LargeTextures.Plus(index), resource.LangAny, 0)
|
|
},
|
|
view.textureName,
|
|
func(newIndex int) {
|
|
view.requestSetLevelTexture(lvl, view.model.selectedAtlasIndex, newIndex)
|
|
})
|
|
imgui.SameLine()
|
|
imgui.Text("Game Textures")
|
|
}
|
|
}
|
|
|
|
func (view *ControlView) renderSurveillanceObjects(lvl *level.Level, readOnly bool) {
|
|
imgui.Separator()
|
|
|
|
if imgui.BeginCombo("Surveillance Object", fmt.Sprintf("Object %d", view.model.selectedSurveillanceObjectIndex)) {
|
|
for i := 0; i < level.SurveillanceObjectCount; i++ {
|
|
if imgui.SelectableV(fmt.Sprintf("Object %d", i), i == view.model.selectedSurveillanceObjectIndex, 0, imgui.Vec2{}) {
|
|
view.model.selectedSurveillanceObjectIndex = i
|
|
}
|
|
}
|
|
imgui.EndCombo()
|
|
}
|
|
sources := lvl.SurveillanceSources()
|
|
surrogates := lvl.SurveillanceSurrogates()
|
|
capacity := lvl.ObjectCapacity()
|
|
|
|
view.renderSliderInt(readOnly, "Surveillance Source", int(sources[view.model.selectedSurveillanceObjectIndex]),
|
|
func(int) string { return "%d" },
|
|
0, capacity,
|
|
func(newValue int) {
|
|
view.requestSetSurveillanceSource(lvl, view.model.selectedSurveillanceObjectIndex, level.ObjectID(newValue))
|
|
})
|
|
view.renderSliderInt(readOnly, "Surveillance Surrogate", int(surrogates[view.model.selectedSurveillanceObjectIndex]),
|
|
func(int) string { return "%d" },
|
|
0, capacity,
|
|
func(newValue int) {
|
|
view.requestSetSurveillanceSurrogate(lvl, view.model.selectedSurveillanceObjectIndex, level.ObjectID(newValue))
|
|
})
|
|
}
|
|
|
|
func (view *ControlView) renderHazards(lvl *level.Level, readOnly bool) {
|
|
imgui.Separator()
|
|
|
|
parameters := lvl.Parameters()
|
|
currentCeiling := currentCeilingHazard(parameters)
|
|
if readOnly {
|
|
imgui.LabelText("Ceiling Hazard", currentCeiling.title)
|
|
} else if imgui.BeginCombo("Ceiling Hazard", currentCeiling.title) {
|
|
for _, info := range ceilingHazards {
|
|
if imgui.SelectableV(info.title, info.title == currentCeiling.title, 0, imgui.Vec2{}) {
|
|
view.requestSetCeilingHazard(lvl, info)
|
|
}
|
|
}
|
|
imgui.EndCombo()
|
|
}
|
|
view.renderSliderInt(readOnly, "Ceiling Hazard Level", int(parameters.CeilingHazardLevel),
|
|
currentCeiling.formatter, 0, 255,
|
|
func(newValue int) {
|
|
view.requestSetCeilingHazardLevel(lvl, byte(newValue))
|
|
})
|
|
|
|
currentFloor := currentFloorHazard(parameters)
|
|
if readOnly {
|
|
imgui.LabelText("Floor Hazard", currentFloor.title)
|
|
} else if imgui.BeginCombo("Floor Hazard", currentFloor.title) {
|
|
for _, info := range floorHazards {
|
|
if imgui.SelectableV(info.title, info.title == currentFloor.title, 0, imgui.Vec2{}) {
|
|
view.requestSetFloorHazard(lvl, info)
|
|
}
|
|
}
|
|
imgui.EndCombo()
|
|
}
|
|
view.renderSliderInt(readOnly, "Floor Hazard Level", int(parameters.FloorHazardLevel),
|
|
currentFloor.formatter, 0, 255,
|
|
func(newValue int) {
|
|
view.requestSetFloorHazardLevel(lvl, byte(newValue))
|
|
})
|
|
}
|
|
|
|
func (view *ControlView) renderTextureAnimations(lvl *level.Level, readOnly bool) {
|
|
imgui.Separator()
|
|
|
|
animations := lvl.TextureAnimations()
|
|
selectedText := ""
|
|
if (view.model.selectedTextureAnimationIndex >= 1) && (view.model.selectedTextureAnimationIndex < len(animations)) {
|
|
selectedText = fmt.Sprintf("%d", view.model.selectedTextureAnimationIndex)
|
|
}
|
|
if imgui.BeginCombo("Texture Animation Group", selectedText) {
|
|
for i := 1; i < len(animations); i++ {
|
|
if imgui.SelectableV(fmt.Sprintf("%2d", i), view.model.selectedTextureAnimationIndex == i, 0, imgui.Vec2{}) {
|
|
view.model.selectedTextureAnimationIndex = i
|
|
}
|
|
}
|
|
imgui.EndCombo()
|
|
}
|
|
|
|
if (view.model.selectedTextureAnimationIndex >= 1) && (view.model.selectedTextureAnimationIndex < len(animations)) {
|
|
animation := animations[view.model.selectedTextureAnimationIndex]
|
|
view.renderSliderInt(readOnly, "Texture Animation Time", int(animation.FrameTime),
|
|
func(int) string { return "%d msec" },
|
|
0, 1000,
|
|
func(newValue int) {
|
|
view.requestSetTextureAnimationTime(lvl, view.model.selectedTextureAnimationIndex, uint16(newValue))
|
|
})
|
|
view.renderSliderInt(readOnly, "Texture Animation Frame Count", int(animation.FrameCount),
|
|
func(int) string { return "%d" },
|
|
0, 10,
|
|
func(newValue int) {
|
|
view.requestSetTextureAnimationFrameCount(lvl, view.model.selectedTextureAnimationIndex, byte(newValue))
|
|
})
|
|
if readOnly {
|
|
imgui.LabelText("Texture Animation Loop Type", animation.LoopType.String())
|
|
} else if imgui.BeginCombo("Texture Animation Loop Type", animation.LoopType.String()) {
|
|
loopTypes := level.TextureAnimationLoopTypes()
|
|
for _, loopType := range loopTypes {
|
|
if imgui.SelectableV(loopType.String(), loopType == animation.LoopType, 0, imgui.Vec2{}) {
|
|
view.requestSetTextureAnimationType(lvl, view.model.selectedTextureAnimationIndex, loopType)
|
|
}
|
|
}
|
|
imgui.EndCombo()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (view *ControlView) renderSliderInt(readOnly bool, label string, selectedValue int,
|
|
formatter func(int) string, min, max int, changeHandler func(int)) {
|
|
selectedString := formatter(selectedValue)
|
|
labelValue := fmt.Sprintf(selectedString, selectedValue)
|
|
if readOnly {
|
|
imgui.LabelText(label, labelValue)
|
|
} else if gui.StepSliderIntV(label, &selectedValue, min, max, selectedString) {
|
|
changeHandler(selectedValue)
|
|
}
|
|
}
|
|
|
|
func (view *ControlView) requestSetZShift(lvl *level.Level, newValue int) {
|
|
lvl.SetHeightShift(level.HeightShift(newValue))
|
|
view.patchLevelResources(lvl, func() {})
|
|
}
|
|
|
|
func (view *ControlView) requestSetLevelTexture(lvl *level.Level, atlasIndex, worldTextureIndex int) {
|
|
lvl.SetTextureAtlasEntry(atlasIndex, level.TextureIndex(worldTextureIndex))
|
|
view.patchLevelResources(lvl, func() {
|
|
view.model.selectedAtlasIndex = atlasIndex
|
|
})
|
|
}
|
|
|
|
func (view *ControlView) requestSetSurveillanceSource(lvl *level.Level, objectIndex int, objectID level.ObjectID) {
|
|
lvl.SetSurveillanceSource(objectIndex, objectID)
|
|
view.patchLevelResources(lvl, func() {
|
|
view.model.selectedSurveillanceObjectIndex = objectIndex
|
|
})
|
|
}
|
|
|
|
func (view *ControlView) requestSetSurveillanceSurrogate(lvl *level.Level, objectIndex int, objectID level.ObjectID) {
|
|
lvl.SetSurveillanceSurrogate(objectIndex, objectID)
|
|
view.patchLevelResources(lvl, func() {
|
|
view.model.selectedSurveillanceObjectIndex = objectIndex
|
|
})
|
|
}
|
|
|
|
func (view *ControlView) requestSetCeilingHazard(lvl *level.Level, info ceilingHazardInfo) {
|
|
parameters := lvl.Parameters()
|
|
parameters.RadiationRegister = 0
|
|
if info.radiationRegister {
|
|
parameters.RadiationRegister = 2
|
|
}
|
|
view.patchLevelResources(lvl, func() {})
|
|
}
|
|
|
|
func (view *ControlView) requestSetCeilingHazardLevel(lvl *level.Level, value byte) {
|
|
lvl.Parameters().CeilingHazardLevel = value
|
|
view.patchLevelResources(lvl, func() {})
|
|
}
|
|
|
|
func (view *ControlView) requestSetFloorHazard(lvl *level.Level, info floorHazardInfo) {
|
|
parameters := lvl.Parameters()
|
|
parameters.BiohazardRegister = 0
|
|
parameters.FloorHazardIsGravity = 0
|
|
if info.isGravity {
|
|
parameters.FloorHazardIsGravity = 1
|
|
} else if info.biohazardRegister {
|
|
parameters.BiohazardRegister = 2
|
|
}
|
|
view.patchLevelResources(lvl, func() {})
|
|
}
|
|
|
|
func (view *ControlView) requestSetFloorHazardLevel(lvl *level.Level, value byte) {
|
|
lvl.Parameters().FloorHazardLevel = value
|
|
view.patchLevelResources(lvl, func() {})
|
|
}
|
|
|
|
func (view *ControlView) requestSetTextureAnimationTime(lvl *level.Level, index int, value uint16) {
|
|
lvl.TextureAnimations()[index].FrameTime = value
|
|
view.patchLevelResources(lvl, func() {
|
|
view.model.selectedTextureAnimationIndex = index
|
|
})
|
|
}
|
|
|
|
func (view *ControlView) requestSetTextureAnimationFrameCount(lvl *level.Level, index int, value byte) {
|
|
lvl.TextureAnimations()[index].FrameCount = value
|
|
view.patchLevelResources(lvl, func() {
|
|
view.model.selectedTextureAnimationIndex = index
|
|
})
|
|
}
|
|
|
|
func (view *ControlView) requestSetTextureAnimationType(lvl *level.Level, index int, value level.TextureAnimationLoopType) {
|
|
lvl.TextureAnimations()[index].LoopType = value
|
|
view.patchLevelResources(lvl, func() {
|
|
view.model.selectedTextureAnimationIndex = index
|
|
})
|
|
}
|
|
|
|
func (view *ControlView) patchLevelResources(lvl *level.Level, extraRestoreState func()) {
|
|
oldLevelID := view.levelSelection.CurrentLevelID()
|
|
extraTask := func(world.Modder) error {
|
|
extraRestoreState()
|
|
return nil
|
|
}
|
|
|
|
err := view.registry.Register(cmd.Named("PatchLevel"),
|
|
cmd.Forward(view.restoreFocusTask()),
|
|
cmd.Forward(view.levelSelection.SetCurrentLevelIDTask(lvl.ID())),
|
|
cmd.Forward(extraTask),
|
|
cmd.Nested(func() error { return view.levels.CommitLevelChanges(lvl.ID()) }),
|
|
cmd.Reverse(extraTask),
|
|
cmd.Reverse(view.levelSelection.SetCurrentLevelIDTask(oldLevelID)),
|
|
cmd.Reverse(view.restoreFocusTask()))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (view *ControlView) restoreFocusTask() cmd.Task {
|
|
return func(modder world.Modder) error {
|
|
view.model.restoreFocus = true
|
|
return nil
|
|
}
|
|
}
|