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
+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{}
}
}