Compare commits

...

31 Commits

Author SHA1 Message Date
cafca9093c login_test: use TestCharCreate from testutil 2024-03-03 13:14:37 -06:00
d84fcd2c93 testutil: added account.go 2024-03-03 13:11:46 -06:00
1f63f9856e testutil: refactoring/organizing
split helpers.go into env.go && dummy.go
2024-03-03 13:11:37 -06:00
de3e067b48 cnet/service: RandomPort no longer binds on 127.0.0.1 2024-02-23 18:34:10 -06:00
02afe67ac3 fix: os.Exit() kills any deferred cleanup functions
os.Exit() itself is now also a deferred function, which will be the last to run.
2024-02-05 11:59:50 -06:00
79f68187bf testutil: DummyPeer now holds onto the *is.Is
makes SendAndRecv a bit cleaner imo
2024-02-04 11:26:48 -06:00
cd93a058ce shard: no longer panics if tdata/NPCs.json isn't found 2024-02-04 11:21:02 -06:00
0a28dbcc3e removed util
- WaitWithTimeout && SelectWithTimeout have been moved to internal/testutil
- GetTime has been moved to cnet/protocol
2024-02-01 18:25:49 -06:00
1a6de671e5 moved 'testutil' to 'internal/testutil' 2024-02-01 17:28:00 -06:00
261ea6505f testutil: fix possible orphaned container in SetupEnvironment 2024-02-01 17:25:11 -06:00
556878544d testutil: refactoring && cleanup
added a simple DummyPeer struct to simplify creation, send/recv and cleanup
2024-02-01 17:21:56 -06:00
bfcbe6d3d6 started testutil: login_test now uses these helpers
should simplify new tests in the future
2024-02-01 17:11:50 -06:00
e5a9ed1481 shardserver: added Service()
also, Start() now returns an error result
2024-02-01 16:53:27 -06:00
23170093ee login_test: fix minor memory leak
defer PutBuffer so that the event packet is returned to the pool
2023-12-07 21:37:16 -06:00
2bd61dc571 login_test: more stale comments lol 2023-12-06 20:23:29 -06:00
cba01a877d login_test: fix sendAndRecv
removed the stale typecast lol
2023-12-06 20:17:36 -06:00
e1b9fa5d99 login_test: refactor, abstracted send and recv
validation
2023-12-06 20:15:06 -06:00
77751a2aa0 login_tests: annotate tests 2023-12-06 17:35:02 -06:00
8e84f0c7b2 more better README
haven't decided if showing an asciinema of the docker compose
environment is really useful or informative. maybe i'll
record a GIF of some gameplay in the future
2023-12-06 17:20:58 -06:00
c902559eac LICENSE: finally added! 2023-12-06 17:19:32 -06:00
1c40998cb6 README: oops, wrong link 2023-12-06 17:12:39 -06:00
988368c307 added unit tests workflow badge to README 2023-12-06 17:10:39 -06:00
01ebf4499f login_test: added TestCharacterSequence
tests the complete account/character creation sequence of packets
2023-12-06 17:08:59 -06:00
3a14d807d2 login: minor refactoring 2023-12-06 17:08:05 -06:00
141858d6c3 db/account: removed debug log 2023-12-06 16:40:06 -06:00
335fdb417c fix: add player to current chunk on LOAD_COMPLETE 2023-12-05 19:17:58 -06:00
2a6fb25f03 use passed context.Context 2023-12-04 20:45:23 -06:00
0ebd162af0 login_test: minor cleanup 2023-12-04 20:40:48 -06:00
d1763418a8 removed useless closure 2023-12-04 20:33:53 -06:00
c4d885cf6d Login && Password need to be at least 4 long 2023-12-04 20:30:58 -06:00
afd5c9ef23 added login_test 2023-12-04 20:28:17 -06:00
21 changed files with 456 additions and 66 deletions

7
LICENSE.md Normal file
View File

@@ -0,0 +1,7 @@
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,5 +1,12 @@
# 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.
## Landwalker demo

9
cnet/protocol/time.go Normal file
View File

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

View File

@@ -43,7 +43,7 @@ type Service struct {
}
func RandomPort() (int, error) {
l, err := net.Listen("tcp", "127.0.0.1:0")
l, err := net.Listen("tcp", ":0")
if err != nil {
return 0, err
}

View File

@@ -12,7 +12,7 @@ import (
"github.com/CPunch/gopenfusion/cnet"
"github.com/CPunch/gopenfusion/cnet/protocol"
"github.com/CPunch/gopenfusion/util"
"github.com/CPunch/gopenfusion/internal/testutil"
"github.com/matryer/is"
)
@@ -32,6 +32,7 @@ func TestMain(m *testing.M) {
panic(err)
}
// this is fine since we don't defer anything
os.Exit(m.Run())
}
@@ -44,7 +45,7 @@ func TestService(t *testing.T) {
// shutdown service when test is done
defer func() {
cancel()
is.True(util.SelectWithTimeout(srvc.Stopped(), timeout)) // wait for service to stop with timeout
is.True(testutil.SelectWithTimeout(srvc.Stopped(), timeout)) // wait for service to stop with timeout
}()
// our dummy packet handler
@@ -67,8 +68,8 @@ func TestService(t *testing.T) {
}
// run service
go func() { is.NoErr(srvc.Start()) }() // srvc.Start error
is.True(util.SelectWithTimeout(srvc.Started(), timeout)) // wait for service to start with timeout
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++ {
@@ -93,5 +94,5 @@ func TestService(t *testing.T) {
}()
}
is.True(util.WaitWithTimeout(&wg, timeout)) // wait for all dummy peers to be done with timeout
is.True(testutil.WaitWithTimeout(&wg, timeout)) // wait for all dummy peers to be done with timeout
}

View File

@@ -2,7 +2,6 @@ package db
import (
"errors"
"log"
"golang.org/x/crypto/bcrypt"
@@ -54,10 +53,14 @@ 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

@@ -18,6 +18,11 @@ 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 {
@@ -35,7 +40,7 @@ func TestMain(m *testing.M) {
panic(err)
}
os.Exit(m.Run())
ret = m.Run()
}
func TestDBAccount(t *testing.T) {

View File

@@ -14,6 +14,11 @@ var (
)
func TestMain(m *testing.M) {
ret := 1
defer func() {
os.Exit(ret)
}()
r, err := miniredis.Run()
if err != nil {
panic(err)
@@ -26,7 +31,7 @@ func TestMain(m *testing.M) {
}
defer rh.Close()
os.Exit(m.Run())
ret = m.Run()
}
func TestRedisLogin(t *testing.T) {

View File

@@ -0,0 +1,68 @@
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

@@ -0,0 +1,50 @@
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()
}

52
internal/testutil/env.go Normal file
View File

@@ -0,0 +1,52 @@
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,14 +1,10 @@
package util
package testutil
import (
"sync"
"time"
)
func GetTime() uint64 {
return uint64(time.Now().UnixMilli())
}
func SelectWithTimeout(ch <-chan struct{}, timeout time.Duration) bool {
select {
case <-ch:

View File

@@ -0,0 +1,35 @@
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

@@ -12,7 +12,6 @@ import (
"github.com/CPunch/gopenfusion/internal/config"
"github.com/CPunch/gopenfusion/internal/db"
"github.com/CPunch/gopenfusion/internal/redis"
"github.com/CPunch/gopenfusion/util"
)
const (
@@ -34,7 +33,7 @@ func (server *LoginServer) AcceptLogin(peer *cnet.Peer, SzID string, IClientVerC
ISlotNum: ISlotNum,
IPaymentFlag: 1,
IOpenBetaFlag: 0,
UiSvrTime: util.GetTime(),
UiSvrTime: protocol.GetTime(),
}
if err := peer.Send(protocol.P_LS2CL_REP_LOGIN_SUCC, resp); err != nil {
@@ -280,11 +279,6 @@ 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))]
@@ -302,6 +296,11 @@ 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,

179
login/login_test.go Normal file
View File

@@ -0,0 +1,179 @@
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

@@ -42,6 +42,10 @@ func NewLoginServer(ctx context.Context, dbHndlr *db.DBHandler, redisHndlr *redi
return server, nil
}
func (server *LoginServer) Service() *cnet.Service {
return server.service
}
func (server *LoginServer) Start() error {
return server.service.Start()
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/CPunch/gopenfusion/cnet/protocol"
"github.com/CPunch/gopenfusion/internal/redis"
"github.com/CPunch/gopenfusion/shard/entity"
"github.com/CPunch/gopenfusion/util"
)
func (server *ShardServer) attachPlayer(peer *cnet.Peer, meta redis.LoginMetadata) (*entity.Player, error) {
@@ -51,7 +50,7 @@ func (server *ShardServer) RequestEnter(peer *cnet.Peer, pkt protocol.Packet) er
resp := &protocol.SP_FE2CL_REP_PC_ENTER_SUCC{
IID: int32(plr.PlayerID),
PCLoadData2CL: plr.ToPCLoadData2CL(),
UiSvrTime: util.GetTime(),
UiSvrTime: protocol.GetTime(),
}
// setup peer
@@ -85,7 +84,9 @@ 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

@@ -6,7 +6,6 @@ import (
"github.com/CPunch/gopenfusion/cnet"
"github.com/CPunch/gopenfusion/cnet/protocol"
"github.com/CPunch/gopenfusion/shard/entity"
"github.com/CPunch/gopenfusion/util"
)
func (server *ShardServer) updatePlayerPosition(plr *entity.Player, X, Y, Z, Angle int) {
@@ -41,7 +40,7 @@ func (server *ShardServer) playerMove(peer *cnet.Peer, pkt protocol.Packet) erro
CKeyValue: move.CKeyValue,
ISpeed: move.ISpeed,
IID: int32(plr.PlayerID),
ISvrTime: util.GetTime(),
ISvrTime: protocol.GetTime(),
})
}
@@ -63,7 +62,7 @@ func (server *ShardServer) playerStop(peer *cnet.Peer, pkt protocol.Packet) erro
IY: stop.IY,
IZ: stop.IZ,
IID: int32(plr.PlayerID),
ISvrTime: util.GetTime(),
ISvrTime: protocol.GetTime(),
})
}
@@ -91,6 +90,6 @@ func (server *ShardServer) playerJump(peer *cnet.Peer, pkt protocol.Packet) erro
CKeyValue: jump.CKeyValue,
ISpeed: jump.ISpeed,
IID: int32(plr.PlayerID),
ISvrTime: util.GetTime(),
ISvrTime: protocol.GetTime(),
})
}

View File

@@ -18,7 +18,8 @@ func (server *ShardServer) LoadNPCs() {
data, err := os.ReadFile(config.GetTDataPath() + "/NPCs.json")
if err != nil {
panic(err)
log.Printf("Warning: failed to load NPCs: %v", err)
return
}
// yes, we have to do it this way so our NPCs IDs will be incremented and unique

View File

@@ -50,9 +50,9 @@ func NewShardServer(ctx context.Context, dbHndlr *db.DBHandler, redisHndlr *redi
return server, nil
}
func (server *ShardServer) Start() {
func (server *ShardServer) Start() error {
server.LoadNPCs()
server.service.Start()
return server.service.Start()
}
func (server *ShardServer) onDisconnect(peer *cnet.Peer) {
@@ -66,3 +66,7 @@ func (server *ShardServer) onDisconnect(peer *cnet.Peer) {
func (server *ShardServer) onConnect(peer *cnet.Peer) {
}
func (server *ShardServer) Service() *cnet.Service {
return server.service
}

View File

@@ -1,35 +0,0 @@
package util_test
import (
"sync"
"testing"
"time"
"github.com/CPunch/gopenfusion/util"
"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(!util.WaitWithTimeout(wg, 500*time.Millisecond)) // timeout should occur
is.True(util.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(!util.SelectWithTimeout(ch, 500*time.Millisecond)) // timeout should occur
is.True(util.SelectWithTimeout(ch, 750*time.Millisecond)) // timeout shouldn't occur
}