Adding modding folder with a hacked build.

This commit is contained in:
ml
2026-09-26 08:53:08 +02:00
parent cdb960dc71
commit c91080aba7
755 changed files with 50424 additions and 0 deletions
@@ -0,0 +1,162 @@
package levels
import (
"github.com/inkyblackness/hacked/editor/render"
"github.com/inkyblackness/hacked/ss1/content/archive/level"
"github.com/inkyblackness/hacked/ui/opengl"
)
var gridVertexShaderSource = `
#version 150
precision mediump float;
in vec3 vertexPosition;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
out vec4 gridColor;
out vec3 originalPosition;
void main(void) {
gl_Position = projectionMatrix * viewMatrix * vec4(vertexPosition, 1.0);
gridColor = vec4(0.0, 0.1, 0.0, 0.6);
originalPosition = vertexPosition;
}
`
var gridFragmentShaderSource = `
#version 150
precision mediump float;
uniform vec4 gridSize;
in vec4 gridColor;
in vec3 originalPosition;
out vec4 fragColor;
float modulo(float x, float y) {
return x - y * floor(x/y);
}
float nearGrid(float stepSize, float value) {
float remainder = modulo(value - (stepSize / 2.0), stepSize) * 2.0;
if (remainder >= stepSize) {
remainder = (stepSize * 2.0) - remainder;
}
return remainder / stepSize;
}
void main(void) {
float alphaX = nearGrid(256.0, originalPosition.x);
float alphaY = nearGrid(256.0, originalPosition.y);
bool beyondX = (originalPosition.x / 256.0) >= gridSize.x || (originalPosition.x < 0.0);
bool beyondY = (originalPosition.y / 256.0) >= gridSize.y || (originalPosition.y < 0.0);
float alpha = 0.0;
if (!beyondX && !beyondY) {
alpha = max(alphaX, alphaY);
} else if (beyondX && !beyondY) {
alpha = alphaX;
} else if (beyondY && !beyondX) {
alpha = alphaY;
} else {
alpha = min(alphaX, alphaY);
}
alpha = pow(2.0, 10.0 * (alpha - 1.0));
fragColor = vec4(gridColor.rgb, gridColor.a * alpha);
}
`
// BackgroundGrid renders a grid with transparent holes.
type BackgroundGrid struct {
context *render.Context
program uint32
vao *opengl.VertexArrayObject
vertexPositionBuffer uint32
vertexPositionAttrib int32
gridSizeUniform opengl.Vector4Uniform
viewMatrixUniform opengl.Matrix4Uniform
projectionMatrixUniform opengl.Matrix4Uniform
lastReportedColumns int
lastReportedRows int
}
// NewBackgroundGrid returns a new instance of BackgroundGrid.
func NewBackgroundGrid(context *render.Context) *BackgroundGrid {
gl := context.OpenGL
program, programErr := opengl.LinkNewStandardProgram(gl, gridVertexShaderSource, gridFragmentShaderSource)
if programErr != nil {
panic(opengl.NamedShaderError{Name: "BackgroundGridShader", Nested: programErr})
}
grid := &BackgroundGrid{
context: context,
program: program,
vao: opengl.NewVertexArrayObject(gl, program),
vertexPositionBuffer: gl.GenBuffers(1)[0],
vertexPositionAttrib: gl.GetAttribLocation(program, "vertexPosition"),
viewMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "viewMatrix")),
projectionMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "projectionMatrix")),
gridSizeUniform: opengl.Vector4Uniform(gl.GetUniformLocation(program, "gridSize")),
}
return grid
}
// Render renders the grid.
func (grid *BackgroundGrid) Render(columns, rows int) {
gl := grid.context.OpenGL
grid.setGridSize(columns, rows)
grid.vao.OnShader(func() {
grid.viewMatrixUniform.Set(gl, grid.context.ViewMatrix)
grid.projectionMatrixUniform.Set(gl, &grid.context.ProjectionMatrix)
grid.gridSizeUniform.Set(gl, &[4]float32{float32(columns), float32(rows), 0, 0})
gl.DrawArrays(opengl.TRIANGLES, 0, 6)
})
}
func (grid *BackgroundGrid) setGridSize(columns, rows int) {
if (grid.lastReportedColumns == columns) && (grid.lastReportedRows == rows) {
return
}
grid.lastReportedColumns = columns
grid.lastReportedRows = rows
gl := grid.context.OpenGL
{
hTiles := float32(columns)
vTiles := float32(rows)
gl.BindBuffer(opengl.ARRAY_BUFFER, grid.vertexPositionBuffer)
fineHalf := level.FineCoordinatesPerTileSide / float32(2.0)
hLimit := level.FineCoordinatesPerTileSide*hTiles + fineHalf
vLimit := level.FineCoordinatesPerTileSide*vTiles + fineHalf
var vertices = []float32{
-fineHalf, -fineHalf, 0.0,
hLimit, -fineHalf, 0.0,
hLimit, vLimit, 0.0,
hLimit, vLimit, 0.0,
-fineHalf, vLimit, 0.0,
-fineHalf, -fineHalf, 0.0}
gl.BufferData(opengl.ARRAY_BUFFER, len(vertices)*4, vertices, opengl.STATIC_DRAW)
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
}
grid.vao.WithSetter(func(gl opengl.OpenGL) {
gl.EnableVertexAttribArray(uint32(grid.vertexPositionAttrib))
gl.BindBuffer(opengl.ARRAY_BUFFER, grid.vertexPositionBuffer)
gl.VertexAttribOffset(uint32(grid.vertexPositionAttrib), 3, opengl.FLOAT, false, 0, 0)
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
})
}
@@ -0,0 +1,34 @@
package levels
import (
"fmt"
)
// ColorDisplay is an enumeration to how a tile should be colored.
type ColorDisplay int
// ColorDisplay constants are listed below.
const (
ColorDisplayNone ColorDisplay = 0
ColorDisplayFloor ColorDisplay = 1
ColorDisplayCeiling ColorDisplay = 2
)
// String returns a textual representation.
func (display ColorDisplay) String() string {
switch display {
case ColorDisplayNone:
return "None"
case ColorDisplayFloor:
return "Floor"
case ColorDisplayCeiling:
return "Ceiling"
default:
return fmt.Sprintf("Unknown%d", int(display))
}
}
// ColorDisplays returns all ColorDisplay constants.
func ColorDisplays() []ColorDisplay {
return []ColorDisplay{ColorDisplayNone, ColorDisplayFloor, ColorDisplayCeiling}
}
@@ -0,0 +1,6 @@
package levels
const (
hintUnknown = "???"
hintReadOnly = " (read-only)"
)
+409
View File
@@ -0,0 +1,409 @@
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
}
}
@@ -0,0 +1,16 @@
package levels
type controlViewModel struct {
selectedAtlasIndex int
selectedSurveillanceObjectIndex int
selectedTextureAnimationIndex int
restoreFocus bool
windowOpen bool
}
func freshControlViewModel() controlViewModel {
return controlViewModel{
selectedTextureAnimationIndex: 1,
}
}
@@ -0,0 +1,39 @@
package levels
import (
"fmt"
"github.com/inkyblackness/hacked/ss1/content/archive/level"
)
func tileHeightFormatterFor(levelHeight level.HeightShift) func(int) string {
return func(value int) string {
tileHeight, err := levelHeight.ValueFromTileHeight(level.TileHeightUnit(value))
tileHeightString := hintUnknown
if err == nil {
tileHeightString = fmt.Sprintf("%2.3f", tileHeight)
}
return tileHeightString + " tile(s) - raw: %d"
}
}
func objectHeightFormatterFor(levelHeight level.HeightShift) func(int) string {
return func(value int) string {
tileHeight, err := levelHeight.ValueFromObjectHeight(level.HeightUnit(value))
tileHeightString := hintUnknown
if err == nil {
tileHeightString = fmt.Sprintf("%2.3f", tileHeight)
}
return tileHeightString + " tile(s) - raw: %d"
}
}
func moveTileHeightFormatterFor(levelHeight level.HeightShift) func(int) string {
normalFormatter := tileHeightFormatterFor(levelHeight)
return func(value int) string {
if (value >= 0) && (value <= int(level.TileHeightUnitMax)) {
return normalFormatter(value)
}
return "Don't change - raw: %d"
}
}
+124
View File
@@ -0,0 +1,124 @@
package levels
import (
mgl "github.com/go-gl/mathgl/mgl32"
"github.com/inkyblackness/hacked/editor/render"
"github.com/inkyblackness/hacked/ui/opengl"
)
var highlighterVertexShaderSource = `
#version 150
precision mediump float;
in vec3 vertexPosition;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
void main(void) {
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(vertexPosition, 1.0);
}
`
var highlighterFragmentShaderSource = `
#version 150
precision mediump float;
uniform vec4 inColor;
out vec4 fragColor;
void main(void) {
fragColor = inColor;
}
`
// Highlighter draws a simple highlighting of a rectangular area.
type Highlighter struct {
context *render.Context
program uint32
vao *opengl.VertexArrayObject
vertexPositionBuffer uint32
vertexPositionAttrib int32
modelMatrixUniform opengl.Matrix4Uniform
viewMatrixUniform opengl.Matrix4Uniform
projectionMatrixUniform opengl.Matrix4Uniform
inColorUniform opengl.Vector4Uniform
}
// NewHighlighter returns a new instance of Highlighter.
func NewHighlighter(context *render.Context) *Highlighter {
gl := context.OpenGL
program, programErr := opengl.LinkNewStandardProgram(gl, highlighterVertexShaderSource, highlighterFragmentShaderSource)
if programErr != nil {
panic(opengl.NamedShaderError{Name: "HighlighterShader", Nested: programErr})
}
highlighter := &Highlighter{
context: context,
program: program,
vao: opengl.NewVertexArrayObject(gl, program),
vertexPositionBuffer: gl.GenBuffers(1)[0],
vertexPositionAttrib: gl.GetAttribLocation(program, "vertexPosition"),
modelMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "modelMatrix")),
viewMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "viewMatrix")),
projectionMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "projectionMatrix")),
inColorUniform: opengl.Vector4Uniform(gl.GetUniformLocation(program, "inColor"))}
{
gl.BindBuffer(opengl.ARRAY_BUFFER, highlighter.vertexPositionBuffer)
half := float32(0.5)
var vertices = []float32{
-half, -half, 0.0,
half, -half, 0.0,
half, half, 0.0,
half, half, 0.0,
-half, half, 0.0,
-half, -half, 0.0}
gl.BufferData(opengl.ARRAY_BUFFER, len(vertices)*4, vertices, opengl.STATIC_DRAW)
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
}
highlighter.vao.OnShader(func() {
gl.EnableVertexAttribArray(uint32(highlighter.vertexPositionAttrib))
gl.BindBuffer(opengl.ARRAY_BUFFER, highlighter.vertexPositionBuffer)
gl.VertexAttribOffset(uint32(highlighter.vertexPositionAttrib), 3, opengl.FLOAT, false, 0, 0)
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
})
return highlighter
}
// Dispose releases all resources.
func (highlighter *Highlighter) Dispose() {
gl := highlighter.context.OpenGL
highlighter.vao.Dispose()
gl.DeleteBuffers([]uint32{highlighter.vertexPositionBuffer})
gl.DeleteShader(highlighter.program)
}
// Render renders the highlights.
func (highlighter *Highlighter) Render(positions []MapPosition, sideLength float32, color [4]float32) {
gl := highlighter.context.OpenGL
highlighter.vao.OnShader(func() {
highlighter.viewMatrixUniform.Set(gl, highlighter.context.ViewMatrix)
highlighter.projectionMatrixUniform.Set(gl, &highlighter.context.ProjectionMatrix)
highlighter.inColorUniform.Set(gl, &color)
for _, pos := range positions {
modelMatrix := mgl.Ident4().
Mul4(mgl.Translate3D(float32(pos.X), float32(pos.Y), 0.0)).
Mul4(mgl.Scale3D(sideLength, sideLength, 1.0))
highlighter.modelMatrixUniform.Set(gl, &modelMatrix)
gl.DrawArrays(opengl.TRIANGLES, 0, 6)
}
})
}
+135
View File
@@ -0,0 +1,135 @@
package levels
import (
"sort"
mgl "github.com/go-gl/mathgl/mgl32"
"github.com/inkyblackness/hacked/ss1/content/archive/level"
)
type hoverItem interface {
Pos() MapPosition
Size() float32
IsIn(lvl *level.Level) bool
}
type tileHoverItem struct {
pos MapPosition
}
func (item tileHoverItem) Pos() MapPosition {
return item.pos
}
func (item tileHoverItem) Size() float32 {
return level.FineCoordinatesPerTileSide
}
func (item tileHoverItem) IsIn(lvl *level.Level) bool {
return lvl.Tile(item.pos.Tile()) != nil
}
type objectHoverItem struct {
id level.ObjectID
pos MapPosition
}
func (item objectHoverItem) Pos() MapPosition {
return item.pos
}
func (item objectHoverItem) Size() float32 {
return level.FineCoordinatesPerTileSide / 4
}
func (item objectHoverItem) IsIn(lvl *level.Level) bool {
obj := lvl.Object(item.id)
return (obj != nil) && (obj.InUse != 0) && (item.pos.X == obj.X) && (item.pos.Y == obj.Y)
}
type hoverItems struct {
available []hoverItem
activeIndex int
activeItem hoverItem
}
func (items *hoverItems) reset() {
items.available = nil
items.activeIndex = 0
items.activeItem = nil
}
func (items *hoverItems) scroll(forward bool) {
count := len(items.available)
if count > 0 {
diff := 1
if !forward {
diff = -1
}
items.activeIndex = (count + (items.activeIndex + diff)) % count
items.activeItem = items.available[items.activeIndex]
}
}
func (items *hoverItems) find(lvl *level.Level, pos MapPosition) {
refVec := mgl.Vec2{float32(pos.X), float32(pos.Y)}
var distances []float32
items.available = nil
lvl.ForEachObject(func(id level.ObjectID, entry level.ObjectMainEntry) {
entryVec := mgl.Vec2{float32(entry.X), float32(entry.Y)}
distance := refVec.Sub(entryVec).Len()
if distance < level.FineCoordinatesPerTileSide/4 {
items.available = append(items.available,
objectHoverItem{
id: id,
pos: MapPosition{X: entry.X, Y: entry.Y},
})
distances = append(distances, distance)
}
})
items.available = append(items.available,
tileHoverItem{
pos: MapPosition{
X: level.CoordinateAt(pos.X.Tile(), level.FineCoordinatesPerTileSide/2),
Y: level.CoordinateAt(pos.Y.Tile(), level.FineCoordinatesPerTileSide/2),
},
})
distances = append(distances, level.FineCoordinatesPerTileSide)
sort.Slice(items.available, func(a, b int) bool { return distances[a] < distances[b] })
items.activeIndex = 0
items.activeItem = items.available[items.activeIndex]
}
func (items *hoverItems) validate(lvl *level.Level) {
allValid := true
for _, item := range items.available {
if !item.IsIn(lvl) {
allValid = false
break
}
}
if allValid {
return
}
newItems := make([]hoverItem, 0, len(items.available))
for index, item := range items.available {
isActive := index == items.activeIndex
if item.IsIn(lvl) {
if isActive {
items.activeIndex = len(newItems)
}
newItems = append(newItems, item)
} else if isActive {
items.activeIndex = 0
}
}
items.available = newItems
items.activeItem = nil
if items.activeIndex < len(items.available) {
items.activeItem = items.available[items.activeIndex]
}
}
@@ -0,0 +1,98 @@
package levels
import (
"math"
mgl "github.com/go-gl/mathgl/mgl32"
)
// LimitedCamera is a camera implementation with ranged controls for zooming and moving.
type LimitedCamera struct {
viewportWidth, viewportHeight float32
minZoom, maxZoom float32
minPos, maxPos float32
requestedZoomLevel float32
viewOffsetX, viewOffsetY float32
viewMatrix mgl.Mat4
}
// NewLimitedCamera returns a new instance of a LimitedCamera.
func NewLimitedCamera(minZoom, maxZoom float32, minPos, maxPos float32) *LimitedCamera {
cam := &LimitedCamera{
viewportWidth: 1.0,
viewportHeight: 1.0,
minZoom: minZoom,
maxZoom: maxZoom,
minPos: minPos,
maxPos: maxPos,
viewMatrix: mgl.Ident4()}
return cam
}
// SetViewportSize notifies the camera how big the view is.
func (cam *LimitedCamera) SetViewportSize(width, height float32) {
if (cam.viewportWidth != width) || (cam.viewportHeight != height) {
cam.viewportWidth, cam.viewportHeight = width, height
cam.updateViewMatrix()
}
}
// ViewMatrix implements the Viewer interface.
func (cam *LimitedCamera) ViewMatrix() *mgl.Mat4 {
return &cam.viewMatrix
}
// MoveBy adjusts the requested view offset by given delta values in world coordinates.
func (cam *LimitedCamera) MoveBy(dx, dy float32) {
cam.MoveTo(cam.viewOffsetX+dx, cam.viewOffsetY+dy)
}
// MoveTo sets the requested view offset to the given world coordinates.
func (cam *LimitedCamera) MoveTo(worldX, worldY float32) {
cam.viewOffsetX = cam.limitValue(worldX, -cam.maxPos, cam.minPos)
cam.viewOffsetY = cam.limitValue(worldY, -cam.maxPos, cam.minPos)
cam.updateViewMatrix()
}
// ZoomAt adjusts the requested zoom level by given delta, centered around given world position.
// Positive values zoom in.
func (cam *LimitedCamera) ZoomAt(levelDelta float32, x, y float32) {
cam.requestedZoomLevel = cam.limitValue(cam.requestedZoomLevel+levelDelta, cam.minZoom, cam.maxZoom)
focusPoint := mgl.Vec4{x, y, 0.0, 1.0}
oldPixel := cam.viewMatrix.Mul4x1(focusPoint)
cam.updateViewMatrix()
newPixel := cam.viewMatrix.Mul4x1(focusPoint)
scaleFactor := cam.scaleFactor()
cam.MoveBy(-(newPixel[0]-oldPixel[0])/scaleFactor, +(newPixel[1]-oldPixel[1])/scaleFactor)
}
func (cam *LimitedCamera) limitValue(value float32, min, max float32) float32 {
result := value
if result < min {
result = min
}
if result > max {
result = max
}
return result
}
func (cam *LimitedCamera) scaleFactor() float32 {
return float32(math.Pow(2.0, float64(cam.requestedZoomLevel)))
}
func (cam *LimitedCamera) updateViewMatrix() {
scaleFactor := cam.scaleFactor()
cam.viewMatrix = mgl.Ident4().
Mul4(mgl.Translate3D(cam.viewportWidth/2.0, cam.viewportHeight/2.0, 0)).
Mul4(mgl.Scale3D(scaleFactor, -scaleFactor, 1.0)).
Mul4(mgl.Translate3D(cam.viewOffsetX, cam.viewOffsetY, 0))
}
+179
View File
@@ -0,0 +1,179 @@
package levels
import (
mgl "github.com/go-gl/mathgl/mgl32"
"github.com/inkyblackness/hacked/editor/render"
"github.com/inkyblackness/hacked/ss1/content/archive/level"
"github.com/inkyblackness/hacked/ui/opengl"
)
var mapColorsVertexShaderSource = `
#version 150
precision mediump float;
in vec4 vertexColor;
in vec2 vertexPosition;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
out vec4 color;
void main(void) {
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(vertexPosition.xy, 0.0, 1.0);
color = vertexColor;
}
`
var mapColorsFragmentShaderSource = `
#version 150
precision mediump float;
in vec4 color;
out vec4 fragColor;
void main(void) {
fragColor = color;
}
`
// ColorQuery returns the color for the specified tile.
type ColorQuery func(level.TilePosition) [4]float32
// MapColors is a renderable for the tile colorings.
type MapColors struct {
context *render.Context
program uint32
vao *opengl.VertexArrayObject
vertexPositionBuffer uint32
vertexPositionAttrib int32
vertexColorBuffer uint32
vertexColorAttrib int32
modelMatrixUniform opengl.Matrix4Uniform
viewMatrixUniform opengl.Matrix4Uniform
projectionMatrixUniform opengl.Matrix4Uniform
lastColorGrid [][][4]float32
lastColorGridColumns int
lastColorGridRows int
}
// NewMapColors returns a new instance of a renderable for tile colorings.
func NewMapColors(context *render.Context) *MapColors {
gl := context.OpenGL
program, programErr := opengl.LinkNewStandardProgram(gl, mapColorsVertexShaderSource, mapColorsFragmentShaderSource)
if programErr != nil {
panic(opengl.NamedShaderError{Name: "MapColorsShader", Nested: programErr})
}
renderable := &MapColors{
context: context,
program: program,
vao: opengl.NewVertexArrayObject(gl, program),
vertexPositionBuffer: gl.GenBuffers(1)[0],
vertexPositionAttrib: gl.GetAttribLocation(program, "vertexPosition"),
vertexColorBuffer: gl.GenBuffers(1)[0],
vertexColorAttrib: gl.GetAttribLocation(program, "vertexColor"),
modelMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "modelMatrix")),
viewMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "viewMatrix")),
projectionMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "projectionMatrix")),
}
{
top := float32(level.FineCoordinatesPerTileSide)
left := float32(0.0)
right := float32(level.FineCoordinatesPerTileSide)
bottom := float32(0.0)
vertices := []float32{
left, bottom,
left, top,
right, top,
right, top,
right, bottom,
left, bottom}
gl.BindBuffer(opengl.ARRAY_BUFFER, renderable.vertexPositionBuffer)
gl.BufferData(opengl.ARRAY_BUFFER, len(vertices)*4, vertices, opengl.STATIC_DRAW)
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
}
renderable.vao.WithSetter(func(gl opengl.OpenGL) {
gl.EnableVertexAttribArray(uint32(renderable.vertexPositionAttrib))
gl.EnableVertexAttribArray(uint32(renderable.vertexColorAttrib))
gl.BindBuffer(opengl.ARRAY_BUFFER, renderable.vertexPositionBuffer)
gl.VertexAttribOffset(uint32(renderable.vertexPositionAttrib), 2, opengl.FLOAT, false, 0, 0)
gl.BindBuffer(opengl.ARRAY_BUFFER, renderable.vertexColorBuffer)
gl.VertexAttribOffset(uint32(renderable.vertexColorAttrib), 4, opengl.FLOAT, false, 0, 0)
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
})
return renderable
}
// Dispose releases any internal resources.
func (renderable *MapColors) Dispose() {
gl := renderable.context.OpenGL
gl.DeleteProgram(renderable.program)
gl.DeleteBuffers([]uint32{renderable.vertexPositionBuffer})
renderable.vao.Dispose()
}
// Render renders the renderable.
func (renderable *MapColors) Render(columnCount, rowCount int, query ColorQuery) {
gl := renderable.context.OpenGL
if (renderable.lastColorGridColumns != columnCount) || (renderable.lastColorGridRows != rowCount) {
renderable.lastColorGrid = make([][][4]float32, rowCount+1)
for y := 0; y < rowCount; y++ {
renderable.lastColorGrid[y] = make([][4]float32, columnCount+1)
}
renderable.lastColorGrid[rowCount] = make([][4]float32, columnCount+1)
renderable.lastColorGridColumns = columnCount
renderable.lastColorGridRows = rowCount
}
for y, row := range renderable.lastColorGrid {
for x := 0; x < columnCount; x++ {
row[x] = query(level.TilePosition{X: byte(x), Y: byte(y)})
}
}
renderable.vao.OnShader(func() {
var colors [24]float32
renderable.viewMatrixUniform.Set(gl, renderable.context.ViewMatrix)
renderable.projectionMatrixUniform.Set(gl, &renderable.context.ProjectionMatrix)
gl.BindBuffer(opengl.ARRAY_BUFFER, renderable.vertexColorBuffer)
for y := 0; y < rowCount; y++ {
for x := 0; x < columnCount; x++ {
colorBottomLeft := renderable.lastColorGrid[y][x]
colorBottomRight := renderable.lastColorGrid[y][x+1]
colorTopLeft := renderable.lastColorGrid[y+1][x]
colorTopRight := renderable.lastColorGrid[y+1][x+1]
modelMatrix := mgl.Ident4().
Mul4(mgl.Translate3D((float32(x))*level.FineCoordinatesPerTileSide, (float32(y))*level.FineCoordinatesPerTileSide, 0.0))
renderable.modelMatrixUniform.Set(gl, &modelMatrix)
copy(colors[0:4], colorBottomLeft[:])
copy(colors[4:8], colorTopLeft[:])
copy(colors[8:12], colorTopRight[:])
copy(colors[12:16], colorTopRight[:])
copy(colors[16:20], colorBottomRight[:])
copy(colors[20:24], colorBottomLeft[:])
gl.BufferData(opengl.ARRAY_BUFFER, len(colors)*4, colors[:], opengl.STATIC_DRAW)
gl.DrawArrays(opengl.TRIANGLES, 0, 6)
}
}
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
})
}
+587
View File
@@ -0,0 +1,587 @@
package levels
import (
"fmt"
mgl "github.com/go-gl/mathgl/mgl32"
"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/level"
"github.com/inkyblackness/hacked/ss1/content/object"
"github.com/inkyblackness/hacked/ss1/edit"
"github.com/inkyblackness/hacked/ss1/resource"
"github.com/inkyblackness/hacked/ss1/world/ids"
"github.com/inkyblackness/hacked/ui/input"
"github.com/inkyblackness/hacked/ui/opengl"
)
const contextMenuName = "MapContextMenu"
// MapDisplay renders a level map.
type MapDisplay struct {
gameObjects *edit.GameObjectsService
levelSelection *edit.LevelSelectionService
editor *edit.LevelEditorService
context render.Context
camera *LimitedCamera
guiScale float32
background *BackgroundGrid
textures *MapTextures
colors *MapColors
mapGrid *MapGrid
highlighter *Highlighter
icons *MapIcons
moveCapture func(pixelX, pixelY float32)
mouseMoved bool
positionPopupPos imgui.Vec2
positionValid bool
position MapPosition
contextMenuRequested bool
selectionReference *level.TilePosition
hoverItems hoverItems
}
// NewMapDisplay returns a new instance.
func NewMapDisplay(gameObjects *edit.GameObjectsService, levelSelection *edit.LevelSelectionService, editor *edit.LevelEditorService,
gl opengl.OpenGL, guiScale float32,
textureQuery TextureQuery) *MapDisplay {
tilesPerMapSide := float32(64)
tileBaseLength := float32(level.FineCoordinatesPerTileSide)
tileBaseHalf := tileBaseLength / 2.0
camLimit := tilesPerMapSide*tileBaseLength - tileBaseHalf
zoomShift := guiScale - 1.0
zoomLevelMin := float32(-5) + zoomShift
zoomLevelMax := float32(1) + zoomShift
display := &MapDisplay{
gameObjects: gameObjects,
levelSelection: levelSelection,
editor: editor,
context: render.Context{
OpenGL: gl,
ProjectionMatrix: mgl.Ident4(),
},
camera: NewLimitedCamera(zoomLevelMin, zoomLevelMax, -tileBaseHalf, camLimit),
guiScale: guiScale,
moveCapture: func(float32, float32) {},
}
display.context.ViewMatrix = display.camera.ViewMatrix()
display.background = NewBackgroundGrid(&display.context)
display.textures = NewMapTextures(&display.context, textureQuery)
display.colors = NewMapColors(&display.context)
display.mapGrid = NewMapGrid(&display.context)
display.highlighter = NewHighlighter(&display.context)
display.icons = NewMapIcons(&display.context)
centerX, centerY := (tilesPerMapSide*tileBaseLength)/-2.0, (tilesPerMapSide*tileBaseLength)/-2.0
display.camera.ZoomAt(-3+zoomShift, centerX, centerY)
display.camera.MoveTo(centerX, centerY)
return display
}
// Render renders the whole map display.
func (display *MapDisplay) Render(
paletteTexture *graphics.PaletteTexture, textureRetriever func(resource.Key) (*graphics.BitmapTexture, error),
textureDisplay TextureDisplay, colorDisplay ColorDisplay) {
lvl := display.editor.Level()
columns, rows, _ := lvl.Size()
display.hoverItems.validate(lvl)
display.background.Render(columns, rows)
if lvl.IsCyberspace() {
display.renderCyberspaceColors(lvl, columns, rows, paletteTexture, colorDisplay)
} else {
display.renderRealWorldTextures(lvl, columns, rows, paletteTexture, textureDisplay)
display.renderRealWorldShadows(lvl, columns, rows, colorDisplay)
}
display.mapGrid.Render(columns, rows, lvl)
display.renderTileSelection()
display.renderObjectBackgrounds(lvl)
display.renderObjectIcons(lvl, paletteTexture, textureRetriever)
display.renderObjectSelection()
display.renderActiveHoverItem()
display.renderPositionOverlay(lvl)
display.renderContextMenu()
}
func (display *MapDisplay) renderCyberspaceColors(lvl *level.Level, columns int, rows int, paletteTexture *graphics.PaletteTexture, colorDisplay ColorDisplay) {
if paletteTexture == nil {
return
}
var colorQuery ColorQuery
palette := paletteTexture.Palette()
if colorDisplay == ColorDisplayFloor {
colorQuery = display.colorQueryFor(lvl, func(tile *level.TileMapEntry) [4]float32 {
rgb := palette[tile.TextureInfo.FloorPaletteIndex()]
return [4]float32{float32(rgb.Red) / 255, float32(rgb.Green) / 255, float32(rgb.Blue) / 255, 0.8}
})
} else if colorDisplay == ColorDisplayCeiling {
colorQuery = display.colorQueryFor(lvl, func(tile *level.TileMapEntry) [4]float32 {
rgb := palette[tile.TextureInfo.CeilingPaletteIndex()]
return [4]float32{float32(rgb.Red) / 255, float32(rgb.Green) / 255, float32(rgb.Blue) / 255, 0.8}
})
}
if colorQuery != nil {
display.colors.Render(columns, rows, colorQuery)
}
}
func (display *MapDisplay) renderRealWorldTextures(lvl *level.Level, columns int, rows int,
paletteTexture *graphics.PaletteTexture, textureDisplay TextureDisplay) {
if paletteTexture == nil {
return
}
display.textures.Render(columns, rows, func(pos level.TilePosition) (level.TileType, level.TextureIndex, int) {
tile := lvl.Tile(pos)
if tile == nil {
return level.TileTypeSolid, 0, 0
}
atlasIndex, textureRotations := textureDisplay.Func()(tile)
atlas := lvl.TextureAtlas()
textureIndex := level.TextureIndex(-1)
if (int(atlasIndex) >= 0) && (int(atlasIndex) < len(atlas)) {
textureIndex = atlas[atlasIndex]
}
return tile.Type, textureIndex, textureRotations
}, paletteTexture)
}
func (display *MapDisplay) renderRealWorldShadows(lvl *level.Level, columns int, rows int, colorDisplay ColorDisplay) {
var colorQuery ColorQuery
if colorDisplay == ColorDisplayFloor {
colorQuery = display.colorQueryFor(lvl, func(tile *level.TileMapEntry) [4]float32 {
return [4]float32{0.0, 0.0, 0.0, float32(tile.Flags.ForRealWorld().FloorShadow()) / 15.0}
})
} else if colorDisplay == ColorDisplayCeiling {
colorQuery = display.colorQueryFor(lvl, func(tile *level.TileMapEntry) [4]float32 {
return [4]float32{0.0, 0.0, 0.0, float32(tile.Flags.ForRealWorld().CeilingShadow()) / 15.0}
})
}
if colorQuery != nil {
display.colors.Render(columns, rows, colorQuery)
}
}
func (display *MapDisplay) renderTileSelection() {
selectedTiles := display.levelSelection.CurrentSelectedTiles()
tileMapPositions := make([]MapPosition, 0, len(selectedTiles))
for _, pos := range selectedTiles {
tileMapPositions = append(tileMapPositions, MapPosition{
X: level.CoordinateAt(pos.X, level.FineCoordinatesPerTileSide/2),
Y: level.CoordinateAt(pos.Y, level.FineCoordinatesPerTileSide/2),
})
}
display.highlighter.Render(tileMapPositions, level.FineCoordinatesPerTileSide, [4]float32{0.0, 0.8, 0.2, 0.5})
}
func (display *MapDisplay) renderObjectBackgrounds(lvl *level.Level) {
var objects []MapPosition
lvl.ForEachObject(func(id level.ObjectID, entry level.ObjectMainEntry) {
objects = append(objects, MapPosition{X: entry.X, Y: entry.Y})
})
display.highlighter.Render(objects, level.FineCoordinatesPerTileSide/4, [4]float32{1.0, 1.0, 1.0, 0.3})
}
func (display *MapDisplay) renderObjectIcons(lvl *level.Level, paletteTexture *graphics.PaletteTexture,
textureRetriever func(resource.Key) (*graphics.BitmapTexture, error)) {
if paletteTexture == nil {
return
}
tripleInfo := display.gameObjects.BitmapInfo()
var icons []iconData
var highlightIcon iconData
var highlightID level.ObjectID
if display.hoverItems.activeItem != nil {
objectItem, isObject := display.hoverItems.activeItem.(objectHoverItem)
if isObject {
highlightID = objectItem.id
}
}
lvl.ForEachObject(func(id level.ObjectID, entry level.ObjectMainEntry) {
triple := entry.Triple()
info, cached := tripleInfo[triple]
if !cached {
return
}
key := resource.KeyOf(ids.ObjectBitmaps, resource.LangAny, info.Start+info.IconRecommendation)
texture, err := textureRetriever(key)
if err == nil {
icon := iconData{pos: MapPosition{X: entry.X, Y: entry.Y}, texture: texture}
if highlightID == id {
highlightIcon = icon
} else {
icons = append(icons, icon)
}
}
})
if (highlightID != 0) && (highlightIcon.texture != nil) {
icons = append(icons, highlightIcon)
}
display.icons.Render(paletteTexture, level.FineCoordinatesPerTileSide/4, icons)
}
func (display *MapDisplay) renderObjectSelection() {
selectedObjects := display.editor.Objects()
selectedObjectHighlights := make([]MapPosition, 0, len(selectedObjects))
for _, obj := range selectedObjects {
objPos := MapPosition{X: obj.X, Y: obj.Y}
selectedObjectHighlights = append(selectedObjectHighlights, objPos)
}
display.highlighter.Render(selectedObjectHighlights, level.FineCoordinatesPerTileSide/4, [4]float32{0.0, 0.8, 0.2, 0.5})
}
func (display *MapDisplay) renderActiveHoverItem() {
if display.hoverItems.activeItem == nil {
return
}
display.highlighter.Render(
[]MapPosition{display.hoverItems.activeItem.Pos()},
display.hoverItems.activeItem.Size(),
[4]float32{0.0, 0.2, 0.8, 0.3})
}
func (display *MapDisplay) colorQueryFor(lvl *level.Level, tileToColor func(*level.TileMapEntry) [4]float32) func(level.TilePosition) [4]float32 {
return func(pos level.TilePosition) [4]float32 {
tile := lvl.Tile(pos)
if tile == nil {
return [4]float32{}
}
return tileToColor(tile)
}
}
func (display *MapDisplay) renderPositionOverlay(lvl *level.Level) {
imgui.SetNextWindowPosV(display.positionPopupPos, imgui.ConditionAlways, imgui.Vec2{X: 1.0, Y: 1.0})
imgui.SetNextWindowSize(imgui.Vec2{X: 140 * display.guiScale, Y: 0})
imgui.SetNextWindowBgAlpha(0.3)
if imgui.BeginV("Position", nil, imgui.WindowFlagsNoMove|imgui.WindowFlagsNoTitleBar|imgui.WindowFlagsNoResize|imgui.WindowFlagsAlwaysAutoResize|
imgui.WindowFlagsNoSavedSettings|imgui.WindowFlagsNoFocusOnAppearing|imgui.WindowFlagsNoNav) {
typeString := "---"
hasPos := false
var pos MapPosition
hasFloor := false
var floorRaw int
floorString := hintUnknown
hasCeiling := false
var ceilingRaw int
ceilingString := hintUnknown
if display.hoverItems.activeItem != nil {
pos = display.hoverItems.activeItem.Pos()
hasPos = true
if _, isTileItem := display.hoverItems.activeItem.(tileHoverItem); isTileItem {
pos = display.position // use raw cursor position for this display
typeString = "Tile"
tile := lvl.Tile(pos.Tile())
if (tile != nil) && (tile.Type != level.TileTypeSolid) {
_, _, heightShift := lvl.Size()
floorHeight := tile.Floor.AbsoluteHeight()
floorHeightInTiles, err := heightShift.ValueFromTileHeight(floorHeight)
if err == nil {
floorString = fmt.Sprintf("%2.3f", floorHeightInTiles)
}
floorRaw = int(floorHeight)
hasFloor = true
ceilingHeight := tile.Ceiling.AbsoluteHeight()
ceilingHeightInTiles, err := heightShift.ValueFromTileHeight(ceilingHeight)
if err == nil {
ceilingString = fmt.Sprintf("%2.3f", ceilingHeightInTiles)
}
ceilingRaw = int(ceilingHeight)
hasCeiling = true
}
} else if objectItem, isObjectItem := display.hoverItems.activeItem.(objectHoverItem); isObjectItem {
_, _, heightShift := lvl.Size()
obj := lvl.Object(objectItem.id)
if obj != nil {
typeString = fmt.Sprintf("%3d = %v", objectItem.id, obj.Triple())
heightInTiles, err := heightShift.ValueFromObjectHeight(obj.Z)
if err == nil {
floorString = fmt.Sprintf("%2.3f", heightInTiles)
}
floorRaw = int(obj.Z)
hasFloor = true
}
}
}
imgui.Text("T: " + typeString)
if hasPos {
imgui.Text(fmt.Sprintf("X: T %2d F %3d", pos.X.Tile(), pos.X.Fine()))
imgui.Text(fmt.Sprintf("Y: T %2d F %3d", pos.Y.Tile(), pos.Y.Fine()))
} else {
imgui.Text("X: T -- F ---")
imgui.Text("Y: T -- F ---")
}
if hasFloor {
imgui.Text(fmt.Sprintf("F: %3d = %s", floorRaw, floorString))
} else {
imgui.Text("F: -- = --.---")
}
if hasCeiling {
imgui.Text(fmt.Sprintf("C: %3d = %s", ceilingRaw, ceilingString))
} else {
imgui.Text("C: -- = --.---")
}
imgui.End()
}
}
func (display *MapDisplay) renderContextMenu() {
if display.contextMenuRequested {
imgui.OpenPopup(contextMenuName)
display.contextMenuRequested = false
}
if imgui.BeginPopupV(contextMenuName, imgui.PopupFlagsMouseButtonRight) {
readOnly := display.editor.IsReadOnly()
if imgui.BeginMenu("New...") {
implicitTriple := display.editor.NewObjectTriple()
canCreateImplicitClass := display.canCreateObjectOf(implicitTriple.Class)
if imgui.MenuItemV("Object", "Ctrl+2ndClick", false, canCreateImplicitClass) {
display.requestCreateNewObject(false, implicitTriple)
}
if imgui.MenuItemV("Object (at grid)", "Ctrl+Shift+2ndClick", false, canCreateImplicitClass) {
display.requestCreateNewObject(true, implicitTriple)
}
imgui.EndMenu()
}
imgui.Separator()
if imgui.MenuItemV("Delete Objects", "", false, !readOnly && display.editor.HasSelectedObjects()) {
_ = display.editor.DeleteObjects()
}
if imgui.MenuItemV("Clear Tiles", "", false, !readOnly && display.editor.HasSelectedTiles()) {
_ = display.editor.ClearTiles()
}
imgui.EndPopup()
}
}
// WindowResized must be called to notify of a change in window geometry.
func (display *MapDisplay) WindowResized(width int, height int) {
display.context.ProjectionMatrix = mgl.Ortho2D(0.0, float32(width), float32(height), 0.0)
display.camera.SetViewportSize(float32(width), float32(height))
display.positionPopupPos.X = float32(width) - 10.0
display.positionPopupPos.Y = float32(height) - 10.0
}
func (display *MapDisplay) unprojectPixel(pixelX, pixelY float32) (x, y float32) {
pixelVec := mgl.Vec4{pixelX, pixelY, 0.0, 1.0}
invertedView := display.camera.ViewMatrix().Inv()
result := invertedView.Mul4x1(pixelVec)
return result[0], result[1]
}
// MouseButtonDown must be called when a button was pressed.
func (display *MapDisplay) MouseButtonDown(mouseX, mouseY float32, button uint32) {
display.updateMouseWorldPosition(mouseX, mouseY)
if button == input.MousePrimary {
lastPixelX, lastPixelY := mouseX, mouseY
display.mouseMoved = false
display.moveCapture = func(pixelX, pixelY float32) {
lastWorldX, lastWorldY := display.unprojectPixel(lastPixelX, lastPixelY)
worldX, worldY := display.unprojectPixel(pixelX, pixelY)
display.camera.MoveBy(worldX-lastWorldX, worldY-lastWorldY)
lastPixelX, lastPixelY = pixelX, pixelY
display.mouseMoved = true
}
}
}
// MouseButtonUp must be called when a button was released.
func (display *MapDisplay) MouseButtonUp(mouseX, mouseY float32, button uint32, modifier input.Modifier) {
display.updateMouseWorldPosition(mouseX, mouseY)
if button == input.MousePrimary {
display.moveCapture = func(float32, float32) {}
if !display.mouseMoved && display.positionValid {
switch {
case modifier.Has(input.ModControl):
display.toggleSelectionAtActiveHoverItem()
case modifier.Has(input.ModShift) && (display.selectionReference != nil):
firstPos := *display.selectionReference
fromX := int(firstPos.X)
fromY := int(firstPos.Y)
toX := int(display.position.X.Tile())
toY := int(display.position.Y.Tile())
xIncrement := 1
yIncrement := 1
if fromX > toX {
xIncrement = -1
}
if fromY > toY {
yIncrement = -1
}
toX += xIncrement
toY += yIncrement
var newList []level.TilePosition
for y := fromY; y != toY; y += yIncrement {
for x := fromX; x != toX; x += xIncrement {
newList = append(newList, level.TilePosition{X: byte(x), Y: byte(y)})
}
}
display.levelSelection.SetCurrentSelectedTiles(newList)
display.levelSelection.SetCurrentSelectedObjects(display.objectsInTiles(newList))
default:
display.setSelectionByActiveHoverItem()
}
}
} else if button == input.MouseSecondary {
if modifier.IsClear() {
display.contextMenuRequested = true
} else if modifier.Has(input.ModControl) {
display.requestCreateNewObject(modifier.Has(input.ModShift), display.editor.NewObjectTriple())
}
}
}
func (display *MapDisplay) canCreateObjectOf(class object.Class) bool {
if display.editor.IsReadOnly() {
return false
}
lvl := display.editor.Level()
return lvl.HasRoomForObjectOf(class) && display.positionValid
}
func (display *MapDisplay) requestCreateNewObject(snapToGrid bool, triple object.Triple) {
if !display.canCreateObjectOf(triple.Class) {
return
}
pos := display.position
if snapToGrid {
toGrid := func(value byte) byte {
cornerSnapDistance := level.FineCoordinatesPerTileSide / 4
switch {
case value < byte(cornerSnapDistance):
return 0x00
case value >= byte(level.FineCoordinatesPerTileSide-cornerSnapDistance):
return level.FineCoordinatesPerTileSide - 1
default:
return level.FineCoordinatesPerTileSide / 2
}
}
pos.X = level.CoordinateAt(pos.X.Tile(), toGrid(pos.X.Fine()))
pos.Y = level.CoordinateAt(pos.Y.Tile(), toGrid(pos.Y.Fine()))
}
err := display.editor.CreateNewObject(triple, func(obj *level.ObjectMainEntry) {
obj.X = pos.X
obj.Y = pos.Y
})
if err != nil {
panic(err)
}
}
func (display *MapDisplay) setSelectionByActiveHoverItem() {
var tiles []level.TilePosition
var objects []level.ObjectID
if display.hoverItems.activeItem != nil {
if tileItem, isTile := display.hoverItems.activeItem.(tileHoverItem); isTile {
tiles = append(tiles, tileItem.pos.Tile())
} else if objectItem, isObject := display.hoverItems.activeItem.(objectHoverItem); isObject {
objects = append(objects, objectItem.id)
}
}
display.levelSelection.SetCurrentSelectedTiles(tiles)
if len(tiles) > 0 {
display.selectionReference = &tiles[0]
display.levelSelection.SetCurrentSelectedObjects(display.objectsInTiles(tiles))
} else {
display.levelSelection.SetCurrentSelectedObjects(objects)
}
}
func (display *MapDisplay) toggleSelectionAtActiveHoverItem() {
if display.hoverItems.activeItem != nil {
if tileItem, isTile := display.hoverItems.activeItem.(tileHoverItem); isTile {
wasSelected := display.levelSelection.IsTileSelected(tileItem.pos.Tile())
tiles := []level.TilePosition{tileItem.pos.Tile()}
if wasSelected {
display.levelSelection.RemoveCurrentSelectedTiles(tiles)
display.levelSelection.RemoveCurrentSelectedObjects(display.objectsInTiles(tiles))
} else {
display.levelSelection.AddCurrentSelectedTiles(tiles)
display.levelSelection.AddCurrentSelectedObjects(display.objectsInTiles(tiles))
}
} else if objectItem, isObject := display.hoverItems.activeItem.(objectHoverItem); isObject {
display.levelSelection.ToggleObjectSelection([]level.ObjectID{objectItem.id})
}
}
}
func (display *MapDisplay) objectsInTiles(tiles []level.TilePosition) []level.ObjectID {
tilesContain := func(pos level.TilePosition) bool {
for _, entry := range tiles {
if entry == pos {
return true
}
}
return false
}
var objects []level.ObjectID
lvl := display.editor.Level()
lvl.ForEachObject(func(id level.ObjectID, entry level.ObjectMainEntry) {
if tilesContain(entry.TilePosition()) {
objects = append(objects, id)
}
})
return objects
}
// MouseMoved must be called for a mouse move.
func (display *MapDisplay) MouseMoved(mouseX, mouseY float32) {
display.updateMouseWorldPosition(mouseX, mouseY)
if display.positionValid {
display.hoverItems.find(display.editor.Level(), display.position)
} else {
display.hoverItems.reset()
}
display.moveCapture(mouseX, mouseY)
}
// MouseScrolled must be called for a mouse scroll.
func (display *MapDisplay) MouseScrolled(mouseX, mouseY float32, deltaX, deltaY float32, modifier input.Modifier) {
if modifier.Has(input.ModControl) {
display.hoverItems.scroll(deltaY > 0)
} else {
worldX, worldY := display.unprojectPixel(mouseX, mouseY)
if deltaY < 0 {
display.camera.ZoomAt(-0.5, worldX, worldY)
}
if deltaY > 0 {
display.camera.ZoomAt(0.5, worldX, worldY)
}
}
}
func (display *MapDisplay) updateMouseWorldPosition(mouseX, mouseY float32) {
worldX, worldY := display.unprojectPixel(mouseX, mouseY)
lvl := display.editor.Level()
width, height, _ := lvl.Size()
worldWidth := float32(width) * level.FineCoordinatesPerTileSide
worldHeight := float32(height) * level.FineCoordinatesPerTileSide
display.positionValid = (worldX >= 0.0) && (worldX < worldWidth) && (worldY >= 0.0) && (worldY < worldHeight)
if display.positionValid {
display.position = MapPosition{X: level.Coordinate(worldX + 0.5), Y: level.Coordinate(worldY + 0.5)}
} else {
display.position = MapPosition{}
}
}
+312
View File
@@ -0,0 +1,312 @@
package levels
import (
mgl "github.com/go-gl/mathgl/mgl32"
"github.com/inkyblackness/hacked/editor/render"
"github.com/inkyblackness/hacked/ss1/content/archive/level"
"github.com/inkyblackness/hacked/ui/opengl"
)
var mapTileGridVertexShaderSource = `
#version 150
precision mediump float;
in vec4 vertexPosition;
out float colorAlpha;
uniform mat4 alphaMatrix;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
void main(void) {
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(vertexPosition.xy, 0.0, 1.0);
colorAlpha = alphaMatrix[int(vertexPosition.z)][int(vertexPosition.w)];
}
`
var mapTileGridFragmentShaderSource = `
#version 150
precision mediump float;
uniform vec4 color;
in float colorAlpha;
out vec4 fragColor;
void main(void) {
fragColor = vec4(color.rgb, color.a*colorAlpha);
}
`
// TileMapper returns basic information to draw a 2D map.
type TileMapper interface {
MapGridInfo(pos level.TilePosition) (level.TileType, level.TileSlopeControl, level.WallHeights)
}
// MapGrid renders the grid of the map, based on calculated wall heights.
type MapGrid struct {
context *render.Context
program uint32
vao *opengl.VertexArrayObject
vertexPositionBuffer uint32
tickVertexPositionBuffer uint32
vertexPositionAttrib int32
colorUniform opengl.Vector4Uniform
/*
The alpha matrix is set up to specify the alpha value used for the wall sections.
There are four cardinal walls with three sections each, plus two diagonal walls (single section), as well
as the slope-ticks that re-use the shader.
The matrix is meant to be used like this:
N N N D1
E E E D2
S S S -
W W W T
D1 and D2 being the two diagonals, and T specifying the cell for the tick-marks.
The vertex buffers are an array of v4 entries, with z/w specifying the row and column index to take.
*/
alphaMatrixUniform opengl.Matrix4Uniform
modelMatrixUniform opengl.Matrix4Uniform
viewMatrixUniform opengl.Matrix4Uniform
projectionMatrixUniform opengl.Matrix4Uniform
slopeTicksByType [][]int
floorTickStarts []int32
ceilingTickStarts []int32
}
// NewMapGrid returns a new instance.
func NewMapGrid(context *render.Context) *MapGrid {
gl := context.OpenGL
program, programErr := opengl.LinkNewStandardProgram(gl, mapTileGridVertexShaderSource, mapTileGridFragmentShaderSource)
if programErr != nil {
panic(opengl.NamedShaderError{Name: "MapGrid", Nested: programErr})
}
grid := &MapGrid{
context: context,
program: program,
vao: opengl.NewVertexArrayObject(gl, program),
vertexPositionBuffer: gl.GenBuffers(1)[0],
tickVertexPositionBuffer: gl.GenBuffers(1)[0],
vertexPositionAttrib: gl.GetAttribLocation(program, "vertexPosition"),
colorUniform: opengl.Vector4Uniform(gl.GetUniformLocation(program, "color")),
alphaMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "alphaMatrix")),
modelMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "modelMatrix")),
viewMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "viewMatrix")),
projectionMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "projectionMatrix")),
}
{
tickVertices := grid.calculateTickVertices()
gl.BindBuffer(opengl.ARRAY_BUFFER, grid.tickVertexPositionBuffer)
gl.BufferData(opengl.ARRAY_BUFFER, len(tickVertices)*4, tickVertices, opengl.STATIC_DRAW)
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
}
{
const parts = 3
left := float32(-0.5)
right := float32(0.5)
top := float32(0.5)
bottom := float32(-0.5)
finePerPart := float32(1.0 / parts)
var wallVertices []float32
for i := 0; i < parts; i++ {
wallVertices = append(wallVertices, left+(finePerPart*float32(i)), top, 0.0, float32(i))
wallVertices = append(wallVertices, left+(finePerPart*float32(i+1)), top, 0.0, float32(i))
}
for i := 0; i < parts; i++ {
wallVertices = append(wallVertices, right, top-(finePerPart*float32(i)), 1.0, float32(i))
wallVertices = append(wallVertices, right, top-(finePerPart*float32(i+1)), 1.0, float32(i))
}
for i := 0; i < parts; i++ {
wallVertices = append(wallVertices, right-(finePerPart*float32(i)), bottom, 2.0, float32(i))
wallVertices = append(wallVertices, right-(finePerPart*float32(i+1)), bottom, 2.0, float32(i))
}
for i := 0; i < parts; i++ {
wallVertices = append(wallVertices, left, bottom+(finePerPart*float32(i)), 3.0, float32(i))
wallVertices = append(wallVertices, left, bottom+(finePerPart*float32(i+1)), 3.0, float32(i))
}
wallVertices = append(wallVertices, left, top, 0.0, 3.0)
wallVertices = append(wallVertices, right, bottom, 0.0, 3.0)
wallVertices = append(wallVertices, right, top, 1.0, 3.0)
wallVertices = append(wallVertices, left, bottom, 1.0, 3.0)
gl.BindBuffer(opengl.ARRAY_BUFFER, grid.vertexPositionBuffer)
gl.BufferData(opengl.ARRAY_BUFFER, len(wallVertices)*4, wallVertices, opengl.STATIC_DRAW)
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
}
{
grid.slopeTicksByType = [][]int{
nil,
nil,
nil,
nil,
nil,
nil,
{0, 1},
{1, 2},
{2, 3},
{3, 0},
{3, 0, 1},
{0, 1, 2},
{1, 2, 3},
{2, 3, 0},
{2},
{3},
{0},
{1},
}
grid.floorTickStarts = []int32{0, 6, 12, 18}
grid.ceilingTickStarts = []int32{3, 9, 15, 21}
}
return grid
}
// Dispose releases any internal resources.
func (grid *MapGrid) Dispose() {
gl := grid.context.OpenGL
gl.DeleteProgram(grid.program)
gl.DeleteBuffers([]uint32{grid.vertexPositionBuffer, grid.tickVertexPositionBuffer})
grid.vao.Dispose()
}
func (grid *MapGrid) calculateTickVertices() []float32 {
dotHalf := float32(0.05)
dotBase := float32(0.5) - (dotHalf * 2.0)
top := dotBase + dotHalf
topEnd := dotBase - dotHalf
left := -dotBase - dotHalf
leftEnd := -dotBase + dotHalf
right := top
rightEnd := topEnd
bottom := left
bottomEnd := leftEnd
return []float32{
left, top, 3.0, 3.0, leftEnd, top, 3.0, 3.0, left, topEnd, 3.0, 3.0,
leftEnd, top, 3.0, 3.0, leftEnd, topEnd, 3.0, 3.0, left, topEnd, 3.0, 3.0,
right, top, 3.0, 3.0, right, topEnd, 3.0, 3.0, rightEnd, top, 3.0, 3.0,
rightEnd, top, 3.0, 3.0, right, topEnd, 3.0, 3.0, rightEnd, topEnd, 3.0, 3.0,
right, bottom, 3.0, 3.0, rightEnd, bottom, 3.0, 3.0, right, bottomEnd, 3.0, 3.0,
right, bottomEnd, 3.0, 3.0, rightEnd, bottom, 3.0, 3.0, rightEnd, bottomEnd, 3.0, 3.0,
left, bottomEnd, 3.0, 3.0, leftEnd, bottom, 3.0, 3.0, left, bottom, 3.0, 3.0,
left, bottomEnd, 3.0, 3.0, leftEnd, bottomEnd, 3.0, 3.0, leftEnd, bottom, 3.0, 3.0,
}
}
// Render renders the grid.
func (grid *MapGrid) Render(columns, rows int, mapper TileMapper) {
gl := grid.context.OpenGL
grid.vao.OnShader(func() {
modelMatrix := mgl.Ident4()
lineColor := [4]float32{0.0, 0.8, 0.0, 1.0}
grid.viewMatrixUniform.Set(gl, grid.context.ViewMatrix)
grid.projectionMatrixUniform.Set(gl, &grid.context.ProjectionMatrix)
heightFactor := [32*2 + 1]float32{
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0,
0.1, 0.2, 0.3, 0.3, 0.4, 0.4, 0.5, 0.7,
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
}
for y := 0; y < rows; y++ {
for x := 0; x < columns; x++ {
tileType, slopeControl, wallHeights := mapper.MapGridInfo(level.TilePosition{X: byte(x), Y: byte(y)})
if tileType == level.TileTypeSolid {
continue
}
modelMatrix = mgl.Ident4().
Mul4(mgl.Translate3D((float32(x)+0.5)*level.FineCoordinatesPerTileSide, (float32(y)+0.5)*level.FineCoordinatesPerTileSide, 0.0)).
Mul4(mgl.Scale3D(level.FineCoordinatesPerTileSide, level.FineCoordinatesPerTileSide, 1.0))
grid.modelMatrixUniform.Set(gl, &modelMatrix)
var alphaMatrix mgl.Mat4
alphaMatrix[15] = 1.0 // for tick marks
gl.EnableVertexAttribArray(uint32(grid.vertexPositionAttrib))
gl.BindBuffer(opengl.ARRAY_BUFFER, grid.vertexPositionBuffer)
gl.VertexAttribOffset(uint32(grid.vertexPositionAttrib), 4, opengl.FLOAT, false, 0, 0)
alphaMatrix[0+0] = heightFactor[int(wallHeights.North[0])+32]
alphaMatrix[0+1] = heightFactor[int(wallHeights.North[1])+32]
alphaMatrix[0+2] = heightFactor[int(wallHeights.North[2])+32]
alphaMatrix[4+0] = heightFactor[int(wallHeights.East[0])+32]
alphaMatrix[4+1] = heightFactor[int(wallHeights.East[1])+32]
alphaMatrix[4+2] = heightFactor[int(wallHeights.East[2])+32]
alphaMatrix[8+0] = heightFactor[int(wallHeights.South[0])+32]
alphaMatrix[8+1] = heightFactor[int(wallHeights.South[1])+32]
alphaMatrix[8+2] = heightFactor[int(wallHeights.South[2])+32]
alphaMatrix[12+0] = heightFactor[int(wallHeights.West[0])+32]
alphaMatrix[12+1] = heightFactor[int(wallHeights.West[1])+32]
alphaMatrix[12+2] = heightFactor[int(wallHeights.West[2])+32]
if tileType == level.TileTypeDiagonalOpenNorthEast || tileType == level.TileTypeDiagonalOpenSouthWest {
alphaMatrix[0+3] = 1.0
}
if tileType == level.TileTypeDiagonalOpenNorthWest || tileType == level.TileTypeDiagonalOpenSouthEast {
alphaMatrix[4+3] = 1.0
}
grid.colorUniform.Set(gl, &lineColor)
grid.alphaMatrixUniform.Set(gl, &alphaMatrix)
gl.DrawArrays(opengl.LINES, 0, 28)
grid.renderSlopeTicks(gl, tileType, slopeControl)
}
}
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
})
}
func (grid *MapGrid) renderSlopeTicks(gl opengl.OpenGL, tileType level.TileType, slopeControl level.TileSlopeControl) {
var floorTicks []int
var ceilingTicks []int
if slopeControl != level.TileSlopeControlFloorFlat {
floorTicks = grid.slopeTicksByType[tileType]
}
if slopeControl == level.TileSlopeControlCeilingMirrored {
ceilingTicks = grid.slopeTicksByType[tileType]
} else if slopeControl != level.TileSlopeControlCeilingFlat {
ceilingTicks = grid.slopeTicksByType[tileType.Info().SlopeInvertedType]
}
gl.EnableVertexAttribArray(uint32(grid.vertexPositionAttrib))
gl.BindBuffer(opengl.ARRAY_BUFFER, grid.tickVertexPositionBuffer)
gl.VertexAttribOffset(uint32(grid.vertexPositionAttrib), 4, opengl.FLOAT, false, 0, 0)
floorTickColor := [4]float32{0.0, 0.8, 0.0, 0.9}
grid.colorUniform.Set(gl, &floorTickColor)
for _, index := range floorTicks {
gl.DrawArrays(opengl.TRIANGLES, grid.floorTickStarts[index], 3)
}
ceilingTickColor := [4]float32{0.8, 0.0, 0.0, 0.9}
grid.colorUniform.Set(gl, &ceilingTickColor)
for _, index := range ceilingTicks {
gl.DrawArrays(opengl.TRIANGLES, grid.ceilingTickStarts[index], 3)
}
}
+198
View File
@@ -0,0 +1,198 @@
package levels
import (
mgl "github.com/go-gl/mathgl/mgl32"
"github.com/inkyblackness/hacked/editor/graphics"
"github.com/inkyblackness/hacked/editor/render"
"github.com/inkyblackness/hacked/ui/opengl"
)
var mapIconsVertexShaderSource = `
#version 150
precision mediump float;
in vec3 vertexPosition;
in vec3 uvPosition;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
uniform vec4 uvLimit;
out vec2 uv;
void main(void) {
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(vertexPosition, 1.0);
uv = vec2(uvPosition.x * uvLimit.x, uvPosition.y * uvLimit.y);
}
`
var mapIconsFragmentShaderSource = `
#version 150
precision mediump float;
uniform sampler2D palette;
uniform sampler2D bitmap;
in vec2 uv;
out vec4 fragColor;
void main(void) {
vec4 pixel = texture(bitmap, uv);
if (pixel.r > 0.0) {
fragColor = texture(palette, vec2(pixel.r, 0.5));
} else {
discard;
}
}
`
// MapIcons is a renderable for simple bitmaps.
type MapIcons struct {
context *render.Context
program uint32
vao *opengl.VertexArrayObject
vertexPositionBuffer uint32
vertexPositionAttrib int32
uvPositionBuffer uint32
uvPositionAttrib int32
modelMatrixUniform opengl.Matrix4Uniform
viewMatrixUniform opengl.Matrix4Uniform
projectionMatrixUniform opengl.Matrix4Uniform
uvLimitUniform opengl.Vector4Uniform
paletteUniform int32
bitmapUniform int32
}
type iconData struct {
pos MapPosition
texture *graphics.BitmapTexture
}
// NewMapIcons returns a new instance.
func NewMapIcons(context *render.Context) *MapIcons {
gl := context.OpenGL
program, programErr := opengl.LinkNewStandardProgram(gl, mapIconsVertexShaderSource, mapIconsFragmentShaderSource)
if programErr != nil {
panic(opengl.NamedShaderError{Name: "MapIconsShader", Nested: programErr})
}
renderable := &MapIcons{
context: context,
program: program,
vao: opengl.NewVertexArrayObject(gl, program),
vertexPositionBuffer: gl.GenBuffers(1)[0],
vertexPositionAttrib: gl.GetAttribLocation(program, "vertexPosition"),
uvPositionBuffer: gl.GenBuffers(1)[0],
uvPositionAttrib: gl.GetAttribLocation(program, "uvPosition"),
modelMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "modelMatrix")),
viewMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "viewMatrix")),
projectionMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "projectionMatrix")),
uvLimitUniform: opengl.Vector4Uniform(gl.GetUniformLocation(program, "uvLimit")),
paletteUniform: gl.GetUniformLocation(program, "palette"),
bitmapUniform: gl.GetUniformLocation(program, "bitmap")}
{
half := float32(0.5)
var vertices = []float32{
-half, half, 0.0,
half, half, 0.0,
half, -half, 0.0,
half, -half, 0.0,
-half, -half, 0.0,
-half, half, 0.0,
}
gl.BindBuffer(opengl.ARRAY_BUFFER, renderable.vertexPositionBuffer)
gl.BufferData(opengl.ARRAY_BUFFER, len(vertices)*4, vertices, opengl.STATIC_DRAW)
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
}
{
var uv = []float32{
0.0, 0.0, 0.0,
1.0, 0.0, 0.0,
1.0, 1.0, 0.0,
1.0, 1.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 0.0,
}
gl.BindBuffer(opengl.ARRAY_BUFFER, renderable.uvPositionBuffer)
gl.BufferData(opengl.ARRAY_BUFFER, len(uv)*4, uv, opengl.STATIC_DRAW)
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
}
renderable.vao.WithSetter(func(gl opengl.OpenGL) {
gl.EnableVertexAttribArray(uint32(renderable.vertexPositionAttrib))
gl.BindBuffer(opengl.ARRAY_BUFFER, renderable.vertexPositionBuffer)
gl.VertexAttribOffset(uint32(renderable.vertexPositionAttrib), 3, opengl.FLOAT, false, 0, 0)
gl.EnableVertexAttribArray(uint32(renderable.uvPositionAttrib))
gl.BindBuffer(opengl.ARRAY_BUFFER, renderable.uvPositionBuffer)
gl.VertexAttribOffset(uint32(renderable.uvPositionAttrib), 3, opengl.FLOAT, false, 0, 0)
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
})
return renderable
}
// Render renders the icons with their center at given position.
func (renderable *MapIcons) Render(paletteTexture *graphics.PaletteTexture, iconSize float32, icons []iconData) {
gl := renderable.context.OpenGL
renderable.vao.OnShader(func() {
renderable.viewMatrixUniform.Set(gl, renderable.context.ViewMatrix)
renderable.projectionMatrixUniform.Set(gl, &renderable.context.ProjectionMatrix)
textureUnit := int32(0)
gl.ActiveTexture(opengl.TEXTURE0 + uint32(textureUnit))
gl.BindTexture(opengl.TEXTURE_2D, paletteTexture.Handle())
gl.Uniform1i(renderable.paletteUniform, textureUnit)
textureUnit = 1
gl.ActiveTexture(opengl.TEXTURE0 + uint32(textureUnit))
gl.Uniform1i(renderable.bitmapUniform, textureUnit)
for _, icon := range icons {
x, y := float32(icon.pos.X), float32(icon.pos.Y)
u, v := icon.texture.UV()
width, height := renderable.limitedSize(iconSize, icon.texture)
modelMatrix := mgl.Ident4().
Mul4(mgl.Translate3D(x, y, 0.0)).
Mul4(mgl.Scale3D(width, height, 1.0))
renderable.modelMatrixUniform.Set(gl, &modelMatrix)
uvLimit := [4]float32{u, v, 0, 0}
renderable.uvLimitUniform.Set(gl, &uvLimit)
gl.BindTexture(opengl.TEXTURE_2D, icon.texture.Handle())
gl.DrawArrays(opengl.TRIANGLES, 0, 6)
}
gl.BindTexture(opengl.TEXTURE_2D, 0)
})
}
func (renderable *MapIcons) limitedSize(iconSize float32, texture *graphics.BitmapTexture) (width, height float32) {
width, height = texture.Size()
referenceSize := float32(16.0)
larger := width
if larger < height {
larger = height
}
if larger > referenceSize {
ratio := referenceSize / larger
width *= ratio
height *= ratio
}
factor := iconSize / referenceSize
width *= factor
height *= factor
return
}
@@ -0,0 +1,19 @@
package levels
import "github.com/inkyblackness/hacked/ss1/content/archive/level"
// MapPosition describes a specific two-dimensional point on the map.
type MapPosition struct {
X level.Coordinate
Y level.Coordinate
}
// Tile returns the tile position of the exact position.
func (pos MapPosition) Tile() level.TilePosition {
return level.TilePosition{X: pos.X.Tile(), Y: pos.Y.Tile()}
}
// Fine returns the fine position of the exact position.
func (pos MapPosition) Fine() level.FinePosition {
return level.FinePosition{X: pos.X.Fine(), Y: pos.Y.Fine()}
}
+255
View File
@@ -0,0 +1,255 @@
package levels
import (
"math"
mgl "github.com/go-gl/mathgl/mgl32"
"github.com/inkyblackness/hacked/editor/graphics"
"github.com/inkyblackness/hacked/editor/render"
"github.com/inkyblackness/hacked/ss1/content/archive/level"
"github.com/inkyblackness/hacked/ui/opengl"
)
var mapTexturesVertexShaderSource = `
#version 150
precision mediump float;
in vec3 vertexPosition;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
uniform mat4 uvMatrix;
out vec2 uv;
void main(void) {
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(vertexPosition, 1.0);
uv = (uvMatrix * vec4(vertexPosition, 1.0)).xy;
}
`
var mapTexturesFragmentShaderSource = `
#version 150
precision mediump float;
in vec2 uv;
uniform sampler2D palette;
uniform sampler2D bitmap;
out vec4 fragColor;
void main(void) {
vec4 pixel = texture(bitmap, uv);
fragColor = texture(palette, vec2(pixel.r, 0.5));
}
`
// TextureQuery is a getter function to retrieve the texture for the given
// level texture index.
type TextureQuery func(index level.TextureIndex) (*graphics.BitmapTexture, error)
// TileTextureQuery is a getter function to retrieve properties for rendering a texture of a tile.
type TileTextureQuery func(pos level.TilePosition) (tileType level.TileType, textureIndex level.TextureIndex, textureRotations int)
// MapTextures is a renderable for textures.
type MapTextures struct {
context *render.Context
program uint32
vao *opengl.VertexArrayObject
vertexPositionBuffer uint32
vertexPositionAttrib int32
modelMatrixUniform opengl.Matrix4Uniform
viewMatrixUniform opengl.Matrix4Uniform
projectionMatrixUniform opengl.Matrix4Uniform
uvMatrixUniform opengl.Matrix4Uniform
paletteUniform int32
bitmapUniform int32
textureQuery TextureQuery
lastTileType level.TileType
}
type tileTypeVertices struct {
vertices []float32
equivalentType level.TileType
}
var tileTypeVerticesLookup []tileTypeVertices
var uvRotations [4]*mgl.Mat4
func init() {
for i := 0; i < 4; i++ {
matrix := mgl.Translate3D(0.5, 0.5, 0.0).
Mul4(mgl.HomogRotate3DZ(math.Pi * float32(i) / -2.0)).
Mul4(mgl.Translate3D(-0.5, -0.5, 0.0)).
Mul4(mgl.Scale3D(1.0, -1.0, 1.0))
uvRotations[i] = &matrix
}
for _, tileType := range level.TileTypes() {
for int(tileType) >= len(tileTypeVerticesLookup) {
tileTypeVerticesLookup = append(tileTypeVerticesLookup, tileTypeVertices{equivalentType: level.TileTypeSolid})
}
switch tileType {
case level.TileTypeSolid:
case level.TileTypeDiagonalOpenNorthEast:
tileTypeVerticesLookup[int(tileType)] = tileTypeVertices{
vertices: []float32{
0.0, 1.0, 0.0,
1.0, 1.0, 0.0,
1.0, 0.0, 0.0,
},
equivalentType: level.TileTypeDiagonalOpenNorthEast,
}
case level.TileTypeDiagonalOpenNorthWest:
tileTypeVerticesLookup[int(tileType)] = tileTypeVertices{
vertices: []float32{
0.0, 1.0, 0.0,
1.0, 1.0, 0.0,
0.0, 0.0, 0.0,
},
equivalentType: level.TileTypeDiagonalOpenNorthWest,
}
case level.TileTypeDiagonalOpenSouthEast:
tileTypeVerticesLookup[int(tileType)] = tileTypeVertices{
vertices: []float32{
1.0, 1.0, 0.0,
1.0, 0.0, 0.0,
0.0, 0.0, 0.0,
},
equivalentType: level.TileTypeDiagonalOpenSouthEast,
}
case level.TileTypeDiagonalOpenSouthWest:
tileTypeVerticesLookup[int(tileType)] = tileTypeVertices{
vertices: []float32{
0.0, 1.0, 0.0,
1.0, 0.0, 0.0,
0.0, 0.0, 0.0,
},
equivalentType: level.TileTypeDiagonalOpenSouthWest,
}
default:
tileTypeVerticesLookup[int(tileType)] = tileTypeVertices{
vertices: []float32{
0.0, 0.0, 0.0,
1.0, 0.0, 0.0,
1.0, 1.0, 0.0,
1.0, 1.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 0.0,
},
equivalentType: level.TileTypeOpen,
}
}
}
}
// NewMapTextures returns a new instance of a renderable for tile map textures.
func NewMapTextures(context *render.Context, textureQuery TextureQuery) *MapTextures {
gl := context.OpenGL
program, programErr := opengl.LinkNewStandardProgram(gl, mapTexturesVertexShaderSource, mapTexturesFragmentShaderSource)
if programErr != nil {
panic(opengl.NamedShaderError{Name: "MapTexturesShader", Nested: programErr})
}
renderable := &MapTextures{
context: context,
program: program,
vao: opengl.NewVertexArrayObject(gl, program),
vertexPositionBuffer: gl.GenBuffers(1)[0],
vertexPositionAttrib: gl.GetAttribLocation(program, "vertexPosition"),
modelMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "modelMatrix")),
viewMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "viewMatrix")),
projectionMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "projectionMatrix")),
uvMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "uvMatrix")),
paletteUniform: gl.GetUniformLocation(program, "palette"),
bitmapUniform: gl.GetUniformLocation(program, "bitmap"),
textureQuery: textureQuery,
lastTileType: level.TileTypeSolid,
}
renderable.vao.WithSetter(func(gl opengl.OpenGL) {
gl.EnableVertexAttribArray(uint32(renderable.vertexPositionAttrib))
gl.BindBuffer(opengl.ARRAY_BUFFER, renderable.vertexPositionBuffer)
gl.VertexAttribOffset(uint32(renderable.vertexPositionAttrib), 3, opengl.FLOAT, false, 0, 0)
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
})
return renderable
}
// Dispose releases any internal resources.
func (renderable *MapTextures) Dispose() {
gl := renderable.context.OpenGL
renderable.vao.Dispose()
gl.DeleteProgram(renderable.program)
gl.DeleteBuffers([]uint32{renderable.vertexPositionBuffer})
}
// Render renders the textures.
func (renderable *MapTextures) Render(columns, rows int, tileTextureQuery TileTextureQuery, paletteTexture *graphics.PaletteTexture) {
gl := renderable.context.OpenGL
renderable.vao.OnShader(func() {
renderable.viewMatrixUniform.Set(gl, renderable.context.ViewMatrix)
renderable.projectionMatrixUniform.Set(gl, &renderable.context.ProjectionMatrix)
textureUnit := int32(0)
gl.ActiveTexture(opengl.TEXTURE0 + uint32(textureUnit))
gl.BindTexture(opengl.TEXTURE_2D, paletteTexture.Handle())
gl.Uniform1i(renderable.paletteUniform, textureUnit)
textureUnit = 1
gl.ActiveTexture(opengl.TEXTURE0 + uint32(textureUnit))
gl.Uniform1i(renderable.bitmapUniform, textureUnit)
scaling := mgl.Scale3D(level.FineCoordinatesPerTileSide, level.FineCoordinatesPerTileSide, 1.0)
for y := 0; y < rows; y++ {
for x := 0; x < columns; x++ {
tileType, textureIndex, textureRotations := tileTextureQuery(level.TilePosition{X: byte(x), Y: byte(y)})
if tileType == level.TileTypeSolid {
continue
}
texture, _ := renderable.textureQuery(textureIndex)
if texture == nil {
continue
}
modelMatrix := mgl.Translate3D(float32(x)*level.FineCoordinatesPerTileSide, float32(y)*level.FineCoordinatesPerTileSide, 0.0).
Mul4(scaling)
uvMatrix := uvRotations[textureRotations]
renderable.uvMatrixUniform.Set(gl, uvMatrix)
renderable.modelMatrixUniform.Set(gl, &modelMatrix)
gl.BindTexture(opengl.TEXTURE_2D, texture.Handle())
renderable.renderTileType(tileType)
}
}
gl.BindTexture(opengl.TEXTURE_2D, 0)
})
}
func (renderable *MapTextures) renderTileType(tileType level.TileType) {
info := tileTypeVerticesLookup[int(tileType)]
vertexCount := len(info.vertices) / 3
gl := renderable.context.OpenGL
if info.equivalentType != renderable.lastTileType {
renderable.lastTileType = info.equivalentType
gl.BindBuffer(opengl.ARRAY_BUFFER, renderable.vertexPositionBuffer)
gl.BufferData(opengl.ARRAY_BUFFER, len(info.vertices)*4, info.vertices, opengl.STATIC_DRAW)
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
}
gl.DrawArrays(opengl.TRIANGLES, 0, int32(vertexCount))
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,10 @@
package levels
type objectsViewModel struct {
restoreFocus bool
windowOpen bool
}
func freshObjectsViewModel() objectsViewModel {
return objectsViewModel{}
}
@@ -0,0 +1,64 @@
package levels
import (
"fmt"
"github.com/inkyblackness/hacked/ss1/content/archive/level"
)
// TextureDisplay is an enumeration which texture to display in a 2D map view.
type TextureDisplay int
// TextureDisplay constants are listed below.
const (
TextureDisplayFloor TextureDisplay = 0
TextureDisplayWall TextureDisplay = 1
TextureDisplayCeiling TextureDisplay = 2
)
// String returns a textual representation.
func (display TextureDisplay) String() string {
switch display {
case TextureDisplayFloor:
return "Floor"
case TextureDisplayWall:
return "Wall"
case TextureDisplayCeiling:
return "Ceiling"
default:
return fmt.Sprintf("Unknown%d", int(display))
}
}
// TextureDisplays returns all TextureDisplay constants.
func TextureDisplays() []TextureDisplay {
return []TextureDisplay{TextureDisplayFloor, TextureDisplayWall, TextureDisplayCeiling}
}
// TextureDisplayFunc is one that resolves texture properties.
type TextureDisplayFunc func(*level.TileMapEntry) (atlasIndex level.AtlasIndex, textureRotations int)
// FloorTexture returns the properties for the floor.
func FloorTexture(tile *level.TileMapEntry) (atlasIndex level.AtlasIndex, textureRotations int) {
return tile.TextureInfo.FloorTextureIndex(), tile.Floor.TextureRotations()
}
// CeilingTexture returns the properties for the ceiling.
func CeilingTexture(tile *level.TileMapEntry) (atlasIndex level.AtlasIndex, textureRotations int) {
return tile.TextureInfo.CeilingTextureIndex(), tile.Ceiling.TextureRotations()
}
// WallTexture returns the properties for the floor.
func WallTexture(tile *level.TileMapEntry) (atlasIndex level.AtlasIndex, textureRotations int) {
return tile.TextureInfo.WallTextureIndex(), 0
}
// Func returns the display func for the current display setting.
func (display TextureDisplay) Func() TextureDisplayFunc {
if display == TextureDisplayFloor {
return FloorTexture
} else if display == TextureDisplayCeiling {
return CeilingTexture
}
return WallTexture
}
+551
View File
@@ -0,0 +1,551 @@
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/editor/values"
"github.com/inkyblackness/hacked/ss1/content/archive/level"
"github.com/inkyblackness/hacked/ss1/content/bitmap"
"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"
)
// TilesView is for tile properties.
type TilesView struct {
editor *edit.LevelEditorService
textCache *text.Cache
textureCache *graphics.TextureCache
guiScale float32
registry cmd.Registry
model tilesViewModel
}
// NewTilesView returns a new instance.
func NewTilesView(editor *edit.LevelEditorService,
guiScale float32, textCache *text.Cache, textureCache *graphics.TextureCache, registry cmd.Registry) *TilesView {
view := &TilesView{
editor: editor,
textCache: textCache,
textureCache: textureCache,
guiScale: guiScale,
model: freshTilesViewModel(),
registry: registry,
}
return view
}
// WindowOpen returns the flag address, to be used with the main menu.
func (view *TilesView) WindowOpen() *bool {
return &view.model.windowOpen
}
// TextureDisplay returns the current setting which textures should be displayed.
func (view TilesView) TextureDisplay() TextureDisplay {
return view.model.textureDisplay
}
// ColorDisplay returns the current setting which colors should be displayed.
func (view TilesView) ColorDisplay() ColorDisplay {
lvl := view.editor.Level()
if lvl.IsCyberspace() {
return view.model.cyberColorDisplay
}
return view.model.shadowDisplay
}
// Render renders the view.
func (view *TilesView) Render() {
if view.model.restoreFocus {
imgui.SetNextWindowFocus()
view.model.restoreFocus = false
view.model.windowOpen = true
}
if view.model.windowOpen {
lvl := view.editor.Level()
tiles := view.editor.Tiles()
readOnly := view.editor.IsReadOnly()
title := fmt.Sprintf("Level Tiles, %d selected", len(tiles))
if readOnly {
title += hintReadOnly
}
imgui.SetNextWindowSizeV(imgui.Vec2{X: 400 * view.guiScale, Y: 500 * view.guiScale}, imgui.ConditionFirstUseEver)
if imgui.BeginV(title+"###Level Tiles", view.WindowOpen(), 0) {
view.renderContent(lvl, tiles, readOnly)
}
imgui.End()
}
}
func (view *TilesView) renderContent(lvl *level.Level, tiles []*level.TileMapEntry, readOnly bool) {
isCyberspace := lvl.IsCyberspace()
tileTypeUnifier := values.NewUnifier()
floorHeightUnifier := values.NewUnifier()
ceilingHeightUnifier := values.NewUnifier()
slopeHeightUnifier := values.NewUnifier()
slopeControlUnifier := values.NewUnifier()
musicZoneUnifier := values.NewUnifier()
floorPaletteIndexUnifier := values.NewUnifier()
ceilingPaletteIndexUnifier := values.NewUnifier()
flightPullTypeUnifier := values.NewUnifier()
gameOfLightStateUnifier := values.NewUnifier()
floorTextureIndexUnifier := values.NewUnifier()
floorTextureRotationsUnifier := values.NewUnifier()
ceilingTextureIndexUnifier := values.NewUnifier()
ceilingTextureRotationsUnifier := values.NewUnifier()
wallTextureIndexUnifier := values.NewUnifier()
wallTextureOffsetUnifier := values.NewUnifier()
useAdjacentWallTextureUnifier := values.NewUnifier()
wallTexturePatternUnifier := values.NewUnifier()
floorLightUnifier := values.NewUnifier()
floorLightDeltaUnifier := values.NewUnifier()
ceilingLightUnifier := values.NewUnifier()
ceilingLightDeltaUnifier := values.NewUnifier()
deconstructedUnifier := values.NewUnifier()
perildUnifier := values.NewUnifier()
floorHazardUnifier := values.NewUnifier()
ceilingHazardUnifier := values.NewUnifier()
for _, tile := range tiles {
tileTypeUnifier.Add(tile.Type)
floorHeightUnifier.Add(tile.Floor.AbsoluteHeight())
ceilingHeightUnifier.Add(tile.Ceiling.AbsoluteHeight())
slopeHeightUnifier.Add(tile.SlopeHeight)
slopeControlUnifier.Add(tile.Flags.SlopeControl())
musicZoneUnifier.Add(tile.Flags.MusicZone())
perildUnifier.Add(tile.Flags.Peril())
if isCyberspace {
floorPaletteIndexUnifier.Add(tile.TextureInfo.FloorPaletteIndex())
ceilingPaletteIndexUnifier.Add(tile.TextureInfo.CeilingPaletteIndex())
flightPullTypeUnifier.Add(tile.Flags.ForCyberspace().FlightPull())
gameOfLightStateUnifier.Add(tile.Flags.ForCyberspace().GameOfLifeState())
} else {
flags := tile.Flags.ForRealWorld()
floorTextureIndexUnifier.Add(int(tile.TextureInfo.FloorTextureIndex()))
floorTextureRotationsUnifier.Add(tile.Floor.TextureRotations())
ceilingTextureIndexUnifier.Add(int(tile.TextureInfo.CeilingTextureIndex()))
ceilingTextureRotationsUnifier.Add(tile.Ceiling.TextureRotations())
wallTextureIndexUnifier.Add(int(tile.TextureInfo.WallTextureIndex()))
wallTextureOffsetUnifier.Add(flags.WallTextureOffset())
useAdjacentWallTextureUnifier.Add(flags.UseAdjacentWallTexture())
wallTexturePatternUnifier.Add(flags.WallTexturePattern())
floorLightUnifier.Add(level.GradesOfShadow - 1 - flags.FloorShadow())
floorLightDeltaUnifier.Add(tile.LightDelta.OfFloor())
ceilingLightUnifier.Add(level.GradesOfShadow - 1 - flags.CeilingShadow())
ceilingLightDeltaUnifier.Add(tile.LightDelta.OfCeiling())
deconstructedUnifier.Add(flags.Deconstructed())
floorHazardUnifier.Add(tile.Floor.HasHazard())
ceilingHazardUnifier.Add(tile.Ceiling.HasHazard())
}
}
imgui.PushItemWidth(-250 * view.guiScale)
_, _, levelHeight := lvl.Size()
tileHeightFormatter := tileHeightFormatterFor(levelHeight)
tileTypes := level.TileTypes()
values.RenderUnifiedCombo(readOnly, "Tile Type", tileTypeUnifier,
func(u values.Unifier) int { return int(u.Unified().(level.TileType)) },
func(value int) string { return tileTypes[value].String() },
len(tileTypes),
func(newValue int) { view.changeTiles(setTileTypeTo(level.TileType(newValue))) })
values.RenderUnifiedSliderInt(readOnly, "Floor Height", floorHeightUnifier,
func(u values.Unifier) int { return int(u.Unified().(level.TileHeightUnit)) },
tileHeightFormatter,
0, int(level.TileHeightUnitMax)-1,
func(newValue int) { view.changeTiles(setFloorHeightTo(level.TileHeightUnit(newValue))) })
values.RenderUnifiedSliderInt(readOnly, "Ceiling Height (abs)", ceilingHeightUnifier,
func(u values.Unifier) int { return int(u.Unified().(level.TileHeightUnit)) },
tileHeightFormatter,
1, int(level.TileHeightUnitMax),
func(newValue int) { view.changeTiles(setCeilingHeightTo(level.TileHeightUnit(newValue))) })
values.RenderUnifiedSliderInt(readOnly, "Slope Height", slopeHeightUnifier,
func(u values.Unifier) int { return int(u.Unified().(level.TileHeightUnit)) },
tileHeightFormatter,
0, int(level.TileHeightUnitMax)-1,
func(newValue int) { view.changeTiles(setSlopeHeightTo(level.TileHeightUnit(newValue))) })
slopeControls := level.TileSlopeControls()
values.RenderUnifiedCombo(readOnly, "Slope Control", slopeControlUnifier,
func(u values.Unifier) int { return int(u.Unified().(level.TileSlopeControl)) },
func(value int) string { return slopeControls[value].String() },
len(slopeControls),
func(newValue int) { view.changeTiles(setSlopeControlTo(slopeControls[newValue])) })
musicZones := level.TileMusicZones()
values.RenderUnifiedCombo(readOnly, "Music Zone", musicZoneUnifier,
func(u values.Unifier) int { return int(u.Unified().(level.TileMusicZone)) },
func(value int) string { return musicZones[value].String() },
len(musicZones),
func(newValue int) { view.changeTiles(setMusicZoneTo(musicZones[newValue])) })
values.RenderUnifiedCheckboxCombo(readOnly, "Music Peril", perildUnifier,
func(newValue bool) { view.changeTiles(setPerilTo(newValue)) })
imgui.Separator()
if isCyberspace {
if imgui.BeginCombo("Color View", view.model.cyberColorDisplay.String()) {
displays := ColorDisplays()
for _, display := range displays {
displayString := display.String()
if imgui.SelectableV(displayString, display == view.model.cyberColorDisplay, 0, imgui.Vec2{}) {
view.model.cyberColorDisplay = display
}
}
imgui.EndCombo()
}
values.RenderUnifiedSliderInt(readOnly, "Floor Color", floorPaletteIndexUnifier,
func(u values.Unifier) int { return int(u.Unified().(byte)) },
func(value int) string { return "%d" },
0, bitmap.PaletteSize-1,
func(newValue int) { view.changeTiles(setFloorPaletteIndexTo(newValue)) })
values.RenderUnifiedSliderInt(readOnly, "Ceiling Color", ceilingPaletteIndexUnifier,
func(u values.Unifier) int { return int(u.Unified().(byte)) },
func(value int) string { return "%d" },
0, bitmap.PaletteSize-1,
func(newValue int) { view.changeTiles(setCeilingPaletteIndexTo(newValue)) })
imgui.Separator()
flightPulls := level.CyberspaceFlightPulls()
values.RenderUnifiedCombo(readOnly, "Flight Pull Type", flightPullTypeUnifier,
func(u values.Unifier) int { return int(u.Unified().(level.CyberspaceFlightPull)) },
func(value int) string { return flightPulls[value].String() },
len(flightPulls),
func(newValue int) { view.changeTiles(setFlightPullTypeTo(flightPulls[newValue])) })
values.RenderUnifiedSliderInt(readOnly, "Game Of Life State", gameOfLightStateUnifier,
func(u values.Unifier) int { return u.Unified().(int) },
func(value int) string { return "%d" },
0, 3,
func(newValue int) { view.changeTiles(setGameOfLightStateTo(newValue)) })
} else {
atlas := lvl.TextureAtlas()
if imgui.BeginCombo("Texture View", view.model.textureDisplay.String()) {
displays := TextureDisplays()
for _, display := range displays {
displayString := display.String()
if imgui.SelectableV(displayString, display == view.model.textureDisplay, 0, imgui.Vec2{}) {
view.model.textureDisplay = display
}
}
imgui.EndCombo()
}
values.RenderUnifiedSliderInt(readOnly, "Floor Texture (atlas index)", floorTextureIndexUnifier,
func(u values.Unifier) int { return u.Unified().(int) },
func(value int) string { return "%d" },
0, level.FloorCeilingTextureLimit-1,
func(newValue int) { view.changeTiles(setFloorTextureIndexTo(level.AtlasIndex(newValue))) })
view.renderTextureSelector(readOnly, "Floor Texture", floorTextureIndexUnifier, atlas, 0, level.FloorCeilingTextureLimit-1,
func(newValue int) { view.changeTiles(setFloorTextureIndexTo(level.AtlasIndex(newValue))) })
values.RenderUnifiedSliderInt(readOnly, "Floor Texture Rotations", floorTextureRotationsUnifier,
func(u values.Unifier) int { return u.Unified().(int) },
func(value int) string { return "%d" },
0, 3,
func(newValue int) { view.changeTiles(setFloorTextureRotationsTo(newValue)) })
values.RenderUnifiedSliderInt(readOnly, "Ceiling Texture (atlas index)", ceilingTextureIndexUnifier,
func(u values.Unifier) int { return u.Unified().(int) },
func(value int) string { return "%d" },
0, level.FloorCeilingTextureLimit-1,
func(newValue int) { view.changeTiles(setCeilingTextureIndexTo(level.AtlasIndex(newValue))) })
view.renderTextureSelector(readOnly, "Ceiling Texture", ceilingTextureIndexUnifier, atlas, 0, level.FloorCeilingTextureLimit-1,
func(newValue int) { view.changeTiles(setCeilingTextureIndexTo(level.AtlasIndex(newValue))) })
values.RenderUnifiedSliderInt(readOnly, "Ceiling Texture Rotations", ceilingTextureRotationsUnifier,
func(u values.Unifier) int { return u.Unified().(int) },
func(value int) string { return "%d" },
0, 3,
func(newValue int) { view.changeTiles(setCeilingTextureRotationsTo(newValue)) })
values.RenderUnifiedSliderInt(readOnly, "Wall Texture (atlas index)", wallTextureIndexUnifier,
func(u values.Unifier) int { return u.Unified().(int) },
func(value int) string { return "%d" },
0, len(atlas)-1,
func(newValue int) { view.changeTiles(setWallTextureIndexTo(level.AtlasIndex(newValue))) })
view.renderTextureSelector(readOnly, "Wall Texture", wallTextureIndexUnifier, atlas, 0, len(atlas)-1,
func(newValue int) { view.changeTiles(setWallTextureIndexTo(level.AtlasIndex(newValue))) })
values.RenderUnifiedSliderInt(readOnly, "Wall Texture Offset", wallTextureOffsetUnifier,
func(u values.Unifier) int { return int(u.Unified().(level.TileHeightUnit)) },
tileHeightFormatter,
0, int(level.TileHeightUnitMax)-1,
func(newValue int) { view.changeTiles(setWallTextureOffsetTo(level.TileHeightUnit(newValue))) })
values.RenderUnifiedCheckboxCombo(readOnly, "Use Adjacent Wall Texture", useAdjacentWallTextureUnifier,
func(newValue bool) { view.changeTiles(setUseAdjacentWallTextureTo(newValue)) })
wallTexturePatterns := level.WallTexturePatterns()
values.RenderUnifiedCombo(readOnly, "Wall Texture Pattern", wallTexturePatternUnifier,
func(u values.Unifier) int { return int(u.Unified().(level.WallTexturePattern)) },
func(value int) string { return wallTexturePatterns[value].String() },
len(wallTexturePatterns),
func(newValue int) { view.changeTiles(setWallTexturePatternTo(wallTexturePatterns[newValue])) })
imgui.Separator()
if imgui.BeginCombo("Shadow View", view.model.shadowDisplay.String()) {
displays := ColorDisplays()
for _, display := range displays {
displayString := display.String()
if imgui.SelectableV(displayString, display == view.model.shadowDisplay, 0, imgui.Vec2{}) {
view.model.shadowDisplay = display
}
}
imgui.EndCombo()
}
values.RenderUnifiedSliderInt(readOnly, "Floor Light", floorLightUnifier,
func(u values.Unifier) int { return u.Unified().(int) },
func(value int) string { return "%d" },
0, level.GradesOfShadow-1,
func(newValue int) { view.changeTiles(setFloorLightTo(newValue)) })
values.RenderUnifiedSliderInt(readOnly, "Floor Light Delta", floorLightDeltaUnifier,
func(u values.Unifier) int { return u.Unified().(int) },
func(value int) string { return "%d" },
0, level.GradesOfShadow-1,
func(newValue int) { view.changeTiles(setFloorLightDeltaTo(newValue)) })
values.RenderUnifiedSliderInt(readOnly, "Ceiling Light", ceilingLightUnifier,
func(u values.Unifier) int { return u.Unified().(int) },
func(value int) string { return "%d" },
0, level.GradesOfShadow-1,
func(newValue int) { view.changeTiles(setCeilingLightTo(newValue)) })
values.RenderUnifiedSliderInt(readOnly, "Ceiling Light Delta", ceilingLightDeltaUnifier,
func(u values.Unifier) int { return u.Unified().(int) },
func(value int) string { return "%d" },
0, level.GradesOfShadow-1,
func(newValue int) { view.changeTiles(setCeilingLightDeltaTo(newValue)) })
imgui.Separator()
values.RenderUnifiedCheckboxCombo(readOnly, "Deconstructed", deconstructedUnifier,
func(newValue bool) { view.changeTiles(setDeconstructedTo(newValue)) })
values.RenderUnifiedCheckboxCombo(readOnly, "Floor Hazard", floorHazardUnifier,
func(newValue bool) { view.changeTiles(setFloorHazardTo(newValue)) })
values.RenderUnifiedCheckboxCombo(readOnly, "Ceiling Hazard", ceilingHazardUnifier,
func(newValue bool) { view.changeTiles(setCeilingHazardTo(newValue)) })
}
imgui.PopItemWidth()
}
func (view *TilesView) renderTextureSelector(readOnly bool, label string, unifier values.Unifier,
atlas level.TextureAtlas, minIndex, maxIndex int, changeHandler func(int)) {
selectedIndex := -1
if unifier.IsUnique() {
selectedIndex = unifier.Unified().(int)
}
count := maxIndex - minIndex + 1
if count > len(atlas) {
count = len(atlas)
}
render.TextureSelector(label, -1, view.guiScale, count, selectedIndex-minIndex,
view.textureCache,
func(index int) resource.Key {
return resource.KeyOf(ids.LargeTextures.Plus(int(atlas[minIndex+index])), resource.LangAny, 0)
},
func(index int) string { return view.textureName(int(atlas[minIndex+index])) },
func(index int) {
if !readOnly {
changeHandler(index)
}
})
}
func (view *TilesView) 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 setTileTypeTo(tileType level.TileType) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.Type = tileType
}
}
func setFloorHeightTo(height level.TileHeightUnit) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.Floor = tile.Floor.WithAbsoluteHeight(height)
}
}
func setCeilingHeightTo(height level.TileHeightUnit) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.Ceiling = tile.Ceiling.WithAbsoluteHeight(height)
}
}
func setSlopeHeightTo(height level.TileHeightUnit) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.SlopeHeight = height
}
}
func setSlopeControlTo(value level.TileSlopeControl) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.Flags = tile.Flags.WithSlopeControl(value)
}
}
func setPerilTo(value bool) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.Flags = tile.Flags.WithPeril(value)
}
}
func setMusicZoneTo(value level.TileMusicZone) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.Flags = tile.Flags.WithMusicZone(value)
}
}
func setFloorTextureIndexTo(value level.AtlasIndex) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.TextureInfo = tile.TextureInfo.WithFloorTextureIndex(value)
}
}
func setFloorTextureRotationsTo(value int) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.Floor = tile.Floor.WithTextureRotations(value)
}
}
func setCeilingTextureIndexTo(value level.AtlasIndex) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.TextureInfo = tile.TextureInfo.WithCeilingTextureIndex(value)
}
}
func setCeilingTextureRotationsTo(value int) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.Ceiling = tile.Ceiling.WithTextureRotations(value)
}
}
func setWallTextureIndexTo(value level.AtlasIndex) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.TextureInfo = tile.TextureInfo.WithWallTextureIndex(value)
}
}
func setWallTextureOffsetTo(value level.TileHeightUnit) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.Flags = tile.Flags.ForRealWorld().WithWallTextureOffset(value).AsTileFlag()
}
}
func setUseAdjacentWallTextureTo(value bool) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.Flags = tile.Flags.ForRealWorld().WithUseAdjacentWallTexture(value).AsTileFlag()
}
}
func setWallTexturePatternTo(value level.WallTexturePattern) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.Flags = tile.Flags.ForRealWorld().WithWallTexturePattern(value).AsTileFlag()
}
}
func setFloorLightTo(value int) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.Flags = tile.Flags.ForRealWorld().WithFloorShadow(level.GradesOfShadow - 1 - value).AsTileFlag()
}
}
func setFloorLightDeltaTo(value int) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.LightDelta = tile.LightDelta.WithFloor(value)
}
}
func setCeilingLightTo(value int) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.Flags = tile.Flags.ForRealWorld().WithCeilingShadow(level.GradesOfShadow - 1 - value).AsTileFlag()
}
}
func setCeilingLightDeltaTo(value int) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.LightDelta = tile.LightDelta.WithCeiling(value)
}
}
func setDeconstructedTo(value bool) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.Flags = tile.Flags.ForRealWorld().WithDeconstructed(value).AsTileFlag()
}
}
func setFloorHazardTo(value bool) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.Floor = tile.Floor.WithHazard(value)
}
}
func setCeilingHazardTo(value bool) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.Ceiling = tile.Ceiling.WithHazard(value)
}
}
func setFloorPaletteIndexTo(value int) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.TextureInfo = tile.TextureInfo.WithFloorPaletteIndex(byte(value))
}
}
func setCeilingPaletteIndexTo(value int) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.TextureInfo = tile.TextureInfo.WithCeilingPaletteIndex(byte(value))
}
}
func setFlightPullTypeTo(value level.CyberspaceFlightPull) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.Flags = tile.Flags.ForCyberspace().WithFlightPull(value).AsTileFlag()
}
}
func setGameOfLightStateTo(value int) tileMapEntryModifier {
return func(tile *level.TileMapEntry) {
tile.Flags = tile.Flags.ForCyberspace().WithGameOfLifeState(value).AsTileFlag()
}
}
type tileMapEntryModifier func(*level.TileMapEntry)
func (view *TilesView) changeTiles(modifier tileMapEntryModifier) {
err := view.registry.Register(cmd.Named("ChangeTiles"),
cmd.Forward(view.restoreFocusTask()),
cmd.Nested(func() error { return view.editor.ChangeTiles(modifier) }),
cmd.Reverse(view.restoreFocusTask()))
if err != nil {
panic(err)
}
}
func (view *TilesView) restoreFocusTask() cmd.Task {
return func(modder world.Modder) error {
view.model.restoreFocus = true
return nil
}
}
@@ -0,0 +1,18 @@
package levels
type tilesViewModel struct {
textureDisplay TextureDisplay
shadowDisplay ColorDisplay
cyberColorDisplay ColorDisplay
restoreFocus bool
windowOpen bool
}
func freshTilesViewModel() tilesViewModel {
return tilesViewModel{
textureDisplay: TextureDisplayFloor,
shadowDisplay: ColorDisplayNone,
cyberColorDisplay: ColorDisplayNone,
}
}
+64
View File
@@ -0,0 +1,64 @@
package levels
import (
"fmt"
"github.com/inkyblackness/hacked/ss1/content/archive/level"
)
type hazardFormatter func(int) string
func rawHazardValue(int) string {
return "raw: %d"
}
func lbpHazardValue(value int) string {
return fmt.Sprintf("%3.1f LBP - raw: %%d", float32(value)/2)
}
func percentHazardValue(value int) string {
return fmt.Sprintf("%v%%%% - raw: %%d", value*25)
}
type ceilingHazardInfo struct {
title string
radiationRegister bool
formatter hazardFormatter
}
var ceilingHazards = []ceilingHazardInfo{
{title: "Off", radiationRegister: false, formatter: rawHazardValue},
{title: "Radiation", radiationRegister: true, formatter: lbpHazardValue},
}
func currentCeilingHazard(param *level.Parameters) ceilingHazardInfo {
radiationRegister := param.RadiationRegister > 1
if radiationRegister {
return ceilingHazards[1]
}
return ceilingHazards[0]
}
type floorHazardInfo struct {
title string
biohazardRegister bool
isGravity bool
formatter hazardFormatter
}
var floorHazards = []floorHazardInfo{
{title: "Off", biohazardRegister: false, isGravity: false, formatter: rawHazardValue},
{title: "Gravity", biohazardRegister: false, isGravity: true, formatter: percentHazardValue},
{title: "Biohazard", biohazardRegister: true, isGravity: false, formatter: lbpHazardValue},
}
func currentFloorHazard(param *level.Parameters) floorHazardInfo {
biohazardRegister := param.BiohazardRegister > 1
isGravity := param.FloorHazardIsGravity != 0
if isGravity {
return floorHazards[1]
} else if biohazardRegister {
return floorHazards[2]
}
return floorHazards[0]
}