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,257 @@
package edit
import (
"github.com/inkyblackness/hacked/ss1/content/archive/level"
"github.com/inkyblackness/hacked/ss1/content/object"
"github.com/inkyblackness/hacked/ss1/edit/undoable/cmd"
"github.com/inkyblackness/hacked/ss1/world"
)
// LevelEditorService provides level editing functionality based on the currently selected level and content.
type LevelEditorService struct {
registry cmd.Registry
gameObjects *GameObjectsService
levels *EditableLevels
levelSelection *LevelSelectionService
newObjectTriple object.Triple
}
// NewLevelEditorService returns a new instance.
func NewLevelEditorService(registry cmd.Registry,
gameObjects *GameObjectsService,
levels *EditableLevels,
levelSelection *LevelSelectionService) *LevelEditorService {
return &LevelEditorService{
registry: registry,
gameObjects: gameObjects,
levels: levels,
levelSelection: levelSelection,
}
}
// IsReadOnly returns true if the currently selected level cannot be modified.
func (service *LevelEditorService) IsReadOnly() bool {
return service.levels.IsLevelReadOnly(service.levelSelection.CurrentLevelID())
}
// Level returns the currently selected level.
func (service *LevelEditorService) Level() *level.Level {
return service.levels.Level(service.levelSelection.CurrentLevelID())
}
// Tiles returns the list of currently selected tiles of the current level.
func (service *LevelEditorService) Tiles() []*level.TileMapEntry {
lvl := service.Level()
positions := service.levelSelection.CurrentSelectedTiles()
tiles := make([]*level.TileMapEntry, len(positions))
for i, pos := range positions {
tiles[i] = lvl.Tile(pos)
}
return tiles
}
// HasSelectedTiles returns true if Tiles() returns at least one entry.
func (service *LevelEditorService) HasSelectedTiles() bool {
return len(service.levelSelection.CurrentSelectedTiles()) > 0
}
// ClearTiles resets the selected tiles.
func (service *LevelEditorService) ClearTiles() error {
return service.ChangeTiles(func(tile *level.TileMapEntry) {
index := tile.FirstObjectIndex
tile.Reset()
tile.FirstObjectIndex = index
})
}
// ChangeTiles performs a modification on all currently selected tiles and commits these changes to the repository.
func (service *LevelEditorService) ChangeTiles(modifier func(*level.TileMapEntry)) error {
positions := service.levelSelection.CurrentSelectedTiles()
if len(positions) == 0 {
return nil
}
lvl := service.Level()
for _, pos := range positions {
modifier(lvl.Tile(pos))
}
levelID := lvl.ID()
return service.registry.Register(cmd.Named("ChangeTiles"),
cmd.Forward(service.levelSelection.SetCurrentLevelIDTask(levelID)),
cmd.Forward(service.setSelectedTilesTask(positions)),
cmd.Nested(func() error { return service.levels.CommitLevelChanges(levelID) }),
cmd.Reverse(service.setSelectedTilesTask(positions)),
cmd.Reverse(service.levelSelection.SetCurrentLevelIDTask(levelID)),
)
}
// Objects returns the list of currently selected objects of the current level.
func (service *LevelEditorService) Objects() []*level.ObjectMainEntry {
lvl := service.Level()
ids := service.levelSelection.CurrentSelectedObjects()
objects := make([]*level.ObjectMainEntry, len(ids))
for i, id := range ids {
objects[i] = lvl.Object(id)
}
return objects
}
// HasSelectedObjects returns true if a call to Objects() returns at least one item.
func (service *LevelEditorService) HasSelectedObjects() bool {
return len(service.levelSelection.CurrentSelectedObjects()) > 0
}
// NewObjectTriple returns the identifier of implicitly created objects.
func (service *LevelEditorService) NewObjectTriple() object.Triple {
return service.newObjectTriple
}
// SetNewObjectTriple sets the identifier for implicitly created objects.
func (service *LevelEditorService) SetNewObjectTriple(value object.Triple) {
service.newObjectTriple = value
}
// CreateNewObject adds a new object to the level and selects it.
func (service *LevelEditorService) CreateNewObject(triple object.Triple, modifier level.ObjectMainEntryModifier) error {
lvl := service.Level()
id, err := lvl.NewObject(triple.Class)
if err != nil {
return err
}
obj := lvl.Object(id)
obj.Subclass = triple.Subclass
obj.Type = triple.Type
service.resetHitpointsToDefault(obj)
modifier(obj)
service.placeObject(lvl, obj, service.atFloorLevelIn(lvl))
lvl.UpdateObjectLocation(id)
return service.patchLevelObjects(lvl, service.levelSelection.CurrentSelectedObjects(), []level.ObjectID{id})
}
// PlaceObjectsOnFloor puts all selected objects to sit on the floor.
func (service *LevelEditorService) PlaceObjectsOnFloor() error {
lvl := service.Level()
atHeight := service.atFloorLevelIn(lvl)
return service.ChangeObjects(func(obj *level.ObjectMainEntry) { service.placeObject(lvl, obj, atHeight) })
}
// PlaceObjectsOnEyeLevel puts all selected objects to be at eye level (approximately).
func (service *LevelEditorService) PlaceObjectsOnEyeLevel() error {
lvl := service.Level()
atHeight := service.atEyeLevelIn(lvl)
return service.ChangeObjects(func(obj *level.ObjectMainEntry) { service.placeObject(lvl, obj, atHeight) })
}
// PlaceObjectsOnCeiling puts all selected objects to hang from the ceiling.
func (service *LevelEditorService) PlaceObjectsOnCeiling() error {
lvl := service.Level()
atHeight := service.atCeilingLevelIn(lvl)
return service.ChangeObjects(func(obj *level.ObjectMainEntry) { service.placeObject(lvl, obj, atHeight) })
}
// ChangeObjects modifies properties of selected objects. The modifier is called for each selected object.
func (service *LevelEditorService) ChangeObjects(modifier level.ObjectMainEntryModifier) error {
objectIDs := service.levelSelection.CurrentSelectedObjects()
if len(objectIDs) == 0 {
return nil
}
lvl := service.Level()
for _, id := range objectIDs {
obj := lvl.Object(id)
oldPosition := obj.TilePosition()
modifier(obj)
if oldPosition != obj.TilePosition() {
lvl.UpdateObjectLocation(id)
}
}
return service.patchLevelObjects(lvl, objectIDs, objectIDs)
}
// DeleteObjects deletes all currently selected objects, clearing the selection afterwards.
func (service *LevelEditorService) DeleteObjects() error {
objectIDs := service.levelSelection.CurrentSelectedObjects()
if len(objectIDs) == 0 {
return nil
}
lvl := service.Level()
for _, id := range objectIDs {
lvl.DelObject(id)
}
return service.patchLevelObjects(lvl, objectIDs, nil)
}
func (service *LevelEditorService) patchLevelObjects(
lvl *level.Level,
reverseObjectIDs []level.ObjectID,
forwardObjectIDs []level.ObjectID) error {
levelID := lvl.ID()
return service.registry.Register(cmd.Named("PatchLevelObjects"),
cmd.Forward(service.levelSelection.SetCurrentLevelIDTask(levelID)),
cmd.Reverse(service.setSelectedObjectsTask(reverseObjectIDs)),
cmd.Nested(func() error { return service.levels.CommitLevelChanges(levelID) }),
cmd.Forward(service.setSelectedObjectsTask(forwardObjectIDs)),
cmd.Reverse(service.levelSelection.SetCurrentLevelIDTask(levelID)),
)
}
func (service *LevelEditorService) setSelectedTilesTask(positions []level.TilePosition) cmd.Task {
return func(world.Modder) error {
service.levelSelection.SetCurrentSelectedTiles(positions)
return nil
}
}
func (service *LevelEditorService) setSelectedObjectsTask(ids []level.ObjectID) cmd.Task {
return func(world.Modder) error {
service.levelSelection.SetCurrentSelectedObjects(ids)
return nil
}
}
type heightCalculatorFunc func(tile *level.TileMapEntry, pos level.FinePosition, objPivot float32) level.HeightUnit
func (service *LevelEditorService) placeObject(lvl *level.Level, obj *level.ObjectMainEntry, atHeight heightCalculatorFunc) {
var objPivot float32
prop, err := service.gameObjects.PropertiesFor(obj.Triple())
if err == nil {
objPivot = object.Pivot(prop.Common)
}
tile := lvl.Tile(obj.TilePosition())
if tile != nil {
obj.Z = atHeight(tile, obj.FinePosition(), objPivot)
}
}
func (service *LevelEditorService) atFloorLevelIn(lvl *level.Level) heightCalculatorFunc {
_, _, height := lvl.Size()
return func(tile *level.TileMapEntry, pos level.FinePosition, objPivot float32) level.HeightUnit {
floorHeight := tile.FloorTileHeightAt(pos, height)
return height.ValueToObjectHeight(floorHeight + objPivot)
}
}
func (service *LevelEditorService) atEyeLevelIn(lvl *level.Level) heightCalculatorFunc {
_, _, height := lvl.Size()
return func(tile *level.TileMapEntry, pos level.FinePosition, objPivot float32) level.HeightUnit {
floorHeight := tile.FloorTileHeightAt(pos, height)
return height.ValueToObjectHeight(floorHeight + 0.75 - objPivot)
}
}
func (service *LevelEditorService) atCeilingLevelIn(lvl *level.Level) heightCalculatorFunc {
_, _, height := lvl.Size()
return func(tile *level.TileMapEntry, pos level.FinePosition, objPivot float32) level.HeightUnit {
floorHeight := tile.CeilingTileHeightAt(pos, height)
return height.ValueToObjectHeight(floorHeight - objPivot)
}
}
func (service *LevelEditorService) resetHitpointsToDefault(obj *level.ObjectMainEntry) {
prop, err := service.gameObjects.PropertiesFor(obj.Triple())
if err != nil {
return
}
obj.Hitpoints = prop.Common.Hitpoints
}