init
This commit is contained in:
commit
c5cc245e25
29 changed files with 926 additions and 0 deletions
2
.envrc
Normal file
2
.envrc
Normal file
|
@ -0,0 +1,2 @@
|
|||
source .env;
|
||||
use flake;
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
.env
|
||||
.direnv
|
100
cmd/codegen-vehicles/main.go
Normal file
100
cmd/codegen-vehicles/main.go
Normal 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)
|
||||
}
|
39
cmd/codegen-vehicles/template.go
Normal file
39
cmd/codegen-vehicles/template.go
Normal 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
|
||||
}
|
27
cmd/codegen-vehicles/vehicle_list.go
Normal file
27
cmd/codegen-vehicles/vehicle_list.go
Normal 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
65
cmd/ws/event_handler.go
Normal 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) {
|
||||
|
||||
}
|
72
cmd/ws/event_handler_test.go
Normal file
72
cmd/ws/event_handler_test.go
Normal 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
22
cmd/ws/event_names.go
Normal 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
|
||||
}
|
14
cmd/ws/event_names_test.go
Normal file
14
cmd/ws/event_names_test.go
Normal 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
14
cmd/ws/ingest.go
Normal 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
42
cmd/ws/pop_event.go
Normal 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
71
cmd/ws/ws.go
Normal 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
10
docker-compose.yaml
Normal 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
58
flake.lock
generated
Normal 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
15
flake.nix
Normal 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
25
go.mod
Normal 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
57
go.sum
Normal 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
2
justfile
Normal file
|
@ -0,0 +1,2 @@
|
|||
codegen:
|
||||
go run ./cmd/codegen-vehicles > pkg/translators/vehicles_map.gen.go
|
8
shell.nix
Normal file
8
shell.nix
Normal file
|
@ -0,0 +1,8 @@
|
|||
{ pkgs ? import <nixpkgs> {} }: pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
go
|
||||
just
|
||||
docker-compose
|
||||
sqlite
|
||||
];
|
||||
}
|
48
store/player.go
Normal file
48
store/player.go
Normal 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
56
translators/loadouts.go
Normal 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
|
||||
}
|
13
translators/loadouts_test.go
Normal file
13
translators/loadouts_test.go
Normal 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
33
translators/vehicles.go
Normal 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
|
||||
}
|
54
translators/vehicles_map.gen.go
Normal file
54
translators/vehicles_map.gen.go
Normal 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,
|
||||
}
|
||||
)
|
13
translators/vehicles_test.go
Normal file
13
translators/vehicles_test.go
Normal 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
10
types/census.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
package types
|
||||
|
||||
type Faction uint8
|
||||
|
||||
const (
|
||||
VS Faction = iota
|
||||
NC
|
||||
TR
|
||||
NSO
|
||||
)
|
23
types/payload.go
Normal file
23
types/payload.go
Normal 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
11
util/map.go
Normal 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
20
util/map_test.go
Normal 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))
|
||||
}
|
Loading…
Add table
Reference in a new issue