mirror of
https://github.com/CPunch/gopenfusion.git
synced 2025-10-24 01:50:13 +00:00
Compare commits
1 Commits
main
...
96130ddc8d
| Author | SHA1 | Date | |
|---|---|---|---|
| 96130ddc8d |
4
.github/workflows/tests.yaml
vendored
4
.github/workflows/tests.yaml
vendored
@@ -5,11 +5,9 @@ on:
|
||||
paths:
|
||||
- cmd/**
|
||||
- config/**
|
||||
- cnet/**
|
||||
- internal/**
|
||||
- login/**
|
||||
- shard/**
|
||||
- util/**
|
||||
- go.mod
|
||||
- go.sum
|
||||
- .github/workflows/tests.yaml
|
||||
@@ -25,4 +23,4 @@ jobs:
|
||||
with:
|
||||
go-version: '1.21.x'
|
||||
- name: Test with the Go CLI
|
||||
run: go test -timeout 10s -v ./...
|
||||
run: go test -v ./...
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
Copyright © 2022 Gopenfusion Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,13 +1,6 @@
|
||||
# gopenfusion
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/CPunch/gopenfusion/actions/workflows/tests.yaml"><img src="https://github.com/CPunch/gopenfusion/actions/workflows/tests.yaml/badge.svg?branch=main" alt="Workflow"></a>
|
||||
<a href="https://github.com/CPunch/gopenfusion/blob/main/LICENSE.md"><img src="https://img.shields.io/github/license/CPunch/gopenfusion" alt="License"></a>
|
||||
<br>
|
||||
<a href="https://asciinema.org/a/625524" target="_blank"><img src="https://asciinema.org/a/625524.svg" /></a>
|
||||
</p>
|
||||
|
||||
A toy implementation of the [Fusionfall Packet Protocol](https://openpunk.com/pages/fusionfall-openfusion/) (see: `cnet/`) and accompanying services, written in Go.
|
||||
A toy implementation of the [Fusionfall Packet Protocol](https://openpunk.com/pages/fusionfall-openfusion/) and accompanying services, written in Go.
|
||||
|
||||
## Landwalker demo
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"flag"
|
||||
"log"
|
||||
|
||||
"github.com/CPunch/gopenfusion/internal/config"
|
||||
"github.com/CPunch/gopenfusion/config"
|
||||
"github.com/CPunch/gopenfusion/login"
|
||||
"github.com/google/subcommands"
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/CPunch/gopenfusion/internal/config"
|
||||
"github.com/CPunch/gopenfusion/config"
|
||||
"github.com/CPunch/gopenfusion/internal/db"
|
||||
"github.com/CPunch/gopenfusion/internal/redis"
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"flag"
|
||||
"log"
|
||||
|
||||
"github.com/CPunch/gopenfusion/internal/config"
|
||||
"github.com/CPunch/gopenfusion/config"
|
||||
"github.com/CPunch/gopenfusion/shard"
|
||||
"github.com/google/subcommands"
|
||||
)
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetTime() uint64 {
|
||||
return uint64(time.Now().UnixMilli())
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package cnet_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/CPunch/gopenfusion/cnet"
|
||||
"github.com/CPunch/gopenfusion/cnet/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/testutil"
|
||||
"github.com/matryer/is"
|
||||
)
|
||||
|
||||
var (
|
||||
srvcPort int
|
||||
)
|
||||
|
||||
const (
|
||||
timeout = 2 * time.Second
|
||||
maxDummyPeers = 5
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
var err error
|
||||
srvcPort, err = cnet.RandomPort()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// this is fine since we don't defer anything
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestService(t *testing.T) {
|
||||
is := is.New(t)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
srvc := cnet.NewService(ctx, "TEST", srvcPort)
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
// shutdown service when test is done
|
||||
defer func() {
|
||||
cancel()
|
||||
is.True(testutil.SelectWithTimeout(srvc.Stopped(), timeout)) // wait for service to stop with timeout
|
||||
}()
|
||||
|
||||
// our dummy packet handler
|
||||
wg.Add(maxDummyPeers)
|
||||
srvc.AddPacketHandler(0x1234, func(peer *cnet.Peer, pkt protocol.Packet) error {
|
||||
log.Printf("Received packet %#v", pkt)
|
||||
wg.Done()
|
||||
return nil
|
||||
})
|
||||
|
||||
// wait for all dummy peers to connect and disconnect
|
||||
wg.Add(maxDummyPeers)
|
||||
srvc.OnConnect = func(peer *cnet.Peer) {
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
wg.Add(maxDummyPeers)
|
||||
srvc.OnDisconnect = func(peer *cnet.Peer) {
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
// run service
|
||||
go func() { is.NoErr(srvc.Start()) }() // srvc.Start error
|
||||
is.True(testutil.SelectWithTimeout(srvc.Started(), timeout)) // wait for service to start with timeout
|
||||
|
||||
wg.Add(maxDummyPeers * 2) // 2 wg.Done() per peer for receiving packets
|
||||
for i := 0; i < maxDummyPeers; i++ {
|
||||
go func() {
|
||||
// make dummy client
|
||||
conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", srvcPort))
|
||||
is.NoErr(err) // net.Dial error
|
||||
|
||||
peer := cnet.NewPeer(ctx, conn)
|
||||
go func() {
|
||||
defer peer.Kill()
|
||||
|
||||
// send dummy packets
|
||||
for i := 0; i < 2; i++ {
|
||||
is.NoErr(peer.Send(0x1234)) // peer.Send error
|
||||
}
|
||||
}()
|
||||
|
||||
// we wait until Handler gracefully exits (peer was killed)
|
||||
peer.Handler(make(chan *cnet.PacketEvent))
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
is.True(testutil.WaitWithTimeout(&wg, timeout)) // wait for all dummy peers to be done with timeout
|
||||
}
|
||||
3
go.mod
3
go.mod
@@ -3,7 +3,6 @@ module github.com/CPunch/gopenfusion
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/alicebob/miniredis/v2 v2.31.0
|
||||
github.com/bitcomplete/sqltestutil v1.0.1
|
||||
github.com/blockloop/scan v1.3.0
|
||||
github.com/georgysavva/scany/v2 v2.0.0
|
||||
@@ -17,7 +16,6 @@ require (
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
@@ -36,7 +34,6 @@ require (
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/stretchr/testify v1.8.0 // indirect
|
||||
github.com/yuin/gopher-lua v1.1.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
12
go.sum
12
go.sum
@@ -1,12 +1,7 @@
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/DmitriyVTitov/size v1.5.0/go.mod h1:le6rNI4CoLQV1b9gzp1+3d7hMAD/uu2QcJ+aYbNgiU0=
|
||||
github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||
github.com/alicebob/miniredis/v2 v2.31.0 h1:ObEFUNlJwoIiyjxdrYF0QIDE7qXcLc7D3WpSH4c22PU=
|
||||
github.com/alicebob/miniredis/v2 v2.31.0/go.mod h1:UB/T2Uztp7MlFSDakaX1sTXUv5CASoprx0wulRT6HBg=
|
||||
github.com/bitcomplete/sqltestutil v1.0.1 h1:rj/RgrXXyuPB8KYrFmxiSjORb1hrhK6sXHpDPaSEBII=
|
||||
github.com/bitcomplete/sqltestutil v1.0.1/go.mod h1:ZgpEnW6t2RBsCo9EIEYsAvjxJeZDwOzC8aVYXK0+gdE=
|
||||
github.com/blockloop/scan v1.3.0 h1:p8xnajpGA3d/V6o23IBFdQ764+JnNJ+PQj+OwT+rkdg=
|
||||
@@ -15,9 +10,6 @@ github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
||||
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.2.0 h1:/5znzg5n373N/3ESjHF5SMLxiW4RKB05Ql//KWfeTFs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -41,7 +33,6 @@ github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaL
|
||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
|
||||
@@ -88,8 +79,6 @@ github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PK
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE=
|
||||
github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
@@ -106,7 +95,6 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
||||
@@ -2,10 +2,11 @@ package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/CPunch/gopenfusion/cnet/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
"github.com/georgysavva/scany/v2/sqlscan"
|
||||
)
|
||||
|
||||
@@ -53,14 +54,10 @@ func (db *DBHandler) TryLogin(Login, Password string) (*Account, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// make sure id && pw are valid
|
||||
if len(Login) < 4 || len(Password) < 4 {
|
||||
return nil, ErrLoginInvalidPassword
|
||||
}
|
||||
|
||||
var account Account
|
||||
row.Next()
|
||||
if err := sqlscan.ScanRow(&account, row); err != nil {
|
||||
log.Printf("Error scanning row: %v", err)
|
||||
return nil, ErrLoginInvalidID
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
|
||||
"github.com/matryer/is"
|
||||
|
||||
"github.com/CPunch/gopenfusion/cnet/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/db"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
"github.com/bitcomplete/sqltestutil"
|
||||
)
|
||||
|
||||
@@ -18,11 +18,6 @@ var (
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
ret := 1
|
||||
defer func() {
|
||||
os.Exit(ret)
|
||||
}()
|
||||
|
||||
ctx := context.Background()
|
||||
psql, err := sqltestutil.StartPostgresContainer(ctx, "15")
|
||||
if err != nil {
|
||||
@@ -40,7 +35,7 @@ func TestMain(m *testing.M) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ret = m.Run()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestDBAccount(t *testing.T) {
|
||||
|
||||
@@ -3,7 +3,7 @@ package db
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/CPunch/gopenfusion/cnet/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
)
|
||||
|
||||
type Inventory struct {
|
||||
|
||||
@@ -3,8 +3,8 @@ package db
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/CPunch/gopenfusion/cnet/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/config"
|
||||
"github.com/CPunch/gopenfusion/config"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
"github.com/blockloop/scan"
|
||||
)
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
|
||||
"github.com/CPunch/gopenfusion/internal/config"
|
||||
"github.com/CPunch/gopenfusion/config"
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,22 +3,8 @@ package entity
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"github.com/CPunch/gopenfusion/internal/config"
|
||||
)
|
||||
|
||||
type ChunkPosition struct {
|
||||
X int
|
||||
Y int
|
||||
}
|
||||
|
||||
func MakeChunkPosition(x, y int) ChunkPosition {
|
||||
return ChunkPosition{
|
||||
X: x / (config.VIEW_DISTANCE / 3),
|
||||
Y: y / (config.VIEW_DISTANCE / 3),
|
||||
}
|
||||
}
|
||||
|
||||
type Chunk struct {
|
||||
Position ChunkPosition
|
||||
entities map[Entity]struct{}
|
||||
15
internal/entity/chunkposition.go
Normal file
15
internal/entity/chunkposition.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package entity
|
||||
|
||||
import "github.com/CPunch/gopenfusion/config"
|
||||
|
||||
type ChunkPosition struct {
|
||||
X int
|
||||
Y int
|
||||
}
|
||||
|
||||
func MakeChunkPosition(x, y int) ChunkPosition {
|
||||
return ChunkPosition{
|
||||
X: x / (config.VIEW_DISTANCE / 3),
|
||||
Y: y / (config.VIEW_DISTANCE / 3),
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package entity
|
||||
|
||||
import "github.com/CPunch/gopenfusion/cnet"
|
||||
import "github.com/CPunch/gopenfusion/internal/protocol"
|
||||
|
||||
type EntityKind int
|
||||
|
||||
@@ -20,6 +20,6 @@ type Entity interface {
|
||||
SetPosition(x, y, z int)
|
||||
SetAngle(angle int)
|
||||
|
||||
DisappearFromViewOf(peer *cnet.Peer)
|
||||
EnterIntoViewOf(peer *cnet.Peer)
|
||||
DisappearFromViewOf(peer *protocol.CNPeer)
|
||||
EnterIntoViewOf(peer *protocol.CNPeer)
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package entity_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/CPunch/gopenfusion/shard/entity"
|
||||
"github.com/CPunch/gopenfusion/internal/entity"
|
||||
"github.com/matryer/is"
|
||||
)
|
||||
|
||||
@@ -3,8 +3,7 @@ package entity
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/CPunch/gopenfusion/cnet"
|
||||
"github.com/CPunch/gopenfusion/cnet/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
)
|
||||
|
||||
type NPC struct {
|
||||
@@ -63,13 +62,13 @@ func (npc *NPC) SetAngle(angle int) {
|
||||
npc.Angle = angle
|
||||
}
|
||||
|
||||
func (npc *NPC) DisappearFromViewOf(peer *cnet.Peer) {
|
||||
func (npc *NPC) DisappearFromViewOf(peer *protocol.CNPeer) {
|
||||
peer.Send(protocol.P_FE2CL_NPC_EXIT, protocol.SP_FE2CL_NPC_EXIT{
|
||||
INPC_ID: int32(npc.ID),
|
||||
})
|
||||
}
|
||||
|
||||
func (npc *NPC) EnterIntoViewOf(peer *cnet.Peer) {
|
||||
func (npc *NPC) EnterIntoViewOf(peer *protocol.CNPeer) {
|
||||
peer.Send(protocol.P_FE2CL_NPC_NEW, protocol.SP_FE2CL_NPC_NEW{
|
||||
NPCAppearanceData: npc.GetAppearanceData(),
|
||||
})
|
||||
@@ -1,18 +1,17 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"github.com/CPunch/gopenfusion/cnet"
|
||||
"github.com/CPunch/gopenfusion/cnet/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/db"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
)
|
||||
|
||||
type Player struct {
|
||||
db.Player
|
||||
Peer *cnet.Peer
|
||||
Peer *protocol.CNPeer
|
||||
Chunk ChunkPosition
|
||||
}
|
||||
|
||||
func NewPlayer(peer *cnet.Peer, player *db.Player) *Player {
|
||||
func NewPlayer(peer *protocol.CNPeer, player *db.Player) *Player {
|
||||
return &Player{
|
||||
Player: *player,
|
||||
Peer: peer,
|
||||
@@ -52,13 +51,13 @@ func (plr *Player) SetAngle(angle int) {
|
||||
plr.Angle = angle
|
||||
}
|
||||
|
||||
func (plr *Player) DisappearFromViewOf(peer *cnet.Peer) {
|
||||
func (plr *Player) DisappearFromViewOf(peer *protocol.CNPeer) {
|
||||
peer.Send(protocol.P_FE2CL_PC_EXIT, protocol.SP_FE2CL_PC_EXIT{
|
||||
IID: int32(plr.PlayerID),
|
||||
})
|
||||
}
|
||||
|
||||
func (plr *Player) EnterIntoViewOf(peer *cnet.Peer) {
|
||||
func (plr *Player) EnterIntoViewOf(peer *protocol.CNPeer) {
|
||||
peer.Send(protocol.P_FE2CL_PC_NEW, protocol.SP_FE2CL_PC_NEW{
|
||||
PCAppearanceData: plr.GetAppearanceData(),
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
package cnet
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -8,8 +8,7 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/CPunch/gopenfusion/cnet/protocol"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,8 +22,8 @@ type PacketEvent struct {
|
||||
PktID uint32
|
||||
}
|
||||
|
||||
// Peer is a simple wrapper for net.Conn connections to send/recv packets over the Fusionfall packet protocol.
|
||||
type Peer struct {
|
||||
// CNPeer is a simple wrapper for net.Conn connections to send/recv packets over the Fusionfall packet protocol.
|
||||
type CNPeer struct {
|
||||
uData interface{}
|
||||
conn net.Conn
|
||||
ctx context.Context
|
||||
@@ -38,38 +37,42 @@ type Peer struct {
|
||||
FE_key []byte
|
||||
}
|
||||
|
||||
func NewPeer(ctx context.Context, conn net.Conn) *Peer {
|
||||
p := &Peer{
|
||||
func GetTime() uint64 {
|
||||
return uint64(time.Now().UnixMilli())
|
||||
}
|
||||
|
||||
func NewCNPeer(ctx context.Context, conn net.Conn) *CNPeer {
|
||||
p := &CNPeer{
|
||||
conn: conn,
|
||||
ctx: ctx,
|
||||
whichKey: USE_E,
|
||||
alive: &atomic.Bool{},
|
||||
|
||||
E_key: []byte(protocol.DEFAULT_KEY),
|
||||
E_key: []byte(DEFAULT_KEY),
|
||||
FE_key: nil,
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (peer *Peer) SetUserData(uData interface{}) {
|
||||
func (peer *CNPeer) SetUserData(uData interface{}) {
|
||||
peer.uData = uData
|
||||
}
|
||||
|
||||
func (peer *Peer) UserData() interface{} {
|
||||
func (peer *CNPeer) UserData() interface{} {
|
||||
return peer.uData
|
||||
}
|
||||
|
||||
func (peer *Peer) Send(typeID uint32, data ...interface{}) error {
|
||||
func (peer *CNPeer) Send(typeID uint32, data ...interface{}) error {
|
||||
// grab buffer from pool
|
||||
buf := protocol.GetBuffer()
|
||||
defer protocol.PutBuffer(buf)
|
||||
buf := GetBuffer()
|
||||
defer PutBuffer(buf)
|
||||
|
||||
// allocate space for packet size
|
||||
buf.Write(make([]byte, 4))
|
||||
|
||||
// body start
|
||||
pkt := protocol.NewPacket(buf)
|
||||
pkt := NewPacket(buf)
|
||||
|
||||
// encode type id
|
||||
if err := pkt.Encode(typeID); err != nil {
|
||||
@@ -94,7 +97,7 @@ func (peer *Peer) Send(typeID uint32, data ...interface{}) error {
|
||||
case USE_FE:
|
||||
key = peer.FE_key
|
||||
}
|
||||
protocol.EncryptData(buf.Bytes()[4:], key)
|
||||
EncryptData(buf.Bytes()[4:], key)
|
||||
|
||||
// send full packet
|
||||
// log.Printf("Sending %#v, sizeof: %d, buffer: %v", data, buf.Len(), buf.Bytes())
|
||||
@@ -104,11 +107,11 @@ func (peer *Peer) Send(typeID uint32, data ...interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (peer *Peer) SetActiveKey(whichKey int) {
|
||||
func (peer *CNPeer) SetActiveKey(whichKey int) {
|
||||
peer.whichKey = whichKey
|
||||
}
|
||||
|
||||
func (peer *Peer) Kill() {
|
||||
func (peer *CNPeer) Kill() {
|
||||
// de-bounce: only kill if alive
|
||||
if !peer.alive.CompareAndSwap(true, false) {
|
||||
return
|
||||
@@ -118,7 +121,7 @@ func (peer *Peer) Kill() {
|
||||
}
|
||||
|
||||
// meant to be invoked as a goroutine
|
||||
func (peer *Peer) Handler(eRecv chan<- *PacketEvent) error {
|
||||
func (peer *CNPeer) Handler(eRecv chan<- *PacketEvent) error {
|
||||
defer func() {
|
||||
close(eRecv)
|
||||
peer.Kill()
|
||||
@@ -137,19 +140,19 @@ func (peer *Peer) Handler(eRecv chan<- *PacketEvent) error {
|
||||
}
|
||||
|
||||
// client should never send a packet size outside of this range
|
||||
if sz > protocol.CN_PACKET_BUFFER_SIZE || sz < 4 {
|
||||
if sz > CN_PACKET_BUFFER_SIZE || sz < 4 {
|
||||
return fmt.Errorf("invalid packet size: %d", sz)
|
||||
}
|
||||
|
||||
// grab buffer && read packet body
|
||||
buf := protocol.GetBuffer()
|
||||
buf := GetBuffer()
|
||||
if _, err := buf.ReadFrom(io.LimitReader(peer.conn, int64(sz))); err != nil {
|
||||
return fmt.Errorf("failed to read packet body: %v", err)
|
||||
}
|
||||
|
||||
// decrypt
|
||||
protocol.DecryptData(buf.Bytes(), peer.E_key)
|
||||
pkt := protocol.NewPacket(buf)
|
||||
DecryptData(buf.Bytes(), peer.E_key)
|
||||
pkt := NewPacket(buf)
|
||||
|
||||
// create packet && read pktID
|
||||
var pktID uint32
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
DEFAULT_KEY = "m@rQn~W#" // if you change this, make sure to update the test data in protocol_test.go
|
||||
DEFAULT_KEY = "m@rQn~W#"
|
||||
KEY_LENGTH = 8
|
||||
)
|
||||
|
||||
@@ -50,14 +50,13 @@ func DecryptData(buff, key []byte) {
|
||||
}
|
||||
|
||||
func CreateNewKey(uTime, iv1, iv2 uint64) []byte {
|
||||
dEKey := binary.LittleEndian.Uint64([]byte(DEFAULT_KEY))
|
||||
|
||||
num := iv1 + 1
|
||||
num2 := iv2 + 1
|
||||
key := dEKey * (uTime * num * num2)
|
||||
|
||||
dEKey := uint64(binary.LittleEndian.Uint64([]byte(DEFAULT_KEY)))
|
||||
key := dEKey * (uTime * num * num2)
|
||||
buf := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(buf, key)
|
||||
binary.LittleEndian.PutUint64(buf, uint64(key))
|
||||
return buf
|
||||
}
|
||||
|
||||
@@ -42,7 +42,10 @@ func (pkt Packet) encodeStructField(field reflect.StructField, value reflect.Val
|
||||
buf16 = buf16[:sz]
|
||||
} else {
|
||||
// grow
|
||||
buf16 = append(buf16, make([]uint16, sz-len(buf16))...)
|
||||
// TODO: probably a better way to do this?
|
||||
for len(buf16) < sz {
|
||||
buf16 = append(buf16, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// write
|
||||
@@ -122,7 +125,8 @@ func (pkt Packet) decodeStructField(field reflect.StructField, value reflect.Val
|
||||
// consume padding bytes
|
||||
pad, err := strconv.Atoi(field.Tag.Get("pad"))
|
||||
if err == nil {
|
||||
if _, err := pkt.readWriter.Read(make([]byte, pad)); err != nil {
|
||||
dummy := make([]byte, pad)
|
||||
if _, err := pkt.readWriter.Read(dummy); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/CPunch/gopenfusion/cnet/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
"github.com/matryer/is"
|
||||
)
|
||||
|
||||
@@ -61,7 +61,7 @@ func TestPacketEncode(t *testing.T) {
|
||||
err := pkt.Encode(testStruct)
|
||||
is.NoErr(err)
|
||||
|
||||
is.Equal(buf.Bytes(), testData[:]) // encoded data should match expected data
|
||||
is.True(bytes.Equal(buf.Bytes(), testData[:])) // encoded data should match expected data
|
||||
}
|
||||
|
||||
func TestPacketDecode(t *testing.T) {
|
||||
@@ -73,7 +73,7 @@ func TestPacketDecode(t *testing.T) {
|
||||
var test TestPacketData
|
||||
err := pkt.Decode(&test)
|
||||
is.NoErr(err)
|
||||
is.Equal(test, testStruct) // decoded data should match testStruct
|
||||
is.True(test == testStruct) // decoded data should match testStruct
|
||||
}
|
||||
|
||||
func TestDataEncrypt(t *testing.T) {
|
||||
@@ -82,7 +82,8 @@ func TestDataEncrypt(t *testing.T) {
|
||||
copy(buf, testData[:])
|
||||
|
||||
protocol.EncryptData(buf, []byte(protocol.DEFAULT_KEY))
|
||||
is.Equal(buf, encTestData) // encrypted data should match expected data
|
||||
|
||||
is.True(bytes.Equal(buf, encTestData)) // encrypted data should match expected data
|
||||
}
|
||||
|
||||
func TestDataDecrypt(t *testing.T) {
|
||||
@@ -91,12 +92,13 @@ func TestDataDecrypt(t *testing.T) {
|
||||
copy(buf, encTestData)
|
||||
|
||||
protocol.DecryptData(buf, []byte(protocol.DEFAULT_KEY))
|
||||
is.Equal(buf, testData[:]) // decrypted data should match expected data
|
||||
|
||||
is.True(bytes.Equal(buf, testData[:])) // decrypted data should match expected data
|
||||
}
|
||||
|
||||
func TestCreateNewKey(t *testing.T) {
|
||||
is := is.New(t)
|
||||
key := protocol.CreateNewKey(123456789, 0x1234567890abcdef, 0x1234567890abcdef)
|
||||
|
||||
is.Equal(key, []byte{0x0, 0x31, 0xb8, 0xcd, 0xd, 0xc3, 0xad, 0x67}) // key should match expected data
|
||||
is.True(bytes.Equal(key, []byte{0x0, 0x31, 0xb8, 0xcd, 0xd, 0xc3, 0xad, 0x67})) // key should match expected data
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/CPunch/gopenfusion/internal/config"
|
||||
"github.com/CPunch/gopenfusion/config"
|
||||
)
|
||||
|
||||
type LoginMetadata struct {
|
||||
|
||||
@@ -7,7 +7,7 @@ package redis
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/CPunch/gopenfusion/internal/config"
|
||||
"github.com/CPunch/gopenfusion/config"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
@@ -17,6 +17,10 @@ type RedisHandler struct {
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
const (
|
||||
SHARD_SET = "shards"
|
||||
)
|
||||
|
||||
func OpenRedis(addr string) (*RedisHandler, error) {
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: addr,
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/CPunch/gopenfusion/internal/redis"
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/matryer/is"
|
||||
)
|
||||
|
||||
var (
|
||||
rh *redis.RedisHandler
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
ret := 1
|
||||
defer func() {
|
||||
os.Exit(ret)
|
||||
}()
|
||||
|
||||
r, err := miniredis.Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
rh, err = redis.OpenRedis(r.Addr())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer rh.Close()
|
||||
|
||||
ret = m.Run()
|
||||
}
|
||||
|
||||
func TestRedisLogin(t *testing.T) {
|
||||
is := is.New(t)
|
||||
|
||||
// test data
|
||||
serialKey := int64(1234)
|
||||
data := redis.LoginMetadata{
|
||||
FEKey: []byte("test"),
|
||||
PlayerID: 1,
|
||||
AccountID: 2,
|
||||
}
|
||||
|
||||
// queue login
|
||||
is.NoErr(rh.QueueLogin(serialKey, data))
|
||||
|
||||
// get login
|
||||
loginData, err := rh.GetLogin(serialKey)
|
||||
is.NoErr(err)
|
||||
|
||||
// compare
|
||||
is.Equal(loginData, data) // received data should be the same as sent data
|
||||
|
||||
// delete login
|
||||
is.NoErr(rh.RemoveLogin(serialKey))
|
||||
|
||||
// get login
|
||||
_, err = rh.GetLogin(serialKey)
|
||||
is.True(err != nil) // should fail to get removed login
|
||||
}
|
||||
|
||||
func TestRedisShard(t *testing.T) {
|
||||
is := is.New(t)
|
||||
|
||||
// test data
|
||||
shard := redis.ShardMetadata{
|
||||
IP: "0.0.0.0",
|
||||
Port: 1234,
|
||||
}
|
||||
|
||||
// register shard
|
||||
is.NoErr(rh.RegisterShard(shard))
|
||||
|
||||
// get shards
|
||||
shards := rh.GetShards()
|
||||
is.True(len(shards) == 1) // should only be 1 shard
|
||||
is.Equal(shards[0], shard) // received data should be the same as sent data
|
||||
}
|
||||
@@ -7,10 +7,6 @@ type ShardMetadata struct {
|
||||
Port int
|
||||
}
|
||||
|
||||
const (
|
||||
SHARD_SET = "shards"
|
||||
)
|
||||
|
||||
func (r *RedisHandler) RegisterShard(shard ShardMetadata) error {
|
||||
value, err := json.Marshal(shard)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package cnet
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -10,13 +10,13 @@ import (
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/CPunch/gopenfusion/cnet/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/config"
|
||||
"github.com/CPunch/gopenfusion/config"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
)
|
||||
|
||||
type PacketHandler func(peer *Peer, pkt protocol.Packet) error
|
||||
type PacketHandler func(peer *protocol.CNPeer, pkt protocol.Packet) error
|
||||
|
||||
func StubbedPacket(_ *Peer, _ protocol.Packet) error {
|
||||
func StubbedPacket(_ *protocol.CNPeer, _ protocol.Packet) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -28,22 +28,22 @@ type Service struct {
|
||||
started chan struct{}
|
||||
stopped chan struct{}
|
||||
packetHandlers map[uint32]PacketHandler
|
||||
peers map[chan *PacketEvent]*Peer
|
||||
peers map[chan *protocol.PacketEvent]*protocol.CNPeer
|
||||
stateLock sync.Mutex
|
||||
|
||||
// OnDisconnect is called when a peer disconnects from the service.
|
||||
// uData is the stored value of the key/value pair in the peer map.
|
||||
// It may not be set while the service is running. (eg. srvc.Start() has been called)
|
||||
OnDisconnect func(peer *Peer)
|
||||
OnDisconnect func(peer *protocol.CNPeer)
|
||||
|
||||
// OnConnect is called when a peer connects to the service.
|
||||
// return value is used as the value in the peer map.
|
||||
// It may not be set while the service is running. (eg. srvc.Start() has been called)
|
||||
OnConnect func(peer *Peer)
|
||||
OnConnect func(peer *protocol.CNPeer)
|
||||
}
|
||||
|
||||
func RandomPort() (int, error) {
|
||||
l, err := net.Listen("tcp", ":0")
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -69,7 +69,7 @@ func NewService(ctx context.Context, name string, port int) *Service {
|
||||
func (srvc *Service) Reset(ctx context.Context) {
|
||||
srvc.ctx = ctx
|
||||
srvc.packetHandlers = make(map[uint32]PacketHandler)
|
||||
srvc.peers = make(map[chan *PacketEvent]*Peer)
|
||||
srvc.peers = make(map[chan *protocol.PacketEvent]*protocol.CNPeer)
|
||||
srvc.started = make(chan struct{})
|
||||
srvc.stopped = make(chan struct{})
|
||||
}
|
||||
@@ -80,8 +80,8 @@ func (srvc *Service) AddPacketHandler(pktID uint32, handler PacketHandler) {
|
||||
}
|
||||
|
||||
type newPeerConnection struct {
|
||||
peer *Peer
|
||||
channel chan *PacketEvent
|
||||
peer *protocol.CNPeer
|
||||
channel chan *protocol.PacketEvent
|
||||
}
|
||||
|
||||
func (srvc *Service) Start() error {
|
||||
@@ -112,22 +112,22 @@ func (srvc *Service) Start() error {
|
||||
}
|
||||
|
||||
// create a new peer and pass it to the event loop
|
||||
peer := NewPeer(srvc.ctx, conn)
|
||||
eRecv := make(chan *PacketEvent)
|
||||
peer := protocol.NewCNPeer(srvc.ctx, conn)
|
||||
eRecv := make(chan *protocol.PacketEvent)
|
||||
peerConnections <- newPeerConnection{channel: eRecv, peer: peer}
|
||||
go peer.Handler(eRecv)
|
||||
}
|
||||
}
|
||||
|
||||
func (srvc *Service) getPeer(channel chan *PacketEvent) *Peer {
|
||||
func (srvc *Service) getPeer(channel chan *protocol.PacketEvent) *protocol.CNPeer {
|
||||
return srvc.peers[channel]
|
||||
}
|
||||
|
||||
func (srvc *Service) setPeer(channel chan *PacketEvent, peer *Peer) {
|
||||
func (srvc *Service) setPeer(channel chan *protocol.PacketEvent, peer *protocol.CNPeer) {
|
||||
srvc.peers[channel] = peer
|
||||
}
|
||||
|
||||
func (srvc *Service) removePeer(channel chan *PacketEvent) {
|
||||
func (srvc *Service) removePeer(channel chan *protocol.PacketEvent) {
|
||||
delete(srvc.peers, channel)
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ func (srvc *Service) Stopped() <-chan struct{} {
|
||||
// if f returns false, the iteration is stopped.
|
||||
// NOTE: the peer map is not locked while iterating, if you're calling this
|
||||
// outside of the service's event loop, you'll need to lock the peer map yourself.
|
||||
func (srvc *Service) RangePeers(f func(peer *Peer) bool) {
|
||||
func (srvc *Service) RangePeers(f func(peer *protocol.CNPeer) bool) {
|
||||
for _, peer := range srvc.peers {
|
||||
if !f(peer) {
|
||||
break
|
||||
@@ -167,7 +167,7 @@ func (srvc *Service) Unlock() {
|
||||
|
||||
func (srvc *Service) stop() {
|
||||
// OnDisconnect handler might need to do something important
|
||||
srvc.RangePeers(func(peer *Peer) bool {
|
||||
srvc.RangePeers(func(peer *protocol.CNPeer) bool {
|
||||
peer.Kill()
|
||||
if srvc.OnDisconnect != nil {
|
||||
srvc.OnDisconnect(peer)
|
||||
@@ -196,7 +196,7 @@ func (srvc *Service) handleEvents(peerPipe <-chan newPeerConnection) {
|
||||
Chan: reflect.ValueOf(peerPipe),
|
||||
})
|
||||
|
||||
addPoll := func(channel chan *PacketEvent) {
|
||||
addPoll := func(channel chan *protocol.PacketEvent) {
|
||||
poll = append(poll, reflect.SelectCase{
|
||||
Dir: reflect.SelectRecv,
|
||||
Chan: reflect.ValueOf(channel),
|
||||
@@ -221,7 +221,7 @@ func (srvc *Service) handleEvents(peerPipe <-chan newPeerConnection) {
|
||||
addPoll(evnt.channel)
|
||||
srvc.connect(evnt.channel, evnt.peer)
|
||||
default: // peer event
|
||||
channel := poll[chosen].Chan.Interface().(chan *PacketEvent)
|
||||
channel := poll[chosen].Chan.Interface().(chan *protocol.PacketEvent)
|
||||
peer := srvc.getPeer(channel)
|
||||
if peer == nil {
|
||||
log.Printf("Unknown peer event: %v", value)
|
||||
@@ -229,7 +229,7 @@ func (srvc *Service) handleEvents(peerPipe <-chan newPeerConnection) {
|
||||
continue
|
||||
}
|
||||
|
||||
evnt, ok := value.Interface().(*PacketEvent)
|
||||
evnt, ok := value.Interface().(*protocol.PacketEvent)
|
||||
if !recvOK || !ok || evnt == nil {
|
||||
// peer disconnected, remove it from our poll queue
|
||||
removePoll(chosen)
|
||||
@@ -250,7 +250,7 @@ func (srvc *Service) handleEvents(peerPipe <-chan newPeerConnection) {
|
||||
}
|
||||
}
|
||||
|
||||
func (srvc *Service) handlePacket(peer *Peer, typeID uint32, pkt protocol.Packet) error {
|
||||
func (srvc *Service) handlePacket(peer *protocol.CNPeer, typeID uint32, pkt protocol.Packet) error {
|
||||
if hndlr, ok := srvc.packetHandlers[typeID]; ok {
|
||||
// fmt.Printf("Handling packet %x\n", typeID)
|
||||
if err := hndlr(peer, pkt); err != nil {
|
||||
@@ -263,7 +263,7 @@ func (srvc *Service) handlePacket(peer *Peer, typeID uint32, pkt protocol.Packet
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srvc *Service) disconnect(channel chan *PacketEvent, peer *Peer) {
|
||||
func (srvc *Service) disconnect(channel chan *protocol.PacketEvent, peer *protocol.CNPeer) {
|
||||
log.Printf("Peer %p disconnected from %s\n", peer, srvc.Name)
|
||||
if srvc.OnDisconnect != nil {
|
||||
srvc.OnDisconnect(peer)
|
||||
@@ -272,7 +272,7 @@ func (srvc *Service) disconnect(channel chan *PacketEvent, peer *Peer) {
|
||||
srvc.removePeer(channel)
|
||||
}
|
||||
|
||||
func (srvc *Service) connect(channel chan *PacketEvent, peer *Peer) {
|
||||
func (srvc *Service) connect(channel chan *protocol.PacketEvent, peer *protocol.CNPeer) {
|
||||
log.Printf("New peer %p connected to %s\n", peer, srvc.Name)
|
||||
if srvc.OnConnect != nil {
|
||||
srvc.OnConnect(peer)
|
||||
117
internal/service/service_test.go
Normal file
117
internal/service/service_test.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package service_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/service"
|
||||
"github.com/matryer/is"
|
||||
)
|
||||
|
||||
var (
|
||||
srvcPort int
|
||||
)
|
||||
|
||||
const (
|
||||
timeout = 2
|
||||
maxDummyPeers = 5
|
||||
)
|
||||
|
||||
func selectWithTimeout(ch <-chan struct{}, seconds int) bool {
|
||||
select {
|
||||
case <-ch:
|
||||
return true
|
||||
case <-time.After(time.Duration(seconds) * time.Second):
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func waitWithTimeout(wg *sync.WaitGroup, seconds int) bool {
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
return selectWithTimeout(done, seconds)
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
var err error
|
||||
srvcPort, err = service.RandomPort()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestService(t *testing.T) {
|
||||
is := is.New(t)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
srvc := service.NewService(ctx, "TEST", srvcPort)
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
// shutdown service when test is done
|
||||
defer func() {
|
||||
cancel()
|
||||
is.True(selectWithTimeout(srvc.Stopped(), timeout)) // wait for service to stop with timeout
|
||||
}()
|
||||
|
||||
// our dummy packet handler
|
||||
srvc.AddPacketHandler(0x1234, func(peer *protocol.CNPeer, pkt protocol.Packet) error {
|
||||
log.Printf("Received packet %#v", pkt)
|
||||
wg.Done()
|
||||
return nil
|
||||
})
|
||||
|
||||
// wait for all dummy peers to connect and disconnect
|
||||
wg.Add(maxDummyPeers)
|
||||
srvc.OnConnect = func(peer *protocol.CNPeer) {
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
wg.Add(maxDummyPeers)
|
||||
srvc.OnDisconnect = func(peer *protocol.CNPeer) {
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
// run service
|
||||
go func() {
|
||||
is.NoErr(srvc.Start()) // srvc.Start error
|
||||
}()
|
||||
|
||||
is.True(selectWithTimeout(srvc.Started(), timeout)) // wait for service to start with timeout
|
||||
|
||||
wg.Add(maxDummyPeers * 3) // 3 wg.Done() calls per dummy peer. 2 per peer for receiving packets, 1 for Handler() exit
|
||||
for i := 0; i < maxDummyPeers; i++ {
|
||||
go func() {
|
||||
// make dummy client
|
||||
conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", srvcPort))
|
||||
is.NoErr(err) // net.Dial error
|
||||
|
||||
peer := protocol.NewCNPeer(ctx, conn)
|
||||
go func() {
|
||||
defer peer.Kill()
|
||||
|
||||
// send dummy packets
|
||||
for i := 0; i < 2; i++ {
|
||||
is.NoErr(peer.Send(0x1234)) // peer.Send error
|
||||
}
|
||||
}()
|
||||
|
||||
// we wait until Handler gracefully exits (peer was killed)
|
||||
peer.Handler(make(chan *protocol.PacketEvent))
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
is.True(waitWithTimeout(&wg, timeout)) // wait for all dummy peers to be done with timeout
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"github.com/CPunch/gopenfusion/cnet/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/db"
|
||||
"github.com/CPunch/gopenfusion/internal/redis"
|
||||
)
|
||||
|
||||
var (
|
||||
TestCharCreate = protocol.SP_CL2LS_REQ_CHAR_CREATE{
|
||||
PCStyle: protocol.SPCStyle{
|
||||
INameCheck: 1, SzFirstName: "Hector",
|
||||
SzLastName: "Bannonvenom", IGender: 1, IFaceStyle: 1,
|
||||
IHairStyle: 17, IHairColor: 11, ISkinColor: 10, IEyeColor: 2,
|
||||
IHeight: 1, IBody: 0, IClass: 0,
|
||||
},
|
||||
SOn_Item: protocol.SOnItem{
|
||||
IEquipHandID: 0, IEquipUBID: 53, IEquipLBID: 17, IEquipFootID: 58,
|
||||
IEquipHeadID: 0, IEquipFaceID: 0, IEquipBackID: 0,
|
||||
},
|
||||
SOn_Item_Index: protocol.SOnItem_Index{
|
||||
IEquipUBID_index: 15, IEquipLBID_index: 12, IEquipFootID_index: 17,
|
||||
IFaceStyle: 2, IHairStyle: 18,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// creates a new account and player in the database
|
||||
func MakeTestPlayer(db *db.DBHandler, id string, password string) (acc *db.Account, plr *db.Player, err error) {
|
||||
acc, err = db.NewAccount(id, password)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var plrID int
|
||||
plrID, err = db.NewPlayer(acc.AccountID, TestCharCreate.PCStyle.SzFirstName, TestCharCreate.PCStyle.SzLastName, 1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
charCreate := TestCharCreate
|
||||
charCreate.PCStyle.IPC_UID = int64(plrID)
|
||||
err = db.FinishPlayer(&charCreate, acc.AccountID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = db.FinishTutorial(plrID, acc.AccountID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
plr, err = db.GetPlayer(plrID)
|
||||
return
|
||||
}
|
||||
|
||||
func QueueLogin(redisHndlr *redis.RedisHandler, FEKey []byte, plrID, accID int) (int64, error) {
|
||||
key, err := protocol.GenSerialKey()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return key, redisHndlr.QueueLogin(key, redis.LoginMetadata{
|
||||
FEKey: FEKey,
|
||||
PlayerID: int32(plrID),
|
||||
AccountID: accID,
|
||||
})
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/CPunch/gopenfusion/cnet"
|
||||
"github.com/CPunch/gopenfusion/cnet/protocol"
|
||||
"github.com/matryer/is"
|
||||
)
|
||||
|
||||
type DummyPeer struct {
|
||||
Recv chan *cnet.PacketEvent
|
||||
Peer *cnet.Peer
|
||||
is *is.I
|
||||
}
|
||||
|
||||
// MakeDummyPeer creates a new dummy peer and returns it
|
||||
func MakeDummyPeer(ctx context.Context, is *is.I, port int) *DummyPeer {
|
||||
conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port))
|
||||
is.NoErr(err)
|
||||
|
||||
recv := make(chan *cnet.PacketEvent)
|
||||
peer := cnet.NewPeer(ctx, conn)
|
||||
go func() {
|
||||
peer.Handler(recv)
|
||||
}()
|
||||
|
||||
return &DummyPeer{Recv: recv, Peer: peer, is: is}
|
||||
}
|
||||
|
||||
// SendAndRecv sends a packet (sID & out), waits for the expected response (rID) and decodes it into in
|
||||
func (dp *DummyPeer) SendAndRecv(sID, rID uint32, out, in interface{}) {
|
||||
// send out packet
|
||||
err := dp.Peer.Send(sID, out)
|
||||
dp.is.NoErr(err) // peer.Send() should not return an error
|
||||
|
||||
// receive response
|
||||
evnt := <-dp.Recv
|
||||
defer protocol.PutBuffer(evnt.Pkt)
|
||||
|
||||
dp.is.Equal(evnt.PktID, rID) // should receive expected type
|
||||
dp.is.NoErr(protocol.NewPacket(evnt.Pkt).Decode(in)) // packet.Decode() should not return an error
|
||||
}
|
||||
|
||||
// Kill closes the peer's connection
|
||||
func (dp *DummyPeer) Kill() {
|
||||
dp.Peer.Kill()
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/CPunch/gopenfusion/internal/db"
|
||||
"github.com/CPunch/gopenfusion/internal/redis"
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/bitcomplete/sqltestutil"
|
||||
)
|
||||
|
||||
// SetupEnvironment spawns a postgres container and returns a db and redis handler
|
||||
// along with a cleanup function
|
||||
func SetupEnvironment(ctx context.Context) (*db.DBHandler, *redis.RedisHandler, func()) {
|
||||
// spawn postgres container
|
||||
psql, err := sqltestutil.StartPostgresContainer(ctx, "15")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// open db handler
|
||||
testDB, err := db.OpenFromConnectionString("postgres", psql.ConnectionString()+"?sslmode=disable")
|
||||
if err != nil {
|
||||
psql.Shutdown(ctx)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err = testDB.Setup(); err != nil {
|
||||
psql.Shutdown(ctx)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// start miniredis
|
||||
r, err := miniredis.Run()
|
||||
if err != nil {
|
||||
psql.Shutdown(ctx)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// open redis handler
|
||||
rh, err := redis.OpenRedis(r.Addr())
|
||||
if err != nil {
|
||||
psql.Shutdown(ctx)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return testDB, rh, func() {
|
||||
psql.Shutdown(ctx)
|
||||
rh.Close()
|
||||
r.Close()
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SelectWithTimeout(ch <-chan struct{}, timeout time.Duration) bool {
|
||||
select {
|
||||
case <-ch:
|
||||
return true
|
||||
case <-time.After(timeout):
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func WaitWithTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
return SelectWithTimeout(done, timeout)
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package testutil_test
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/CPunch/gopenfusion/internal/testutil"
|
||||
"github.com/matryer/is"
|
||||
)
|
||||
|
||||
func TestWaitWithTimeout(t *testing.T) {
|
||||
is := is.New(t)
|
||||
wg := &sync.WaitGroup{}
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
is.True(!testutil.WaitWithTimeout(wg, 500*time.Millisecond)) // timeout should occur
|
||||
is.True(testutil.WaitWithTimeout(wg, 750*time.Millisecond)) // timeout shouldn't occur
|
||||
}
|
||||
|
||||
func TestSelectWithTimeout(t *testing.T) {
|
||||
is := is.New(t)
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
is.True(!testutil.SelectWithTimeout(ch, 500*time.Millisecond)) // timeout should occur
|
||||
is.True(testutil.SelectWithTimeout(ch, 750*time.Millisecond)) // timeout shouldn't occur
|
||||
}
|
||||
@@ -7,10 +7,9 @@ import (
|
||||
"log"
|
||||
"math/rand"
|
||||
|
||||
"github.com/CPunch/gopenfusion/cnet"
|
||||
"github.com/CPunch/gopenfusion/cnet/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/config"
|
||||
"github.com/CPunch/gopenfusion/config"
|
||||
"github.com/CPunch/gopenfusion/internal/db"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/redis"
|
||||
)
|
||||
|
||||
@@ -26,7 +25,7 @@ const (
|
||||
LOGIN_UPDATED_EUALA_REQUIRED = 9
|
||||
)
|
||||
|
||||
func (server *LoginServer) AcceptLogin(peer *cnet.Peer, SzID string, IClientVerC int32, ISlotNum int8, data []protocol.SP_LS2CL_REP_CHAR_INFO) error {
|
||||
func (server *LoginServer) AcceptLogin(peer *protocol.CNPeer, SzID string, IClientVerC int32, ISlotNum int8, data []protocol.SP_LS2CL_REP_CHAR_INFO) error {
|
||||
resp := protocol.SP_LS2CL_REP_LOGIN_SUCC{
|
||||
SzID: SzID,
|
||||
ICharCount: int8(len(data)),
|
||||
@@ -62,7 +61,7 @@ func (server *LoginServer) AcceptLogin(peer *cnet.Peer, SzID string, IClientVerC
|
||||
return nil
|
||||
}
|
||||
|
||||
func (server *LoginServer) Login(peer *cnet.Peer, pkt protocol.Packet) error {
|
||||
func (server *LoginServer) Login(peer *protocol.CNPeer, pkt protocol.Packet) error {
|
||||
var loginPkt protocol.SP_CL2LS_REQ_LOGIN
|
||||
pkt.Decode(&loginPkt)
|
||||
|
||||
@@ -138,7 +137,7 @@ func (server *LoginServer) Login(peer *cnet.Peer, pkt protocol.Packet) error {
|
||||
return server.AcceptLogin(peer, loginPkt.SzID, loginPkt.IClientVerC, 1, charInfo[:len(plrs)])
|
||||
}
|
||||
|
||||
func (server *LoginServer) CheckCharacterName(peer *cnet.Peer, pkt protocol.Packet) error {
|
||||
func (server *LoginServer) CheckCharacterName(peer *protocol.CNPeer, pkt protocol.Packet) error {
|
||||
var charPkt protocol.SP_CL2LS_REQ_CHECK_CHAR_NAME
|
||||
pkt.Decode(&charPkt)
|
||||
|
||||
@@ -149,7 +148,7 @@ func (server *LoginServer) CheckCharacterName(peer *cnet.Peer, pkt protocol.Pack
|
||||
})
|
||||
}
|
||||
|
||||
func (server *LoginServer) SaveCharacterName(peer *cnet.Peer, pkt protocol.Packet) error {
|
||||
func (server *LoginServer) SaveCharacterName(peer *protocol.CNPeer, pkt protocol.Packet) error {
|
||||
var charPkt protocol.SP_CL2LS_REQ_SAVE_CHAR_NAME
|
||||
pkt.Decode(&charPkt)
|
||||
|
||||
@@ -202,7 +201,7 @@ func validateCharacterCreation(character *protocol.SP_CL2LS_REQ_CHAR_CREATE) boo
|
||||
return true
|
||||
}
|
||||
|
||||
func SendFail(peer *cnet.Peer) error {
|
||||
func SendFail(peer *protocol.CNPeer) error {
|
||||
if err := peer.Send(protocol.P_LS2CL_REP_SHARD_SELECT_FAIL, protocol.SP_LS2CL_REP_SHARD_SELECT_FAIL{
|
||||
IErrorCode: 2,
|
||||
}); err != nil {
|
||||
@@ -212,7 +211,7 @@ func SendFail(peer *cnet.Peer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (server *LoginServer) CharacterCreate(peer *cnet.Peer, pkt protocol.Packet) error {
|
||||
func (server *LoginServer) CharacterCreate(peer *protocol.CNPeer, pkt protocol.Packet) error {
|
||||
var charPkt protocol.SP_CL2LS_REQ_CHAR_CREATE
|
||||
pkt.Decode(&charPkt)
|
||||
|
||||
@@ -245,7 +244,7 @@ func (server *LoginServer) CharacterCreate(peer *cnet.Peer, pkt protocol.Packet)
|
||||
})
|
||||
}
|
||||
|
||||
func (server *LoginServer) CharacterDelete(peer *cnet.Peer, pkt protocol.Packet) error {
|
||||
func (server *LoginServer) CharacterDelete(peer *protocol.CNPeer, pkt protocol.Packet) error {
|
||||
var charPkt protocol.SP_CL2LS_REQ_CHAR_DELETE
|
||||
pkt.Decode(&charPkt)
|
||||
|
||||
@@ -264,7 +263,7 @@ func (server *LoginServer) CharacterDelete(peer *cnet.Peer, pkt protocol.Packet)
|
||||
})
|
||||
}
|
||||
|
||||
func (server *LoginServer) ShardSelect(peer *cnet.Peer, pkt protocol.Packet) error {
|
||||
func (server *LoginServer) ShardSelect(peer *protocol.CNPeer, pkt protocol.Packet) error {
|
||||
var selection protocol.SP_CL2LS_REQ_CHAR_SELECT
|
||||
pkt.Decode(&selection)
|
||||
|
||||
@@ -279,6 +278,11 @@ func (server *LoginServer) ShardSelect(peer *cnet.Peer, pkt protocol.Packet) err
|
||||
return fmt.Errorf("loginServer has found no linked shards")
|
||||
}
|
||||
|
||||
key, err := protocol.GenSerialKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: better shard selection logic pls
|
||||
// for now, pick random shard
|
||||
shard := shards[rand.Intn(len(shards))]
|
||||
@@ -296,11 +300,6 @@ func (server *LoginServer) ShardSelect(peer *cnet.Peer, pkt protocol.Packet) err
|
||||
return SendFail(peer)
|
||||
}
|
||||
|
||||
key, err := protocol.GenSerialKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// share the login attempt
|
||||
server.redisHndlr.QueueLogin(key, redis.LoginMetadata{
|
||||
FEKey: peer.FE_key,
|
||||
@@ -320,7 +319,7 @@ func (server *LoginServer) ShardSelect(peer *cnet.Peer, pkt protocol.Packet) err
|
||||
return peer.Send(protocol.P_LS2CL_REP_SHARD_SELECT_SUCC, resp)
|
||||
}
|
||||
|
||||
func (server *LoginServer) FinishTutorial(peer *cnet.Peer, pkt protocol.Packet) error {
|
||||
func (server *LoginServer) FinishTutorial(peer *protocol.CNPeer, pkt protocol.Packet) error {
|
||||
var charPkt protocol.SP_CL2LS_REQ_SAVE_CHAR_TUTOR
|
||||
pkt.Decode(&charPkt)
|
||||
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
package login_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/CPunch/gopenfusion/cnet"
|
||||
"github.com/CPunch/gopenfusion/cnet/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/db"
|
||||
"github.com/CPunch/gopenfusion/internal/redis"
|
||||
"github.com/CPunch/gopenfusion/internal/testutil"
|
||||
"github.com/CPunch/gopenfusion/login"
|
||||
"github.com/matryer/is"
|
||||
)
|
||||
|
||||
var (
|
||||
loginSrv *login.LoginServer
|
||||
loginPort int
|
||||
testDB *db.DBHandler
|
||||
rh *redis.RedisHandler
|
||||
)
|
||||
|
||||
/*
|
||||
test data was scraped by dumping packets, just adding a println to the LoginService
|
||||
to print the packet data
|
||||
*/
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
ret := 1
|
||||
defer func() {
|
||||
os.Exit(ret)
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// setup environment
|
||||
var closer func()
|
||||
testDB, rh, closer = testutil.SetupEnvironment(ctx)
|
||||
defer closer()
|
||||
|
||||
var err error
|
||||
loginPort, err = cnet.RandomPort()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// start login server
|
||||
loginSrv, err = login.NewLoginServer(ctx, testDB, rh, loginPort)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := loginSrv.Start(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// wait for login server to start, then start tests
|
||||
<-loginSrv.Service().Started()
|
||||
ret = m.Run()
|
||||
cancel()
|
||||
<-loginSrv.Service().Stopped()
|
||||
}
|
||||
|
||||
// This test tries a typical login sequence.
|
||||
func TestLoginSuccSequence(t *testing.T) {
|
||||
is := is.New(t)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
dummy := testutil.MakeDummyPeer(ctx, is, loginPort)
|
||||
defer dummy.Kill()
|
||||
|
||||
// send login request (this should create an account)
|
||||
var resp protocol.SP_LS2CL_REP_LOGIN_SUCC
|
||||
dummy.SendAndRecv(protocol.P_CL2LS_REQ_LOGIN, protocol.P_LS2CL_REP_LOGIN_SUCC,
|
||||
protocol.SP_CL2LS_REQ_LOGIN{
|
||||
SzID: "testLoginSequence",
|
||||
SzPassword: "test",
|
||||
}, &resp)
|
||||
|
||||
// verify response
|
||||
is.Equal(resp.SzID, "testLoginSequence") // should have the same ID
|
||||
is.Equal(resp.ICharCount, int8(0)) // should have 0 characters
|
||||
|
||||
// verify account was created
|
||||
_, err := testDB.TryLogin("testLoginSequence", "test")
|
||||
is.NoErr(err) // TryLogin() should not return an error
|
||||
}
|
||||
|
||||
// This test tries a typical login sequence, but with an invalid password.
|
||||
func TestLoginFailSequence(t *testing.T) {
|
||||
is := is.New(t)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
dummy := testutil.MakeDummyPeer(ctx, is, loginPort)
|
||||
defer dummy.Kill()
|
||||
|
||||
// send login request (this should not create an account)
|
||||
var resp protocol.SP_LS2CL_REP_LOGIN_FAIL
|
||||
dummy.SendAndRecv(protocol.P_CL2LS_REQ_LOGIN, protocol.P_LS2CL_REP_LOGIN_FAIL,
|
||||
protocol.SP_CL2LS_REQ_LOGIN{
|
||||
SzID: "",
|
||||
SzPassword: "",
|
||||
}, &resp)
|
||||
|
||||
// verify response
|
||||
is.Equal(resp.SzID, "") // should have the same ID
|
||||
is.Equal(resp.IErrorCode, int32(login.LOGIN_ID_AND_PASSWORD_DO_NOT_MATCH)) // should respond with LOGIN_ID_AND_PASSWORD_DO_NOT_MATCH
|
||||
}
|
||||
|
||||
// This test tries a typical login sequence, and creates a character
|
||||
func TestCharacterSequence(t *testing.T) {
|
||||
is := is.New(t)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
dummy := testutil.MakeDummyPeer(ctx, is, loginPort)
|
||||
defer dummy.Kill()
|
||||
|
||||
// send login request (this should create an account)
|
||||
var resp protocol.SP_LS2CL_REP_LOGIN_SUCC
|
||||
dummy.SendAndRecv(protocol.P_CL2LS_REQ_LOGIN, protocol.P_LS2CL_REP_LOGIN_SUCC,
|
||||
protocol.SP_CL2LS_REQ_LOGIN{
|
||||
SzID: "testCharacterSequence",
|
||||
SzPassword: "test",
|
||||
}, &resp)
|
||||
|
||||
// verify response
|
||||
is.Equal(resp.SzID, "testCharacterSequence") // should have the same ID
|
||||
is.Equal(resp.ICharCount, int8(0)) // should have 0 characters
|
||||
|
||||
// perform key swap
|
||||
dummy.Peer.E_key = protocol.CreateNewKey(
|
||||
resp.UiSvrTime,
|
||||
uint64(resp.ICharCount+1),
|
||||
uint64(resp.ISlotNum+1),
|
||||
)
|
||||
dummy.Peer.FE_key = protocol.CreateNewKey(
|
||||
binary.LittleEndian.Uint64([]byte(protocol.DEFAULT_KEY)),
|
||||
0,
|
||||
1,
|
||||
)
|
||||
|
||||
// send character name check request
|
||||
var charResp protocol.SP_LS2CL_REP_SAVE_CHAR_NAME_SUCC
|
||||
dummy.SendAndRecv(protocol.P_CL2LS_REQ_SAVE_CHAR_NAME, protocol.P_LS2CL_REP_SAVE_CHAR_NAME_SUCC,
|
||||
protocol.SP_CL2LS_REQ_SAVE_CHAR_NAME{
|
||||
ISlotNum: 1,
|
||||
IGender: 1,
|
||||
IFNCode: 260,
|
||||
ILNCode: 551,
|
||||
IMNCode: 33,
|
||||
SzFirstName: testutil.TestCharCreate.PCStyle.SzFirstName,
|
||||
SzLastName: testutil.TestCharCreate.PCStyle.SzLastName,
|
||||
}, &charResp)
|
||||
|
||||
// verify response
|
||||
is.Equal(charResp.ISlotNum, int8(1)) // should have the same slot number
|
||||
is.Equal(charResp.IGender, int8(1)) // should have the same gender
|
||||
is.Equal(charResp.SzFirstName, testutil.TestCharCreate.PCStyle.SzFirstName) // should have the same first name
|
||||
is.Equal(charResp.SzLastName, testutil.TestCharCreate.PCStyle.SzLastName) // should have the same last name
|
||||
|
||||
// send character create request
|
||||
charCreate := testutil.TestCharCreate
|
||||
charCreate.PCStyle.IPC_UID = charResp.IPC_UID
|
||||
var charCreateResp protocol.SP_LS2CL_REP_CHAR_CREATE_SUCC
|
||||
dummy.SendAndRecv(protocol.P_CL2LS_REQ_CHAR_CREATE, protocol.P_LS2CL_REP_CHAR_CREATE_SUCC,
|
||||
charCreate, &charCreateResp)
|
||||
|
||||
// verify response
|
||||
is.Equal(charCreate.PCStyle, charCreateResp.SPC_Style) // should have the same PCStyle
|
||||
is.Equal(charCreate.SOn_Item, charCreateResp.SOn_Item) // should have the same SOn_Item
|
||||
}
|
||||
@@ -3,20 +3,20 @@ package login
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/CPunch/gopenfusion/cnet"
|
||||
"github.com/CPunch/gopenfusion/cnet/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/db"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/redis"
|
||||
"github.com/CPunch/gopenfusion/internal/service"
|
||||
)
|
||||
|
||||
type LoginServer struct {
|
||||
service *cnet.Service
|
||||
service *service.Service
|
||||
dbHndlr *db.DBHandler
|
||||
redisHndlr *redis.RedisHandler
|
||||
}
|
||||
|
||||
func NewLoginServer(ctx context.Context, dbHndlr *db.DBHandler, redisHndlr *redis.RedisHandler, port int) (*LoginServer, error) {
|
||||
srvc := cnet.NewService(ctx, "LOGIN", port)
|
||||
srvc := service.NewService(ctx, "LOGIN", port)
|
||||
|
||||
server := &LoginServer{
|
||||
service: srvc,
|
||||
@@ -30,22 +30,18 @@ func NewLoginServer(ctx context.Context, dbHndlr *db.DBHandler, redisHndlr *redi
|
||||
srvc.AddPacketHandler(protocol.P_CL2LS_REQ_CHAR_CREATE, server.CharacterCreate)
|
||||
srvc.AddPacketHandler(protocol.P_CL2LS_REQ_CHAR_SELECT, server.ShardSelect)
|
||||
srvc.AddPacketHandler(protocol.P_CL2LS_REQ_CHAR_DELETE, server.CharacterDelete)
|
||||
srvc.AddPacketHandler(protocol.P_CL2LS_REQ_SHARD_SELECT, cnet.StubbedPacket)
|
||||
srvc.AddPacketHandler(protocol.P_CL2LS_REQ_SHARD_LIST_INFO, cnet.StubbedPacket)
|
||||
srvc.AddPacketHandler(protocol.P_CL2LS_CHECK_NAME_LIST, cnet.StubbedPacket)
|
||||
srvc.AddPacketHandler(protocol.P_CL2LS_REQ_SHARD_SELECT, service.StubbedPacket)
|
||||
srvc.AddPacketHandler(protocol.P_CL2LS_REQ_SHARD_LIST_INFO, service.StubbedPacket)
|
||||
srvc.AddPacketHandler(protocol.P_CL2LS_CHECK_NAME_LIST, service.StubbedPacket)
|
||||
srvc.AddPacketHandler(protocol.P_CL2LS_REQ_SAVE_CHAR_TUTOR, server.FinishTutorial)
|
||||
srvc.AddPacketHandler(protocol.P_CL2LS_REQ_PC_EXIT_DUPLICATE, cnet.StubbedPacket)
|
||||
srvc.AddPacketHandler(protocol.P_CL2LS_REP_LIVE_CHECK, cnet.StubbedPacket)
|
||||
srvc.AddPacketHandler(protocol.P_CL2LS_REQ_CHANGE_CHAR_NAME, cnet.StubbedPacket)
|
||||
srvc.AddPacketHandler(protocol.P_CL2LS_REQ_SERVER_SELECT, cnet.StubbedPacket)
|
||||
srvc.AddPacketHandler(protocol.P_CL2LS_REQ_PC_EXIT_DUPLICATE, service.StubbedPacket)
|
||||
srvc.AddPacketHandler(protocol.P_CL2LS_REP_LIVE_CHECK, service.StubbedPacket)
|
||||
srvc.AddPacketHandler(protocol.P_CL2LS_REQ_CHANGE_CHAR_NAME, service.StubbedPacket)
|
||||
srvc.AddPacketHandler(protocol.P_CL2LS_REQ_SERVER_SELECT, service.StubbedPacket)
|
||||
|
||||
return server, nil
|
||||
}
|
||||
|
||||
func (server *LoginServer) Service() *cnet.Service {
|
||||
return server.service
|
||||
}
|
||||
|
||||
func (server *LoginServer) Start() error {
|
||||
return server.service.Start()
|
||||
}
|
||||
|
||||
@@ -3,12 +3,11 @@ package shard
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/CPunch/gopenfusion/cnet"
|
||||
"github.com/CPunch/gopenfusion/cnet/protocol"
|
||||
"github.com/CPunch/gopenfusion/shard/entity"
|
||||
"github.com/CPunch/gopenfusion/internal/entity"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
)
|
||||
|
||||
func (server *ShardServer) freeChat(peer *cnet.Peer, pkt protocol.Packet) error {
|
||||
func (server *ShardServer) freeChat(peer *protocol.CNPeer, pkt protocol.Packet) error {
|
||||
var chat protocol.SP_CL2FE_REQ_SEND_FREECHAT_MESSAGE
|
||||
pkt.Decode(&chat)
|
||||
|
||||
@@ -25,7 +24,7 @@ func (server *ShardServer) freeChat(peer *cnet.Peer, pkt protocol.Packet) error
|
||||
})
|
||||
}
|
||||
|
||||
func (server *ShardServer) menuChat(peer *cnet.Peer, pkt protocol.Packet) error {
|
||||
func (server *ShardServer) menuChat(peer *protocol.CNPeer, pkt protocol.Packet) error {
|
||||
var chat protocol.SP_CL2FE_REQ_SEND_MENUCHAT_MESSAGE
|
||||
pkt.Decode(&chat)
|
||||
|
||||
@@ -42,7 +41,7 @@ func (server *ShardServer) menuChat(peer *cnet.Peer, pkt protocol.Packet) error
|
||||
})
|
||||
}
|
||||
|
||||
func (server *ShardServer) emoteChat(peer *cnet.Peer, pkt protocol.Packet) error {
|
||||
func (server *ShardServer) emoteChat(peer *protocol.CNPeer, pkt protocol.Packet) error {
|
||||
var chat protocol.SP_CL2FE_REQ_PC_AVATAR_EMOTES_CHAT
|
||||
pkt.Decode(&chat)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package shard
|
||||
|
||||
import (
|
||||
"github.com/CPunch/gopenfusion/shard/entity"
|
||||
"github.com/CPunch/gopenfusion/internal/entity"
|
||||
)
|
||||
|
||||
func (server *ShardServer) addEntity(e entity.Entity) {
|
||||
|
||||
@@ -4,13 +4,12 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/CPunch/gopenfusion/cnet"
|
||||
"github.com/CPunch/gopenfusion/cnet/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/entity"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/redis"
|
||||
"github.com/CPunch/gopenfusion/shard/entity"
|
||||
)
|
||||
|
||||
func (server *ShardServer) attachPlayer(peer *cnet.Peer, meta redis.LoginMetadata) (*entity.Player, error) {
|
||||
func (server *ShardServer) attachPlayer(peer *protocol.CNPeer, meta redis.LoginMetadata) (*entity.Player, error) {
|
||||
dbPlr, err := server.dbHndlr.GetPlayer(int(meta.PlayerID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -25,7 +24,7 @@ func (server *ShardServer) attachPlayer(peer *cnet.Peer, meta redis.LoginMetadat
|
||||
return plr, nil
|
||||
}
|
||||
|
||||
func (server *ShardServer) RequestEnter(peer *cnet.Peer, pkt protocol.Packet) error {
|
||||
func (server *ShardServer) RequestEnter(peer *protocol.CNPeer, pkt protocol.Packet) error {
|
||||
var enter protocol.SP_CL2FE_REQ_PC_ENTER
|
||||
pkt.Decode(&enter)
|
||||
|
||||
@@ -56,7 +55,7 @@ func (server *ShardServer) RequestEnter(peer *cnet.Peer, pkt protocol.Packet) er
|
||||
// setup peer
|
||||
peer.E_key = protocol.CreateNewKey(resp.UiSvrTime, uint64(resp.IID+1), uint64(resp.PCLoadData2CL.IFusionMatter+1))
|
||||
peer.FE_key = loginData.FEKey
|
||||
peer.SetActiveKey(cnet.USE_FE)
|
||||
peer.SetActiveKey(protocol.USE_FE)
|
||||
|
||||
log.Printf("Player %d (AccountID %d) entered\n", resp.IID, loginData.AccountID)
|
||||
if err := peer.Send(protocol.P_FE2CL_REP_PC_ENTER_SUCC, resp); err != nil {
|
||||
@@ -66,7 +65,7 @@ func (server *ShardServer) RequestEnter(peer *cnet.Peer, pkt protocol.Packet) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func (server *ShardServer) LoadingComplete(peer *cnet.Peer, pkt protocol.Packet) error {
|
||||
func (server *ShardServer) LoadingComplete(peer *protocol.CNPeer, pkt protocol.Packet) error {
|
||||
var loadComplete protocol.SP_CL2FE_REQ_PC_LOADING_COMPLETE
|
||||
pkt.Decode(&loadComplete)
|
||||
|
||||
@@ -84,9 +83,7 @@ func (server *ShardServer) LoadingComplete(peer *cnet.Peer, pkt protocol.Packet)
|
||||
// we send the chunk updates (PC_NEW, NPC_NEW, etc.) after the enter packet
|
||||
chunkPos := entity.MakeChunkPosition(plr.X, plr.Y)
|
||||
viewableChunks := server.getViewableChunks(chunkPos)
|
||||
|
||||
plr.SetChunkPos(chunkPos)
|
||||
server.getChunk(chunkPos).AddEntity(plr)
|
||||
server.addEntityToChunks(plr, viewableChunks)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,9 +3,8 @@ package shard
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/CPunch/gopenfusion/cnet"
|
||||
"github.com/CPunch/gopenfusion/cnet/protocol"
|
||||
"github.com/CPunch/gopenfusion/shard/entity"
|
||||
"github.com/CPunch/gopenfusion/internal/entity"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
)
|
||||
|
||||
func (server *ShardServer) updatePlayerPosition(plr *entity.Player, X, Y, Z, Angle int) {
|
||||
@@ -16,7 +15,7 @@ func (server *ShardServer) updatePlayerPosition(plr *entity.Player, X, Y, Z, Ang
|
||||
server.updateEntityChunk(plr, plr.GetChunkPos(), entity.MakeChunkPosition(X, Y))
|
||||
}
|
||||
|
||||
func (server *ShardServer) playerMove(peer *cnet.Peer, pkt protocol.Packet) error {
|
||||
func (server *ShardServer) playerMove(peer *protocol.CNPeer, pkt protocol.Packet) error {
|
||||
var move protocol.SP_CL2FE_REQ_PC_MOVE
|
||||
pkt.Decode(&move)
|
||||
|
||||
@@ -44,7 +43,7 @@ func (server *ShardServer) playerMove(peer *cnet.Peer, pkt protocol.Packet) erro
|
||||
})
|
||||
}
|
||||
|
||||
func (server *ShardServer) playerStop(peer *cnet.Peer, pkt protocol.Packet) error {
|
||||
func (server *ShardServer) playerStop(peer *protocol.CNPeer, pkt protocol.Packet) error {
|
||||
var stop protocol.SP_CL2FE_REQ_PC_STOP
|
||||
pkt.Decode(&stop)
|
||||
|
||||
@@ -66,7 +65,7 @@ func (server *ShardServer) playerStop(peer *cnet.Peer, pkt protocol.Packet) erro
|
||||
})
|
||||
}
|
||||
|
||||
func (server *ShardServer) playerJump(peer *cnet.Peer, pkt protocol.Packet) error {
|
||||
func (server *ShardServer) playerJump(peer *protocol.CNPeer, pkt protocol.Packet) error {
|
||||
var jump protocol.SP_CL2FE_REQ_PC_JUMP
|
||||
pkt.Decode(&jump)
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/CPunch/gopenfusion/internal/config"
|
||||
"github.com/CPunch/gopenfusion/shard/entity"
|
||||
"github.com/CPunch/gopenfusion/config"
|
||||
"github.com/CPunch/gopenfusion/internal/entity"
|
||||
)
|
||||
|
||||
type NPCData struct {
|
||||
@@ -18,8 +18,7 @@ func (server *ShardServer) LoadNPCs() {
|
||||
|
||||
data, err := os.ReadFile(config.GetTDataPath() + "/NPCs.json")
|
||||
if err != nil {
|
||||
log.Printf("Warning: failed to load NPCs: %v", err)
|
||||
return
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// yes, we have to do it this way so our NPCs IDs will be incremented and unique
|
||||
|
||||
@@ -3,25 +3,25 @@ package shard
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/CPunch/gopenfusion/cnet"
|
||||
"github.com/CPunch/gopenfusion/cnet/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/config"
|
||||
"github.com/CPunch/gopenfusion/config"
|
||||
"github.com/CPunch/gopenfusion/internal/db"
|
||||
"github.com/CPunch/gopenfusion/internal/entity"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/redis"
|
||||
"github.com/CPunch/gopenfusion/shard/entity"
|
||||
"github.com/CPunch/gopenfusion/internal/service"
|
||||
)
|
||||
|
||||
type PacketHandler func(peer *cnet.Peer, pkt protocol.Packet) error
|
||||
type PacketHandler func(peer *protocol.CNPeer, pkt protocol.Packet) error
|
||||
|
||||
type ShardServer struct {
|
||||
service *cnet.Service
|
||||
service *service.Service
|
||||
dbHndlr *db.DBHandler
|
||||
redisHndlr *redis.RedisHandler
|
||||
chunks map[entity.ChunkPosition]*entity.Chunk
|
||||
}
|
||||
|
||||
func NewShardServer(ctx context.Context, dbHndlr *db.DBHandler, redisHndlr *redis.RedisHandler, port int) (*ShardServer, error) {
|
||||
srvc := cnet.NewService(ctx, "SHARD", port)
|
||||
srvc := service.NewService(ctx, "SHARD", port)
|
||||
|
||||
server := &ShardServer{
|
||||
service: srvc,
|
||||
@@ -50,12 +50,12 @@ func NewShardServer(ctx context.Context, dbHndlr *db.DBHandler, redisHndlr *redi
|
||||
return server, nil
|
||||
}
|
||||
|
||||
func (server *ShardServer) Start() error {
|
||||
func (server *ShardServer) Start() {
|
||||
server.LoadNPCs()
|
||||
return server.service.Start()
|
||||
server.service.Start()
|
||||
}
|
||||
|
||||
func (server *ShardServer) onDisconnect(peer *cnet.Peer) {
|
||||
func (server *ShardServer) onDisconnect(peer *protocol.CNPeer) {
|
||||
// remove from chunks
|
||||
plr, ok := peer.UserData().(*entity.Player)
|
||||
if ok && plr != nil {
|
||||
@@ -63,10 +63,6 @@ func (server *ShardServer) onDisconnect(peer *cnet.Peer) {
|
||||
}
|
||||
}
|
||||
|
||||
func (server *ShardServer) onConnect(peer *cnet.Peer) {
|
||||
func (server *ShardServer) onConnect(peer *protocol.CNPeer) {
|
||||
|
||||
}
|
||||
|
||||
func (server *ShardServer) Service() *cnet.Service {
|
||||
return server.service
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user