Compare commits

..

1 Commits

Author SHA1 Message Date
96130ddc8d TestService: wait for OnConnect and OnDisconnect 2023-12-01 13:56:15 -06:00
48 changed files with 290 additions and 818 deletions

View File

@@ -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 ./...

View File

@@ -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.

View File

@@ -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

View File

@@ -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"
)

View File

@@ -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"

View File

@@ -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"
)

View File

@@ -1,9 +0,0 @@
package protocol
import (
"time"
)
func GetTime() uint64 {
return uint64(time.Now().UnixMilli())
}

View File

@@ -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
View File

@@ -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
View File

@@ -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=

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -3,7 +3,7 @@ package db
import (
"database/sql"
"github.com/CPunch/gopenfusion/cnet/protocol"
"github.com/CPunch/gopenfusion/internal/protocol"
)
type Inventory struct {

View File

@@ -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"
)

View File

@@ -10,7 +10,7 @@ import (
_ "embed"
"fmt"
"github.com/CPunch/gopenfusion/internal/config"
"github.com/CPunch/gopenfusion/config"
_ "github.com/lib/pq"
)

View File

@@ -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{}

View 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),
}
}

View File

@@ -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)
}

View File

@@ -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"
)

View File

@@ -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(),
})

View File

@@ -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(),
})

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -5,7 +5,7 @@ import (
"fmt"
"strconv"
"github.com/CPunch/gopenfusion/internal/config"
"github.com/CPunch/gopenfusion/config"
)
type LoginMetadata struct {

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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)

View 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
}

View File

@@ -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,
})
}

View File

@@ -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()
}

View File

@@ -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()
}
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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()
}

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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
}