Files
hnh-map/internal/app/admin_merge.go
Nikolay Tatarinov 82cb8a13f5 Update project documentation and improve frontend functionality
- Updated the backend documentation in CONTRIBUTING.md and README.md to reflect changes in application structure and API endpoints.
- Enhanced the frontend components in MapView.vue for better handling of context menu actions.
- Added new types and interfaces in TypeScript for improved type safety in the frontend.
- Introduced new utility classes for managing characters and markers in the map.
- Updated .gitignore to include .vscode directory for better development environment management.
2026-02-24 23:32:50 +03:00

306 lines
7.1 KiB
Go

package app
import (
"archive/zip"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"go.etcd.io/bbolt"
)
func (a *App) merge(rw http.ResponseWriter, req *http.Request) {
if s := a.getSession(req); s == nil || !s.Auths.Has(AUTH_ADMIN) {
http.Error(rw, "Unauthorized", http.StatusUnauthorized)
return
}
err := req.ParseMultipartForm(1024 * 1024 * 500)
if err != nil {
log.Println(err)
http.Error(rw, "internal error", http.StatusInternalServerError)
return
}
mergef, hdr, err := req.FormFile("merge")
if err != nil {
log.Println(err)
http.Error(rw, "request error", http.StatusBadRequest)
return
}
zr, err := zip.NewReader(mergef, hdr.Size)
if err != nil {
log.Println(err)
http.Error(rw, "request error", http.StatusBadRequest)
return
}
ops := []struct {
mapid int
x, y int
f string
}{}
newTiles := map[string]struct{}{}
err = a.db.Update(func(tx *bbolt.Tx) error {
grids, err := tx.CreateBucketIfNotExists([]byte("grids"))
if err != nil {
return err
}
tiles, err := tx.CreateBucketIfNotExists([]byte("tiles"))
if err != nil {
return err
}
mb, err := tx.CreateBucketIfNotExists([]byte("markers"))
if err != nil {
return err
}
mgrid, err := mb.CreateBucketIfNotExists([]byte("grid"))
if err != nil {
return err
}
idB, err := mb.CreateBucketIfNotExists([]byte("id"))
if err != nil {
return err
}
configb, err := tx.CreateBucketIfNotExists([]byte("config"))
if err != nil {
return err
}
for _, fhdr := range zr.File {
if strings.HasSuffix(fhdr.Name, ".json") {
f, err := fhdr.Open()
if err != nil {
return err
}
md := mapData{}
err = json.NewDecoder(f).Decode(&md)
if err != nil {
return err
}
for _, ms := range md.Markers {
for _, mraw := range ms {
key := []byte(fmt.Sprintf("%s_%d_%d", mraw.GridID, mraw.Position.X, mraw.Position.Y))
if mgrid.Get(key) != nil {
continue
}
if mraw.Image == "" {
mraw.Image = "gfx/terobjs/mm/custom"
}
id, err := idB.NextSequence()
if err != nil {
return err
}
idKey := []byte(strconv.Itoa(int(id)))
m := Marker{
Name: mraw.Name,
ID: int(id),
GridID: mraw.GridID,
Position: Position{
X: mraw.Position.X,
Y: mraw.Position.Y,
},
Image: mraw.Image,
}
raw, _ := json.Marshal(m)
mgrid.Put(key, raw)
idB.Put(idKey, key)
}
}
mapB, err := tx.CreateBucketIfNotExists([]byte("maps"))
if err != nil {
return err
}
newGrids := map[Coord]string{}
maps := map[int]struct{ X, Y int }{}
for k, v := range md.Grids {
c := Coord{}
_, err := fmt.Sscanf(k, "%d_%d", &c.X, &c.Y)
if err != nil {
return err
}
newGrids[c] = v
gridRaw := grids.Get([]byte(v))
if gridRaw != nil {
gd := GridData{}
json.Unmarshal(gridRaw, &gd)
maps[gd.Map] = struct{ X, Y int }{gd.Coord.X - c.X, gd.Coord.Y - c.Y}
}
}
if len(maps) == 0 {
seq, err := mapB.NextSequence()
if err != nil {
return err
}
mi := MapInfo{
ID: int(seq),
Name: strconv.Itoa(int(seq)),
Hidden: configb.Get([]byte("defaultHide")) != nil,
}
raw, _ := json.Marshal(mi)
err = mapB.Put([]byte(strconv.Itoa(int(seq))), raw)
if err != nil {
return err
}
for c, grid := range newGrids {
cur := GridData{}
cur.ID = grid
cur.Map = int(seq)
cur.Coord = c
raw, err := json.Marshal(cur)
if err != nil {
return err
}
grids.Put([]byte(grid), raw)
}
continue
}
mapid := -1
offset := struct{ X, Y int }{}
for id, off := range maps {
mi := MapInfo{}
mraw := mapB.Get([]byte(strconv.Itoa(id)))
if mraw != nil {
json.Unmarshal(mraw, &mi)
}
if mi.Priority {
mapid = id
offset = off
break
}
if id < mapid || mapid == -1 {
mapid = id
offset = off
}
}
for c, grid := range newGrids {
cur := GridData{}
if curRaw := grids.Get([]byte(grid)); curRaw != nil {
continue
}
cur.ID = grid
cur.Map = mapid
cur.Coord.X = c.X + offset.X
cur.Coord.Y = c.Y + offset.Y
raw, err := json.Marshal(cur)
if err != nil {
return err
}
grids.Put([]byte(grid), raw)
}
if len(maps) > 1 {
grids.ForEach(func(k, v []byte) error {
gd := GridData{}
json.Unmarshal(v, &gd)
if gd.Map == mapid {
return nil
}
if merge, ok := maps[gd.Map]; ok {
var td *TileData
mapb, err := tiles.CreateBucketIfNotExists([]byte(strconv.Itoa(gd.Map)))
if err != nil {
return err
}
zoom, err := mapb.CreateBucketIfNotExists([]byte(strconv.Itoa(0)))
if err != nil {
return err
}
tileraw := zoom.Get([]byte(gd.Coord.Name()))
if tileraw != nil {
json.Unmarshal(tileraw, &td)
}
gd.Map = mapid
gd.Coord.X += offset.X - merge.X
gd.Coord.Y += offset.Y - merge.Y
raw, _ := json.Marshal(gd)
if td != nil {
ops = append(ops, struct {
mapid int
x int
y int
f string
}{
mapid: mapid,
x: gd.Coord.X,
y: gd.Coord.Y,
f: td.File,
})
}
grids.Put(k, raw)
}
return nil
})
}
for mergeid, merge := range maps {
if mapid == mergeid {
continue
}
mapB.Delete([]byte(strconv.Itoa(mergeid)))
log.Println("Reporting merge", mergeid, mapid)
a.reportMerge(mergeid, mapid, Coord{X: offset.X - merge.X, Y: offset.Y - merge.Y})
}
} else if strings.HasSuffix(fhdr.Name, ".png") {
os.MkdirAll(filepath.Join(a.gridStorage, "grids"), 0777)
f, err := os.Create(filepath.Join(a.gridStorage, "grids", filepath.Base(fhdr.Name)))
if err != nil {
return err
}
r, err := fhdr.Open()
if err != nil {
f.Close()
return err
}
io.Copy(f, r)
r.Close()
f.Close()
newTiles[strings.TrimSuffix(filepath.Base(fhdr.Name), ".png")] = struct{}{}
}
}
for gid := range newTiles {
gridRaw := grids.Get([]byte(gid))
if gridRaw != nil {
gd := GridData{}
json.Unmarshal(gridRaw, &gd)
ops = append(ops, struct {
mapid int
x int
y int
f string
}{
mapid: gd.Map,
x: gd.Coord.X,
y: gd.Coord.Y,
f: filepath.Join("grids", gid+".png"),
})
}
}
return nil
})
if err != nil {
log.Println(err)
http.Error(rw, "internal error", http.StatusInternalServerError)
return
}
for _, op := range ops {
a.SaveTile(op.mapid, Coord{X: op.x, Y: op.y}, 0, op.f, time.Now().UnixNano())
}
a.doRebuildZooms()
}