diff --git a/modding/README.md b/modding/README.md index 235b22d..3299513 100644 --- a/modding/README.md +++ b/modding/README.md @@ -324,7 +324,10 @@ are layered this way, a card for any resource a later layer actually overrides shows both versions side by side - the first directory's own ("base") and the winning one ("this build") - so a mod's effect is always checked against the original it's replacing, not recalled from memory or a -separate base-only gallery run. +separate base-only gallery run. Cards are sectioned by the `-data` +directory they come from - "base game" first, then one section per mod +(named after `dist/mods/`) - so a mod's own animations, for example +all of `upscale_medbot`'s, are listed together. `gen_animgallery.sh` is a from-scratch Go decoder, not the game engine - it never exercises the engine's own texture upload/caching code at all. diff --git a/modding/src/animgallery/main.go b/modding/src/animgallery/main.go index 1bda26e..aaeef4e 100644 --- a/modding/src/animgallery/main.go +++ b/modding/src/animgallery/main.go @@ -7,7 +7,9 @@ // browsed by eye instead of guessed at by resource ID. Layering a mod's own // build output over the base game data (repeated -data) renders the result // as actually played - and for any resource the mod overrides, the base -// game's own version alongside it, for a direct comparison. +// game's own version alongside it, for a direct comparison. The index page +// sections its cards by the -data layer they come from: the base game, then +// each mod. package main import ( @@ -43,6 +45,11 @@ type galleryEntry struct { transparent bool gifName string filmstripName string + // group names the winning -data layer (see groupName), dirIndex is that + // layer's position among the -data directories - the index page + // sections its cards by group, in dirIndex order. + group string + dirIndex int // baseGifName/baseFilmstripName are set only when this resource's // winning layer isn't the first -data directory - the same content // rendered from that first (base) directory alone, for a side-by-side @@ -61,6 +68,20 @@ type dataDirs []string func (d *dataDirs) String() string { return strings.Join(*d, ",") } func (d *dataDirs) Set(v string) error { *d = append(*d, v); return nil } +// groupName labels one -data layer for the index page: "base game" for the +// first one, otherwise the mod's name. A built mod lives at +// dist/mods//mod, so a directory named "mod" is labeled by its parent. +func groupName(absDir string, dirIndex int) string { + if dirIndex == 0 { + return "base game" + } + name := filepath.Base(absDir) + if name == "mod" { + name = filepath.Base(filepath.Dir(absDir)) + } + return name +} + // loadDataDir merges every .res/.dat file directly inside dir into one // manifest entry, bypassing hacked's own fileAllowlist // (ss1/world/FileLoader.go): that check is only skipped when a file is @@ -142,11 +163,13 @@ func main() { // later layer (a mod) overrides. sourceFileByKey := make(map[resource.Key]string) sourceDirIndexByKey := make(map[resource.Key]int) + groupByDirIndex := make(map[int]string) for dirIndex, dir := range dirs { absDir, err := filepath.Abs(dir) if err != nil { log.Fatalf("resolving -data %q: %v", dir, err) } + groupByDirIndex[dirIndex] = groupName(absDir, dirIndex) entry, err := loadDataDir(absDir) if err != nil { // A mod that doesn't touch any resource file yet (e.g. an @@ -234,6 +257,8 @@ func main() { if !ok { continue } + entry.dirIndex = sourceDirIndexByKey[key] + entry.group = groupByDirIndex[entry.dirIndex] // A later layer wins this key over the first (base) -data directory // - also render the base directory's own version of it, so the two @@ -663,6 +688,23 @@ func writeIndex(path string, entries []galleryEntry) error { b.WriteString("#basket:empty::before{content:'(nothing selected yet - check animations below to add them here)';color:#678;font-style:italic}") b.WriteString("#basket .card{background:#2a3a2a}") + // One quick-filter button per group, in -data order. + groupNames := make(map[int]string) + for _, e := range entries { + groupNames[e.dirIndex] = e.group + } + groupIndexes := make([]int, 0, len(groupNames)) + for i := range groupNames { + groupIndexes = append(groupIndexes, i) + } + sort.Ints(groupIndexes) + groupButtons := `
Groups:` + for _, i := range groupIndexes { + name := html.EscapeString(groupNames[i]) + groupButtons += fmt.Sprintf(` `, name, name) + } + groupButtons += `` + b.WriteString(`
` + `` + `` + `
` + + `` + `` + + groupButtons + `` + `
`) @@ -680,17 +724,31 @@ func writeIndex(path string, entries []galleryEntry) error { fmt.Fprintf(&b, "

%d animated resource(s)

", len(entries)) - // Animation descriptors and multi-block bitmaps are genuinely distinct - // content types (different decode path, see contentTypeName) - grouped - // into separate sections rather than interleaved. - var animations, bitmaps []galleryEntry + // Cards are sectioned per -data layer (the base game, then each mod), and + // within one by content type: animation descriptors and multi-block + // bitmaps are genuinely distinct (different decode path, see + // contentTypeName). + type section struct { + group string + dirIndex int + animations, bitmaps []galleryEntry + } + var sections []*section + sectionByDir := make(map[int]*section) for _, e := range entries { + sec := sectionByDir[e.dirIndex] + if sec == nil { + sec = §ion{group: e.group, dirIndex: e.dirIndex} + sectionByDir[e.dirIndex] = sec + sections = append(sections, sec) + } if contentTypeName(e.kind) == "Animation" { - animations = append(animations, e) + sec.animations = append(sec.animations, e) } else { - bitmaps = append(bitmaps, e) + sec.bitmaps = append(sec.bitmaps, e) } } + sort.Slice(sections, func(i, j int) bool { return sections[i].dirIndex < sections[j].dirIndex }) writeCards := func(group []galleryEntry, idPrefix string) { for i, e := range group { @@ -700,7 +758,7 @@ func writeIndex(path string, entries []galleryEntry) error { } search := strings.ToLower(strings.Join([]string{ fmt.Sprintf("0x%x", int(e.id)), fmt.Sprintf("%d", int(e.id)), - e.lang.String(), e.kind, e.sourceFile, + e.lang.String(), e.kind, e.sourceFile, e.group, fmt.Sprintf("%dx%d", e.width, e.height), transparency, }, " ")) fmt.Fprintf(&b, "
", html.EscapeString(search)) @@ -728,15 +786,24 @@ func writeIndex(path string, entries []galleryEntry) error { b.WriteString("
") } } - fmt.Fprintf(&b, "

Animations (%d)

", len(animations)) - writeCards(animations, "a") - fmt.Fprintf(&b, "

Bitmaps (%d)

", len(bitmaps)) - writeCards(bitmaps, "b") + for _, sec := range sections { + b.WriteString("
") + fmt.Fprintf(&b, "

%s (%d)

", html.EscapeString(sec.group), len(sec.animations)+len(sec.bitmaps)) + if len(sec.animations) > 0 { + fmt.Fprintf(&b, "

Animations (%d)

", len(sec.animations)) + writeCards(sec.animations, fmt.Sprintf("%da", sec.dirIndex)) + } + if len(sec.bitmaps) > 0 { + fmt.Fprintf(&b, "

Bitmaps (%d)

", len(sec.bitmaps)) + writeCards(sec.bitmaps, fmt.Sprintf("%db", sec.dirIndex)) + } + b.WriteString("
") + } b.WriteString(``)