Adding modding folder with a hacked build.
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
package archives
|
||||
|
||||
import (
|
||||
"github.com/inkyblackness/imgui-go/v3"
|
||||
|
||||
"github.com/inkyblackness/hacked/editor/values"
|
||||
"github.com/inkyblackness/hacked/ss1/content/archive"
|
||||
"github.com/inkyblackness/hacked/ss1/edit"
|
||||
"github.com/inkyblackness/hacked/ui/gui"
|
||||
)
|
||||
|
||||
type editBooleanVariableDialog struct {
|
||||
machine gui.ModalStateMachine
|
||||
view *View
|
||||
|
||||
service *edit.GameStateService
|
||||
index int
|
||||
varOverride bool
|
||||
info archive.GameVariableInfo
|
||||
|
||||
opened bool
|
||||
}
|
||||
|
||||
func (dialog *editBooleanVariableDialog) Render() {
|
||||
if !dialog.opened {
|
||||
dialog.opened = true
|
||||
dialog.varOverride = dialog.service.BooleanVariableOverride(dialog.index)
|
||||
dialog.info = dialog.service.BooleanVariable(dialog.index)
|
||||
imgui.OpenPopup("Edit boolean variable")
|
||||
}
|
||||
|
||||
imgui.SetNextWindowSize(imgui.Vec2{X: 600, Y: 0}.Times(dialog.view.guiScale))
|
||||
if imgui.BeginPopupModalV("Edit boolean variable", nil,
|
||||
imgui.WindowFlagsNoSavedSettings|imgui.WindowFlagsAlwaysAutoResize) {
|
||||
dialog.renderControls()
|
||||
imgui.Separator()
|
||||
if imgui.Button("OK") {
|
||||
dialog.applyChanges()
|
||||
dialog.close()
|
||||
}
|
||||
imgui.SameLine()
|
||||
if imgui.Button("Cancel") {
|
||||
dialog.close()
|
||||
}
|
||||
imgui.EndPopup()
|
||||
} else {
|
||||
dialog.machine.SetState(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (dialog *editBooleanVariableDialog) applyChanges() {
|
||||
if dialog.varOverride {
|
||||
_ = dialog.service.SetBooleanVariable(dialog.index, dialog.info)
|
||||
} else {
|
||||
_ = dialog.service.DefaultBooleanVariable(dialog.index)
|
||||
}
|
||||
}
|
||||
|
||||
func (dialog *editBooleanVariableDialog) close() {
|
||||
dialog.machine.SetState(nil)
|
||||
imgui.CloseCurrentPopup()
|
||||
}
|
||||
|
||||
func (dialog *editBooleanVariableDialog) renderControls() {
|
||||
imgui.PushItemWidth(-150 * dialog.view.guiScale)
|
||||
values.RenderUnifiedCheckboxCombo(false, "Override", values.UnifierFor(dialog.varOverride), func(newValue bool) {
|
||||
dialog.varOverride = newValue
|
||||
})
|
||||
|
||||
textFlags := imgui.InputTextFlagsNoUndoRedo
|
||||
if !dialog.varOverride {
|
||||
textFlags |= imgui.InputTextFlagsReadOnly
|
||||
}
|
||||
imgui.InputTextV("Name", &dialog.info.Name, textFlags, nil)
|
||||
imgui.InputTextMultilineV("Description", &dialog.info.Description, imgui.Vec2{X: 0, Y: 100 * dialog.view.guiScale},
|
||||
textFlags, nil)
|
||||
|
||||
imgui.Separator()
|
||||
defaultValues := map[int]string{0: "False", 1: "True"}
|
||||
valueLabels := map[bool]string{false: "Default", true: "Special"}
|
||||
hasSpecialValues := dialog.info.ValueNames != nil
|
||||
if dialog.varOverride {
|
||||
if imgui.BeginCombo("Values", valueLabels[hasSpecialValues]) {
|
||||
if imgui.SelectableV(valueLabels[false], !hasSpecialValues, 0, imgui.Vec2{}) {
|
||||
dialog.info.ValueNames = nil
|
||||
}
|
||||
if imgui.SelectableV(valueLabels[true], hasSpecialValues, 0, imgui.Vec2{}) {
|
||||
dialog.info.ValueNames = map[int16]string{0: defaultValues[0], 1: defaultValues[1]}
|
||||
}
|
||||
imgui.EndCombo()
|
||||
}
|
||||
} else {
|
||||
imgui.LabelText("Values", valueLabels[hasSpecialValues])
|
||||
}
|
||||
hasSpecialValues = dialog.info.ValueNames != nil
|
||||
if hasSpecialValues {
|
||||
for i := int16(0); i < 2; i++ {
|
||||
text := dialog.info.ValueNames[i]
|
||||
if imgui.InputTextV(defaultValues[int(i)], &text, textFlags, nil) {
|
||||
dialog.info.ValueNames[i] = text
|
||||
}
|
||||
}
|
||||
} else {
|
||||
imgui.LabelText(defaultValues[0], defaultValues[0])
|
||||
imgui.LabelText(defaultValues[1], defaultValues[1])
|
||||
}
|
||||
|
||||
initUnifier := values.UnifierFor(dialog.info.ResetValueInt())
|
||||
values.RenderUnifiedCombo(dialog.info.Hardcoded || !dialog.varOverride, "Reset Value", initUnifier,
|
||||
func(u values.Unifier) int { return int(u.Unified().(int16)) },
|
||||
func(value int) string {
|
||||
name, found := dialog.info.ValueNames[int16(value)]
|
||||
if found {
|
||||
return name
|
||||
}
|
||||
return defaultValues[value]
|
||||
},
|
||||
2,
|
||||
func(newValue int) {
|
||||
value16 := int16(newValue)
|
||||
dialog.info.InitValue = &value16
|
||||
})
|
||||
|
||||
imgui.PopItemWidth()
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
package archives
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/inkyblackness/imgui-go/v3"
|
||||
|
||||
"github.com/inkyblackness/hacked/editor/values"
|
||||
"github.com/inkyblackness/hacked/ss1/content/archive"
|
||||
"github.com/inkyblackness/hacked/ss1/edit"
|
||||
"github.com/inkyblackness/hacked/ui/gui"
|
||||
)
|
||||
|
||||
type editIntegerVariableDialog struct {
|
||||
machine gui.ModalStateMachine
|
||||
view *View
|
||||
|
||||
service *edit.GameStateService
|
||||
index int
|
||||
varOverride bool
|
||||
info archive.GameVariableInfo
|
||||
|
||||
selectedEnum int
|
||||
opened bool
|
||||
}
|
||||
|
||||
func (dialog *editIntegerVariableDialog) Render() {
|
||||
if !dialog.opened {
|
||||
dialog.opened = true
|
||||
dialog.varOverride = dialog.service.IntegerVariableOverride(dialog.index)
|
||||
dialog.info = dialog.service.IntegerVariable(dialog.index)
|
||||
imgui.OpenPopup("Edit integer variable")
|
||||
}
|
||||
|
||||
imgui.SetNextWindowSize(imgui.Vec2{X: 600, Y: 0}.Times(dialog.view.guiScale))
|
||||
if imgui.BeginPopupModalV("Edit integer variable", nil,
|
||||
imgui.WindowFlagsNoSavedSettings|imgui.WindowFlagsAlwaysAutoResize) {
|
||||
dialog.renderControls()
|
||||
imgui.Separator()
|
||||
if imgui.Button("OK") {
|
||||
dialog.applyChanges()
|
||||
dialog.close()
|
||||
}
|
||||
imgui.SameLine()
|
||||
if imgui.Button("Cancel") {
|
||||
dialog.close()
|
||||
}
|
||||
imgui.EndPopup()
|
||||
} else {
|
||||
dialog.machine.SetState(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (dialog *editIntegerVariableDialog) applyChanges() {
|
||||
if dialog.varOverride {
|
||||
_ = dialog.service.SetIntegerVariable(dialog.index, dialog.info)
|
||||
} else {
|
||||
_ = dialog.service.DefaultIntegerVariable(dialog.index)
|
||||
}
|
||||
}
|
||||
|
||||
func (dialog *editIntegerVariableDialog) close() {
|
||||
dialog.machine.SetState(nil)
|
||||
imgui.CloseCurrentPopup()
|
||||
}
|
||||
|
||||
func (dialog *editIntegerVariableDialog) renderControls() {
|
||||
imgui.PushItemWidth(-150 * dialog.view.guiScale)
|
||||
values.RenderUnifiedCheckboxCombo(false, "Override", values.UnifierFor(dialog.varOverride), func(newValue bool) {
|
||||
dialog.varOverride = newValue
|
||||
})
|
||||
|
||||
textFlags := imgui.InputTextFlagsNoUndoRedo
|
||||
if !dialog.varOverride {
|
||||
textFlags |= imgui.InputTextFlagsReadOnly
|
||||
}
|
||||
imgui.InputTextV("Name", &dialog.info.Name, textFlags, nil)
|
||||
imgui.InputTextMultilineV("Description", &dialog.info.Description, imgui.Vec2{X: 0, Y: 100 * dialog.view.guiScale},
|
||||
textFlags, nil)
|
||||
|
||||
imgui.Separator()
|
||||
|
||||
valueLabels := map[bool]string{false: "Numeric", true: "Enumerated"}
|
||||
isEnumerated := len(dialog.info.ValueNames) > 0
|
||||
if imgui.BeginCombo("Values", valueLabels[isEnumerated]) {
|
||||
if imgui.SelectableV(valueLabels[false], !isEnumerated, 0, imgui.Vec2{}) {
|
||||
dialog.info.ValueNames = nil
|
||||
}
|
||||
if imgui.SelectableV(valueLabels[true], isEnumerated, 0, imgui.Vec2{}) {
|
||||
dialog.info.ValueNames = map[int16]string{0: "Zero"}
|
||||
dialog.selectedEnum = 0
|
||||
}
|
||||
imgui.EndCombo()
|
||||
}
|
||||
if len(dialog.info.ValueNames) > 0 {
|
||||
gui.StepSliderInt("Index", &dialog.selectedEnum, math.MinInt16, math.MaxInt16)
|
||||
if dialog.varOverride {
|
||||
text := dialog.info.ValueNames[int16(dialog.selectedEnum)]
|
||||
if imgui.InputTextV("Value", &text, textFlags, nil) {
|
||||
if len(text) > 0 {
|
||||
dialog.info.ValueNames[int16(dialog.selectedEnum)] = text
|
||||
} else {
|
||||
delete(dialog.info.ValueNames, int16(dialog.selectedEnum))
|
||||
if len(dialog.info.ValueNames) == 0 {
|
||||
dialog.info.ValueNames[0] = "Zero"
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
imgui.LabelText("Value", dialog.info.ValueNames[int16(dialog.selectedEnum)])
|
||||
}
|
||||
|
||||
resetValue := dialog.info.ResetValueInt()
|
||||
enumEntries := toEnumEntries(dialog.info.ValueNames)
|
||||
linearInitIndex := linearIndex(enumEntries, resetValue)
|
||||
initUnifier := values.UnifierFor(linearInitIndex)
|
||||
values.RenderUnifiedCombo(dialog.info.Hardcoded || !dialog.varOverride, "Reset Value", initUnifier,
|
||||
func(u values.Unifier) int { return u.Unified().(int) },
|
||||
func(value int) string {
|
||||
if value < 0 {
|
||||
return fmt.Sprintf("%d: (unmapped)", resetValue)
|
||||
}
|
||||
entry := enumEntries[value]
|
||||
return fmt.Sprintf("%d: '%s'", entry.index, entry.value)
|
||||
},
|
||||
len(enumEntries),
|
||||
func(newValue int) {
|
||||
dialog.info.InitValue = &enumEntries[newValue].index
|
||||
})
|
||||
} else {
|
||||
minValue := math.MinInt16
|
||||
maxValue := math.MaxInt16
|
||||
|
||||
if dialog.info.Limits != nil {
|
||||
minValue = int(dialog.info.Limits.Minimum)
|
||||
maxValue = int(dialog.info.Limits.Maximum)
|
||||
}
|
||||
if minValue > maxValue {
|
||||
maxValue = minValue
|
||||
}
|
||||
if dialog.varOverride {
|
||||
if gui.StepSliderInt("Minimum", &minValue, math.MinInt16, math.MaxInt16) {
|
||||
if minValue > maxValue {
|
||||
maxValue = minValue
|
||||
}
|
||||
}
|
||||
if gui.StepSliderInt("Maximum", &maxValue, math.MinInt16, math.MaxInt16) {
|
||||
if maxValue < minValue {
|
||||
minValue = maxValue
|
||||
}
|
||||
}
|
||||
if (minValue == math.MinInt16) && (maxValue == math.MaxInt16) {
|
||||
dialog.info.Limits = nil
|
||||
} else {
|
||||
if dialog.info.Limits == nil {
|
||||
dialog.info.Limits = &archive.GameVariableLimits{}
|
||||
}
|
||||
dialog.info.Limits.Minimum = int16(minValue)
|
||||
dialog.info.Limits.Maximum = int16(maxValue)
|
||||
}
|
||||
} else {
|
||||
imgui.LabelText("Minimum", fmt.Sprintf("%d", minValue))
|
||||
imgui.LabelText("Maximum", fmt.Sprintf("%d", maxValue))
|
||||
}
|
||||
|
||||
resetValue := int(dialog.info.ResetValueInt())
|
||||
if dialog.info.Hardcoded || !dialog.varOverride {
|
||||
imgui.LabelText("Reset Value", fmt.Sprintf("%d", resetValue))
|
||||
} else if gui.StepSliderInt("Reset Value", &resetValue, minValue, maxValue) {
|
||||
resetValue16 := int16(resetValue)
|
||||
dialog.info.InitValue = &resetValue16
|
||||
}
|
||||
}
|
||||
|
||||
imgui.PopItemWidth()
|
||||
}
|
||||
|
||||
type enumEntry struct {
|
||||
index int16
|
||||
value string
|
||||
}
|
||||
|
||||
func toEnumEntries(mapped map[int16]string) []enumEntry {
|
||||
minIndex := math.MaxInt16
|
||||
maxIndex := math.MinInt16
|
||||
for index := range mapped {
|
||||
if int(index) < minIndex {
|
||||
minIndex = int(index)
|
||||
}
|
||||
if int(index) > maxIndex {
|
||||
maxIndex = int(index)
|
||||
}
|
||||
}
|
||||
orderedEntries := make([]enumEntry, 0, len(mapped))
|
||||
for index, value := range mapped {
|
||||
orderedEntries = append(orderedEntries, enumEntry{
|
||||
index: index,
|
||||
value: value,
|
||||
})
|
||||
}
|
||||
sort.Slice(orderedEntries, func(a, b int) bool { return orderedEntries[a].index < orderedEntries[b].index })
|
||||
return orderedEntries
|
||||
}
|
||||
|
||||
func linearIndex(entries []enumEntry, index int16) int {
|
||||
for linear, entry := range entries {
|
||||
if entry.index == index {
|
||||
return linear
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package archives
|
||||
|
||||
import (
|
||||
"github.com/inkyblackness/hacked/ss1/resource"
|
||||
"github.com/inkyblackness/hacked/ss1/world"
|
||||
)
|
||||
|
||||
type setArchiveDataCommand struct {
|
||||
model *viewModel
|
||||
|
||||
selectedLevel int
|
||||
|
||||
oldData map[resource.ID][]byte
|
||||
newData map[resource.ID][]byte
|
||||
}
|
||||
|
||||
func (command setArchiveDataCommand) Do(modder world.Modder) error {
|
||||
command.delResources(modder, command.oldData)
|
||||
return command.perform(modder, command.newData)
|
||||
}
|
||||
|
||||
func (command setArchiveDataCommand) Undo(modder world.Modder) error {
|
||||
command.delResources(modder, command.newData)
|
||||
return command.perform(modder, command.oldData)
|
||||
}
|
||||
|
||||
func (command setArchiveDataCommand) delResources(modder world.Modder, data map[resource.ID][]byte) {
|
||||
for id := range data {
|
||||
modder.DelResource(resource.LangAny, id)
|
||||
}
|
||||
}
|
||||
|
||||
// nolint: interfacer
|
||||
func (command setArchiveDataCommand) perform(modder world.Modder, data map[resource.ID][]byte) error {
|
||||
for id, blockData := range data {
|
||||
if len(blockData) > 0 {
|
||||
modder.SetResourceBlocks(resource.LangAny, id, [][]byte{blockData})
|
||||
} else {
|
||||
modder.DelResource(resource.LangAny, id)
|
||||
}
|
||||
}
|
||||
command.model.restoreFocus = true
|
||||
command.model.selectedLevel = command.selectedLevel
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,934 @@
|
||||
package archives
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/inkyblackness/imgui-go/v3"
|
||||
|
||||
"github.com/inkyblackness/hacked/editor/values"
|
||||
"github.com/inkyblackness/hacked/ss1/content/archive"
|
||||
"github.com/inkyblackness/hacked/ss1/content/archive/level"
|
||||
"github.com/inkyblackness/hacked/ss1/content/archive/level/lvlids"
|
||||
"github.com/inkyblackness/hacked/ss1/content/archive/level/lvlobj"
|
||||
"github.com/inkyblackness/hacked/ss1/content/interpreters"
|
||||
"github.com/inkyblackness/hacked/ss1/content/numbers"
|
||||
"github.com/inkyblackness/hacked/ss1/content/object"
|
||||
"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/citadel"
|
||||
"github.com/inkyblackness/hacked/ss1/world/ids"
|
||||
"github.com/inkyblackness/hacked/ui/gui"
|
||||
)
|
||||
|
||||
const (
|
||||
hintUnknown = "???"
|
||||
)
|
||||
|
||||
var variableContextList = map[edit.VariableBaseContextIdentifier]struct {
|
||||
title string
|
||||
info string
|
||||
}{
|
||||
edit.VariableContextCitadel: {
|
||||
title: "Citadel Mission",
|
||||
info: "Variables are listed based on use on Citadel.\n" +
|
||||
"Useful for Citadel-based savegames and mods, such as 'New Game+' variants.",
|
||||
},
|
||||
edit.VariableContextEngine: {
|
||||
title: "Engine",
|
||||
info: "Variables are listed based on the hardcoded engine behaviour.\n" +
|
||||
"Useful for dedicated missions.",
|
||||
},
|
||||
}
|
||||
|
||||
// View provides edit controls for the archive.
|
||||
type View struct {
|
||||
registry cmd.Registry
|
||||
gameStateService *edit.GameStateService
|
||||
mod *world.Mod
|
||||
textCache *text.Cache
|
||||
cp text.Codepage
|
||||
|
||||
modalStateMachine gui.ModalStateMachine
|
||||
guiScale float32
|
||||
commander cmd.Commander
|
||||
|
||||
model viewModel
|
||||
}
|
||||
|
||||
// NewArchiveView returns a new instance.
|
||||
func NewArchiveView(registry cmd.Registry,
|
||||
gameStateService *edit.GameStateService, mod *world.Mod,
|
||||
textCache *text.Cache, cp text.Codepage,
|
||||
modalStateMachine gui.ModalStateMachine, guiScale float32, commander cmd.Commander) *View {
|
||||
view := &View{
|
||||
registry: registry,
|
||||
gameStateService: gameStateService,
|
||||
mod: mod,
|
||||
textCache: textCache,
|
||||
cp: cp,
|
||||
|
||||
modalStateMachine: modalStateMachine,
|
||||
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: 650 * view.guiScale, Y: 400 * view.guiScale}, imgui.ConditionFirstUseEver)
|
||||
if imgui.BeginV("Archive", view.WindowOpen(), imgui.WindowFlagsNoCollapse) {
|
||||
view.renderContent()
|
||||
}
|
||||
imgui.End()
|
||||
}
|
||||
}
|
||||
|
||||
func (view *View) renderContent() {
|
||||
imgui.BeginTabBar("archive-tab")
|
||||
|
||||
if imgui.BeginTabItem("Levels") {
|
||||
view.renderLevelsContent()
|
||||
imgui.EndTabItem()
|
||||
}
|
||||
if imgui.BeginTabItem("Game State") {
|
||||
view.renderGameStateContent()
|
||||
imgui.EndTabItem()
|
||||
}
|
||||
|
||||
imgui.EndTabBar()
|
||||
}
|
||||
|
||||
func (view *View) renderLevelsContent() {
|
||||
imgui.BeginChildV("Levels", imgui.Vec2{X: -100 * view.guiScale, Y: 0}, true, 0)
|
||||
for id := 0; id < archive.MaxLevels; id++ {
|
||||
inMod := view.hasLevelInMod(id)
|
||||
info := fmt.Sprintf("%d", id)
|
||||
if !inMod {
|
||||
if view.hasGameStateInMod() && (id == view.effectiveGameState().CurrentLevel()) {
|
||||
// current level should really be in the archive.
|
||||
imgui.PushStyleColor(imgui.StyleColorText, imgui.Vec4{X: 1.0, Y: 0.0, Z: 0.0, W: 1.0})
|
||||
} else {
|
||||
imgui.PushStyleColor(imgui.StyleColorText, imgui.Vec4{X: 1.0, Y: 1.0, Z: 1.0, W: 0.8})
|
||||
}
|
||||
info += " (not in mod, read-only)"
|
||||
}
|
||||
if imgui.SelectableV(info, id == view.model.selectedLevel, 0, imgui.Vec2{}) {
|
||||
view.model.selectedLevel = id
|
||||
}
|
||||
if !inMod {
|
||||
imgui.PopStyleColor()
|
||||
}
|
||||
}
|
||||
imgui.EndChild()
|
||||
imgui.SameLine()
|
||||
imgui.BeginGroup()
|
||||
if imgui.ButtonV("Clear", imgui.Vec2{X: -1, Y: 0}) {
|
||||
view.requestClearLevel(view.model.selectedLevel)
|
||||
}
|
||||
if imgui.ButtonV("Remove", imgui.Vec2{X: -1, Y: 0}) {
|
||||
view.requestRemoveLevel(view.model.selectedLevel)
|
||||
}
|
||||
imgui.EndGroup()
|
||||
}
|
||||
|
||||
func (view *View) renderGameStateContent() {
|
||||
data := view.gameStateData()
|
||||
modStateIsDefaulting := false
|
||||
|
||||
imgui.PushItemWidth(-350 * view.guiScale)
|
||||
if data.isPresent() {
|
||||
modState := data.toInstance()
|
||||
if !modState.IsSavegame() {
|
||||
modStateIsDefaulting = modState.IsDefaulting()
|
||||
if modStateIsDefaulting {
|
||||
if imgui.Button("Override") {
|
||||
view.requestSetGameState(citadel.DefaultGameState().Raw())
|
||||
}
|
||||
if imgui.IsItemHovered() {
|
||||
imgui.SetTooltip("Values of new game archives are only considered by special engines.\n" +
|
||||
"Overriding them will only work in those.")
|
||||
}
|
||||
} else if imgui.Button("Remove") {
|
||||
view.requestSetGameState(archive.ZeroGameStateData())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readOnly := false
|
||||
var editState *archive.GameState
|
||||
if !data.isPresent() || modStateIsDefaulting {
|
||||
editState = citadel.DefaultGameState()
|
||||
readOnly = true
|
||||
} else {
|
||||
editState = data.imported().toInstance()
|
||||
}
|
||||
|
||||
onChange := func() {
|
||||
view.requestSetGameState(editState.Raw())
|
||||
}
|
||||
if imgui.TreeNodeV("Game State: General", imgui.TreeNodeFlagsDefaultOpen|imgui.TreeNodeFlagsFramed) {
|
||||
imgui.LabelText("Hacker Name", "\""+editState.HackerName(view.cp)+"\"")
|
||||
view.createPropertyControls(readOnly, editState.Instance, func(key string, modifier func(uint32) uint32) {
|
||||
view.setInterpreterValueKeyed(editState.Instance, key, modifier)
|
||||
onChange()
|
||||
})
|
||||
imgui.TreePop()
|
||||
}
|
||||
if imgui.TreeNodeV("Inventory", imgui.TreeNodeFlagsFramed) {
|
||||
view.createInventoryControls(readOnly, editState, onChange)
|
||||
imgui.TreePop()
|
||||
}
|
||||
if imgui.TreeNodeV("Hard-/Software", imgui.TreeNodeFlagsFramed) {
|
||||
view.createWareControls(readOnly, editState, onChange)
|
||||
imgui.TreePop()
|
||||
}
|
||||
if imgui.TreeNodeV("Game State: Variables", imgui.TreeNodeFlagsFramed) {
|
||||
view.createVariableControls(readOnly, editState, onChange)
|
||||
imgui.TreePop()
|
||||
}
|
||||
if imgui.TreeNodeV("Game State: Messages", imgui.TreeNodeFlagsFramed) {
|
||||
view.createMessageControls(readOnly, editState, onChange)
|
||||
imgui.TreePop()
|
||||
}
|
||||
|
||||
imgui.PopItemWidth()
|
||||
}
|
||||
|
||||
type gameStateData struct{ raw []byte }
|
||||
|
||||
func (data gameStateData) isPresent() bool {
|
||||
return len(data.raw) > 0
|
||||
}
|
||||
|
||||
func (data gameStateData) toInstance() *archive.GameState {
|
||||
return archive.NewGameState(data.raw)
|
||||
}
|
||||
|
||||
func (data gameStateData) snapshot() gameStateData {
|
||||
copied := make([]byte, len(data.raw))
|
||||
copy(copied, data.raw)
|
||||
return gameStateData{raw: copied}
|
||||
}
|
||||
|
||||
func (data gameStateData) imported() gameStateData {
|
||||
// Avoid truncating data when working with savegames.
|
||||
if len(data.raw) >= archive.GameStateSize {
|
||||
return data.snapshot()
|
||||
}
|
||||
copied := archive.ZeroGameStateData()
|
||||
copy(copied, data.raw)
|
||||
return gameStateData{raw: copied}
|
||||
}
|
||||
|
||||
func (view *View) gameStateData() gameStateData {
|
||||
raw := view.mod.ModifiedBlock(resource.LangAny, ids.GameState, 0)
|
||||
return gameStateData{raw: raw}
|
||||
}
|
||||
|
||||
func (view *View) effectiveGameState() *archive.GameState {
|
||||
state := view.gameStateData().toInstance()
|
||||
if state.IsDefaulting() {
|
||||
return citadel.DefaultGameState()
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
func (view *View) hasGameStateInMod() bool {
|
||||
return len(view.mod.ModifiedBlocks(resource.LangAny, ids.GameState)) > 0
|
||||
}
|
||||
|
||||
func (view *View) hasLevelInMod(id int) bool {
|
||||
return len(view.mod.ModifiedBlocks(resource.LangAny, ids.LevelResourcesStart.Plus(lvlids.PerLevel*id+lvlids.FirstUsed))) > 0
|
||||
}
|
||||
|
||||
func (view *View) requestClearLevel(id int) {
|
||||
if (id >= 0) && (id < archive.MaxLevels) {
|
||||
command := setArchiveDataCommand{
|
||||
model: &view.model,
|
||||
selectedLevel: id,
|
||||
newData: make(map[resource.ID][]byte),
|
||||
oldData: make(map[resource.ID][]byte),
|
||||
}
|
||||
|
||||
if !view.hasGameStateInMod() {
|
||||
command.newData[ids.ArchiveName] = text.DefaultCodepage().Encode("Starting Game | by InkyBlackness HackEd")
|
||||
command.newData[ids.GameState] = archive.ZeroGameStateData()
|
||||
}
|
||||
|
||||
param := level.EmptyLevelParameters{
|
||||
Cyberspace: world.IsConsideredCyberspaceByDefault(id),
|
||||
}
|
||||
effectiveGameState := view.effectiveGameState()
|
||||
if id == effectiveGameState.CurrentLevel() {
|
||||
posX, posY := effectiveGameState.HackerMapPosition()
|
||||
param.MapModifier = func(x, y int, entry *level.TileMapEntry) {
|
||||
if (x == int(posX.Tile())) && (y == int(posY.Tile())) {
|
||||
entry.Type = level.TileTypeOpen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
levelData := level.EmptyLevelData(param)
|
||||
levelIDBegin := ids.LevelResourcesStart.Plus(lvlids.PerLevel * id)
|
||||
for offset, newData := range &levelData {
|
||||
if (offset < lvlids.FirstUsed) || (offset >= lvlids.FirstUnused) {
|
||||
continue
|
||||
}
|
||||
resourceID := levelIDBegin.Plus(offset)
|
||||
oldData := view.mod.ModifiedBlock(resource.LangAny, resourceID, 0)
|
||||
if len(oldData) > 0 {
|
||||
command.oldData[resourceID] = oldData
|
||||
}
|
||||
if len(newData) > 0 {
|
||||
command.newData[resourceID] = newData
|
||||
}
|
||||
}
|
||||
|
||||
view.commander.Queue(command)
|
||||
}
|
||||
}
|
||||
|
||||
func (view *View) requestRemoveLevel(id int) {
|
||||
if (id >= 0) && (id < archive.MaxLevels) && view.hasLevelInMod(id) {
|
||||
command := setArchiveDataCommand{
|
||||
model: &view.model,
|
||||
selectedLevel: id,
|
||||
newData: make(map[resource.ID][]byte),
|
||||
oldData: make(map[resource.ID][]byte),
|
||||
}
|
||||
|
||||
levelIDBegin := ids.LevelResourcesStart.Plus(lvlids.PerLevel * id)
|
||||
for offset := lvlids.FirstUsed; offset < lvlids.PerLevel; offset++ {
|
||||
resourceID := levelIDBegin.Plus(offset)
|
||||
oldData := view.mod.ModifiedBlock(resource.LangAny, resourceID, 0)
|
||||
if len(oldData) > 0 {
|
||||
command.oldData[resourceID] = oldData
|
||||
}
|
||||
}
|
||||
|
||||
view.commander.Queue(command)
|
||||
}
|
||||
}
|
||||
|
||||
func (view *View) requestSetGameState(newData []byte) {
|
||||
command := setArchiveDataCommand{
|
||||
model: &view.model,
|
||||
selectedLevel: view.model.selectedLevel,
|
||||
newData: make(map[resource.ID][]byte),
|
||||
oldData: make(map[resource.ID][]byte),
|
||||
}
|
||||
|
||||
command.oldData[ids.GameState] = view.gameStateData().snapshot().raw
|
||||
command.newData[ids.GameState] = newData
|
||||
|
||||
view.commander.Queue(command)
|
||||
}
|
||||
|
||||
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) createInventoryControls(readOnly bool, gameState *archive.GameState, onChange func()) {
|
||||
if imgui.TreeNodeV("Weapons", imgui.TreeNodeFlagsFramed) {
|
||||
for i := 0; i < archive.InventoryWeaponSlots; i++ {
|
||||
if i > 0 {
|
||||
imgui.Separator()
|
||||
}
|
||||
imgui.PushID(fmt.Sprintf("weapon%d", i))
|
||||
view.createInventoryWeaponSlotControls(readOnly, gameState.InventoryWeaponSlot(i), onChange)
|
||||
imgui.PopID()
|
||||
}
|
||||
imgui.TreePop()
|
||||
}
|
||||
if imgui.TreeNodeV("Ammo", imgui.TreeNodeFlagsFramed) {
|
||||
for i := 0; i < archive.AmmoTypeCount; i++ {
|
||||
imgui.Separator()
|
||||
imgui.PushID(fmt.Sprintf("ammo%d", i))
|
||||
view.createInventoryAmmoControls(readOnly, gameState.InventoryAmmo(i), onChange)
|
||||
imgui.PopID()
|
||||
}
|
||||
imgui.TreePop()
|
||||
}
|
||||
if imgui.TreeNodeV("Grenades", imgui.TreeNodeFlagsFramed) {
|
||||
for i := 0; i < archive.GrenadeTypeCount; i++ {
|
||||
imgui.Separator()
|
||||
imgui.PushID(fmt.Sprintf("grenade%d", i))
|
||||
view.createInventoryGrenadeControls(readOnly, gameState.InventoryGrenade(i), onChange)
|
||||
imgui.PopID()
|
||||
}
|
||||
imgui.TreePop()
|
||||
}
|
||||
if imgui.TreeNodeV("Patches", imgui.TreeNodeFlagsFramed) {
|
||||
for i := 0; i < archive.GrenadeTypeCount; i++ {
|
||||
imgui.PushID(fmt.Sprintf("patch%d", i))
|
||||
view.createPatchStateControls(readOnly, gameState.PatchState(i), onChange)
|
||||
imgui.PopID()
|
||||
}
|
||||
imgui.TreePop()
|
||||
}
|
||||
if imgui.TreeNodeV("General", imgui.TreeNodeFlagsFramed) {
|
||||
for i := 0; i < archive.GeneralInventorySlotCount; i++ {
|
||||
imgui.PushID(fmt.Sprintf("general%d", i))
|
||||
view.createGeneralInventoryControls(readOnly, gameState.GeneralInventorySlot(i), onChange)
|
||||
imgui.PopID()
|
||||
}
|
||||
imgui.TreePop()
|
||||
}
|
||||
}
|
||||
|
||||
func (view *View) createWareControls(readOnly bool, gameState *archive.GameState, onChange func()) {
|
||||
if imgui.TreeNodeV("Hardware", imgui.TreeNodeFlagsFramed) {
|
||||
for i := 0; i < archive.HardwareTypeCount; i++ {
|
||||
imgui.PushID(fmt.Sprintf("hw%d", i))
|
||||
view.createHardwareControls(readOnly, gameState.HardwareState(i), onChange)
|
||||
imgui.PopID()
|
||||
}
|
||||
imgui.TreePop()
|
||||
}
|
||||
|
||||
if imgui.TreeNodeV("Software", imgui.TreeNodeFlagsFramed) {
|
||||
for i := 0; i < archive.SoftwareOffenseTypeCount; i++ {
|
||||
imgui.PushID(fmt.Sprintf("swoffense%d", i))
|
||||
view.createVersionedSoftwareControls(readOnly, gameState.VersionedOffenseSoftwareState(i), 0, onChange)
|
||||
imgui.PopID()
|
||||
}
|
||||
for i := 0; i < archive.SoftwareDefenseTypeCount; i++ {
|
||||
imgui.PushID(fmt.Sprintf("swdefense%d", i))
|
||||
view.createVersionedSoftwareControls(readOnly, gameState.VersionedDefenseSoftwareState(i), 1, onChange)
|
||||
imgui.PopID()
|
||||
}
|
||||
for i := 0; i < archive.SoftwareOneshotTypeCount; i++ {
|
||||
imgui.PushID(fmt.Sprintf("swoneshot%d", i))
|
||||
view.createCountedSoftwareControls(readOnly, gameState.OneshotSoftwareState(i), 2, onChange)
|
||||
imgui.PopID()
|
||||
}
|
||||
imgui.TreePop()
|
||||
}
|
||||
}
|
||||
|
||||
func (view *View) createHardwareControls(readOnly bool, state archive.HardwareState, onChange func()) {
|
||||
values.RenderUnifiedSliderInt(readOnly,
|
||||
fmt.Sprintf("Version (%s)", view.indexedName(object.ClassHardware, state.Index)),
|
||||
values.UnifierFor(state.Version()),
|
||||
func(u values.Unifier) int {
|
||||
return u.Unified().(int)
|
||||
}, func(value int) string {
|
||||
return "%d"
|
||||
}, 0, 4,
|
||||
func(newValue int) {
|
||||
state.SetVersion(newValue)
|
||||
onChange()
|
||||
})
|
||||
|
||||
values.RenderUnifiedCheckboxCombo(readOnly, "Is Active", values.UnifierFor(state.IsActive()), func(newActive bool) {
|
||||
state.SetActive(newActive)
|
||||
onChange()
|
||||
})
|
||||
}
|
||||
|
||||
func (view *View) createVersionedSoftwareControls(readOnly bool, sw archive.VersionedSoftwareState, subclass int, onChange func()) {
|
||||
values.RenderUnifiedSliderInt(readOnly,
|
||||
fmt.Sprintf("Version (%s)", view.tripleName(object.TripleFrom(int(object.ClassSoftware), subclass, sw.Index))),
|
||||
values.UnifierFor(sw.Version()),
|
||||
func(u values.Unifier) int {
|
||||
return u.Unified().(int)
|
||||
}, func(value int) string {
|
||||
return "%d"
|
||||
}, 0, 0xFF,
|
||||
func(newValue int) {
|
||||
sw.SetVersion(newValue)
|
||||
onChange()
|
||||
})
|
||||
}
|
||||
|
||||
func (view *View) createCountedSoftwareControls(readOnly bool, sw archive.CountedSoftwareState, subclass int, onChange func()) {
|
||||
values.RenderUnifiedSliderInt(readOnly,
|
||||
fmt.Sprintf("Count (%s)", view.tripleName(object.TripleFrom(int(object.ClassSoftware), subclass, sw.Index))),
|
||||
values.UnifierFor(sw.Count()),
|
||||
func(u values.Unifier) int {
|
||||
return u.Unified().(int)
|
||||
}, func(value int) string {
|
||||
return "%d"
|
||||
}, 0, 0xFF,
|
||||
func(newValue int) {
|
||||
sw.SetCount(newValue)
|
||||
onChange()
|
||||
})
|
||||
}
|
||||
|
||||
func (view *View) createGeneralInventoryControls(readOnly bool, slot archive.GeneralInventorySlot, onChange func()) {
|
||||
values.RenderUnifiedSliderInt(readOnly,
|
||||
fmt.Sprintf("ObjectIndex (Slot %d)", slot.Index+1),
|
||||
values.UnifierFor(slot.ObjectID()),
|
||||
func(u values.Unifier) int {
|
||||
return int(u.Unified().(level.ObjectID))
|
||||
}, func(value int) string {
|
||||
return "%d"
|
||||
}, 0, 871, // TODO: get ID limit of current level
|
||||
func(newValue int) {
|
||||
slot.SetObjectID(level.ObjectID(newValue))
|
||||
onChange()
|
||||
})
|
||||
}
|
||||
|
||||
func (view *View) createPatchStateControls(readOnly bool, patch archive.PatchState, onChange func()) {
|
||||
values.RenderUnifiedSliderInt(readOnly,
|
||||
fmt.Sprintf("Patch Count (%s)", view.indexedName(object.ClassDrug, patch.Index)),
|
||||
values.UnifierFor(patch.Count()),
|
||||
func(u values.Unifier) int {
|
||||
return u.Unified().(int)
|
||||
}, func(value int) string {
|
||||
return "%d"
|
||||
}, 0, 0xFF,
|
||||
func(newValue int) {
|
||||
patch.SetCount(newValue)
|
||||
onChange()
|
||||
})
|
||||
}
|
||||
|
||||
func (view *View) createInventoryGrenadeControls(readOnly bool, grenade archive.InventoryGrenade, onChange func()) {
|
||||
values.RenderUnifiedSliderInt(readOnly,
|
||||
fmt.Sprintf("Grenade Count (%s)", view.indexedName(object.ClassGrenade, grenade.Index)),
|
||||
values.UnifierFor(grenade.Count()),
|
||||
func(u values.Unifier) int {
|
||||
return u.Unified().(int)
|
||||
}, func(value int) string {
|
||||
return "%d"
|
||||
}, 0, 0xFF,
|
||||
func(newValue int) {
|
||||
grenade.SetCount(newValue)
|
||||
onChange()
|
||||
})
|
||||
|
||||
values.RenderUnifiedSliderInt(readOnly, "Timer", values.UnifierFor(grenade.TimerSetting()),
|
||||
func(u values.Unifier) int {
|
||||
return int(u.Unified().(archive.GrenadeTimerSetting))
|
||||
}, func(value int) string {
|
||||
setting := archive.GrenadeTimerSetting(value)
|
||||
return fmt.Sprintf("%.01fs -- raw: %%d", setting.InSeconds())
|
||||
}, 0, archive.GrenadeTimerMaximum,
|
||||
func(newValue int) {
|
||||
grenade.SetTimerSetting(archive.GrenadeTimerSetting(newValue))
|
||||
onChange()
|
||||
})
|
||||
}
|
||||
|
||||
func (view *View) createInventoryAmmoControls(readOnly bool, ammo archive.InventoryAmmo, onChange func()) {
|
||||
values.RenderUnifiedSliderInt(readOnly,
|
||||
fmt.Sprintf("Clip Count (%s)", view.indexedName(object.ClassAmmo, ammo.Index)),
|
||||
values.UnifierFor(ammo.FullClipCount()),
|
||||
func(u values.Unifier) int {
|
||||
return u.Unified().(int)
|
||||
}, func(value int) string {
|
||||
return "%d"
|
||||
}, 0, 0xFF,
|
||||
func(newValue int) {
|
||||
ammo.SetFullClipCount(newValue)
|
||||
onChange()
|
||||
})
|
||||
values.RenderUnifiedSliderInt(readOnly,
|
||||
"Extra rounds",
|
||||
values.UnifierFor(ammo.ExtraRoundsCount()),
|
||||
func(u values.Unifier) int {
|
||||
return u.Unified().(int)
|
||||
}, func(value int) string {
|
||||
return "%d"
|
||||
}, 0, 0xFF,
|
||||
func(newValue int) {
|
||||
ammo.SetExtraRoundsCount(newValue)
|
||||
onChange()
|
||||
})
|
||||
}
|
||||
|
||||
func (view *View) createInventoryWeaponSlotControls(readOnly bool, slot archive.InventoryWeaponSlot, onChange func()) {
|
||||
isInUse := slot.IsInUse()
|
||||
inUseName := fmt.Sprintf("Weapon %d In Use", slot.Index+1)
|
||||
|
||||
values.RenderUnifiedCheckboxCombo(readOnly, inUseName, values.UnifierFor(isInUse), func(newUse bool) {
|
||||
if newUse {
|
||||
slot.SetInUse(0, 0)
|
||||
} else {
|
||||
slot.SetFree()
|
||||
}
|
||||
onChange()
|
||||
})
|
||||
if !isInUse {
|
||||
return
|
||||
}
|
||||
|
||||
currentWeaponTriple := slot.Triple()
|
||||
if imgui.BeginCombo("Type", view.tripleName(currentWeaponTriple)) {
|
||||
allTypes := view.mod.ObjectProperties().TriplesInClass(currentWeaponTriple.Class)
|
||||
for _, triple := range allTypes {
|
||||
if imgui.SelectableV(view.tripleName(triple), triple == currentWeaponTriple, 0, imgui.Vec2{}) {
|
||||
slot.SetInUse(triple.Subclass, triple.Type)
|
||||
onChange()
|
||||
}
|
||||
}
|
||||
imgui.EndCombo()
|
||||
}
|
||||
isEnergyWeapon := currentWeaponTriple.Subclass >= 4
|
||||
if isEnergyWeapon {
|
||||
temperature, chargeAndOverload := slot.WeaponState()
|
||||
values.RenderUnifiedSliderInt(readOnly, "Temperature", values.UnifierFor(int(temperature)),
|
||||
func(u values.Unifier) int {
|
||||
return u.Unified().(int)
|
||||
}, func(value int) string {
|
||||
return fmt.Sprintf("%.02f%%%% -- raw: %%d", (float32(value)/float32(lvlobj.EnergyWeaponMaxTemperature))*100)
|
||||
}, 0x00, lvlobj.EnergyWeaponMaxTemperature,
|
||||
func(newValue int) {
|
||||
slot.SetWeaponState(byte(newValue), chargeAndOverload)
|
||||
onChange()
|
||||
})
|
||||
|
||||
overloadState := chargeAndOverload & 0x80
|
||||
charge := chargeAndOverload & 0x7F
|
||||
values.RenderUnifiedCheckboxCombo(readOnly, "Overload", values.UnifierFor(overloadState != 0), func(newOverload bool) {
|
||||
if newOverload {
|
||||
slot.SetWeaponState(temperature, 0x80|charge)
|
||||
} else {
|
||||
slot.SetWeaponState(temperature, charge)
|
||||
}
|
||||
onChange()
|
||||
})
|
||||
values.RenderUnifiedSliderInt(readOnly, "Charge", values.UnifierFor(int(charge)),
|
||||
func(u values.Unifier) int {
|
||||
return u.Unified().(int)
|
||||
}, func(value int) string {
|
||||
return fmt.Sprintf("%.02f%%%% -- raw: %%d", (float32(value)/float32(lvlobj.EnergyWeaponMaxEnergy))*100)
|
||||
}, 0x00, 0x7F,
|
||||
func(newValue int) {
|
||||
slot.SetWeaponState(temperature, overloadState|byte(newValue))
|
||||
onChange()
|
||||
})
|
||||
} else {
|
||||
rounds, ammoType := slot.WeaponState()
|
||||
values.RenderUnifiedSliderInt(readOnly, "Rounds", values.UnifierFor(int(rounds)),
|
||||
func(u values.Unifier) int {
|
||||
return u.Unified().(int)
|
||||
}, func(value int) string {
|
||||
return fmt.Sprintf("%d", value)
|
||||
}, 0x00, 0xFF,
|
||||
func(newValue int) {
|
||||
slot.SetWeaponState(byte(newValue), ammoType)
|
||||
onChange()
|
||||
})
|
||||
values.RenderUnifiedCombo(readOnly, "Ammo Type", values.UnifierFor(int(ammoType)),
|
||||
func(u values.Unifier) int {
|
||||
return u.Unified().(int)
|
||||
},
|
||||
func(value int) string {
|
||||
switch value {
|
||||
case 0:
|
||||
return "Standard"
|
||||
case 1:
|
||||
return "Special"
|
||||
default:
|
||||
return fmt.Sprintf("Unknown %d", ammoType)
|
||||
}
|
||||
}, 2,
|
||||
func(newValue int) {
|
||||
slot.SetWeaponState(rounds, byte(newValue))
|
||||
onChange()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (view *View) createVariableControls(readOnly bool, gameState *archive.GameState, onChange func()) {
|
||||
currentContextIdentifier := view.gameStateService.VariableBaseContext()
|
||||
if imgui.BeginCombo("Base Context", variableContextList[currentContextIdentifier].title) {
|
||||
contextIdentifiers := edit.VariableContextIdentifiers()
|
||||
for _, identifier := range contextIdentifiers {
|
||||
contextInfo := variableContextList[identifier]
|
||||
if imgui.SelectableV(contextInfo.title, identifier == currentContextIdentifier, 0, imgui.Vec2{}) {
|
||||
view.gameStateService.SetVariableBaseContext(identifier)
|
||||
}
|
||||
if imgui.IsItemHovered() {
|
||||
imgui.SetTooltip(contextInfo.info)
|
||||
}
|
||||
}
|
||||
imgui.EndCombo()
|
||||
}
|
||||
if imgui.IsItemHovered() {
|
||||
imgui.SetTooltip("The base context defines to what variables default to\n" +
|
||||
"in case they are not overridden by the project.")
|
||||
}
|
||||
|
||||
if imgui.Button("Remove All Overrides") {
|
||||
_ = view.gameStateService.DefaultAllVariables()
|
||||
}
|
||||
if imgui.IsItemHovered() {
|
||||
imgui.SetTooltip("Removes all overrides and their parameters.")
|
||||
}
|
||||
if !readOnly {
|
||||
imgui.SameLine()
|
||||
if imgui.Button("Reset All") {
|
||||
for i := 0; i < archive.BooleanVarCount; i++ {
|
||||
gameState.SetBooleanVar(i, view.gameStateService.BooleanVariable(i).ResetValueBool())
|
||||
}
|
||||
for i := 0; i < archive.IntegerVarCount; i++ {
|
||||
gameState.SetIntegerVar(i, view.gameStateService.IntegerVariable(i).ResetValueInt())
|
||||
}
|
||||
onChange()
|
||||
}
|
||||
if imgui.IsItemHovered() {
|
||||
imgui.SetTooltip("Resets all variables to their configured initial value.\n" +
|
||||
"To reset a single variable, hover over the specific variable and use the context menu.")
|
||||
}
|
||||
}
|
||||
|
||||
isSavegame := gameState.IsSavegame()
|
||||
if imgui.TreeNodeV("Boolean Variables", imgui.TreeNodeFlagsFramed) {
|
||||
intConverter := func(u values.Unifier) int {
|
||||
bValue := u.Unified().(bool)
|
||||
if bValue {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
defaultNames := map[int]string{0: "False", 1: "True"}
|
||||
|
||||
for i := 0; i < archive.BooleanVarCount; i++ {
|
||||
varIndex := i
|
||||
info := view.gameStateService.BooleanVariable(varIndex)
|
||||
|
||||
varReadOnly := readOnly || (info.Hardcoded && !isSavegame)
|
||||
varLabel := fmt.Sprintf("Var%03d", varIndex)
|
||||
varName := fmt.Sprintf("Var%03d: %s", varIndex, info.Name)
|
||||
varUnifier := values.UnifierFor(gameState.BooleanVar(varIndex))
|
||||
|
||||
values.RenderUnifiedCombo(varReadOnly, varName, varUnifier, intConverter,
|
||||
func(value int) string {
|
||||
name, found := info.ValueNames[int16(value)]
|
||||
if found {
|
||||
return name
|
||||
}
|
||||
return defaultNames[value]
|
||||
},
|
||||
2,
|
||||
func(newValue int) {
|
||||
gameState.SetBooleanVar(varIndex, newValue != 0)
|
||||
onChange()
|
||||
})
|
||||
|
||||
if imgui.BeginPopupContextItemV(varLabel+"-Popup", 1) {
|
||||
if imgui.Selectable("Edit info...") {
|
||||
view.modalStateMachine.SetState(&editBooleanVariableDialog{
|
||||
machine: view.modalStateMachine,
|
||||
view: view,
|
||||
service: view.gameStateService,
|
||||
index: varIndex,
|
||||
})
|
||||
}
|
||||
if !varReadOnly {
|
||||
imgui.Separator()
|
||||
if imgui.Selectable("Reset") {
|
||||
gameState.SetBooleanVar(varIndex, info.ResetValueBool())
|
||||
onChange()
|
||||
}
|
||||
}
|
||||
imgui.EndPopup()
|
||||
} else if imgui.IsItemHovered() && (len(info.Description) > 0) {
|
||||
imgui.SetTooltip(info.Description)
|
||||
}
|
||||
}
|
||||
imgui.TreePop()
|
||||
}
|
||||
if imgui.TreeNodeV("Integer Variables", imgui.TreeNodeFlagsFramed) {
|
||||
intConverter := func(u values.Unifier) int {
|
||||
return int(u.Unified().(int16))
|
||||
}
|
||||
for i := 0; i < archive.IntegerVarCount; i++ {
|
||||
varIndex := i
|
||||
info := view.gameStateService.IntegerVariable(varIndex)
|
||||
|
||||
varReadOnly := readOnly || (info.Hardcoded && !isSavegame)
|
||||
varLabel := fmt.Sprintf("Var%02d", varIndex)
|
||||
varName := fmt.Sprintf("Var%02d: %s", varIndex, info.Name)
|
||||
varUnifier := values.UnifierFor(gameState.IntegerVar(varIndex))
|
||||
changeHandler := func(newValue int) {
|
||||
gameState.SetIntegerVar(varIndex, int16(newValue))
|
||||
onChange()
|
||||
}
|
||||
|
||||
switch {
|
||||
case archive.IsRandomIntegerVariable(varIndex):
|
||||
values.RenderUnifiedSliderInt(readOnly, varName, varUnifier,
|
||||
func(u values.Unifier) int { return int(numbers.FromBinaryCodedDecimal(uint16(intConverter(u)))) },
|
||||
func(value int) string { return "%03d" },
|
||||
0, 999,
|
||||
func(newValue int) {
|
||||
changeHandler(int(numbers.ToBinaryCodedDecimal(uint16(newValue))))
|
||||
})
|
||||
case len(info.ValueNames) > 0:
|
||||
var max int16
|
||||
for key := range info.ValueNames {
|
||||
if key > max {
|
||||
max = key
|
||||
}
|
||||
}
|
||||
values.RenderUnifiedCombo(varReadOnly, varName, varUnifier, intConverter,
|
||||
func(value int) string {
|
||||
name, found := info.ValueNames[int16(value)]
|
||||
if found {
|
||||
return name
|
||||
}
|
||||
return fmt.Sprintf("Unknown %d", value)
|
||||
},
|
||||
int(max+1),
|
||||
changeHandler)
|
||||
default:
|
||||
min := math.MinInt16
|
||||
max := math.MaxInt16
|
||||
if info.Limits != nil {
|
||||
min = int(info.Limits.Minimum)
|
||||
max = int(info.Limits.Maximum)
|
||||
}
|
||||
|
||||
values.RenderUnifiedSliderInt(varReadOnly, varName, varUnifier, intConverter,
|
||||
func(value int) string {
|
||||
return "%d"
|
||||
},
|
||||
min, max,
|
||||
changeHandler)
|
||||
}
|
||||
if imgui.BeginPopupContextItemV(varLabel+"-Popup", 1) {
|
||||
if imgui.Selectable("Edit info...") {
|
||||
view.modalStateMachine.SetState(&editIntegerVariableDialog{
|
||||
machine: view.modalStateMachine,
|
||||
view: view,
|
||||
service: view.gameStateService,
|
||||
index: varIndex,
|
||||
})
|
||||
}
|
||||
if !varReadOnly {
|
||||
imgui.Separator()
|
||||
if imgui.Selectable("Reset") {
|
||||
gameState.SetIntegerVar(varIndex, info.ResetValueInt())
|
||||
onChange()
|
||||
}
|
||||
}
|
||||
imgui.EndPopup()
|
||||
} else if imgui.IsItemHovered() && (len(info.Description) > 0) {
|
||||
imgui.SetTooltip(info.Description)
|
||||
}
|
||||
}
|
||||
imgui.TreePop()
|
||||
}
|
||||
}
|
||||
|
||||
func (view *View) createMessageControls(readOnly bool, gameState *archive.GameState, onChange func()) {
|
||||
view.createMessageControlsFor(readOnly, "EMail",
|
||||
&view.model.emailIndex, archive.EMailCount, gameState.EMailState, onChange)
|
||||
view.createMessageControlsFor(readOnly, "Log",
|
||||
&view.model.logIndex, archive.LogCount, gameState.LogState, onChange)
|
||||
view.createMessageControlsFor(readOnly, "Fragment",
|
||||
&view.model.fragmentIndex, archive.FragmentCount, gameState.FragmentState, onChange)
|
||||
}
|
||||
|
||||
func (view *View) createMessageControlsFor(readOnly bool, name string, index *int, limit int,
|
||||
retriever func(int) archive.MessageState, onChange func()) {
|
||||
imgui.PushID(name)
|
||||
gui.StepSliderInt(name+" Index", index, 0, limit-1)
|
||||
messageState := retriever(*index)
|
||||
values.RenderUnifiedCheckboxCombo(readOnly, "Received", values.UnifierFor(messageState.Received()),
|
||||
func(newState bool) {
|
||||
messageState.SetReceived(newState)
|
||||
onChange()
|
||||
})
|
||||
values.RenderUnifiedCheckboxCombo(readOnly, "Read", values.UnifierFor(messageState.Read()),
|
||||
func(newState bool) {
|
||||
messageState.SetRead(newState)
|
||||
onChange()
|
||||
})
|
||||
imgui.PopID()
|
||||
}
|
||||
|
||||
func (view *View) tripleName(triple object.Triple) string {
|
||||
suffix := hintUnknown
|
||||
linearIndex := view.mod.ObjectProperties().TripleIndex(triple)
|
||||
if linearIndex >= 0 {
|
||||
key := resource.KeyOf(ids.ObjectLongNames, resource.LangDefault, linearIndex)
|
||||
objName, err := view.textCache.Text(key)
|
||||
if err == nil {
|
||||
suffix = singleLineString(objName)
|
||||
}
|
||||
}
|
||||
return triple.String() + ": " + suffix
|
||||
}
|
||||
|
||||
func (view *View) indexedName(class object.Class, index int) string {
|
||||
startIndex := view.mod.ObjectProperties().TripleIndex(object.TripleFrom(int(class), 0, 0))
|
||||
if startIndex >= 0 {
|
||||
key := resource.KeyOf(ids.ObjectLongNames, resource.LangDefault, startIndex+index)
|
||||
objName, err := view.textCache.Text(key)
|
||||
if err == nil {
|
||||
return singleLineString(objName)
|
||||
}
|
||||
}
|
||||
return hintUnknown
|
||||
}
|
||||
|
||||
func singleLineString(str string) string {
|
||||
return strings.ReplaceAll(strings.ReplaceAll(str, "\r", ""), "\n", "")
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package archives
|
||||
|
||||
type viewModel struct {
|
||||
windowOpen bool
|
||||
restoreFocus bool
|
||||
|
||||
selectedLevel int
|
||||
|
||||
emailIndex int
|
||||
logIndex int
|
||||
fragmentIndex int
|
||||
}
|
||||
|
||||
func freshViewModel() viewModel {
|
||||
return viewModel{}
|
||||
}
|
||||
Reference in New Issue
Block a user