Files
aego/modules/settings/module.go
T
sanspie f22242e5aa
ci / release (push) Successful in 5m5s
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci / checks (push) Successful in 4m41s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 7m14s
Stage 11g2–11g3: registry texts, CLI and the site in Russian (D348)
The remaining editor strings and undo step names; component, field, enum, setting, diagnostic and Script API texts under schema.*, enum.*, setting.*, diag.* and api.*; the launcher, the in-game Settings View with a Language setting, the CLI and aego schema show --lang. Every documentation page has a .ru.md translation; the site has a full Russian tree with hreflang, a language switch and ru/llms.txt; aego docs check --lang ru. New doc.tutorial and doc.recipe icons. Fix: an animator restored by a rewind prepares its controller before the replayed step.
2026-10-25 15:31:22 +03:00

218 lines
5.5 KiB
Go

package settings
import (
"aego/engine/i18n"
"aego/platform"
"aego/schema"
"slices"
"aego/contracts/inputactions"
"aego/contracts/scriptevents"
"aego/engine"
"aego/engine/module"
es "aego/engine/settings"
"aego/scene/frame"
"aego/script/abi"
"aego/script/vm"
)
const (
ID = "settings"
ScriptModule = "aego/settings"
ServiceView = ID + ".view"
SystemTick = ID + ".tick"
SystemDraw = ID + ".draw"
ActionToggle = "ToggleSettings"
ContextView = "Settings"
saveDelay = 0.5
EventChanged = "OnSettingChanged"
KeyLanguage = ID + ".language"
)
// languages are the choices of the game's language setting; English and Russian name themselves.
var languages = []es.Choice{
{Name: i18n.Auto, Label: "Automatic", Value: schema.Enum(0)},
{Name: i18n.English, Label: "English", Value: schema.Enum(1)},
{Name: i18n.Russian, Label: "Русский", Value: schema.Enum(2)},
}
type Module struct {
st *es.Store
draft *es.Tx
now float64
touched float64
unsub func()
ev scriptevents.EventID
worlds []*engine.World
arg [1]vm.Value
editor bool
locales []string
}
func New() *Module {
return &Module{}
}
func (*Module) Info() module.Info {
return module.Info{ID: ID, Version: engine.Version, Requires: []string{"input"}, Builtin: true, DisplayName: "Settings", DocsOrder: 110, DocsIcon: "cat.settings"}
}
func (m *Module) Register(r *module.Registry) error {
registerDocs(r)
r.Provide(inputactions.Kind, inputactions.Set{
Actions: []inputactions.Action{{Name: ActionToggle, Type: "button", Bindings: []string{"Key.F10"}}},
Contexts: []inputactions.Context{{Name: ContextView, Priority: 1000, Consume: true, Actions: []string{ActionToggle}, Default: true}},
})
r.Setting(es.Desc{
ID: KeyLanguage, Kind: schema.KindEnum, Default: schema.Enum(0), Title: "Language",
Doc: "Language of the settings and of the game's translated texts. Automatic follows the system.",
Category: es.CategoryLanguage, Order: 10, Flags: es.User | es.Project, Choices: languages, Apply: m.applyLanguage,
})
m.ev = r.ScriptEvent(scriptevents.Event{
Name: EventChanged,
Params: []abi.TypeRef{abi.T(abi.String)},
Names: []string{"id"},
Doc: "OnSettingChanged runs on every script with this method after a setting's effective value changed: Apply, Revert, an override or a confirmation timeout.",
})
r.System(engine.SystemDesc{ID: SystemTick, Phase: engine.PhaseFrameBegin, Scope: engine.ScopeFrame, System: tickSystem{m: m}})
r.System(engine.SystemDesc{
ID: SystemDraw, Phase: engine.PhaseRenderUI, Scope: engine.ScopePlay,
After: []string{frame.SystemPresent}, Before: []string{frame.SystemOverlays}, System: drawSystem{},
})
if r.Bindings() != nil {
installScript(r.ScriptModule(ScriptModule), m)
}
return nil
}
func (m *Module) Init(ctx *module.InitContext) error {
m.st = ctx.Settings
m.editor = ctx.Editor
if ctx.Engine != nil {
if l, ok := ctx.Engine.Window().(platform.Localer); ok {
m.locales = l.PreferredLocales()
}
}
if m.st != nil {
m.unsub = m.st.Subscribe(0, m.changed)
if k := m.st.Key(KeyLanguage); k != 0 {
_ = m.applyLanguage(m.st.Value(k))
}
}
return nil
}
// applyLanguage switches the game's texts; inside the editor the editor's own language stays.
func (m *Module) applyLanguage(v schema.Value) error {
if m.editor {
return nil
}
pref := i18n.Auto
if i := int(v.I); i >= 0 && i < len(languages) {
pref = languages[i].Name
}
b := i18n.Default()
b.SetLanguage(i18n.Resolve(pref, m.locales, b.Languages()))
return nil
}
func (m *Module) Shutdown() {
if m.st != nil && m.st.Dirty() {
_ = m.st.Save()
}
if m.unsub != nil {
m.unsub()
m.unsub = nil
}
}
func (m *Module) AttachWorld(ctx *module.AttachContext) error {
if !ctx.Play || ctx.World == nil || m.st == nil {
return nil
}
ctx.World.SetView(ServiceView, NewView(m.st, ctx.World))
m.worlds = append(m.worlds, ctx.World)
return nil
}
func (m *Module) DetachWorld(ctx *module.AttachContext) {
if !ctx.Play || ctx.World == nil {
return
}
if v := Of(ctx.World); v != nil {
v.Close()
}
ctx.World.RemoveService(ServiceView)
m.worlds = slices.DeleteFunc(m.worlds, func(w *engine.World) bool { return w == ctx.World })
}
func (m *Module) tx() *es.Tx {
if m.draft == nil || m.draft.Done() {
m.draft = m.st.Begin()
}
return m.draft
}
func Of(w *engine.World) *View {
v, _ := engine.ServiceOf[*View](w, ServiceView)
return v
}
type tickSystem struct {
m *Module
}
func (t tickSystem) Run(ctx *engine.Context) {
m := t.m
if m.st == nil {
return
}
m.now = ctx.Engine.Platform().Now()
m.st.Tick(m.now)
if _, waiting := m.st.Pending(); !waiting && m.st.Dirty() && m.now-m.touched >= saveDelay {
_ = m.st.Save()
}
}
type drawSystem struct{}
func (drawSystem) Run(ctx *engine.Context) {
v := Of(ctx.World)
if v == nil {
return
}
if r, ok := engine.ServiceOf[inputactions.Reader](ctx.World, inputactions.ReaderID); ok && r.Pressed(ActionToggle) {
v.Toggle()
}
if !v.open {
return
}
if fr := frame.Of(ctx); fr != nil && fr.Renderer() != nil {
v.draw(ctx, fr)
}
}
func (m *Module) changed(k es.Key) {
m.touched = m.now
if m.ev == scriptevents.NoEvent || len(m.worlds) == 0 {
return
}
d := m.st.Desc(k)
if d == nil {
return
}
for _, w := range m.worlds {
sink, ok := engine.ServiceOf[scriptevents.Sink](w, scriptevents.ServiceID)
if !ok {
continue
}
b, ok1 := sink.(scriptevents.Broadcaster)
in, ok2 := sink.(scriptevents.Strings)
if !ok1 || !ok2 {
continue
}
m.arg[0] = in.Intern(d.ID)
b.Broadcast(m.ev, m.arg[:])
}
}