Initial commit
Partially copied from justinian/ark, updated to be a single process that watches save files and updates a single sqlite3 database.
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
menagerie
|
||||
*.ark
|
||||
*.db
|
||||
/*.json
|
||||
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "obelisk"]
|
||||
path = obelisk
|
||||
url = https://github.com/arkutils/Obelisk.git
|
||||
20
Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
||||
FROM golang:1.16 as build
|
||||
|
||||
WORKDIR /build
|
||||
ADD . /build
|
||||
RUN go build -o menagerie
|
||||
|
||||
|
||||
FROM alpine:latest
|
||||
RUN apk --no-cache add ca-certificates
|
||||
WORKDIR /app
|
||||
|
||||
EXPOSE 8090
|
||||
VOLUME /app/run
|
||||
|
||||
ADD static /app/static
|
||||
ADD obelisk/data/wiki/species.json /app/
|
||||
ADD obelisk/data/wiki/items.json /app/
|
||||
COPY --from=0 /build/menagerie /app/
|
||||
|
||||
CMD ["./menagerie", "-s", "species.json", "-s", "items.json", "-o", "/app/run/ark.db", "/app/saves"]
|
||||
148
api_handler.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
type apiHandler struct {
|
||||
lock sync.Mutex
|
||||
loader *Loader
|
||||
stmt *sqlx.Stmt
|
||||
}
|
||||
|
||||
const getAllDinos = `
|
||||
SELECT
|
||||
d.name,
|
||||
w.name as world,
|
||||
c1.name as class_name,
|
||||
d.dino_id1|d.dino_id2 as dino_id,
|
||||
level_wild,
|
||||
level_tamed,
|
||||
level_total,
|
||||
is_cryo,
|
||||
c2.name as parent_class,
|
||||
parent_name,
|
||||
x, y, z,
|
||||
color0, color1, color2, color3, color4, color5,
|
||||
health_current, stamina_current, torpor_current, oxygen_current, food_current, weight_current, melee_current, speed_current,
|
||||
health_wild, stamina_wild, torpor_wild, oxygen_wild, food_wild, weight_wild, melee_wild, speed_wild,
|
||||
health_tamed, stamina_tamed, torpor_tamed, oxygen_tamed, food_tamed, weight_tamed, melee_tamed, speed_tamed,
|
||||
health_total, stamina_total, torpor_total, oxygen_total, food_total, weight_total, melee_total, speed_total
|
||||
FROM
|
||||
dinos d
|
||||
LEFT JOIN worlds w ON d.world == w.id
|
||||
LEFT JOIN classes c1 ON d.class == c1.id
|
||||
LEFT JOIN classes c2 ON d.parent_class == c2.id
|
||||
`
|
||||
|
||||
type dinoResult struct {
|
||||
Name string `json:"name" db:"name"`
|
||||
World string `json:"world" db:"world"`
|
||||
Class string `json:"class_name" db:"class_name"`
|
||||
DinoId int `json:"dino_id" db:"dino_id"`
|
||||
LevelsWild int `json:"levels_wild" db:"level_wild"`
|
||||
LevelsTamed int `json:"levels_tamed" db:"level_tamed"`
|
||||
LevelsTotal int `json:"levels_total" db:"level_total"`
|
||||
|
||||
IsCryopod bool `json:"is_cryo" db:"is_cryo"`
|
||||
ParentClass *string `json:"parent_class" db:"parent_class"`
|
||||
ParentName *string `json:"parent_name" db:"parent_name"`
|
||||
|
||||
X float64 `json:"x" db:"x"`
|
||||
Y float64 `json:"y" db:"y"`
|
||||
Z float64 `json:"z" db:"z"`
|
||||
|
||||
Color0 int `json:"color0" db:"color0"`
|
||||
Color1 int `json:"color1" db:"color1"`
|
||||
Color2 int `json:"color2" db:"color2"`
|
||||
Color3 int `json:"color3" db:"color3"`
|
||||
Color4 int `json:"color4" db:"color4"`
|
||||
Color5 int `json:"color5" db:"color5"`
|
||||
|
||||
HealthCurrent float64 `json:"health_current" db:"health_current"`
|
||||
StaminaCurrent float64 `json:"stamina_current" db:"stamina_current"`
|
||||
TorporCurrent float64 `json:"torpor_current" db:"torpor_current"`
|
||||
OxygenCurrent float64 `json:"oxygen_current" db:"oxygen_current"`
|
||||
FoodCurrent float64 `json:"food_current" db:"food_current"`
|
||||
WeightCurrent float64 `json:"weight_current" db:"weight_current"`
|
||||
MeleeCurrent float64 `json:"melee_current" db:"melee_current"`
|
||||
SpeedCurrent float64 `json:"speed_current" db:"speed_current"`
|
||||
|
||||
HealthWild int64 `json:"health_wild" db:"health_wild"`
|
||||
StaminaWild int64 `json:"stamina_wild" db:"stamina_wild"`
|
||||
TorporWild int64 `json:"torpor_wild" db:"torpor_wild"`
|
||||
OxygenWild int64 `json:"oxygen_wild" db:"oxygen_wild"`
|
||||
FoodWild int64 `json:"food_wild" db:"food_wild"`
|
||||
WeightWild int64 `json:"weight_wild" db:"weight_wild"`
|
||||
MeleeWild int64 `json:"melee_wild" db:"melee_wild"`
|
||||
SpeedWild int64 `json:"speed_wild" db:"speed_wild"`
|
||||
|
||||
HealthTamed int64 `json:"health_tamed" db:"health_tamed"`
|
||||
StaminaTamed int64 `json:"stamina_tamed" db:"stamina_tamed"`
|
||||
TorporTamed int64 `json:"torpor_tamed" db:"torpor_tamed"`
|
||||
OxygenTamed int64 `json:"oxygen_tamed" db:"oxygen_tamed"`
|
||||
FoodTamed int64 `json:"food_tamed" db:"food_tamed"`
|
||||
WeightTamed int64 `json:"weight_tamed" db:"weight_tamed"`
|
||||
MeleeTamed int64 `json:"melee_tamed" db:"melee_tamed"`
|
||||
SpeedTamed int64 `json:"speed_tamed" db:"speed_tamed"`
|
||||
|
||||
HealthTotal int64 `json:"health_total" db:"health_total"`
|
||||
StaminaTotal int64 `json:"stamina_total" db:"stamina_total"`
|
||||
TorporTotal int64 `json:"torpor_total" db:"torpor_total"`
|
||||
OxygenTotal int64 `json:"oxygen_total" db:"oxygen_total"`
|
||||
FoodTotal int64 `json:"food_total" db:"food_total"`
|
||||
WeightTotal int64 `json:"weight_total" db:"weight_total"`
|
||||
MeleeTotal int64 `json:"melee_total" db:"melee_total"`
|
||||
SpeedTotal int64 `json:"speed_total" db:"speed_total"`
|
||||
}
|
||||
|
||||
func (ah *apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ah.loader.lock.Lock()
|
||||
defer ah.loader.lock.Unlock()
|
||||
|
||||
db := ah.loader.db
|
||||
|
||||
if ah.stmt == nil {
|
||||
stmt, err := db.Preparex(getAllDinos)
|
||||
if err != nil {
|
||||
log.Printf("Error preparing SQL: %s", err)
|
||||
http.Error(w, "Database Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
ah.stmt = stmt
|
||||
}
|
||||
|
||||
var result []dinoResult
|
||||
err := ah.stmt.Select(&result)
|
||||
if err != nil {
|
||||
log.Printf("Error querying database: %s", err)
|
||||
http.Error(w, "Database Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
log.Printf("Error marshalling result: %s", err)
|
||||
http.Error(w, "JSON Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func runServer(loader *Loader, addr string) {
|
||||
apiHandler := &apiHandler{loader: loader}
|
||||
|
||||
sm := http.NewServeMux()
|
||||
sm.Handle("/api/dinos", apiHandler)
|
||||
sm.Handle("/", http.FileServer(http.Dir("static")))
|
||||
|
||||
log.Printf("Listening on: %s", addr)
|
||||
log.Fatal(http.ListenAndServe(addr, loggingWrapper(sm)))
|
||||
}
|
||||
91
classes.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var keynames = []string{"items", "species"}
|
||||
var whitespace = regexp.MustCompile(`\s+`)
|
||||
var cSuffix = regexp.MustCompile(`_C$`)
|
||||
|
||||
type classSpec struct {
|
||||
Name string `json:"name"`
|
||||
Blueprint string `json:"bp"`
|
||||
}
|
||||
|
||||
type Class struct {
|
||||
Name string
|
||||
Id int
|
||||
}
|
||||
|
||||
type ClassMap struct {
|
||||
Map map[string]Class
|
||||
nextId int
|
||||
}
|
||||
|
||||
func (cm *ClassMap) Get(bpName string) *Class {
|
||||
if class, ok := cm.Map[bpName]; ok {
|
||||
return &class
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cm *ClassMap) Add(bpName string) *Class {
|
||||
class := Class{
|
||||
Name: cSuffix.ReplaceAllString(bpName, ""),
|
||||
Id: cm.nextId,
|
||||
}
|
||||
|
||||
cm.Map[bpName] = class
|
||||
cm.nextId++
|
||||
|
||||
return &class
|
||||
}
|
||||
|
||||
func readSpecFiles(paths ...string) (*ClassMap, error) {
|
||||
classCount := 1 // leave 0 for "none"
|
||||
classNames := make(map[string]Class)
|
||||
|
||||
for _, path := range paths {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Opening spec file %f:\n%w", path, err)
|
||||
}
|
||||
|
||||
jsonData, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Reading spec file %f:\n%w", path, err)
|
||||
}
|
||||
|
||||
var values map[string]json.RawMessage
|
||||
if err := json.Unmarshal(jsonData, &values); err != nil {
|
||||
return nil, fmt.Errorf("Loading spec file %f:\n%w", path, err)
|
||||
}
|
||||
|
||||
for _, key := range keynames {
|
||||
if raw, ok := values[key]; ok {
|
||||
var specs []classSpec
|
||||
if err := json.Unmarshal(raw, &specs); err != nil {
|
||||
return nil, fmt.Errorf("Loading specs from file %f:\n%w", path, err)
|
||||
}
|
||||
|
||||
for _, spec := range specs {
|
||||
parts := strings.Split(spec.Blueprint, ".")
|
||||
className := parts[len(parts)-1]
|
||||
classNames[className] = Class{
|
||||
Name: whitespace.ReplaceAllString(spec.Name, " "),
|
||||
Id: classCount,
|
||||
}
|
||||
classCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &ClassMap{Map: classNames, nextId: classCount}, nil
|
||||
}
|
||||
80
database_schema.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package main
|
||||
|
||||
var databaseSchema = []string{`
|
||||
CREATE TABLE worlds (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT UNIQUE,
|
||||
iter INTEGER DEFAULT 0
|
||||
);`,
|
||||
|
||||
`CREATE TABLE classes (
|
||||
id INTEGER PRIMARY KEY,
|
||||
class TEXT,
|
||||
name TEXT
|
||||
);`,
|
||||
|
||||
`CREATE TABLE dinos (
|
||||
id INTEGER,
|
||||
list INTEGER,
|
||||
world INTEGER,
|
||||
class INTEGER,
|
||||
name TEXT,
|
||||
level_wild INTEGER,
|
||||
level_tamed INTEGER,
|
||||
dino_id1 INTEGER,
|
||||
dino_id2 INTEGER,
|
||||
is_cryo BOOLEAN,
|
||||
parent_class INTEGER,
|
||||
parent_name TEXT,
|
||||
x FLOAT,
|
||||
y FLOAT,
|
||||
z FLOAT,
|
||||
|
||||
color0 INTEGER,
|
||||
color1 INTEGER,
|
||||
color2 INTEGER,
|
||||
color3 INTEGER,
|
||||
color4 INTEGER,
|
||||
color5 INTEGER,
|
||||
|
||||
health_current FLOAT,
|
||||
stamina_current FLOAT,
|
||||
torpor_current FLOAT,
|
||||
oxygen_current FLOAT,
|
||||
food_current FLOAT,
|
||||
weight_current FLOAT,
|
||||
melee_current FLOAT,
|
||||
speed_current FLOAT,
|
||||
|
||||
health_wild INTEGER,
|
||||
stamina_wild INTEGER,
|
||||
torpor_wild INTEGER,
|
||||
oxygen_wild INTEGER,
|
||||
food_wild INTEGER,
|
||||
weight_wild INTEGER,
|
||||
melee_wild INTEGER,
|
||||
speed_wild INTEGER,
|
||||
|
||||
health_tamed INTEGER,
|
||||
stamina_tamed INTEGER,
|
||||
torpor_tamed INTEGER,
|
||||
oxygen_tamed INTEGER,
|
||||
food_tamed INTEGER,
|
||||
weight_tamed INTEGER,
|
||||
melee_tamed INTEGER,
|
||||
speed_tamed INTEGER,
|
||||
|
||||
level_total INTEGER AS (level_wild+level_tamed),
|
||||
|
||||
health_total INTEGER AS (health_wild+health_tamed),
|
||||
stamina_total INTEGER AS (stamina_wild+stamina_tamed),
|
||||
torpor_total INTEGER AS (torpor_wild+torpor_tamed),
|
||||
oxygen_total INTEGER AS (oxygen_wild+oxygen_tamed),
|
||||
food_total INTEGER AS (food_wild+food_tamed),
|
||||
weight_total INTEGER AS (weight_wild+weight_tamed),
|
||||
melee_total INTEGER AS (melee_wild+melee_tamed),
|
||||
speed_total INTEGER AS (speed_wild+speed_tamed),
|
||||
|
||||
PRIMARY KEY (id, list, world)
|
||||
);`,
|
||||
}
|
||||
11
go.mod
Normal file
@@ -0,0 +1,11 @@
|
||||
module github.com/justinian/menagerie
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.5.0
|
||||
github.com/jmoiron/sqlx v1.3.4
|
||||
github.com/justinian/ark v0.0.0-20210822045058-dd888d05317b
|
||||
github.com/mattn/go-sqlite3 v1.14.8
|
||||
github.com/spf13/pflag v1.0.5
|
||||
)
|
||||
19
go.sum
Normal file
@@ -0,0 +1,19 @@
|
||||
github.com/fsnotify/fsnotify v1.5.0 h1:NO5hkcB+srp1x6QmwvNZLeaOgbM8cmBTN32THzjvu2k=
|
||||
github.com/fsnotify/fsnotify v1.5.0/go.mod h1:BX0DCEr5pT4jm2CnQdVP1lFV521fcCNcyEeNp4DQQDk=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w=
|
||||
github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
|
||||
github.com/justinian/ark v0.0.0-20210822045058-dd888d05317b h1:U/6HBeRnUoS1i+fymZxc/h9AeyThMyOEcA35wMd89Gw=
|
||||
github.com/justinian/ark v0.0.0-20210822045058-dd888d05317b/go.mod h1:rWERZwRn9NUgudnTqcamOqfGHG8OW+6bTYxidxmSCJI=
|
||||
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
|
||||
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
303
loader.go
Normal file
@@ -0,0 +1,303 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/justinian/ark"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
type Loader struct {
|
||||
lock sync.Mutex
|
||||
db *sqlx.DB
|
||||
classMap *ClassMap
|
||||
saveFiles []string
|
||||
}
|
||||
|
||||
func createLoader(dbname string, specfiles, savefiles []string) (*Loader, error) {
|
||||
// Always start with a fresh-loaded db, because options could have
|
||||
// changed.
|
||||
err := os.Remove(dbname)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("Could not move old db file:\n%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Creating sqlite3 database: %s", dbname)
|
||||
|
||||
db, err := sqlx.Connect("sqlite3", dbname)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not open db file:\n%w", err)
|
||||
}
|
||||
|
||||
for _, table := range databaseSchema {
|
||||
_, err = db.Exec(table)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not create SQL schema:\n%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
classMap, err := readSpecFiles(specfiles...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Reading spec files:\n%w", err)
|
||||
}
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not begin SQL transaction:\n%w", err)
|
||||
}
|
||||
|
||||
stmt, err := tx.Prepare("INSERT INTO classes VALUES (?,?,?)")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not prepare SQL class insert:\n%w", err)
|
||||
}
|
||||
|
||||
for bpName, class := range classMap.Map {
|
||||
_, err = stmt.Exec(class.Id, bpName, class.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Inserting class: (%d, %s):\n%w", class.Id, class.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Inserted %d class names from %d spec files.", len(classMap.Map), len(specfiles))
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not commit class names:\n%w", err)
|
||||
}
|
||||
|
||||
return &Loader{db: db, classMap: classMap, saveFiles: savefiles}, nil
|
||||
}
|
||||
|
||||
func (l *Loader) run() error {
|
||||
for _, savefile := range l.saveFiles {
|
||||
err := l.processSavefile(savefile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Processing %s:\n%w", savefile, err)
|
||||
}
|
||||
}
|
||||
|
||||
go l.watcher()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Loader) processSavefile(filename string) error {
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
|
||||
log.Printf("Processing save file: %s", filename)
|
||||
|
||||
archive, err := ark.OpenArchive(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not open save file:\n%w", err)
|
||||
}
|
||||
|
||||
save, err := ark.ReadSaveGame(archive)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not read save game:\n%w", err)
|
||||
}
|
||||
|
||||
worldName := save.DataFiles[0]
|
||||
if strings.HasSuffix(worldName, "_P") {
|
||||
worldName = worldName[:len(worldName)-2]
|
||||
}
|
||||
|
||||
tx, err := l.db.Begin()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not begin SQL transaction:\n%w", err)
|
||||
}
|
||||
|
||||
res, err := tx.Exec(`
|
||||
INSERT INTO worlds (name) VALUES (?)
|
||||
ON CONFLICT (name) DO UPDATE SET iter=iter+1`, worldName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not insert world name:\n%w", err)
|
||||
}
|
||||
|
||||
worldId, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not get world id:\n%w", err)
|
||||
}
|
||||
|
||||
_, err = tx.Exec("DELETE FROM dinos WHERE world = ?", worldId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not clear previous world iteration:\n%w", err)
|
||||
}
|
||||
|
||||
err = l.insertDinos(save.Objects, int(worldId), tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Inserting dino:\n%w", err)
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not commit SQL transaction:\n%w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Loader) insertDinos(objlists [][]*ark.GameObject, world int, tx *sql.Tx) error {
|
||||
stmt, err := tx.Prepare(`INSERT INTO dinos VALUES (
|
||||
?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,
|
||||
?,?,?,?,?,?,
|
||||
?,?,?,?,?,?,?,?,
|
||||
?,?,?,?,?,?,?,?,
|
||||
?,?,?,?,?,?,?,?)`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not prepare SQL insert:\n%w", err)
|
||||
}
|
||||
|
||||
for listNum, objlist := range objlists {
|
||||
for i, obj := range objlist {
|
||||
// TamedOnServerName is a good canary for tamed dinos
|
||||
server := obj.Properties.Get("TamedOnServerName", 0)
|
||||
if server == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
name := obj.Properties.GetString("TamedName", 0)
|
||||
statsCurrent := make([]float64, 12)
|
||||
pointsWild := make([]int64, 12)
|
||||
pointsTamed := make([]int64, 12)
|
||||
var levelWild int64
|
||||
var levelTamed int64
|
||||
|
||||
loc := obj.Location
|
||||
|
||||
var err error
|
||||
parentClass := 0
|
||||
parentName := ""
|
||||
if obj.Parent != nil {
|
||||
loc = obj.Parent.Location
|
||||
parentClass, err = l.getOrAddClass(tx, obj.Parent.ClassName.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parentName = obj.Parent.Properties.GetString("BoxName", 0)
|
||||
if parentName == "" {
|
||||
parentName = obj.Parent.Properties.GetString("PlayerName", 0)
|
||||
}
|
||||
}
|
||||
|
||||
cscProp := obj.Properties.GetTyped("MyCharacterStatusComponent", 0, ark.ObjectPropertyType)
|
||||
if cscProp != nil {
|
||||
cscId := cscProp.(*ark.ObjectProperty).Id
|
||||
csc := objlist[cscId]
|
||||
|
||||
for index := 0; index < 12; index++ {
|
||||
statsCurrent[index] = csc.Properties.GetFloat("CurrentStatusValues", index)
|
||||
pointsWild[index] = csc.Properties.GetInt("NumberOfLevelUpPointsApplied", index)
|
||||
pointsTamed[index] = csc.Properties.GetInt("NumberOfLevelUpPointsAppliedTamed", index)
|
||||
}
|
||||
|
||||
levelWild = csc.Properties.GetInt("BaseCharacterLevel", 0)
|
||||
levelTamed = csc.Properties.GetInt("ExtraCharacterLevel", 0)
|
||||
}
|
||||
|
||||
dinoId1 := obj.Properties.GetInt("DinoID1", 0)
|
||||
dinoId2 := obj.Properties.GetInt("DinoID2", 0)
|
||||
|
||||
colors := make([]int64, 6)
|
||||
for i := range colors {
|
||||
colors[i] = obj.Properties.GetInt("ColorSetIndices", i)
|
||||
}
|
||||
|
||||
classId, err := l.getOrAddClass(tx, obj.ClassName.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(
|
||||
i,
|
||||
listNum,
|
||||
world,
|
||||
classId,
|
||||
name,
|
||||
levelWild,
|
||||
levelTamed,
|
||||
dinoId1,
|
||||
dinoId2,
|
||||
obj.IsCryopod,
|
||||
parentClass,
|
||||
parentName,
|
||||
|
||||
loc.X, loc.Y, loc.Z,
|
||||
|
||||
colors[0], colors[1], colors[2],
|
||||
colors[3], colors[4], colors[5],
|
||||
|
||||
statsCurrent[0], statsCurrent[1], statsCurrent[2], statsCurrent[3],
|
||||
statsCurrent[4], statsCurrent[7], statsCurrent[8], statsCurrent[9],
|
||||
|
||||
pointsWild[0], pointsWild[1], pointsWild[2], pointsWild[3],
|
||||
pointsWild[4], pointsWild[7], pointsWild[8], pointsWild[9],
|
||||
|
||||
pointsTamed[0], pointsTamed[1], pointsTamed[2], pointsTamed[3],
|
||||
pointsTamed[4], pointsTamed[7], pointsTamed[8], pointsTamed[9],
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not insert object %d:\n%w", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Loader) getOrAddClass(tx *sql.Tx, bpName string) (int, error) {
|
||||
class := l.classMap.Get(bpName)
|
||||
if class == nil {
|
||||
class = l.classMap.Add(bpName)
|
||||
_, err := tx.Exec("INSERT INTO classes (id, class, name) VALUES (?,?,?)",
|
||||
class.Id, bpName, class.Name)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("Adding %s to the class table:\n%w", bpName, err)
|
||||
}
|
||||
}
|
||||
|
||||
return class.Id, nil
|
||||
}
|
||||
|
||||
func (l *Loader) watcher() {
|
||||
for {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating file watcher: %s", err)
|
||||
}
|
||||
|
||||
for _, path := range l.saveFiles {
|
||||
err = watcher.Add(path)
|
||||
if err != nil {
|
||||
log.Fatalf("Error watching %s: %s", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case event := <-watcher.Events:
|
||||
err = watcher.Close()
|
||||
if err != nil {
|
||||
log.Fatalf("Error closing watcher: %s", err)
|
||||
}
|
||||
|
||||
err = l.processSavefile(event.Name)
|
||||
if err != nil {
|
||||
log.Fatalf("Error reloading save %s: %s", err)
|
||||
}
|
||||
|
||||
case err := <-watcher.Errors:
|
||||
log.Fatalf("Error watching save file: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
24
logging_wrapper.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type statusSaver struct {
|
||||
s int
|
||||
w http.ResponseWriter
|
||||
}
|
||||
|
||||
func (s *statusSaver) Status() int { return s.s }
|
||||
func (s *statusSaver) Header() http.Header { return s.w.Header() }
|
||||
func (s *statusSaver) Write(b []byte) (int, error) { return s.w.Write(b) }
|
||||
func (s *statusSaver) WriteHeader(c int) { s.s = c; s.w.WriteHeader(c) }
|
||||
|
||||
func loggingWrapper(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
s := statusSaver{s: 200, w: w}
|
||||
h.ServeHTTP(&s, r)
|
||||
log.Printf("%21s %3d%7s %s", r.RemoteAddr, s.Status(), r.Method, r.URL)
|
||||
})
|
||||
}
|
||||
74
main.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var output string
|
||||
var address string
|
||||
var specfiles []string
|
||||
pflag.StringVarP(&output, "out", "o", "ark.db", "Filename of the database to create")
|
||||
pflag.StringVarP(&address, "addr", "a", "[::]:8090", "Address to listen on")
|
||||
pflag.StringArrayVarP(&specfiles, "spec", "s", nil, "JSON species/item files to load")
|
||||
pflag.Parse()
|
||||
|
||||
args := pflag.Args()
|
||||
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s [options] <savefile> ...\n", os.Args[0])
|
||||
fmt.Fprintf(os.Stderr, " %s -h for help\n", os.Args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
saves := make([]string, 0, len(args))
|
||||
for _, savepath := range args {
|
||||
info, err := os.Stat(savepath)
|
||||
if err != nil {
|
||||
log.Fatal("%s: %s", savepath, err)
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
saves = append(saves, savepath)
|
||||
continue
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(savepath)
|
||||
if err != nil {
|
||||
log.Fatal("Directory %s: %s", savepath, err)
|
||||
}
|
||||
|
||||
for _, ent := range entries {
|
||||
if ent.IsDir() {
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(ent.Name(), ".ark") {
|
||||
saves = append(saves, path.Join(savepath, ent.Name()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Print("Menagerie starting.")
|
||||
for _, save := range saves {
|
||||
log.Printf("Using save file: %s", save)
|
||||
}
|
||||
|
||||
loader, err := createLoader(output, specfiles, saves)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer loader.db.Close()
|
||||
|
||||
err = loader.run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
runServer(loader, address)
|
||||
}
|
||||
1
obelisk
Submodule
132
static/colors.js
Normal file
@@ -0,0 +1,132 @@
|
||||
var colors = [
|
||||
null,
|
||||
{"id": 1, "name": "Red", "color": "#ff0000"},
|
||||
{"id": 2, "name": "Blue", "color": "#0000ff"},
|
||||
{"id": 3, "name": "Green", "color": "#00ff00"},
|
||||
{"id": 4, "name": "Yellow", "color": "#ffff00"},
|
||||
{"id": 5, "name": "Cyan", "color": "#00ffff"},
|
||||
{"id": 6, "name": "Magenta", "color": "#ff00ff"},
|
||||
{"id": 7, "name": "Light Green", "color": "#c0ffba"},
|
||||
{"id": 8, "name": "Light Grey", "color": "#c8caca"},
|
||||
{"id": 9, "name": "Light Brown", "color": "#786759"},
|
||||
{"id": 10, "name": "Light Orange", "color": "#ffb46c"},
|
||||
{"id": 11, "name": "Light Yellow", "color": "#fffa8a"},
|
||||
{"id": 12, "name": "Light Red", "color": "#ff756c"},
|
||||
{"id": 13, "name": "Dark Grey", "color": "#7b7b7b"},
|
||||
{"id": 14, "name": "Black", "color": "#3b3b3b"},
|
||||
{"id": 15, "name": "Brown", "color": "#593a2a"},
|
||||
{"id": 16, "name": "Dark Green", "color": "#224900"},
|
||||
{"id": 17, "name": "Dark Red", "color": "#812118"},
|
||||
{"id": 18, "name": "White", "color": "#ffffff"},
|
||||
{"id": 19, "name": "Dino Light Red", "color": "#ffa8a8"},
|
||||
{"id": 20, "name": "Dino Dark Red", "color": "#592b2b"},
|
||||
{"id": 21, "name": "Dino Light Orange", "color": "#ffb694"},
|
||||
{"id": 22, "name": "Dino Dark Orange", "color": "#88532f"},
|
||||
{"id": 23, "name": "Dino Light Yellow", "color": "#cacaa0"},
|
||||
{"id": 24, "name": "Dino Dark Yellow", "color": "#94946c"},
|
||||
{"id": 25, "name": "Dino Light Green", "color": "#e0ffe0"},
|
||||
{"id": 26, "name": "Dino Medium Green", "color": "#799479"},
|
||||
{"id": 27, "name": "Dino Dark Green", "color": "#224122"},
|
||||
{"id": 28, "name": "Dino Light Blue", "color": "#d9e0ff"},
|
||||
{"id": 29, "name": "Dino Dark Blue", "color": "#394263"},
|
||||
{"id": 30, "name": "Dino Light Purple", "color": "#e4d9ff"},
|
||||
{"id": 31, "name": "Dino Dark Purple", "color": "#403459"},
|
||||
{"id": 32, "name": "Dino Light Brown", "color": "#ffe0ba"},
|
||||
{"id": 33, "name": "Dino Medium Brown", "color": "#948575"},
|
||||
{"id": 34, "name": "Dino Dark Brown", "color": "#594e41"},
|
||||
{"id": 35, "name": "Dino Darker Grey", "color": "#595959"},
|
||||
{"id": 36, "name": "Dino Albino", "color": "#ffffff"},
|
||||
{"id": 37, "name": "BigFoot0", "color": "#b79683"},
|
||||
{"id": 38, "name": "BigFoot4", "color": "#eadad5"},
|
||||
{"id": 39, "name": "BigFoot5", "color": "#d0a794"},
|
||||
{"id": 40, "name": "WolfFur", "color": "#c3b39f"},
|
||||
{"id": 41, "name": "DarkWolfFur", "color": "#887666"},
|
||||
{"id": 42, "name": "DragonBase0", "color": "#a0664b"},
|
||||
{"id": 43, "name": "DragonBase1", "color": "#cb7956"},
|
||||
{"id": 44, "name": "DragonFire", "color": "#bc4f00"},
|
||||
{"id": 45, "name": "DragonGreen0", "color": "#79846c"},
|
||||
{"id": 46, "name": "DragonGreen1", "color": "#909c79"},
|
||||
{"id": 47, "name": "DragonGreen2", "color": "#a5a48b"},
|
||||
{"id": 48, "name": "DragonGreen3", "color": "#74939c"},
|
||||
{"id": 49, "name": "WyvernPurple0", "color": "#787496"},
|
||||
{"id": 50, "name": "WyvernPurple1", "color": "#b0a2c0"},
|
||||
{"id": 51, "name": "WyvernBlue0", "color": "#6281a7"},
|
||||
{"id": 52, "name": "WyvernBlue1", "color": "#485c75"},
|
||||
{"id": 53, "name": "Dino Medium Blue", "color": "#5fa4ea"},
|
||||
{"id": 54, "name": "Dino Deep Blue", "color": "#4568d4"},
|
||||
{"id": 55, "name": "NearWhite", "color": "#ededed"},
|
||||
{"id": 56, "name": "NearBlack", "color": "#515151"},
|
||||
{"id": 57, "name": "DarkTurquoise", "color": "#184546"},
|
||||
{"id": 58, "name": "MediumTurquoise", "color": "#007060"},
|
||||
{"id": 59, "name": "Turquoise", "color": "#00c5ab"},
|
||||
{"id": 60, "name": "GreenSlate", "color": "#40594c"},
|
||||
{"id": 61, "name": "Sage", "color": "#3e4f40"},
|
||||
{"id": 62, "name": "DarkWarmGray", "color": "#3b3938"},
|
||||
{"id": 63, "name": "MediumWarmGray", "color": "#585554"},
|
||||
{"id": 64, "name": "LightWarmGray", "color": "#9b9290"},
|
||||
{"id": 65, "name": "DarkCement", "color": "#525b56"},
|
||||
{"id": 66, "name": "LightCement", "color": "#8aa196"},
|
||||
{"id": 67, "name": "LightPink", "color": "#e8b0ff"},
|
||||
{"id": 68, "name": "DeepPink", "color": "#ff119a"},
|
||||
{"id": 69, "name": "DarkViolet", "color": "#730046"},
|
||||
{"id": 70, "name": "DarkMagenta", "color": "#b70042"},
|
||||
{"id": 71, "name": "BurntSienna", "color": "#7e331e"},
|
||||
{"id": 72, "name": "MediumAutumn", "color": "#a93000"},
|
||||
{"id": 73, "name": "Vermillion", "color": "#ef3100"},
|
||||
{"id": 74, "name": "Coral", "color": "#ff5834"},
|
||||
{"id": 75, "name": "Orange", "color": "#ff7f00"},
|
||||
{"id": 76, "name": "Peach", "color": "#ffa73a"},
|
||||
{"id": 77, "name": "LightAutumn", "color": "#ae7000"},
|
||||
{"id": 78, "name": "Mustard", "color": "#949427"},
|
||||
{"id": 79, "name": "ActualBlack", "color": "#171717"},
|
||||
{"id": 80, "name": "MidnightBlue", "color": "#191d36"},
|
||||
{"id": 81, "name": "DarkBlue", "color": "#152b3a"},
|
||||
{"id": 82, "name": "BlackSands", "color": "#302531"},
|
||||
{"id": 83, "name": "LemonLime", "color": "#a8ff44"},
|
||||
{"id": 84, "name": "Mint", "color": "#38e985"},
|
||||
{"id": 85, "name": "Jade", "color": "#008840"},
|
||||
{"id": 86, "name": "PineGreen", "color": "#0f552e"},
|
||||
{"id": 87, "name": "SpruceGreen", "color": "#005b45"},
|
||||
{"id": 88, "name": "LeafGreen", "color": "#5b9725"},
|
||||
{"id": 89, "name": "DarkLavender", "color": "#5e275f"},
|
||||
{"id": 90, "name": "MediumLavender", "color": "#853587"},
|
||||
{"id": 91, "name": "Lavender", "color": "#bd77be"},
|
||||
{"id": 92, "name": "DarkTeal", "color": "#0e404a"},
|
||||
{"id": 93, "name": "MediumTeal", "color": "#105563"},
|
||||
{"id": 94, "name": "Teal", "color": "#14849c"},
|
||||
{"id": 95, "name": "PowderBlue", "color": "#82a7ff"},
|
||||
{"id": 96, "name": "Glacial", "color": "#aceaff"},
|
||||
{"id": 97, "name": "Cammo", "color": "#505118"},
|
||||
{"id": 98, "name": "DryMoss", "color": "#766e3f"},
|
||||
{"id": 99, "name": "Custard", "color": "#c0bd5e"},
|
||||
{"id": 100, "name": "Cream", "color": "#f4ffc0"},
|
||||
]
|
||||
|
||||
var dyes = [
|
||||
{"id": 201, "name": "Black Dye", "color": "#1f1f1f"},
|
||||
{"id": 202, "name": "Blue Dye", "color": "#0000ff"},
|
||||
{"id": 203, "name": "Brown Dye", "color": "#756147"},
|
||||
{"id": 204, "name": "Cyan Dye", "color": "#00ffff"},
|
||||
{"id": 205, "name": "Forest Dye", "color": "#006c00"},
|
||||
{"id": 206, "name": "Green Dye", "color": "#00ff00"},
|
||||
{"id": 207, "name": "Unused Purple Dye", "color": "#6c00ba"},
|
||||
{"id": 208, "name": "Orange Dye", "color": "#ff8800"},
|
||||
{"id": 209, "name": "Parchment Dye", "color": "#ffffba"},
|
||||
{"id": 210, "name": "Pink Dye", "color": "#ff7be1"},
|
||||
{"id": 211, "name": "Purple Dye", "color": "#7b00e0"},
|
||||
{"id": 212, "name": "Red Dye", "color": "#ff0000"},
|
||||
{"id": 213, "name": "Royalty Dye", "color": "#7b00a8"},
|
||||
{"id": 214, "name": "Silver Dye", "color": "#e0e0e0"},
|
||||
{"id": 215, "name": "Sky Dye", "color": "#bad4ff"},
|
||||
{"id": 216, "name": "Tan Dye", "color": "#ffed82"},
|
||||
{"id": 217, "name": "Tangerine Dye", "color": "#ad652c"},
|
||||
{"id": 218, "name": "White Dye", "color": "#fefefe"},
|
||||
{"id": 219, "name": "Yellow Dye", "color": "#ffff00"},
|
||||
{"id": 220, "name": "Magenta Dye", "color": "#e71fd9"},
|
||||
{"id": 221, "name": "Brick Dye", "color": "#94341f"},
|
||||
{"id": 222, "name": "Cantaloupe Dye", "color": "#ff9a00"},
|
||||
{"id": 223, "name": "Mud Dye", "color": "#473b2b"},
|
||||
{"id": 224, "name": "Navy Dye", "color": "#34346c"},
|
||||
{"id": 225, "name": "Olive Dye", "color": "#baba59"},
|
||||
{"id": 226, "name": "Slate Dye", "color": "#595959"},
|
||||
]
|
||||
BIN
static/images/maps/Aberration.webp
Executable file
|
After Width: | Height: | Size: 732 KiB |
BIN
static/images/maps/CrystalIsles.webp
Executable file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
static/images/maps/Extinction.webp
Executable file
|
After Width: | Height: | Size: 455 KiB |
BIN
static/images/maps/Genesis1.webp
Executable file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
static/images/maps/Genesis2.webp
Executable file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
static/images/maps/Ragnarok.webp
Executable file
|
After Width: | Height: | Size: 442 KiB |
BIN
static/images/maps/ScorchedEarth.webp
Executable file
|
After Width: | Height: | Size: 420 KiB |
BIN
static/images/maps/TheIsland.webp
Executable file
|
After Width: | Height: | Size: 250 KiB |
BIN
static/images/maps/Valguero.webp
Executable file
|
After Width: | Height: | Size: 550 KiB |
182
static/index.html
Normal file
@@ -0,0 +1,182 @@
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="https://cdn.jsdelivr.net/npm/bootswatch@5/dist/yeti/bootstrap.min.css">
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="https://cdn.datatables.net/v/bs5/jq-3.3.1/dt-1.10.25/sb-1.1.0/sl-1.3.3/datatables.min.css"/>
|
||||
|
||||
<script type="text/javascript"
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.0.1/js/bootstrap.bundle.min.js"></script>
|
||||
<script type="text/javascript"
|
||||
src="https://cdn.datatables.net/v/bs5/jq-3.3.1/dt-1.10.25/sb-1.1.0/sl-1.3.3/datatables.min.js"></script>
|
||||
|
||||
<script type="text/javascript" src="colors.js"></script>
|
||||
<script type="text/javascript" src="main.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready( function () {
|
||||
table = $('#dinos').DataTable(tableOptions);
|
||||
|
||||
table.searchBuilder.container().prependTo( $('#searchBuilderDiv') );
|
||||
$( '#searchInput' ).on( 'keyup', function () {
|
||||
table.search( this.value ).draw();
|
||||
} );
|
||||
|
||||
$( '#showBase' ).on( 'click', showStats );
|
||||
$( '#showTamed' ).on( 'click', showStats );
|
||||
$( '#showTotal' ).on( 'click', showStats );
|
||||
$( '#showCurrent' ).on( 'click', showStats );
|
||||
$( '#showColor' ).on( 'click', showStats );
|
||||
|
||||
table.on( "select", function () {
|
||||
row = table.row({"selected":true}).data();
|
||||
if (row.name) {
|
||||
$( '#dinoName' ).html(row.name);
|
||||
} else {
|
||||
$( '#dinoName' ).html("Unnamed " + row.class_name);
|
||||
}
|
||||
|
||||
$( '#dinoWorldName' ).html(row.world);
|
||||
$( '#dinoId' ).html(row.dino_id);
|
||||
$( '#dinoClass' ).html(row.class_name);
|
||||
|
||||
if (row.is_cryo) {
|
||||
if (row.parent_name) {
|
||||
$( '#dinoStored' ).html(row.parent_name);
|
||||
} else {
|
||||
$( '#dinoStored' ).html(row.parent_class);
|
||||
}
|
||||
} else {
|
||||
$( '#dinoStored' ).html("");
|
||||
}
|
||||
|
||||
for (var i = 0; i < 6; i++) {
|
||||
var id = "#dinoColor" + i;
|
||||
var color = row["color" + i];
|
||||
if (color >= 0) {
|
||||
c = colors[color];
|
||||
} else {
|
||||
c = dyes[color+55];
|
||||
}
|
||||
if (c === undefined || c === null) {
|
||||
$( id ).css("background-color", "");
|
||||
$( id ).css("border", "");
|
||||
} else {
|
||||
$( id ).css("background-color", c.color);
|
||||
$( id ).css("border", "1px solid black");
|
||||
}
|
||||
}
|
||||
|
||||
var canvas = document.getElementById("dinoWorldMap");
|
||||
drawMap(canvas, row.world, row.x, row.y);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.swatch {
|
||||
float: left;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
margin-right: 10px;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.dinoInfo {
|
||||
font-size: smaller;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container-fluid p-4">
|
||||
<div class="row gx-5">
|
||||
|
||||
<div class="col-8 rounded-3 m-3 p-1 bg-light">
|
||||
<table id="dinos" class="table table-striped nowrap" width="100%"></table>
|
||||
</div>
|
||||
|
||||
<div class="col-3 m-3 p-3">
|
||||
<div class="row">
|
||||
<div class="btn-group" width="100%" role="group">
|
||||
<input type="radio" class="btn-check" name="show" id="showBase" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="showBase">Base Stats</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="show" id="showTamed" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="showTamed">Tamed Points</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="show" id="showTotal" checked autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="showTotal">Total Stats</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="show" id="showCurrent" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="showCurrent">Current Stats</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="show" id="showColor" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="showColor">Colors</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row my-2 p-2">
|
||||
<input type="text" class="form-control" id="searchInput" placeholder="Search">
|
||||
</div>
|
||||
|
||||
<div class="row my-2 p-2 rounded bg-light" id="searchBuilderDiv">
|
||||
</div>
|
||||
|
||||
<div class="row my-2 p-2 rounded bg-light" id="dinoInfo">
|
||||
<h3 id="dinoName">No Selection</h3>
|
||||
|
||||
<div class="p-3">
|
||||
<table width="100%" class="dinoInfo table">
|
||||
<tr>
|
||||
<th>World</th>
|
||||
<td id="dinoWorldName"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Class</th>
|
||||
<td id="dinoClass"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Dino ID</th>
|
||||
<td id="dinoId"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Stored In</th>
|
||||
<td id="dinoStored"></td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="p-3">
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<th>0</th>
|
||||
<th>1</th>
|
||||
<th>2</th>
|
||||
<th>3</th>
|
||||
<th>4</th>
|
||||
<th>5</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id="dinoColor0"> </td>
|
||||
<td id="dinoColor1"> </td>
|
||||
<td id="dinoColor2"> </td>
|
||||
<td id="dinoColor3"> </td>
|
||||
<td id="dinoColor4"> </td>
|
||||
<td id="dinoColor5"> </td>
|
||||
</tr>
|
||||
|
||||
<canvas id="dinoWorldMap"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
169
static/main.js
Normal file
@@ -0,0 +1,169 @@
|
||||
|
||||
var colorFunc = function (data, type, row, meta) {
|
||||
var c = null;
|
||||
if (data < 0) {
|
||||
c = dyes[data+55]
|
||||
} else {
|
||||
c = colors[data];
|
||||
}
|
||||
|
||||
if (c) {
|
||||
return "<div class='swatch' style='background-color: "
|
||||
+ c.color + ";'></div> " + c.id + ": " + c.name;
|
||||
} else {
|
||||
if (data != 0) {
|
||||
console.log("Unknown color value: " + data + " for slot " + index);
|
||||
}
|
||||
return "<div class='swatch'></div> " + data + ": Unused";
|
||||
}
|
||||
}
|
||||
|
||||
var showStats = function () {
|
||||
var showBase = $( '#showBase' )[0].checked;
|
||||
var showTamed = $( '#showTamed' )[0].checked;
|
||||
var showTotal = $( '#showTotal' )[0].checked;
|
||||
var showCurrent = $( '#showCurrent' )[0].checked;
|
||||
var showColor = $( '#showColor' )[0].checked;
|
||||
|
||||
table.columns(3).visible(showBase);
|
||||
table.columns(4).visible(showTamed);
|
||||
table.columns(5).visible(showTotal);
|
||||
|
||||
table.columns([12, 13, 14, 15, 16, 17]).visible(showColor);
|
||||
table.columns([18, 19, 20, 21, 22, 23, 24, 25]).visible(showCurrent);
|
||||
table.columns([26, 27, 28, 29, 30, 31, 32, 33]).visible(showBase);
|
||||
table.columns([34, 35, 36, 37, 38, 39, 40, 41]).visible(showTamed);
|
||||
table.columns([42, 43, 44, 45, 46, 47, 48, 49]).visible(showTotal);
|
||||
};
|
||||
|
||||
var fixedFloat = function (data, type, row, meta) {
|
||||
return data.toFixed(0);
|
||||
};
|
||||
|
||||
var maps = {
|
||||
"TheIsland": {"shiftx": 0.5, "shifty": 0.5, "mulx": 800000, "muly": 800000},
|
||||
"ScorchedEarth": {"shiftx": 0.5, "shifty": 0.5, "mulx": 800000, "muly": 800000},
|
||||
"Aberration": {"shiftx": 0.5, "shifty": 0.5, "mulx": 800000, "muly": 800000},
|
||||
"Extinction": {"shiftx": 0.5, "shifty": 0.5, "mulx": 800000, "muly": 800000},
|
||||
"Ragnarok": {"shiftx": 0.5, "shifty": 0.5, "mulx": 1310000, "muly": 1310000},
|
||||
"Valguero": {"shiftx": 0.5, "shifty": 0.5, "mulx": 816000, "muly": 816000},
|
||||
"CrystalIsles": {"shiftx": 0.4875, "shifty": 0.5, "mulx": 1600000, "muly": 1700000},
|
||||
"Genesis1": {"shiftx": 0.5, "shifty": 0.5, "mulx": 1050000, "muly": 1050000},
|
||||
"Genesis2": {"shiftx": 0.49655, "shifty": 0.49655, "mulx": 1450000, "muly": 1450000},
|
||||
};
|
||||
|
||||
var drawMap = function (canvas, world, x, y) {
|
||||
var mapInfo = maps[world];
|
||||
if (mapInfo === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
var mapImage = new Image();
|
||||
mapImage.onload = function () {
|
||||
canvas.height = mapImage.height;
|
||||
canvas.width = mapImage.width;
|
||||
|
||||
ctx = canvas.getContext("2d");
|
||||
ctx.drawImage(mapImage, 0, 0);
|
||||
|
||||
mx = (x / mapInfo["mulx"] + mapInfo["shiftx"]) * canvas.width;
|
||||
my = (y / mapInfo["muly"] + mapInfo["shifty"]) * canvas.height;
|
||||
pointSize = canvas.width / 125;
|
||||
|
||||
ctx.fillStyle = "#ff2020";
|
||||
ctx.beginPath();
|
||||
ctx.arc(mx, my, pointSize, 0, 2 * Math.PI, true);
|
||||
ctx.fill();
|
||||
|
||||
canvas.style.width = "100%";
|
||||
};
|
||||
mapImage.src = "images/maps/" + world + ".webp";
|
||||
};
|
||||
|
||||
var columns = [
|
||||
{"data": "name", "title": "Name"},
|
||||
{"data": "world", "title": "World", "visible": false},
|
||||
{"data": "class_name", "title": "Class"},
|
||||
{"data": "levels_wild", "title": "Base Lvl", "visible": false},
|
||||
{"data": "levels_tamed", "title": "Tame Lvl", "visible": false},
|
||||
{"data": "levels_total", "title": "Total Lvl"},
|
||||
|
||||
{"data": "is_cryo", "title": "Stored?", "visible": false},
|
||||
{"data": "parent_class", "title": "Container Type", "visible": false},
|
||||
{"data": "parent_name", "title": "Container Name", "visible": false},
|
||||
|
||||
{"data": "x", "visible": false},
|
||||
{"data": "y", "visible": false},
|
||||
{"data": "z", "visible": false},
|
||||
|
||||
{"data": "color0", "render": colorFunc, "title": "C0", "searchBuilderTitle": "Color 0", "visible": false},
|
||||
{"data": "color1", "render": colorFunc, "title": "C1", "searchBuilderTitle": "Color 1", "visible": false},
|
||||
{"data": "color2", "render": colorFunc, "title": "C2", "searchBuilderTitle": "Color 2", "visible": false},
|
||||
{"data": "color3", "render": colorFunc, "title": "C3", "searchBuilderTitle": "Color 3", "visible": false},
|
||||
{"data": "color4", "render": colorFunc, "title": "C4", "searchBuilderTitle": "Color 4", "visible": false},
|
||||
{"data": "color5", "render": colorFunc, "title": "C5", "searchBuilderTitle": "Color 5", "visible": false},
|
||||
|
||||
{"data": "health_current", "render": fixedFloat, "title": "H", "searchBuilderTitle": "Current Health", "visible": false},
|
||||
{"data": "stamina_current", "render": fixedFloat, "title": "St", "searchBuilderTitle": "Current Stamina", "visible": false},
|
||||
{"data": "torpor_current", "render": fixedFloat, "title": "T", "searchBuilderTitle": "Current Torpor", "visible": false},
|
||||
{"data": "oxygen_current", "render": fixedFloat, "title": "O", "searchBuilderTitle": "Current Oxygen", "visible": false},
|
||||
{"data": "food_current", "render": fixedFloat, "title": "F", "searchBuilderTitle": "Current Food", "visible": false},
|
||||
{"data": "weight_current", "render": fixedFloat, "title": "W", "searchBuilderTitle": "Current Weight", "visible": false},
|
||||
{"data": "melee_current", "render": fixedFloat, "title": "M", "searchBuilderTitle": "Current Melee", "visible": false},
|
||||
{"data": "speed_current", "render": fixedFloat, "title": "Sp", "searchBuilderTitle": "Current Speed", "visible": false},
|
||||
|
||||
{"data": "health_wild", "title": "H", "searchBuilderTitle": "Base Health", "visible": false},
|
||||
{"data": "stamina_wild", "title": "St", "searchBuilderTitle": "Base Stamina", "visible": false},
|
||||
{"data": "torpor_wild", "title": "T", "searchBuilderTitle": "Base Torpor", "visible": false},
|
||||
{"data": "oxygen_wild", "title": "O", "searchBuilderTitle": "Base Oxygen", "visible": false},
|
||||
{"data": "food_wild", "title": "F", "searchBuilderTitle": "Base Food", "visible": false},
|
||||
{"data": "weight_wild", "title": "W", "searchBuilderTitle": "Base Weight", "visible": false},
|
||||
{"data": "melee_wild", "title": "M", "searchBuilderTitle": "Base Melee", "visible": false},
|
||||
{"data": "speed_wild", "title": "Sp", "searchBuilderTitle": "Base Speed", "visible": false},
|
||||
|
||||
{"data": "health_tamed", "title": "H", "searchBuilderTitle": "Health Tamed Points", "visible": false},
|
||||
{"data": "stamina_tamed", "title": "St", "searchBuilderTitle": "Stamina Tamed Points", "visible": false},
|
||||
{"data": "torpor_tamed", "title": "T", "searchBuilderTitle": "Torpor Tamed Points", "visible": false},
|
||||
{"data": "oxygen_tamed", "title": "O", "searchBuilderTitle": "Oxygen Tamed Points", "visible": false},
|
||||
{"data": "food_tamed", "title": "F", "searchBuilderTitle": "Food Tamed Points", "visible": false},
|
||||
{"data": "weight_tamed", "title": "W", "searchBuilderTitle": "Weight Tamed Points", "visible": false},
|
||||
{"data": "melee_tamed", "title": "M", "searchBuilderTitle": "Melee Tamed Points", "visible": false},
|
||||
{"data": "speed_tamed", "title": "Sp", "searchBuilderTitle": "Speed Tamed Points", "visible": false},
|
||||
|
||||
{"data": "health_total", "title":"H", "searchBuilderTitle": "Total Health"},
|
||||
{"data": "stamina_total", "title":"St", "searchBuilderTitle": "Total Stamina"},
|
||||
{"data": "torpor_total", "title":"T", "searchBuilderTitle": "Total Torpor"},
|
||||
{"data": "oxygen_total", "title":"O", "searchBuilderTitle": "Total Oxygen"},
|
||||
{"data": "food_total", "title":"F", "searchBuilderTitle": "Total Food"},
|
||||
{"data": "weight_total", "title":"W", "searchBuilderTitle": "Total Weight"},
|
||||
{"data": "melee_total", "title":"M", "searchBuilderTitle": "Total Melee"},
|
||||
{"data": "speed_total", "title":"Sp", "searchBuilderTitle": "Total Speed"}
|
||||
];
|
||||
|
||||
var tableOptions = {
|
||||
"ajax": {"url":"api/dinos", "dataSrc":""},
|
||||
"columns": columns,
|
||||
|
||||
"dom": "rtpil",
|
||||
"pageLength": 50,
|
||||
"scrollX": true,
|
||||
|
||||
"language": {
|
||||
"searchBuilder": {
|
||||
"title": ""
|
||||
},
|
||||
},
|
||||
|
||||
"select": {
|
||||
"info": false,
|
||||
"style": "single",
|
||||
},
|
||||
|
||||
"searchBuilder": {
|
||||
"columns": [0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 13, 14, 15, 16, 17,
|
||||
18, 19, 20, 21, 22, 23, 24, 25,
|
||||
26, 27, 28, 29, 30, 31, 32, 33,
|
||||
34, 35, 36, 37, 38, 39, 40, 41,
|
||||
42, 43, 44, 45, 46, 47, 48, 49]
|
||||
}
|
||||
};
|
||||