This commit is contained in:
+4
-1
@@ -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/<name>`) - 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.
|
||||
|
||||
@@ -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/<name>/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}</style></head><body>")
|
||||
|
||||
// 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 := `<br><span id="group-filters">Groups:`
|
||||
for _, i := range groupIndexes {
|
||||
name := html.EscapeString(groupNames[i])
|
||||
groupButtons += fmt.Sprintf(` <button type="button" class="group-filter" data-filter="%s">%s</button>`, name, name)
|
||||
}
|
||||
groupButtons += `</span>`
|
||||
|
||||
b.WriteString(`<div id="controls">` +
|
||||
`<label>Model: <select id="model">` +
|
||||
`<option>realesrgan-x4plus-anime</option>` +
|
||||
@@ -672,7 +714,9 @@ func writeIndex(path string, entries []galleryEntry) error {
|
||||
`<label>Scale: <input id="scale" type="number" value="2" min="1" max="4" style="width:4em"></label>` +
|
||||
`<button id="clear-sel" type="button">Clear selection</button>` +
|
||||
`<br><label>Filter: <input type="text" id="search" placeholder="id, language, file, transparent..." style="width:22em"></label>` +
|
||||
`<button id="clear-filter" type="button">Clear filter</button>` +
|
||||
`<span id="shown-count"></span>` +
|
||||
groupButtons +
|
||||
`<textarea id="mdl-output" rows="12" readonly placeholder="Select animations below to generate an MDL upscale definition here - copy it into modding/mods/<name>/bitmaps/<label>.json"></textarea>` +
|
||||
`</div>`)
|
||||
|
||||
@@ -680,17 +724,31 @@ func writeIndex(path string, entries []galleryEntry) error {
|
||||
|
||||
fmt.Fprintf(&b, "<h1>%d animated resource(s)</h1>", 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, "<div class=\"card\" data-search=\"%s\">", html.EscapeString(search))
|
||||
@@ -728,15 +786,24 @@ func writeIndex(path string, entries []galleryEntry) error {
|
||||
b.WriteString("</div>")
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(&b, "<h2>Animations (%d)</h2>", len(animations))
|
||||
writeCards(animations, "a")
|
||||
fmt.Fprintf(&b, "<h2>Bitmaps (%d)</h2>", len(bitmaps))
|
||||
writeCards(bitmaps, "b")
|
||||
for _, sec := range sections {
|
||||
b.WriteString("<div class=\"group\">")
|
||||
fmt.Fprintf(&b, "<h2>%s (%d)</h2>", html.EscapeString(sec.group), len(sec.animations)+len(sec.bitmaps))
|
||||
if len(sec.animations) > 0 {
|
||||
fmt.Fprintf(&b, "<h3>Animations (%d)</h3>", len(sec.animations))
|
||||
writeCards(sec.animations, fmt.Sprintf("%da", sec.dirIndex))
|
||||
}
|
||||
if len(sec.bitmaps) > 0 {
|
||||
fmt.Fprintf(&b, "<h3>Bitmaps (%d)</h3>", len(sec.bitmaps))
|
||||
writeCards(sec.bitmaps, fmt.Sprintf("%db", sec.dirIndex))
|
||||
}
|
||||
b.WriteString("</div>")
|
||||
}
|
||||
|
||||
b.WriteString(`<script>
|
||||
var basket = document.getElementById('basket');
|
||||
// Where each card lives when not selected, so unchecking it puts it back
|
||||
// in its original spot (Animations/Bitmaps section, original order)
|
||||
// in its original spot (its group's Animations/Bitmaps section, original order)
|
||||
// instead of just appending it back at the end.
|
||||
var cardHomes = new Map();
|
||||
Array.prototype.forEach.call(document.querySelectorAll('.card'), function (card) {
|
||||
@@ -796,9 +863,24 @@ function applyFilter() {
|
||||
card.style.display = match ? '' : 'none';
|
||||
if (match) shown++;
|
||||
});
|
||||
// A group with no visible card left hides its headings too.
|
||||
Array.prototype.forEach.call(document.querySelectorAll('.group'), function (group) {
|
||||
var visible = Array.prototype.some.call(group.querySelectorAll('.card'), function (card) {
|
||||
return card.style.display !== 'none';
|
||||
});
|
||||
group.style.display = visible ? '' : 'none';
|
||||
});
|
||||
document.getElementById('shown-count').textContent = shown + ' / ' + cards.length + ' shown';
|
||||
}
|
||||
document.getElementById('search').addEventListener('input', applyFilter);
|
||||
function setFilter(text) {
|
||||
document.getElementById('search').value = text;
|
||||
applyFilter();
|
||||
}
|
||||
document.getElementById('clear-filter').addEventListener('click', function () { setFilter(''); });
|
||||
Array.prototype.forEach.call(document.querySelectorAll('.group-filter'), function (btn) {
|
||||
btn.addEventListener('click', function () { setFilter(btn.dataset.filter); });
|
||||
});
|
||||
applyFilter();
|
||||
</script>`)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user