This commit is contained in:
41666 2024-06-13 22:33:29 -04:00
commit c5cc245e25
29 changed files with 926 additions and 0 deletions

2
.envrc Normal file
View file

@ -0,0 +1,2 @@
source .env;
use flake;

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
.env
.direnv

View file

@ -0,0 +1,100 @@
// / Generate pkgs/translators/vehicles_map.gen.go
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"regexp"
"strings"
"time"
)
type VehicleResponse struct {
VehicleList []Vehicle `json:"vehicle_list"`
}
type Vehicle struct {
VehicleID string `json:"vehicle_id"`
Name LocaleString `json:"name"`
}
type LocaleString struct {
En string `json:"en"`
}
func fetchCensusVehicles() (VehicleResponse, error) {
var vehicleResponse VehicleResponse
client := http.Client{
Timeout: time.Second * 30,
}
resp, err := client.Get("https://census.lithafalcon.cc/get/ps2/vehicle")
if err != nil {
return vehicleResponse, fmt.Errorf("census request failed: %w", err)
}
err = json.NewDecoder(resp.Body).Decode(&vehicleResponse)
if err != nil {
return vehicleResponse, fmt.Errorf("census response decode failed: %w", err)
}
return vehicleResponse, nil
}
func generateRegexp(vehicles []string) *regexp.Regexp {
pipes := strings.Join(vehicles, "|")
expr := fmt.Sprintf("(%s)", pipes)
log.Println(expr)
return regexp.MustCompile(expr)
}
func main() {
filterRegexp := generateRegexp(AllVehicles)
censusVehicles, err := fetchCensusVehicles()
if err != nil {
log.Fatalln("fetch census failed", err)
}
vehicles := []VehicleItem{}
for _, vehicle := range censusVehicles.VehicleList {
if vehicle.Name.En == "" || strings.Contains(vehicle.Name.En, "Turret") {
continue
}
match := filterRegexp.FindString(strings.ToLower(vehicle.Name.En))
if match == "" {
continue
}
switch match {
case "wasp":
match = "valkyrie"
case "deliverer":
match = "ant"
case "lodestar":
match = "galaxy"
}
enumName := fmt.Sprintf("%s%s", strings.ToUpper(match[0:1]), match[1:])
vehicles = append(vehicles, VehicleItem{
VehicleID: vehicle.VehicleID,
VehicleEnumName: enumName,
})
}
output, err := renderTemplate(TemplateData{
Vehicles: vehicles,
})
if err != nil {
log.Fatalln("render failed", err)
}
fmt.Println(output)
}

View file

@ -0,0 +1,39 @@
package main
import (
"bytes"
"fmt"
"text/template"
)
var (
vehicleMapTmpl = `package translators
var (
VehicleMap = map[string]Vehicle{
{{ range .Vehicles }}"{{ .VehicleID }}": {{ .VehicleEnumName }},
{{end}}
}
)`
vehicleMapTemplate = template.Must(template.New("vehicle_map").Parse(vehicleMapTmpl))
)
type TemplateData struct {
Vehicles []VehicleItem
}
type VehicleItem struct {
VehicleID string
VehicleEnumName string
}
func renderTemplate(data TemplateData) (string, error) {
var buffer bytes.Buffer
err := vehicleMapTemplate.Execute(&buffer, data)
if err != nil {
return "", fmt.Errorf("template render failed, %w", err)
}
return buffer.String(), nil
}

View file

@ -0,0 +1,27 @@
package main
var (
AllVehicles = []string{
"flash",
"sunderer",
"lightning",
"scythe",
"vanguard",
"prowler",
"reaver",
"mosquito",
"galaxy",
"valkyrie",
"wasp",
"deliverer",
"lodestar",
"liberator",
"ant",
"harasser",
"dervish",
"chimera",
"javelin",
"corsair",
"magrider",
}
)

65
cmd/ws/event_handler.go Normal file
View file

@ -0,0 +1,65 @@
package main
import (
"context"
"log"
"github.com/genudine/saerro-go/types"
)
type EventHandler struct {
Ingest *Ingest
}
func (eh *EventHandler) HandleEvent(ctx context.Context, event types.ESSEvent) {
if event.EventName == "" {
log.Println("invalid event; dropping")
}
if event.EventName == "Death" || event.EventName == "VehicleDestroy" {
go eh.HandleDeath(ctx, event)
} else if event.EventName == "GainExperience" {
go eh.HandleExperience(ctx, event)
}
go eh.HandleAnalytics(ctx, event)
}
func (eh *EventHandler) HandleDeath(ctx context.Context, event types.ESSEvent) {
if event.CharacterID != "" && event.CharacterID != "0" {
log.Println("got pop event")
pe := PopEventFromESSEvent(event, false)
eh.Ingest.TrackPop(ctx, pe)
}
if event.AttackerCharacterID != "" && event.AttackerCharacterID != "0" && event.AttackerTeamID != 0 {
log.Println("got attacker pop event")
pe := PopEventFromESSEvent(event, true)
eh.Ingest.TrackPop(ctx, pe)
}
}
func (eh *EventHandler) HandleExperience(ctx context.Context, event types.ESSEvent) {
// Detect specific vehicles via related experience IDs
vehicleID := ""
switch event.ExperienceID {
case 201: // Galaxy Spawn Bonus
vehicleID = "11"
break
case 233: // Sunderer Spawn Bonus
vehicleID = "2"
break
case 674: // ANT stuff
case 675:
vehicleID = "160"
break
}
event.VehicleID = vehicleID
pe := PopEventFromESSEvent(event, false)
eh.Ingest.TrackPop(ctx, pe)
}
func (eh *EventHandler) HandleAnalytics(ctx context.Context, event types.ESSEvent) {
}

View file

@ -0,0 +1,72 @@
package main
import (
"context"
"database/sql"
"testing"
"time"
"github.com/genudine/saerro-go/translators"
"github.com/genudine/saerro-go/types"
"github.com/stretchr/testify/assert"
_ "modernc.org/sqlite"
)
func getEventHandlerTestShim(t *testing.T) (EventHandler, *sql.DB) {
db, err := sql.Open("sqlite", ":memory:")
if err != nil {
t.Fatalf("test shim: sqlite open failed, %v", err)
}
return EventHandler{
Ingest: &Ingest{
DB: db,
},
}, db
}
func TestHandleDeath(t *testing.T) {
eh, db := getEventHandlerTestShim(t)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
defer cancel()
event := types.ESSEvent{
EventName: "Death",
WorldID: 17,
ZoneID: 2,
CharacterID: "DollNC",
LoadoutID: "3",
TeamID: types.NC,
AttackerCharacterID: "Lyyti",
AttackerLoadoutID: "3",
AttackerTeamID: types.TR,
}
eh.HandleDeath(ctx, event)
type player struct {
CharacterID string `json:"character_id"`
ClassName string `json:"class_name"`
}
var player1 player
err := db.QueryRowContext(ctx, "SELECT * FROM players WHERE character_id = ?", event.CharacterID).Scan(&player1)
if err != nil {
t.Error(err)
}
assert.Equal(t, event.CharacterID, player1.CharacterID)
assert.Equal(t, translators.ClassFromLoadout(event.LoadoutID), player1.ClassName)
var player2 player
err = db.QueryRowContext(ctx, "SELECT * FROM players WHERE character_id = ?", event.AttackerCharacterID).Scan(&player2)
if err != nil {
t.Error(err)
}
assert.Equal(t, event.AttackerCharacterID, player2.CharacterID)
assert.Equal(t, translators.ClassFromLoadout(event.AttackerLoadoutID), player2.ClassName)
}

22
cmd/ws/event_names.go Normal file
View file

@ -0,0 +1,22 @@
package main
import (
"fmt"
"github.com/genudine/saerro-go/util"
)
var experienceIDs = []int{
2, 3, 4, 5, 6, 7, 34, 51, 53, 55, 57, 86, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
100, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 201, 233, 293,
294, 302, 303, 353, 354, 355, 438, 439, 503, 505, 579, 581, 584, 653, 656, 674, 675,
}
func getEventNames() []string {
events := util.Map(experienceIDs, func(i int) string {
return fmt.Sprintf("GainExperience_experience_id_%d", i)
})
events = append(events, "Death", "VehicleDestroy")
return events
}

View file

@ -0,0 +1,14 @@
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestEventNames(t *testing.T) {
result := getEventNames()
assert.Contains(t, result, "GainExperience_experience_id_55")
assert.Contains(t, result, "Death")
assert.Contains(t, result, "VehicleDestroy")
}

14
cmd/ws/ingest.go Normal file
View file

@ -0,0 +1,14 @@
package main
import (
"context"
"database/sql"
)
type Ingest struct {
DB *sql.DB
}
func (i *Ingest) TrackPop(ctx context.Context, event PopEvent) {
}

42
cmd/ws/pop_event.go Normal file
View file

@ -0,0 +1,42 @@
package main
import (
"github.com/genudine/saerro-go/translators"
"github.com/genudine/saerro-go/types"
)
type PopEvent struct {
WorldID uint16
ZoneID uint32
CharacterID string
LoadoutID string
TeamID types.Faction
VehicleID string
VehicleName translators.Vehicle
ClassName translators.Class
}
func PopEventFromESSEvent(event types.ESSEvent, attacker bool) PopEvent {
pe := PopEvent{
WorldID: event.WorldID,
ZoneID: event.ZoneID,
}
if !attacker {
pe.CharacterID = event.CharacterID
pe.LoadoutID = event.LoadoutID
pe.TeamID = event.TeamID
pe.VehicleID = event.VehicleID
} else {
pe.CharacterID = event.AttackerCharacterID
pe.LoadoutID = event.AttackerLoadoutID
pe.TeamID = event.AttackerTeamID
pe.VehicleID = event.AttackerVehicleID
}
pe.ClassName = translators.ClassFromLoadout(pe.LoadoutID)
pe.VehicleName = translators.VehicleNameFromID(pe.VehicleID)
return pe
}

71
cmd/ws/ws.go Normal file
View file

@ -0,0 +1,71 @@
package main
import (
"context"
"database/sql"
"log"
"os"
"time"
"github.com/genudine/saerro-go/types"
"nhooyr.io/websocket"
"nhooyr.io/websocket/wsjson"
)
func main() {
wsAddr := os.Getenv("WS_ADDR")
if wsAddr == "" {
log.Fatalln("WS_ADDR is not set.")
}
db, err := sql.Open("sqlite", ":memory:")
if err != nil {
log.Fatalln("database connection failed", err)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
wsConn, _, err := websocket.Dial(ctx, wsAddr, nil)
if err != nil {
log.Fatalln("Connection to ESS failed.", err)
}
defer wsConn.Close(websocket.StatusInternalError, "internal error. bye")
err = wsjson.Write(ctx, wsConn, map[string]interface{}{
"action": "subscribe",
"worlds": "all",
"eventNames": getEventNames(),
"characters": []string{"all"},
"service": "event",
"logicalAndCharactersWithWorlds": true,
})
if err != nil {
log.Fatalln("subscription write failed", err)
}
log.Println("subscribe done")
eventHandler := EventHandler{
Ingest: &Ingest{
DB: db,
},
}
for {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
var event types.ESSData
err := wsjson.Read(ctx, wsConn, &event)
if err != nil {
log.Println("wsjson read failed", err)
cancel()
continue
}
go eventHandler.HandleEvent(ctx, event.Payload)
}
wsConn.Close(websocket.StatusNormalClosure, "")
}

10
docker-compose.yaml Normal file
View file

@ -0,0 +1,10 @@
services:
tsdb:
image: docker.io/timescale/timescaledb:latest-pg14
environment:
POSTGRES_PASSWORD: saerro321
POSTGRES_USER: saerrouser
POSTGRES_DB: data
ports:
- 5432:5432

58
flake.lock generated Normal file
View file

@ -0,0 +1,58 @@
{
"nodes": {
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1717285511,
"narHash": "sha256-iKzJcpdXih14qYVcZ9QC9XuZYnPc6T8YImb6dX166kw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "2a55567fcf15b1b1c7ed712a2c6fadaec7412ea8",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1717786204,
"narHash": "sha256-4q0s6m0GUcN7q+Y2DqD27iLvbcd1G50T2lv08kKxkSI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "051f920625ab5aabe37c920346e3e69d7d34400e",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1717284937,
"narHash": "sha256-lIbdfCsf8LMFloheeE6N31+BMIeixqyQWbSr2vk79EQ=",
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/eb9ceca17df2ea50a250b6b27f7bf6ab0186f198.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/eb9ceca17df2ea50a250b6b27f7bf6ab0186f198.tar.gz"
}
},
"root": {
"inputs": {
"flake-parts": "flake-parts",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

15
flake.nix Normal file
View file

@ -0,0 +1,15 @@
{
description = "saerro";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
};
outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } {
systems = [ "x86_64-linux" "aarch64-linux" ];
perSystem = { config, self', pkgs, lib, system, ... }: {
devShells.default = import ./shell.nix { inherit pkgs; };
};
};
}

25
go.mod Normal file
View file

@ -0,0 +1,25 @@
module github.com/genudine/saerro-go
go 1.22.3
require (
github.com/glebarez/go-sqlite v1.22.0
github.com/stretchr/testify v1.9.0
nhooyr.io/websocket v1.8.11
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/sys v0.19.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.52.1 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/sqlite v1.30.1 // indirect
)

57
go.sum Normal file
View file

@ -0,0 +1,57 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk=
modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.17.10 h1:6wrtRozgrhCxieCeJh85QsxkX/2FFrT9hdaWPlbn4Zo=
modernc.org/ccgo/v4 v4.17.10/go.mod h1:0NBHgsqTTpm9cA5z2ccErvGZmtntSM9qD2kFAs6pjXM=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/libc v1.52.1 h1:uau0VoiT5hnR+SpoWekCKbLqm7v6dhRL3hI+NQhgN3M=
modernc.org/libc v1.52.1/go.mod h1:HR4nVzFDSDizP620zcMCgjb1/8xk2lg5p/8yjfGv1IQ=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.30.1 h1:YFhPVfu2iIgUf9kuA1CR7iiHdcEEsI2i+yjRYHscyxk=
modernc.org/sqlite v1.30.1/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
nhooyr.io/websocket v1.8.11 h1:f/qXNc2/3DpoSZkHt1DQu6rj4zGC8JmkkLkWss0MgN0=
nhooyr.io/websocket v1.8.11/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=

2
justfile Normal file
View file

@ -0,0 +1,2 @@
codegen:
go run ./cmd/codegen-vehicles > pkg/translators/vehicles_map.gen.go

8
shell.nix Normal file
View file

@ -0,0 +1,8 @@
{ pkgs ? import <nixpkgs> {} }: pkgs.mkShell {
buildInputs = with pkgs; [
go
just
docker-compose
sqlite
];
}

48
store/player.go Normal file
View file

@ -0,0 +1,48 @@
package store
import (
"context"
"database/sql"
"log"
"time"
)
type PlayerStore struct {
DB *sql.DB
}
func NewPlayerStore(db *sql.DB) *PlayerStore {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
ps := &PlayerStore{
DB: db,
}
ps.RunMigration(ctx, false)
return ps
}
func (ps *PlayerStore) RunMigration(ctx context.Context, force bool) {
if !force {
// check if migrated first...
}
log.Println("(Re)-creating players table")
ps.DB.ExecContext(ctx, `
DROP TABLE IF EXISTS players;
CREATE TABLE players (
character_id TEXT NOT NULL PRIMARY KEY,
last_updated TIMESTAMPTZ NOT NULL,
world_id INT NOT NULL,
faction_id INT NOT NULL,
zone_id INT NOT NULL,
class_name TEXT NOT NULL
);
`)
log.Println("Done, players table is initialized.")
}

56
translators/loadouts.go Normal file
View file

@ -0,0 +1,56 @@
package translators
type Class string
const (
Infiltrator Class = "infiltrator"
LightAssault Class = "light_assault"
CombatMedic Class = "combat_medic"
Engineer Class = "engineer"
HeavyAssault Class = "heavy_assault"
MAX Class = "max"
)
var (
LoadoutMap = map[string]Class{
"1": Infiltrator,
"8": Infiltrator,
"15": Infiltrator,
"28": Infiltrator,
"3": LightAssault,
"10": LightAssault,
"17": LightAssault,
"29": LightAssault,
"4": CombatMedic,
"11": CombatMedic,
"18": CombatMedic,
"30": CombatMedic,
"5": Engineer,
"12": Engineer,
"19": Engineer,
"31": Engineer,
"6": HeavyAssault,
"13": HeavyAssault,
"20": HeavyAssault,
"32": HeavyAssault,
"7": MAX,
"14": MAX,
"21": MAX,
"45": MAX,
}
)
func ClassFromLoadout(loadoutID string) Class {
c, ok := LoadoutMap[loadoutID]
if !ok {
return "unknown"
}
return c
}

View file

@ -0,0 +1,13 @@
package translators_test
import (
"testing"
"github.com/genudine/saerro-go/pkg/translators"
"github.com/stretchr/testify/assert"
)
func TestLoadouts(t *testing.T) {
assert.Equal(t, translators.ClassFromLoadout("1"), translators.Infiltrator)
assert.Equal(t, translators.ClassFromLoadout("0"), translators.Class("unknown"))
}

33
translators/vehicles.go Normal file
View file

@ -0,0 +1,33 @@
package translators
type Vehicle string
const (
Flash Vehicle = "flash"
Sunderer Vehicle = "sunderer"
Lightning Vehicle = "lightning"
Magrider Vehicle = "magrider"
Vanguard Vehicle = "vanguard"
Prowler Vehicle = "prowler"
Scythe Vehicle = "scythe"
Reaver Vehicle = "reaver"
Mosquito Vehicle = "mosquito"
Liberator Vehicle = "liberator"
Galaxy Vehicle = "galaxy"
Harasser Vehicle = "harasser"
Valkyrie Vehicle = "valkyrie"
Ant Vehicle = "ant"
Dervish Vehicle = "dervish"
Chimera Vehicle = "chimera"
Javelin Vehicle = "javelin"
Corsair Vehicle = "corsair"
)
func VehicleNameFromID(id string) Vehicle {
v, ok := VehicleMap[id]
if !ok {
return "unknown"
}
return v
}

View file

@ -0,0 +1,54 @@
package translators
var (
VehicleMap = map[string]Vehicle{
"1": Flash,
"2": Sunderer,
"3": Lightning,
"4": Magrider,
"5": Vanguard,
"6": Prowler,
"7": Scythe,
"8": Reaver,
"9": Mosquito,
"10": Liberator,
"11": Galaxy,
"12": Harasser,
"14": Valkyrie,
"15": Ant,
"160": Ant,
"161": Ant,
"162": Ant,
"1001": Flash,
"1002": Sunderer,
"1004": Magrider,
"1005": Vanguard,
"1007": Scythe,
"1008": Reaver,
"1009": Mosquito,
"1010": Liberator,
"1011": Galaxy,
"1105": Vanguard,
"2010": Flash,
"2033": Javelin,
"2039": Ant,
"2040": Valkyrie,
"2122": Mosquito,
"2123": Reaver,
"2124": Scythe,
"2125": Javelin,
"2129": Javelin,
"2130": Sunderer,
"2131": Galaxy,
"2132": Valkyrie,
"2133": Magrider,
"2134": Vanguard,
"2135": Prowler,
"2136": Dervish,
"2137": Chimera,
"2139": Ant,
"2140": Galaxy,
"2141": Valkyrie,
"2142": Corsair,
}
)

View file

@ -0,0 +1,13 @@
package translators_test
import (
"testing"
"github.com/genudine/saerro-go/pkg/translators"
"github.com/stretchr/testify/assert"
)
func TestVehicles(t *testing.T) {
assert.Equal(t, translators.VehicleNameFromID("12"), translators.Harasser)
assert.Equal(t, translators.VehicleNameFromID("0"), translators.Vehicle("unknown"))
}

10
types/census.go Normal file
View file

@ -0,0 +1,10 @@
package types
type Faction uint8
const (
VS Faction = iota
NC
TR
NSO
)

23
types/payload.go Normal file
View file

@ -0,0 +1,23 @@
package types
type ESSData struct {
Payload ESSEvent
}
type ESSEvent struct {
EventName string `json:"event_name"`
WorldID uint16 `json:"world_id"`
ZoneID uint32 `json:"zone_id"`
CharacterID string `json:"character_id"`
LoadoutID string `json:"loadout_id"`
VehicleID string `json:"vehicle_id"`
TeamID Faction `json:"team_id"`
AttackerCharacterID string `json:"attacker_character_id"`
AttackerLoadoutID string `json:"attacker_loadout_id"`
AttackerVehicleID string `json:"attacker_vehicle_id"`
AttackerTeamID Faction `json:"attacker_team_id"`
ExperienceID uint32
}

11
util/map.go Normal file
View file

@ -0,0 +1,11 @@
package util
func Map[In, Out any](inSlice []In, predicate func(In) Out) []Out {
outSlice := make([]Out, len(inSlice))
for i := range inSlice {
outSlice[i] = predicate(inSlice[i])
}
return outSlice
}

20
util/map_test.go Normal file
View file

@ -0,0 +1,20 @@
package util_test
import (
"strconv"
"testing"
"github.com/genudine/saerro-go/util"
"github.com/stretchr/testify/assert"
)
func TestMap(t *testing.T) {
dolls := []int64{44203, 41666, 79579, 63741, 57213}
result := util.Map(dolls, func(doll int64) string {
return strconv.FormatInt(doll, 16)
})
assert.Contains(t, result, "acab")
assert.Len(t, result, len(dolls))
}