Files
questshock/modding/src/hacked/editor/objects/View.go
T
2026-09-26 15:20:07 +02:00

711 lines
24 KiB
Go

package objects
import (
"fmt"
"strings"
"github.com/inkyblackness/imgui-go/v3"
"github.com/inkyblackness/hacked/editor/external"
"github.com/inkyblackness/hacked/editor/graphics"
"github.com/inkyblackness/hacked/editor/render"
"github.com/inkyblackness/hacked/editor/values"
"github.com/inkyblackness/hacked/ss1/content/bitmap"
"github.com/inkyblackness/hacked/ss1/content/interpreters"
"github.com/inkyblackness/hacked/ss1/content/object"
"github.com/inkyblackness/hacked/ss1/content/object/objprop"
"github.com/inkyblackness/hacked/ss1/content/text"
"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"
)
// View provides edit controls for game objects.
type View struct {
mod *world.Mod
textCache *text.Cache
cp text.Codepage
imageCache *graphics.TextureCache
paletteCache *graphics.PaletteCache
modalStateMachine gui.ModalStateMachine
clipboard external.Clipboard
guiScale float32
commander cmd.Commander
model viewModel
}
// NewView returns a new instance.
func NewView(mod *world.Mod, textCache *text.Cache, cp text.Codepage,
imageCache *graphics.TextureCache, paletteCache *graphics.PaletteCache,
modalStateMachine gui.ModalStateMachine,
clipboard external.Clipboard, guiScale float32, commander cmd.Commander) *View {
view := &View{
mod: mod,
textCache: textCache,
cp: cp,
imageCache: imageCache,
paletteCache: paletteCache,
modalStateMachine: modalStateMachine,
clipboard: clipboard,
guiScale: guiScale,
commander: commander,
model: freshViewModel(),
}
return view
}
// WindowOpen returns the flag address, to be used with the main menu.
func (view *View) WindowOpen() *bool {
return &view.model.windowOpen
}
// Render renders the view.
func (view *View) Render() {
if view.model.restoreFocus {
imgui.SetNextWindowFocus()
view.model.restoreFocus = false
view.model.windowOpen = true
}
if view.model.windowOpen {
imgui.SetNextWindowSizeV(imgui.Vec2{X: 800 * view.guiScale, Y: 600 * view.guiScale}, imgui.ConditionFirstUseEver)
if imgui.BeginV("Game Objects", view.WindowOpen(), imgui.WindowFlagsNoCollapse|imgui.WindowFlagsHorizontalScrollbar) {
view.renderContent()
}
imgui.End()
}
}
func (view *View) renderContent() {
if imgui.BeginChildV("Properties", imgui.Vec2{X: -330 * view.guiScale, Y: 0}, false, imgui.WindowFlagsHorizontalScrollbar) {
imgui.PushItemWidth(-260 * view.guiScale)
classString := func(class object.Class) string {
return fmt.Sprintf("%2d: %v", int(class), class)
}
if imgui.BeginCombo("Object Class", classString(view.model.currentObject.Class)) {
for _, class := range object.Classes() {
if imgui.SelectableV(classString(class), class == view.model.currentObject.Class, 0, imgui.Vec2{}) {
view.model.currentObject = object.TripleFrom(int(class), 0, 0)
view.model.currentBitmap = 0
}
}
imgui.EndCombo()
}
if imgui.BeginCombo("Object Type", view.tripleName(view.model.currentObject)) {
allTypes := view.mod.ObjectProperties().TriplesInClass(view.model.currentObject.Class)
for _, triple := range allTypes {
if imgui.SelectableV(view.tripleName(triple), triple == view.model.currentObject, 0, imgui.Vec2{}) {
view.model.currentObject = triple
view.model.currentBitmap = 0
}
}
imgui.EndCombo()
}
readOnly := !view.mod.HasModifiableObjectProperties()
properties, propErr := view.mod.ObjectProperties().ForObject(view.model.currentObject)
imgui.Separator()
bitmapLimit := 0
if propErr == nil {
bitmapLimit = int(properties.Common.Bitmap3D.FrameNumber() + 2)
}
gui.StepSliderInt("Bitmap", &view.model.currentBitmap, 0, bitmapLimit)
render.TextureSelector("BitmapSelector", -1, view.guiScale, bitmapLimit+1, view.model.currentBitmap,
view.imageCache, view.currentBitmapKeyFor, func(int) string { return "" }, func(newValue int) {
view.model.currentBitmap = newValue
})
imgui.Separator()
if imgui.BeginCombo("Language", view.model.currentLang.String()) {
languages := resource.Languages()
for _, lang := range languages {
if imgui.SelectableV(lang.String(), lang == view.model.currentLang, 0, imgui.Vec2{}) {
view.model.currentLang = lang
}
}
imgui.EndCombo()
}
view.renderText(false, "Long Name",
view.objectName(view.model.currentObject, view.model.currentLang, true),
func(newValue string) {
view.requestSetObjectName(view.model.currentObject, true, newValue)
})
view.renderText(false, "Short Name",
view.objectName(view.model.currentObject, view.model.currentLang, false),
func(newValue string) {
view.requestSetObjectName(view.model.currentObject, false, newValue)
})
if propErr == nil {
if imgui.TreeNodeV("Common Properties", imgui.TreeNodeFlagsDefaultOpen|imgui.TreeNodeFlagsFramed) {
view.renderCommonProperties(readOnly, properties)
imgui.TreePop()
}
if imgui.TreeNodeV("Generic Properties", imgui.TreeNodeFlagsFramed) {
view.renderGenericProperties(readOnly, properties)
imgui.TreePop()
}
if imgui.TreeNodeV("Specific Properties", imgui.TreeNodeFlagsFramed) {
view.renderSpecificProperties(readOnly, properties)
imgui.TreePop()
}
} else {
imgui.Text("(properties unavailable)")
}
imgui.PopItemWidth()
}
imgui.EndChild()
imgui.SameLine()
imgui.BeginGroup()
view.renderObjectBitmap()
imgui.EndGroup()
}
func (view *View) renderText(readOnly bool, label string, value string, changeCallback func(string)) {
imgui.LabelText(label, value)
view.clipboardPopup(readOnly, label, value, changeCallback)
}
func (view *View) tripleName(triple object.Triple) string {
return triple.String() + ": " + view.objectName(triple, resource.LangDefault, true)
}
func (view *View) objectName(triple object.Triple, lang resource.Language, longName bool) string {
result := "???"
linearIndex := view.mod.ObjectProperties().TripleIndex(triple)
if linearIndex >= 0 {
nameID := ids.ObjectShortNames
if longName {
nameID = ids.ObjectLongNames
}
key := resource.KeyOf(nameID, lang, linearIndex)
objName, err := view.textCache.Text(key)
if err == nil {
result = objName
}
}
return result
}
func (view *View) clipboardPopup(readOnly bool, label string, value string, changeCallback func(string)) {
if imgui.BeginPopupContextItemV(label+"-Popup", 1) {
if imgui.Selectable("Copy to Clipboard") {
view.clipboard.SetString(value)
}
if !readOnly && imgui.Selectable("Copy from Clipboard") {
newValue, err := view.clipboard.String()
if err == nil {
changeCallback(newValue)
}
}
imgui.EndPopup()
}
}
func (view *View) requestSetObjectName(triple object.Triple, longName bool, newValue string) {
linearIndex := view.mod.ObjectProperties().TripleIndex(triple)
if linearIndex >= 0 {
id := ids.ObjectShortNames
if longName {
id = ids.ObjectLongNames
}
key := resource.KeyOf(id, view.model.currentLang, linearIndex)
oldValue, _ := view.textCache.Text(key)
if oldValue != newValue {
command := setObjectTextCommand{
model: &view.model,
triple: view.model.currentObject,
bitmap: view.model.currentBitmap,
key: key,
oldData: view.cp.Encode(oldValue),
newData: view.cp.Encode(text.Blocked(newValue)[0]),
}
view.commander.Queue(command)
}
}
}
func (view *View) requestSetObjectProperties(modifier func(*object.Properties)) {
command := setObjectPropertiesCommand{
model: &view.model,
triple: view.model.currentObject,
bitmap: view.model.currentBitmap,
}
currentProp, err := view.mod.ObjectProperties().ForObject(command.triple)
if err != nil {
return
}
command.oldProperties = currentProp.Clone()
command.newProperties = currentProp.Clone()
modifier(&command.newProperties)
view.commander.Queue(command)
}
func (view *View) renderCommonProperties(readOnly bool, properties *object.Properties) {
intIdentity := func(u values.Unifier) int { return u.Unified().(int) }
intFormat := func(value int) string { return "%d" }
if imgui.TreeNode("Flags") {
renderFlag := func(flag object.CommonFlag) {
flagUnifier := values.NewUnifier()
flagUnifier.Add(properties.Common.Flags.Has(flag))
values.RenderUnifiedCheckboxCombo(readOnly, flag.String(), flagUnifier, func(newValue bool) {
view.requestSetObjectProperties(func(prop *object.Properties) {
if newValue {
prop.Common.Flags = prop.Common.Flags.With(flag)
} else {
prop.Common.Flags = prop.Common.Flags.Without(flag)
}
})
})
}
for _, flag := range object.CommonFlags() {
renderFlag(flag)
}
imgui.TreePop()
}
lightTypes := object.LightTypes()
lightTypeUnifier := values.NewUnifier()
lightTypeUnifier.Add(int(properties.Common.Flags.LightType()))
values.RenderUnifiedCombo(readOnly, "LightType", lightTypeUnifier, intIdentity,
func(value int) string {
return object.LightType(value).String()
}, len(lightTypes), func(newValue int) {
view.requestSetObjectProperties(func(prop *object.Properties) {
prop.Common.Flags = prop.Common.Flags.WithLightType(object.LightType(newValue))
})
})
useModeUnifier := values.NewUnifier()
useModeUnifier.Add(int(properties.Common.Flags.UseMode()))
values.RenderUnifiedCombo(readOnly, "UseMode", useModeUnifier, intIdentity,
func(value int) string {
return object.UseMode(value).String()
}, 4, func(newValue int) {
view.requestSetObjectProperties(func(prop *object.Properties) {
prop.Common.Flags = prop.Common.Flags.WithUseMode(object.UseMode(newValue))
})
})
massUnifier := values.NewUnifier()
massUnifier.Add(int(properties.Common.Mass))
values.RenderUnifiedSliderInt(readOnly, "Mass", massUnifier, intIdentity, intFormat, -1, 5000,
func(newValue int) {
view.requestSetObjectProperties(func(prop *object.Properties) {
prop.Common.Mass = int32(newValue)
})
})
hitpointsUnifier := values.NewUnifier()
hitpointsUnifier.Add(int(properties.Common.Hitpoints))
values.RenderUnifiedSliderInt(readOnly, "Hitpoints", hitpointsUnifier, intIdentity, intFormat, 0, 10000,
func(newValue int) {
view.requestSetObjectProperties(func(prop *object.Properties) {
prop.Common.Hitpoints = int16(newValue)
})
})
armorUnifier := values.NewUnifier()
armorUnifier.Add(int(properties.Common.Armor))
values.RenderUnifiedSliderInt(readOnly, "Armor", armorUnifier, intIdentity, intFormat, 0, 255,
func(newValue int) {
view.requestSetObjectProperties(func(prop *object.Properties) {
prop.Common.Armor = byte(newValue)
})
})
renderTypeUnifier := values.NewUnifier()
renderTypeUnifier.Add(int(properties.Common.RenderType))
values.RenderUnifiedCombo(readOnly, "Render Type", renderTypeUnifier, intIdentity,
func(value int) string { return object.RenderType(value).String() },
len(object.RenderTypes()), func(newValue int) {
view.requestSetObjectProperties(func(prop *object.Properties) {
prop.Common.RenderType = object.RenderType(newValue)
})
})
physicsModelUnifier := values.NewUnifier()
physicsModelUnifier.Add(int(properties.Common.PhysicsModel))
values.RenderUnifiedCombo(readOnly, "Physics Model", physicsModelUnifier, intIdentity,
func(value int) string { return object.PhysicsModel(value).String() },
len(object.PhysicsModels()), func(newValue int) {
view.requestSetObjectProperties(func(prop *object.Properties) {
prop.Common.PhysicsModel = object.PhysicsModel(newValue)
})
})
hardnessUnifier := values.NewUnifier()
hardnessUnifier.Add(int(properties.Common.Hardness))
values.RenderUnifiedSliderInt(readOnly, "Hardness", hardnessUnifier, intIdentity, intFormat, 0, object.HardnessLimit,
func(newValue int) {
view.requestSetObjectProperties(func(prop *object.Properties) {
prop.Common.Hardness = byte(newValue)
})
})
physicsXRUnifier := values.NewUnifier()
physicsXRUnifier.Add(int(properties.Common.PhysicsXR))
values.RenderUnifiedSliderInt(readOnly, "Physics XR", physicsXRUnifier, intIdentity, intFormat, 0, object.PhysicsXRLimit,
func(newValue int) {
view.requestSetObjectProperties(func(prop *object.Properties) {
prop.Common.PhysicsXR = byte(newValue)
})
})
physicsZUnifier := values.NewUnifier()
physicsZUnifier.Add(int(properties.Common.PhysicsZ))
values.RenderUnifiedSliderInt(readOnly, "Physics Z", physicsZUnifier, intIdentity, intFormat, 0, 255,
func(newValue int) {
view.requestSetObjectProperties(func(prop *object.Properties) {
prop.Common.PhysicsZ = byte(newValue)
})
})
if imgui.TreeNode("Vulnerabilities") {
renderVulnerability := func(damageType object.DamageType) {
damageUnifier := values.NewUnifier()
damageUnifier.Add(properties.Common.Vulnerabilities.Has(damageType))
values.RenderUnifiedCheckboxCombo(readOnly, damageType.String(), damageUnifier, func(newValue bool) {
view.requestSetObjectProperties(func(prop *object.Properties) {
if newValue {
prop.Common.Vulnerabilities = prop.Common.Vulnerabilities.With(damageType)
} else {
prop.Common.Vulnerabilities = prop.Common.Vulnerabilities.Without(damageType)
}
})
})
}
for _, damageType := range object.DamageTypes() {
renderVulnerability(damageType)
}
primaryUnifier := values.NewUnifier()
primaryUnifier.Add(properties.Common.SpecialVulnerabilities.PrimaryValue())
values.RenderUnifiedSliderInt(readOnly, "Primary (double dmg)", primaryUnifier, intIdentity, intFormat, 0, object.SpecialDamageTypeLimit,
func(newValue int) {
view.requestSetObjectProperties(func(prop *object.Properties) {
prop.Common.SpecialVulnerabilities = prop.Common.SpecialVulnerabilities.WithPrimaryValue(newValue)
})
})
superUnifier := values.NewUnifier()
superUnifier.Add(properties.Common.SpecialVulnerabilities.SuperValue())
values.RenderUnifiedSliderInt(readOnly, "Super (quad dmg)", superUnifier, intIdentity, intFormat, 0, object.SpecialDamageTypeLimit,
func(newValue int) {
view.requestSetObjectProperties(func(prop *object.Properties) {
prop.Common.SpecialVulnerabilities = prop.Common.SpecialVulnerabilities.WithSuperValue(newValue)
})
})
imgui.TreePop()
}
defenseUnifier := values.NewUnifier()
defenseUnifier.Add(int(properties.Common.Defense))
values.RenderUnifiedSliderInt(readOnly, "Defense", defenseUnifier, intIdentity,
func(value int) string {
if value == object.DefenseNoCriticals {
return "%d - (no criticals)"
}
return "%d"
}, 0, 255,
func(newValue int) {
view.requestSetObjectProperties(func(prop *object.Properties) {
prop.Common.Defense = byte(newValue)
})
})
toughnessUnifier := values.NewUnifier()
toughnessUnifier.Add(int(properties.Common.Toughness))
values.RenderUnifiedSliderInt(readOnly, "Toughness", toughnessUnifier, intIdentity,
func(value int) string {
if value == object.ToughnessNoDamage {
return "(no damage) -- raw: %d"
}
return fmt.Sprintf("%d:1 dmg -- raw: %%d", 1<<uint(value))
},
0, 7,
func(newValue int) {
view.requestSetObjectProperties(func(prop *object.Properties) {
prop.Common.Toughness = byte(newValue)
})
})
mfdOrMeshIDUnifier := values.NewUnifier()
mfdOrMeshIDUnifier.Add(int(properties.Common.MfdOrMeshID))
values.RenderUnifiedSliderInt(readOnly, "MFD/Mesh ID", mfdOrMeshIDUnifier, intIdentity, intFormat, 0, 1000,
func(newValue int) {
view.requestSetObjectProperties(func(prop *object.Properties) {
prop.Common.MfdOrMeshID = uint16(newValue)
})
})
bitmap3DBitmapNumUnifier := values.NewUnifier()
bitmap3DBitmapNumUnifier.Add(int(properties.Common.Bitmap3D.BitmapNumber()))
values.RenderUnifiedSliderInt(readOnly, "Bitmap Number", bitmap3DBitmapNumUnifier, intIdentity, intFormat, 0, int(object.Bitmap3DBitmapNumberLimit),
func(newValue int) {
view.requestSetObjectProperties(func(prop *object.Properties) {
prop.Common.Bitmap3D = prop.Common.Bitmap3D.WithBitmapNumber(uint16(newValue))
})
})
bitmap3DFrameNumUnifier := values.NewUnifier()
bitmap3DFrameNumUnifier.Add(int(properties.Common.Bitmap3D.FrameNumber()))
values.RenderUnifiedSliderInt(readOnly, "Frame Number", bitmap3DFrameNumUnifier, intIdentity, intFormat, 0, int(object.Bitmap3DFrameNumberLimit),
func(newValue int) {
view.requestSetObjectProperties(func(prop *object.Properties) {
prop.Common.Bitmap3D = prop.Common.Bitmap3D.WithFrameNumber(uint16(newValue))
})
})
bitmap3DAnimUnifier := values.NewUnifier()
bitmap3DAnimUnifier.Add(properties.Common.Bitmap3D.Animation())
values.RenderUnifiedCheckboxCombo(readOnly, "Animation", bitmap3DAnimUnifier,
func(newValue bool) {
view.requestSetObjectProperties(func(prop *object.Properties) {
prop.Common.Bitmap3D = prop.Common.Bitmap3D.WithAnimation(newValue)
})
})
bitmap3DRepeatUnifier := values.NewUnifier()
bitmap3DRepeatUnifier.Add(properties.Common.Bitmap3D.Repeat())
values.RenderUnifiedCheckboxCombo(readOnly, "Repeat", bitmap3DRepeatUnifier,
func(newValue bool) {
view.requestSetObjectProperties(func(prop *object.Properties) {
prop.Common.Bitmap3D = prop.Common.Bitmap3D.WithRepeat(newValue)
})
})
destroyEffectValueUnifier := values.NewUnifier()
destroyEffectValueUnifier.Add(int(properties.Common.DestroyEffect.Value()))
values.RenderUnifiedSliderInt(readOnly, "DestroyEffect Value", destroyEffectValueUnifier,
intIdentity, intFormat, 0, int(object.DestroyEffectValueLimit),
func(newValue int) {
view.requestSetObjectProperties(func(prop *object.Properties) {
prop.Common.DestroyEffect = prop.Common.DestroyEffect.WithValue(byte(newValue))
})
})
destroyEffectPlaySoundUnifier := values.NewUnifier()
destroyEffectPlaySoundUnifier.Add(properties.Common.DestroyEffect.PlaySound())
values.RenderUnifiedCheckboxCombo(readOnly, "DestroyEffect PlaySound", destroyEffectPlaySoundUnifier,
func(newValue bool) {
view.requestSetObjectProperties(func(prop *object.Properties) {
prop.Common.DestroyEffect = prop.Common.DestroyEffect.WithSound(newValue)
})
})
destroyEffectShowExplosionUnifier := values.NewUnifier()
destroyEffectShowExplosionUnifier.Add(properties.Common.DestroyEffect.ShowExplosion())
values.RenderUnifiedCheckboxCombo(readOnly, "DestroyEffect ShowExplosion", destroyEffectShowExplosionUnifier,
func(newValue bool) {
view.requestSetObjectProperties(func(prop *object.Properties) {
prop.Common.DestroyEffect = prop.Common.DestroyEffect.WithExplosion(newValue)
})
})
}
func (view *View) renderGenericProperties(readOnly bool, properties *object.Properties) {
readInterpreter := objprop.GenericProperties(view.model.currentObject.Class, properties.Generic)
view.createPropertyControls(readOnly, readInterpreter, func(key string, modifier func(uint32) uint32) {
view.requestSetObjectProperties(func(prop *object.Properties) {
writeInterpreter := objprop.GenericProperties(view.model.currentObject.Class, prop.Generic)
view.setInterpreterValueKeyed(writeInterpreter, key, modifier)
})
})
}
func (view *View) renderSpecificProperties(readOnly bool, properties *object.Properties) {
readInterpreter := objprop.SpecificProperties(view.model.currentObject, properties.Specific)
view.createPropertyControls(readOnly, readInterpreter, func(key string, modifier func(uint32) uint32) {
view.requestSetObjectProperties(func(prop *object.Properties) {
writeInterpreter := objprop.SpecificProperties(view.model.currentObject, prop.Specific)
view.setInterpreterValueKeyed(writeInterpreter, key, modifier)
})
})
}
func (view *View) createPropertyControls(readOnly bool, rootInterpreter *interpreters.Instance,
updater func(string, func(uint32) uint32)) {
objTypeRenderer := values.ObjectTypeControlRenderer{
Meta: view.mod.ObjectProperties(),
TextCache: view.textCache,
}
var processInterpreter func(string, *interpreters.Instance)
processInterpreter = func(path string, interpreter *interpreters.Instance) {
for _, key := range interpreter.Keys() {
fullKey := path + key
unifier := values.NewUnifier()
unifier.Add(int32(interpreter.Get(key)))
simplifier := values.StandardSimplifier(readOnly, fullKey, unifier,
func(modifier func(uint32) uint32) {
updater(fullKey, modifier)
}, objTypeRenderer)
interpreter.Describe(key, simplifier)
}
for _, key := range interpreter.ActiveRefinements() {
fullKey := path + key
if len(fullKey) > 0 {
imgui.Separator()
imgui.Text(fullKey + ":")
}
processInterpreter(fullKey+".", interpreter.Refined(key))
}
}
processInterpreter("", rootInterpreter)
}
func (view *View) setInterpreterValueKeyed(instance *interpreters.Instance, key string, modifier func(uint32) uint32) {
resolvedInterpreter := instance
keys := strings.Split(key, ".")
keyCount := len(keys)
if len(keys) > 1 {
for _, subKey := range keys[:keyCount-1] {
resolvedInterpreter = resolvedInterpreter.Refined(subKey)
}
}
resolvedInterpreter.Set(keys[keyCount-1], modifier(resolvedInterpreter.Get(keys[keyCount-1])))
}
func (view *View) renderObjectBitmap() {
render.TextureImage("BitmapImage", view.imageCache, view.currentBitmapKey(), imgui.Vec2{X: 320 * view.guiScale, Y: 240 * view.guiScale})
if imgui.Button("Clear") {
view.requestClearBitmap()
}
imgui.SameLine()
if imgui.Button("Import") {
view.requestImportBitmap()
}
imgui.SameLine()
if imgui.Button("Export") {
view.requestExportBitmap()
}
if view.hasModCurrentBitmap() {
imgui.SameLine()
if imgui.Button("Remove") {
view.requestSetBitmapData(nil)
}
}
}
func (view *View) requestClearBitmap() {
bmp := bitmap.Bitmap{
Header: bitmap.Header{
Width: 1,
Height: 1,
},
Pixels: []byte{0x00},
}
view.requestSetBitmap(bmp)
}
func (view *View) requestImportBitmap() {
paletteRetriever := func() (bitmap.Palette, error) {
palette, err := view.paletteCache.Palette(0)
if err != nil {
return bitmap.Palette{}, err
}
return palette.Palette(), nil
}
external.ImportImage(view.modalStateMachine, paletteRetriever, func(bmp bitmap.Bitmap) {
view.requestSetBitmap(bmp)
})
}
func (view *View) requestExportBitmap() {
key := view.currentBitmapKey()
texture, err := view.imageCache.Texture(key)
if err != nil {
return
}
palette, err := view.paletteCache.Palette(0)
if err != nil {
return
}
rawPalette := palette.Palette()
filename := fmt.Sprintf("%02d_%d_%02d-%02d.png",
view.model.currentObject.Class, view.model.currentObject.Subclass, view.model.currentObject.Type, view.model.currentBitmap)
width, height := texture.Size()
bmp := bitmap.Bitmap{
Header: bitmap.Header{
Width: int16(width),
Height: int16(height),
},
Pixels: texture.PixelData(),
Palette: &rawPalette,
}
external.ExportImage(view.modalStateMachine, filename, bmp)
}
func (view *View) hasModCurrentBitmap() bool {
key := view.currentBitmapKey()
return len(view.mod.ModifiedBlock(key.Lang, key.ID, key.Index)) > 0
}
func (view *View) requestSetBitmap(bmp bitmap.Bitmap) {
highestBitShift := func(value int16) (result byte) {
if value != 0 {
for (value >> result) != 1 {
result++
}
}
return
}
bmp.Header.Flags = bitmap.FlagTransparent
bmp.Header.Type = bitmap.TypeFlat8Bit
bmp.Header.WidthFactor = highestBitShift(bmp.Header.Width)
bmp.Header.HeightFactor = highestBitShift(bmp.Header.Height)
bmp.Header.Stride = uint16(bmp.Header.Width)
data := bitmap.Encode(&bmp, 0)
view.requestSetBitmapData(data)
}
func (view *View) requestSetBitmapData(newData []byte) {
resourceKey := view.currentBitmapKey()
command := setObjectBitmapCommand{
model: &view.model,
triple: view.model.currentObject,
bitmap: view.model.currentBitmap,
resourceKey: resourceKey,
oldData: view.mod.ModifiedBlock(resourceKey.Lang, resourceKey.ID, resourceKey.Index),
newData: newData,
}
view.commander.Queue(command)
}
func (view *View) currentBitmapKey() resource.Key {
return view.currentBitmapKeyFor(view.model.currentBitmap)
}
func (view *View) currentBitmapKeyFor(offset int) resource.Key {
baseOffset := 1
done := false
view.mod.ObjectProperties().Iterate(func(triple object.Triple, prop *object.Properties) bool {
if !done && triple == view.model.currentObject {
done = true
}
if done {
return false
}
numExtra := int(prop.Common.Bitmap3D.FrameNumber())
baseOffset += 3 + numExtra
return true
})
return resource.KeyOf(ids.ObjectBitmaps, resource.LangAny, baseOffset+offset)
}