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,167 @@
package values
import (
"fmt"
"sort"
"strings"
"github.com/inkyblackness/hacked/ss1/content/interpreters"
"github.com/inkyblackness/hacked/ss1/content/object"
)
// StandardSimplifier returns a simplifier with common property controls.
func StandardSimplifier(readOnly bool, fullKey string, unifier Unifier,
updater func(func(uint32) uint32), objTypeRenderer ObjectTypeControlRenderer) *interpreters.Simplifier {
keys := strings.Split(fullKey, ".")
key := keys[len(keys)-1]
label := key + "###" + fullKey
simplifier := interpreters.NewSimplifier(func(minValue, maxValue int64, formatter interpreters.RawValueFormatter) {
RenderUnifiedSliderInt(readOnly, label, unifier,
func(u Unifier) int {
unifiedValue := u.Unified().(int32)
if (minValue == -1) && (maxValue == 0x7FFF) {
unifiedValue = int32(int16(unifiedValue))
}
return int(unifiedValue)
},
func(value int) string {
result := formatter(value)
if len(result) == 0 {
result = "%d"
} else {
result += " - raw: %d"
}
return result
},
int(minValue), int(maxValue),
func(newValue int) {
updater(func(oldValue uint32) uint32 { return uint32(newValue) })
})
})
simplifier.SetEnumValueHandler(func(enumValues map[uint32]string) {
valueKeys := make([]uint32, 0, len(enumValues))
for valueKey := range enumValues {
valueKeys = append(valueKeys, valueKey)
}
sort.Slice(valueKeys, func(indexA, indexB int) bool { return valueKeys[indexA] < valueKeys[indexB] })
RenderUnifiedCombo(readOnly, label, unifier,
func(u Unifier) int {
unifiedValue := uint32(u.Unified().(int32))
for index, valueKey := range valueKeys {
if valueKey == unifiedValue {
return index
}
}
return -1
},
func(index int) string {
if index < 0 {
return ""
}
return enumValues[valueKeys[index]]
},
len(valueKeys),
func(newIndex int) {
updater(func(oldValue uint32) uint32 { return valueKeys[newIndex] })
})
})
simplifier.SetBitfieldHandler(func(maskNames map[uint32]string) {
masks := make([]uint32, 0, len(maskNames))
for mask := range maskNames {
masks = append(masks, mask)
}
sort.Slice(masks, func(indexA, indexB int) bool { return masks[indexA] < masks[indexB] })
addMaskedItem := func(mask uint32) {
maxValue := mask
shift := 0
maskedLabel := key + "." + maskNames[mask] + "###" + label + "-" + maskNames[mask]
for (maxValue & 1) == 0 {
shift++
maxValue >>= 1
}
if maxValue == 1 {
booleanUnifier := NewUnifier()
if unifier.IsUnique() {
booleanUnifier.Add((uint32(unifier.Unified().(int32)) & mask) != 0)
}
RenderUnifiedCheckboxCombo(readOnly, maskedLabel, booleanUnifier,
func(newValue bool) {
updater(func(oldValue uint32) uint32 {
result := oldValue & ^mask
if newValue {
result |= mask
}
return result
})
})
} else {
RenderUnifiedSliderInt(readOnly, maskedLabel, unifier,
func(u Unifier) int { return int((uint32(u.Unified().(int32)) & mask) >> uint32(shift)) },
func(value int) string { return "%d" },
0, int(maxValue),
func(newValue int) {
updater(func(oldValue uint32) uint32 {
return (oldValue & ^mask) | (uint32(newValue) << uint32(shift))
})
})
}
}
for _, mask := range masks {
addMaskedItem(mask)
}
})
simplifier.SetRotationHandler(func(minValue, maxValue int64) {
RenderUnifiedRotation(readOnly, label, unifier,
int(minValue), int(maxValue),
RotationInfo{
// This code is specialized for Hacker-Yaw. To re-use it for other rotations, details must change.
Horizontal: true,
Positive: true,
Clockwise: false,
},
func(newValue int) {
updater(func(oldValue uint32) uint32 { return uint32(newValue) })
})
})
simplifier.SetSpecialHandler("ObjectTriple", func() {
var classNames [object.ClassCount]string
for index, class := range object.Classes() {
classNames[index] = class.String()
}
tripleResolver := func(u Unifier) object.Triple { return object.TripleFromInt(int(u.Unified().(int32))) }
RenderUnifiedCombo(readOnly, key+"-Class###"+fullKey+"-Class", unifier,
func(u Unifier) int {
triple := tripleResolver(u)
return int(triple.Class)
},
func(value int) string { return fmt.Sprintf("%2d: %v", value, object.Class(value)) },
object.ClassCount,
func(newValue int) {
triple := object.TripleFrom(newValue, 0, 0)
updater(func(oldValue uint32) uint32 { return uint32(triple.Int()) })
})
objTypeRenderer.Render(readOnly, key+"###"+fullKey+"-Type", unifier, unifier,
tripleResolver,
func(newValue object.Triple) {
updater(func(oldValue uint32) uint32 { return uint32(newValue.Int()) })
})
})
simplifier.SetSpecialHandler("Mistake", func() {})
simplifier.SetSpecialHandler("Ignored", func() {})
simplifier.SetSpecialHandler("Unknown", func() {})
simplifier.SetSpecialHandler("Internal", func() {})
return simplifier
}
@@ -0,0 +1,71 @@
package values
import (
"github.com/inkyblackness/imgui-go/v3"
"github.com/inkyblackness/hacked/ss1/content/object"
"github.com/inkyblackness/hacked/ss1/content/text"
"github.com/inkyblackness/hacked/ss1/resource"
"github.com/inkyblackness/hacked/ss1/world/ids"
)
// ObjectTypeControlRenderer renders a control to select an object.
type ObjectTypeControlRenderer struct {
// Meta describes the layout of classes, subclasses, and types.
Meta object.PropertiesTable
// TextCache is used to retrieve the name of a type.
TextCache *text.Cache
}
// Render creates the controls according to the given parameters.
func (renderer ObjectTypeControlRenderer) Render(readOnly bool, label string,
classUnifier Unifier, typeUnifier Unifier,
tripleResolver func(Unifier) object.Triple,
changeHandler func(object.Triple)) {
switch {
case classUnifier.IsUnique():
class := tripleResolver(classUnifier).Class
triples := renderer.Meta.TriplesInClass(class)
selectedIndex := -1
if typeUnifier.IsUnique() {
triple := tripleResolver(typeUnifier)
for index, availableTriple := range triples {
if availableTriple == triple {
selectedIndex = index
}
}
if selectedIndex < 0 {
selectedIndex = len(triples)
triples = append(triples, triple)
}
}
RenderUnifiedCombo(readOnly, label, typeUnifier,
func(u Unifier) int { return selectedIndex },
func(value int) string {
triple := triples[value]
return renderer.tripleName(triple)
},
len(triples),
func(newValue int) {
triple := triples[newValue]
changeHandler(triple)
})
case !classUnifier.IsEmpty():
imgui.LabelText(label, "(multiple classes)")
default:
imgui.LabelText(label, "")
}
}
func (renderer ObjectTypeControlRenderer) tripleName(triple object.Triple) string {
suffix := "???"
linearIndex := renderer.Meta.TripleIndex(triple)
if (linearIndex >= 0) && (renderer.TextCache != nil) {
key := resource.KeyOf(ids.ObjectLongNames, resource.LangDefault, linearIndex)
objName, err := renderer.TextCache.Text(key)
if err == nil {
suffix = objName
}
}
return triple.String() + ": " + suffix
}
+156
View File
@@ -0,0 +1,156 @@
package values
import (
"fmt"
"image/color"
"math"
"github.com/inkyblackness/imgui-go/v3"
"github.com/inkyblackness/hacked/ui/gui"
)
const (
hintMultiple = "(multiple)"
hintYes = "Yes"
hintNo = "No"
)
// RenderUnifiedSliderInt renders a control for an unified integer value.
func RenderUnifiedSliderInt(readOnly bool, label string, unifier Unifier,
intConverter func(Unifier) int, formatter func(int) string, min, max int, changeHandler func(int)) {
var labelValue string
var selectedString string
selectedValue := -1
if unifier.IsUnique() {
selectedValue = intConverter(unifier)
selectedString = formatter(selectedValue)
labelValue = fmt.Sprintf(selectedString, selectedValue)
} else if !unifier.IsEmpty() {
selectedString = hintMultiple
labelValue = selectedString
}
if readOnly {
imgui.LabelText(label, labelValue)
} else if gui.StepSliderIntV(label, &selectedValue, min, max, selectedString) {
changeHandler(selectedValue)
}
}
// RenderUnifiedCombo renders a control for a unified enumeration value.
func RenderUnifiedCombo(readOnly bool, label string, unifier Unifier,
intConverter func(Unifier) int, formatter func(int) string, count int, changeHandler func(int)) {
var selectedString string
selectedIndex := -1
if unifier.IsUnique() {
selectedIndex = intConverter(unifier)
selectedString = formatter(selectedIndex)
} else if !unifier.IsEmpty() {
selectedString = hintMultiple
}
if readOnly {
imgui.LabelText(label, selectedString)
} else if imgui.BeginCombo(label, selectedString) {
for i := 0; i < count; i++ {
entryString := formatter(i)
if imgui.SelectableV(entryString, i == selectedIndex, 0, imgui.Vec2{}) {
changeHandler(i)
}
}
imgui.EndCombo()
}
}
// RenderUnifiedCheckboxCombo renders a control for a unified boolean value.
func RenderUnifiedCheckboxCombo(readOnly bool, label string, unifier Unifier, changeHandler func(bool)) {
selectedString := ""
if unifier.IsUnique() {
selectedValue := unifier.Unified().(bool)
if selectedValue {
selectedString = hintYes
} else {
selectedString = hintNo
}
} else if !unifier.IsEmpty() {
selectedString = hintMultiple
}
if readOnly {
imgui.LabelText(label, selectedString)
} else if imgui.BeginCombo(label, selectedString) {
for i, text := range []string{hintNo, hintYes} {
if imgui.SelectableV(text, text == selectedString, 0, imgui.Vec2{}) {
changeHandler(i != 0)
}
}
imgui.EndCombo()
}
}
// RotationInfo describes how the rotation is aligned in its zero position.
type RotationInfo struct {
Horizontal bool
Positive bool
Clockwise bool
}
// RenderUnifiedRotation renders a control for rotation value.
func RenderUnifiedRotation(readOnly bool, label string, unifier Unifier,
min, max int, info RotationInfo, changeHandler func(int)) {
calcValuePercent := func(value int) float64 {
valueRange := (max - min) + 1
return float64(value) / float64(valueRange)
}
RenderUnifiedSliderInt(readOnly, label, unifier,
func(u Unifier) int {
unifiedValue := u.Unified().(int32)
return int(unifiedValue)
},
func(value int) string {
valuePercent := calcValuePercent(value)
result := fmt.Sprintf("%3.02f° - raw: %%d", valuePercent*360.0)
return result
},
min, max,
changeHandler)
if (imgui.IsItemFocused() || imgui.IsItemActive() || imgui.IsItemHovered()) && unifier.IsUnique() {
valuePercent := calcValuePercent(int(unifier.Unified().(int32)))
valueRadian := valuePercent * (math.Pi * 2)
lineColor := imgui.Packed(color.RGBA{R: 0x21, G: 0xFF, B: 0x43, A: 0xFF}) // color copied from style.
lineThickness := float32(4.0)
imgui.BeginTooltip()
imgui.Dummy(imgui.Vec2{X: imgui.TextLineHeightWithSpacing() * 2, Y: imgui.TextLineHeightWithSpacing() * 2})
dl := imgui.WindowDrawList()
winTopLeft := imgui.WindowPos()
winSize := imgui.WindowSize()
center := winTopLeft.Plus(winSize.Times(0.5))
circleRadius := ((winSize.X / 2) * 5) / 6
dl.AddCircleV(center, circleRadius, lineColor, 100, lineThickness)
targetX := 0.0
targetY := 0.0
if info.Horizontal {
targetX = 1.0
if !info.Positive {
targetX = -1.0
}
} else {
targetY = -1.0
if !info.Positive {
targetY = 1.0
}
}
var target imgui.Vec2
if info.Clockwise {
target.X = center.X - float32((float64(circleRadius)*targetX)*math.Cos(valueRadian)+(float64(circleRadius)*targetY)*math.Sin(valueRadian))
target.Y = center.Y + float32((float64(circleRadius)*targetY)*math.Cos(valueRadian)-(float64(circleRadius)*targetX)*math.Sin(valueRadian))
} else {
target.X = center.X + float32((float64(circleRadius)*targetX)*math.Cos(valueRadian)-(float64(circleRadius)*targetY)*math.Sin(valueRadian))
target.Y = center.Y - float32((float64(circleRadius)*targetY)*math.Cos(valueRadian)+(float64(circleRadius)*targetX)*math.Sin(valueRadian))
}
dl.AddLineV(center, target, lineColor, lineThickness)
imgui.EndTooltip()
}
}
+107
View File
@@ -0,0 +1,107 @@
package values
// Unifier can be used to verify whether a list of values has equal entries.
type Unifier struct {
state unifierState
}
// NewUnifier returns a new instance.
func NewUnifier() Unifier {
return Unifier{state: unifierInitState{}}
}
// UnifierFor returns a unifier for a single value.
func UnifierFor(value interface{}) Unifier {
unifier := NewUnifier()
unifier.Add(value)
return unifier
}
// Add unifies the given value to the current state.
// The given value must be comparable.
func (u *Unifier) Add(value interface{}) {
u.state = u.state.add(value)
}
// Unified returns the result of the unification.
// If all values that were added to the unifier were equal, then the first
// added value will be returned. Otherwise, nil will be returned.
func (u *Unifier) Unified() interface{} {
return u.state.unified()
}
// IsUnique returns true if the unifier has received only equal values.
func (u Unifier) IsUnique() bool {
return u.state.isUnique()
}
// IsEmpty returns true if the unifier has not received any value.
func (u Unifier) IsEmpty() bool {
return (u.state == nil) || u.state.isEmpty()
}
type unifierState interface {
add(value interface{}) unifierState
unified() interface{}
isUnique() bool
isEmpty() bool
}
type unifierInitState struct{}
func (state unifierInitState) add(value interface{}) unifierState {
return unifierMatchedState{value: value}
}
func (state unifierInitState) unified() interface{} {
return nil
}
func (state unifierInitState) isUnique() bool {
return false
}
func (state unifierInitState) isEmpty() bool {
return true
}
type unifierMatchedState struct {
value interface{}
}
func (state unifierMatchedState) add(value interface{}) unifierState {
if state.value == value {
return state
}
return unifierMismatchedState{}
}
func (state unifierMatchedState) unified() interface{} {
return state.value
}
func (state unifierMatchedState) isUnique() bool {
return true
}
func (state unifierMatchedState) isEmpty() bool {
return false
}
type unifierMismatchedState struct{}
func (state unifierMismatchedState) add(value interface{}) unifierState {
return state
}
func (state unifierMismatchedState) unified() interface{} {
return nil
}
func (state unifierMismatchedState) isUnique() bool {
return false
}
func (state unifierMismatchedState) isEmpty() bool {
return false
}
@@ -0,0 +1,84 @@
package values_test
import (
"fmt"
"testing"
"github.com/inkyblackness/hacked/editor/values"
"github.com/stretchr/testify/assert"
)
func TestUnifier_Integers(t *testing.T) {
tt := []struct {
values []int
expectedValue interface{}
expectedUnified bool
}{
{[]int{1, 1, 1}, 1, true},
{[]int{}, nil, false},
{[]int{1, 2, 3}, nil, false},
}
for tn, tc := range tt {
u := values.NewUnifier()
for _, value := range tc.values {
u.Add(value)
}
resultValue := u.Unified()
resultUnified := u.IsUnique()
assert.Equal(t, tc.expectedValue, resultValue, fmt.Sprintf("Wrong result for test number %d", tn))
assert.Equal(t, tc.expectedUnified, resultUnified, fmt.Sprintf("Wrong unified for test number %d", tn))
}
}
func TestUnifier_Strings(t *testing.T) {
tt := []struct {
values []string
expectedValue interface{}
expectedUnified bool
}{
{[]string{"a", "a", "a"}, "a", true},
{[]string{}, nil, false},
{[]string{"a", "b", "c"}, nil, false},
}
for tn, tc := range tt {
u := values.NewUnifier()
for _, value := range tc.values {
u.Add(value)
}
resultValue := u.Unified()
resultUnified := u.IsUnique()
assert.Equal(t, tc.expectedValue, resultValue, fmt.Sprintf("Wrong result for test number %d", tn))
assert.Equal(t, tc.expectedUnified, resultUnified, fmt.Sprintf("Wrong unified for test number %d", tn))
}
}
func TestUnifier_Types(t *testing.T) {
type simpleType struct {
value int
text string
}
tt := []struct {
values []simpleType
expectedValue interface{}
expectedUnified bool
}{
{[]simpleType{{value: 1, text: "a"}, {value: 1, text: "a"}, {value: 1, text: "a"}}, simpleType{value: 1, text: "a"}, true},
{[]simpleType{}, nil, false},
{[]simpleType{{value: 1, text: "a"}, {value: 1, text: "a"}, {value: 2, text: "a"}}, nil, false},
{[]simpleType{{value: 1, text: "a"}, {value: 1, text: "a"}, {value: 1, text: "b"}}, nil, false},
}
for tn, tc := range tt {
u := values.NewUnifier()
for _, value := range tc.values {
u.Add(value)
}
resultValue := u.Unified()
resultUnified := u.IsUnique()
assert.Equal(t, tc.expectedValue, resultValue, fmt.Sprintf("Wrong result for test number %d", tn))
assert.Equal(t, tc.expectedUnified, resultUnified, fmt.Sprintf("Wrong unified for test number %d", tn))
}
}