mirror of
https://github.com/genxium/DelayNoMore
synced 2024-12-26 11:48:56 +00:00
Merge pull request #5 from genxium/integer_positioning
Integer positioning.
This commit is contained in:
commit
63164569b1
@ -9,12 +9,13 @@ _(how input delay roughly works)_
|
||||
_(how rollback-and-chase in this project roughly works)_
|
||||
|
||||
![rollback_and_chase_intro](./charts/RollbackAndChase.jpg)
|
||||
![floating_point_accumulation_err](./charts/AvoidingFloatingPointAccumulationErr.jpg)
|
||||
|
||||
_(in game screenshot)_
|
||||
|
||||
![screenshot-1](./charts/screenshot-1.png)
|
||||
|
||||
Please checkout [this demo video](https://pan.baidu.com/s/123LlWcT9X-wbcYybqYnvmA?pwd=qrlw) to see whether the source codes are doing what you expect for synchronization.
|
||||
Please checkout [this demo video](https://pan.baidu.com/s/1YkfuHjNLzlFVnKiEj6wrDQ?pwd=tkr5) to see whether the source codes are doing what you expect for synchronization.
|
||||
|
||||
The video mainly shows the following features.
|
||||
- The backend receives inputs from frontend peers and [by a GGPO-alike manner](https://github.com/pond3r/ggpo/blob/master/doc/README.md) broadcasts back for synchronization.
|
||||
|
@ -1,6 +1,11 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"battle_srv/api"
|
||||
. "battle_srv/common"
|
||||
"battle_srv/common/utils"
|
||||
"battle_srv/models"
|
||||
"battle_srv/storage"
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
@ -10,11 +15,6 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"server/api"
|
||||
. "server/common"
|
||||
"server/common/utils"
|
||||
"server/models"
|
||||
"server/storage"
|
||||
"strconv"
|
||||
|
||||
. "dnmshared"
|
||||
@ -79,7 +79,6 @@ func (p *playerController) SMSCaptchaGet(c *gin.Context) {
|
||||
c.Set(api.RET, Constants.RetCode.UnknownError)
|
||||
return
|
||||
}
|
||||
// Redis剩余时长校验
|
||||
if ttl >= ConstVals.Player.CaptchaMaxTTL {
|
||||
Logger.Info("There's an existing SmsCaptcha record in Redis-server: ", zap.String("key", redisKey), zap.Duration("ttl", ttl))
|
||||
c.Set(api.RET, Constants.RetCode.SmsCaptchaRequestedTooFrequently)
|
||||
@ -89,7 +88,6 @@ func (p *playerController) SMSCaptchaGet(c *gin.Context) {
|
||||
pass := false
|
||||
var succRet int
|
||||
if Conf.General.ServerEnv == SERVER_ENV_TEST {
|
||||
// 测试环境,优先从数据库校验`player.name`,不通过再走机器人magic name校验
|
||||
player, err := models.GetPlayerByName(req.Num)
|
||||
if nil == err && nil != player {
|
||||
pass = true
|
||||
@ -98,7 +96,6 @@ func (p *playerController) SMSCaptchaGet(c *gin.Context) {
|
||||
}
|
||||
|
||||
if !pass {
|
||||
// 机器人magic name校验,不通过再走手机号校验
|
||||
player, err := models.GetPlayerByName(req.Num)
|
||||
if nil == err && nil != player {
|
||||
pass = true
|
||||
@ -111,7 +108,6 @@ func (p *playerController) SMSCaptchaGet(c *gin.Context) {
|
||||
succRet = Constants.RetCode.Ok
|
||||
pass = true
|
||||
}
|
||||
// Hardecoded 只验证国内手机号格式
|
||||
if req.CountryCode == "86" {
|
||||
if RE_CHINA_PHONE_NUM.MatchString(req.Num) {
|
||||
succRet = Constants.RetCode.Ok
|
||||
@ -133,7 +129,6 @@ func (p *playerController) SMSCaptchaGet(c *gin.Context) {
|
||||
}{Ret: succRet}
|
||||
var captcha string
|
||||
if ttl >= 0 {
|
||||
// 已有未过期的旧验证码记录,续验证码有效期。
|
||||
storage.RedisManagerIns.Expire(redisKey, ConstVals.Player.CaptchaExpire)
|
||||
captcha = storage.RedisManagerIns.Get(redisKey).Val()
|
||||
if ttl >= ConstVals.Player.CaptchaExpire/4 {
|
||||
@ -147,7 +142,6 @@ func (p *playerController) SMSCaptchaGet(c *gin.Context) {
|
||||
}
|
||||
Logger.Info("Extended ttl of existing SMSCaptcha record in Redis:", zap.String("key", redisKey), zap.String("captcha", captcha))
|
||||
} else {
|
||||
// 校验通过,进行验证码生成处理
|
||||
captcha = strconv.Itoa(utils.Rand.Number(1000, 9999))
|
||||
if succRet == Constants.RetCode.Ok {
|
||||
getSmsCaptchaRespErrorCode := sendSMSViaVendor(req.Num, req.CountryCode, captcha)
|
||||
@ -234,7 +228,6 @@ func (p *playerController) WechatLogin(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
//baseInfo ResAccessToken 获取用户授权access_token的返回结果
|
||||
baseInfo, err := utils.WechatIns.GetOauth2Basic(req.Authcode)
|
||||
|
||||
if err != nil {
|
||||
@ -250,7 +243,6 @@ func (p *playerController) WechatLogin(c *gin.Context) {
|
||||
c.Set(api.RET, Constants.RetCode.WechatServerError)
|
||||
return
|
||||
}
|
||||
//fserver不会返回openId
|
||||
userInfo.OpenID = baseInfo.OpenID
|
||||
|
||||
player, err := p.maybeCreatePlayerWechatAuthBinding(userInfo)
|
||||
@ -316,7 +308,6 @@ func (p *playerController) WechatGameLogin(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
//baseInfo ResAccessToken 获取用户授权access_token的返回结果
|
||||
baseInfo, err := utils.WechatGameIns.GetOauth2Basic(req.Authcode)
|
||||
|
||||
if err != nil {
|
||||
@ -337,7 +328,6 @@ func (p *playerController) WechatGameLogin(c *gin.Context) {
|
||||
c.Set(api.RET, Constants.RetCode.WechatServerError)
|
||||
return
|
||||
}
|
||||
//fserver不会返回openId
|
||||
userInfo.OpenID = baseInfo.OpenID
|
||||
|
||||
player, err := p.maybeCreatePlayerWechatGameAuthBinding(userInfo)
|
||||
@ -395,7 +385,6 @@ func (p *playerController) IntAuthTokenLogin(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
//kobako: 从player获取display name等
|
||||
player, err := models.GetPlayerById(playerLogin.PlayerID)
|
||||
if err != nil {
|
||||
Logger.Error("Get player by id in IntAuthTokenLogin function error: ", zap.Error(err))
|
||||
@ -479,7 +468,6 @@ func (p *playerController) TokenAuth(c *gin.Context) {
|
||||
c.Abort()
|
||||
}
|
||||
|
||||
// 以下是内部私有函数
|
||||
func (p *playerController) maybeCreateNewPlayer(req smsCaptchaReq) (*models.Player, error) {
|
||||
extAuthID := req.extAuthID()
|
||||
if Conf.General.ServerEnv == SERVER_ENV_TEST {
|
||||
@ -492,7 +480,7 @@ func (p *playerController) maybeCreateNewPlayer(req smsCaptchaReq) (*models.Play
|
||||
Logger.Info("Got a test env player:", zap.Any("phonenum", req.Num), zap.Any("playerId", player.Id))
|
||||
return player, nil
|
||||
}
|
||||
} else { //正式环境检查是否为bot用户
|
||||
} else {
|
||||
botPlayer, err := models.GetPlayerByName(req.Num)
|
||||
if err != nil {
|
||||
Logger.Error("Seeking bot player error:", zap.Error(err))
|
||||
@ -537,7 +525,6 @@ func (p *playerController) maybeCreatePlayerWechatAuthBinding(userInfo utils.Use
|
||||
return nil, err
|
||||
}
|
||||
if player != nil {
|
||||
{ //更新玩家姓名及头像
|
||||
updateInfo := models.Player{
|
||||
Avatar: userInfo.HeadImgURL,
|
||||
DisplayName: userInfo.Nickname,
|
||||
@ -550,7 +537,6 @@ func (p *playerController) maybeCreatePlayerWechatAuthBinding(userInfo utils.Use
|
||||
} else {
|
||||
tx.Commit()
|
||||
}
|
||||
}
|
||||
return player, nil
|
||||
}
|
||||
}
|
||||
@ -575,7 +561,6 @@ func (p *playerController) maybeCreatePlayerWechatGameAuthBinding(userInfo utils
|
||||
return nil, err
|
||||
}
|
||||
if player != nil {
|
||||
{ //更新玩家姓名及头像
|
||||
updateInfo := models.Player{
|
||||
Avatar: userInfo.HeadImgURL,
|
||||
DisplayName: userInfo.Nickname,
|
||||
@ -588,7 +573,6 @@ func (p *playerController) maybeCreatePlayerWechatGameAuthBinding(userInfo utils
|
||||
} else {
|
||||
tx.Commit()
|
||||
}
|
||||
}
|
||||
return player, nil
|
||||
}
|
||||
}
|
||||
@ -672,15 +656,13 @@ func sendSMSViaVendor(mobile string, nationcode string, captchaCode string) int
|
||||
Nationcode: nationcode,
|
||||
}
|
||||
var captchaExpireMin string
|
||||
//短信有效期hardcode
|
||||
if Conf.General.ServerEnv == SERVER_ENV_TEST {
|
||||
//测试环境下有效期为20秒 先hardcode了
|
||||
captchaExpireMin = "0.5"
|
||||
captchaExpireMin = "0.5" // Hardcoded
|
||||
} else {
|
||||
captchaExpireMin = strconv.Itoa(int(ConstVals.Player.CaptchaExpire) / 60000000000)
|
||||
}
|
||||
params := [2]string{captchaCode, captchaExpireMin}
|
||||
appkey := "41a5142feff0b38ade02ea12deee9741" // TODO: Should read from config file!
|
||||
appkey := "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" // TODO: Should read from config file!
|
||||
rand := strconv.Itoa(utils.Rand.Number(1000, 9999))
|
||||
now := utils.UnixtimeSec()
|
||||
|
||||
@ -694,7 +676,7 @@ func sendSMSViaVendor(mobile string, nationcode string, captchaCode string) int
|
||||
Extend: "",
|
||||
Params: ¶ms,
|
||||
Sig: sig,
|
||||
Sign: "洛克互娱",
|
||||
Sign: "YYYYYYYYYYYYYYYYY",
|
||||
Tel: tel,
|
||||
Time: now,
|
||||
Tpl_id: 207399,
|
||||
@ -705,7 +687,7 @@ func sendSMSViaVendor(mobile string, nationcode string, captchaCode string) int
|
||||
Logger.Info("json marshal", zap.Any("err:", err))
|
||||
return -1
|
||||
}
|
||||
resp, err := http.Post("https://yun.tim.qq.com/v5/tlssmssvr/sendsms?sdkappid=1400150185&random="+rand,
|
||||
resp, err := http.Post("https://yun.tim.qq.com/v5/tlssmssvr/sendsms?sdkappid=uuuuuuuuuuuuuuuuuuuuuuuu&random="+rand,
|
||||
"application/json",
|
||||
req)
|
||||
if err != nil {
|
||||
|
@ -1,6 +1,8 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
. "battle_srv/common"
|
||||
. "battle_srv/configs"
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
. "dnmshared"
|
||||
@ -11,8 +13,6 @@ import (
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
. "server/common"
|
||||
. "server/configs"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
@ -250,10 +250,6 @@ func (w *wechat) getTicketFromServer() (ticket resTicket, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
//jsAPITicketCacheKey := fmt.Sprintf("jsapi_ticket_%s", w.config.AppID)
|
||||
//expires := ticket.ExpiresIn - 1500
|
||||
//set
|
||||
//err = js.Cache.Set(jsAPITicketCacheKey, ticket.Ticket, time.Duration(expires)*time.Second)
|
||||
return
|
||||
}
|
||||
|
||||
@ -276,9 +272,6 @@ func (w *wechat) getAccessTokenFromServer() (accessToken string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
//accessTokenCacheKey := fmt.Sprintf("access_token_%s", w.config.AppID)
|
||||
//expires := r.ExpiresIn - 1500
|
||||
//set to redis err = ctx.Cache.Set(accessTokenCacheKey, r.AccessToken, time.Duration(expires)*time.Second)
|
||||
accessToken = r.AccessToken
|
||||
return
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
package env_tools
|
||||
|
||||
import (
|
||||
. "battle_srv/common"
|
||||
"battle_srv/common/utils"
|
||||
"battle_srv/models"
|
||||
"battle_srv/storage"
|
||||
. "dnmshared"
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"go.uber.org/zap"
|
||||
. "server/common"
|
||||
"server/common/utils"
|
||||
"server/models"
|
||||
"server/storage"
|
||||
)
|
||||
|
||||
func LoadPreConf() {
|
||||
|
@ -1,11 +1,11 @@
|
||||
package env_tools
|
||||
|
||||
import (
|
||||
. "battle_srv/common"
|
||||
"battle_srv/common/utils"
|
||||
"battle_srv/models"
|
||||
"battle_srv/storage"
|
||||
. "dnmshared"
|
||||
. "server/common"
|
||||
"server/common/utils"
|
||||
"server/models"
|
||||
"server/storage"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
|
@ -1,4 +1,4 @@
|
||||
module server
|
||||
module battle_srv
|
||||
|
||||
go 1.19
|
||||
|
||||
|
@ -1,19 +1,19 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"battle_srv/api"
|
||||
"battle_srv/api/v1"
|
||||
. "battle_srv/common"
|
||||
"battle_srv/env_tools"
|
||||
"battle_srv/models"
|
||||
"battle_srv/storage"
|
||||
"battle_srv/ws"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"server/api"
|
||||
"server/api/v1"
|
||||
. "server/common"
|
||||
"server/env_tools"
|
||||
"server/models"
|
||||
"server/storage"
|
||||
"server/ws"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
. "dnmshared"
|
||||
. "dnmshared/sharedprotos"
|
||||
)
|
||||
|
||||
type Barrier struct {
|
||||
|
@ -1,77 +1,22 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
. "dnmshared"
|
||||
pb "server/pb_output"
|
||||
. "battle_srv/protos"
|
||||
. "dnmshared/sharedprotos"
|
||||
)
|
||||
|
||||
func toPbVec2D(modelInstance *Vec2D) *pb.Vec2D {
|
||||
toRet := &pb.Vec2D{
|
||||
X: modelInstance.X,
|
||||
Y: modelInstance.Y,
|
||||
}
|
||||
return toRet
|
||||
}
|
||||
|
||||
func toPbPolygon2D(modelInstance *Polygon2D) *pb.Polygon2D {
|
||||
toRet := &pb.Polygon2D{
|
||||
Anchor: toPbVec2D(modelInstance.Anchor),
|
||||
Points: make([]*pb.Vec2D, len(modelInstance.Points)),
|
||||
}
|
||||
for index, p := range modelInstance.Points {
|
||||
toRet.Points[index] = toPbVec2D(p)
|
||||
}
|
||||
return toRet
|
||||
}
|
||||
|
||||
func toPbVec2DList(modelInstance *Vec2DList) *pb.Vec2DList {
|
||||
toRet := &pb.Vec2DList{
|
||||
Vec2DList: make([]*pb.Vec2D, len(*modelInstance)),
|
||||
}
|
||||
for k, v := range *modelInstance {
|
||||
toRet.Vec2DList[k] = toPbVec2D(v)
|
||||
}
|
||||
return toRet
|
||||
}
|
||||
|
||||
func ToPbVec2DListMap(modelInstances map[string]*Vec2DList) map[string]*pb.Vec2DList {
|
||||
toRet := make(map[string]*pb.Vec2DList, len(modelInstances))
|
||||
for k, v := range modelInstances {
|
||||
toRet[k] = toPbVec2DList(v)
|
||||
}
|
||||
return toRet
|
||||
}
|
||||
|
||||
func toPbPolygon2DList(modelInstance *Polygon2DList) *pb.Polygon2DList {
|
||||
toRet := &pb.Polygon2DList{
|
||||
Polygon2DList: make([]*pb.Polygon2D, len(*modelInstance)),
|
||||
}
|
||||
for k, v := range *modelInstance {
|
||||
toRet.Polygon2DList[k] = toPbPolygon2D(v)
|
||||
}
|
||||
return toRet
|
||||
}
|
||||
|
||||
func ToPbPolygon2DListMap(modelInstances map[string]*Polygon2DList) map[string]*pb.Polygon2DList {
|
||||
toRet := make(map[string]*pb.Polygon2DList, len(modelInstances))
|
||||
for k, v := range modelInstances {
|
||||
toRet[k] = toPbPolygon2DList(v)
|
||||
}
|
||||
return toRet
|
||||
}
|
||||
|
||||
func toPbPlayers(modelInstances map[int32]*Player) map[int32]*pb.Player {
|
||||
toRet := make(map[int32]*pb.Player, 0)
|
||||
func toPbPlayers(modelInstances map[int32]*Player) map[int32]*PlayerDownsync {
|
||||
toRet := make(map[int32]*PlayerDownsync, 0)
|
||||
if nil == modelInstances {
|
||||
return toRet
|
||||
}
|
||||
|
||||
for k, last := range modelInstances {
|
||||
toRet[k] = &pb.Player{
|
||||
toRet[k] = &PlayerDownsync{
|
||||
Id: last.Id,
|
||||
X: last.X,
|
||||
Y: last.Y,
|
||||
Dir: &pb.Direction{
|
||||
VirtualGridX: last.VirtualGridX,
|
||||
VirtualGridY: last.VirtualGridY,
|
||||
Dir: &Direction{
|
||||
Dx: last.Dir.Dx,
|
||||
Dy: last.Dir.Dy,
|
||||
},
|
||||
|
@ -2,7 +2,7 @@ package models
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
. "dnmshared"
|
||||
. "dnmshared/sharedprotos"
|
||||
"fmt"
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/jmoiron/sqlx"
|
||||
@ -33,30 +33,32 @@ func InitPlayerBattleStateIns() {
|
||||
}
|
||||
|
||||
type Player struct {
|
||||
// Meta info fields
|
||||
Id int32 `json:"id,omitempty" db:"id"`
|
||||
X float64 `json:"x,omitempty"`
|
||||
Y float64 `json:"y,omitempty"`
|
||||
Dir *Direction `json:"dir,omitempty"`
|
||||
Speed float64 `json:"speed,omitempty"`
|
||||
BattleState int32 `json:"battleState,omitempty"`
|
||||
LastMoveGmtMillis int32 `json:"lastMoveGmtMillis,omitempty"`
|
||||
Score int32 `json:"score,omitempty"`
|
||||
Removed bool `json:"removed,omitempty"`
|
||||
JoinIndex int32
|
||||
|
||||
Name string `json:"name,omitempty" db:"name"`
|
||||
DisplayName string `json:"displayName,omitempty" db:"display_name"`
|
||||
Avatar string `json:"avatar,omitempty"`
|
||||
ColliderRadius float64 `json:"-"`
|
||||
|
||||
FrozenAtGmtMillis int64 `json:"-" db:"-"`
|
||||
AddSpeedAtGmtMillis int64 `json:"-" db:"-"`
|
||||
CreatedAt int64 `json:"-" db:"created_at"`
|
||||
UpdatedAt int64 `json:"-" db:"updated_at"`
|
||||
DeletedAt NullInt64 `json:"-" db:"deleted_at"`
|
||||
TutorialStage int `json:"-" db:"tutorial_stage"`
|
||||
AckingFrameId int32 `json:"ackingFrameId"`
|
||||
AckingInputFrameId int32 `json:"-"`
|
||||
LastSentInputFrameId int32 `json:"-"`
|
||||
// DB only fields
|
||||
CreatedAt int64 `db:"created_at"`
|
||||
UpdatedAt int64 `db:"updated_at"`
|
||||
DeletedAt NullInt64 `db:"deleted_at"`
|
||||
TutorialStage int `db:"tutorial_stage"`
|
||||
|
||||
// in-battle info fields
|
||||
VirtualGridX int32
|
||||
VirtualGridY int32
|
||||
Dir *Direction
|
||||
Speed int32
|
||||
BattleState int32
|
||||
LastMoveGmtMillis int32
|
||||
Score int32
|
||||
Removed bool
|
||||
JoinIndex int32
|
||||
AckingFrameId int32
|
||||
AckingInputFrameId int32
|
||||
LastSentInputFrameId int32
|
||||
}
|
||||
|
||||
func ExistPlayerByName(name string) (bool, error) {
|
||||
|
@ -1,9 +1,9 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"battle_srv/storage"
|
||||
"database/sql"
|
||||
. "dnmshared"
|
||||
"server/storage"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
@ -1,10 +1,10 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
. "battle_srv/common"
|
||||
"battle_srv/common/utils"
|
||||
"battle_srv/storage"
|
||||
"database/sql"
|
||||
. "server/common"
|
||||
"server/common/utils"
|
||||
"server/storage"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
)
|
||||
|
@ -1,11 +1,11 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
. "battle_srv/common"
|
||||
"battle_srv/common/utils"
|
||||
"database/sql"
|
||||
. "dnmshared"
|
||||
"errors"
|
||||
. "server/common"
|
||||
"server/common/utils"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
@ -1,7 +1,11 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
. "battle_srv/common"
|
||||
"battle_srv/common/utils"
|
||||
. "battle_srv/protos"
|
||||
. "dnmshared"
|
||||
. "dnmshared/sharedprotos"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"github.com/golang/protobuf/proto"
|
||||
@ -13,9 +17,6 @@ import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
. "server/common"
|
||||
"server/common/utils"
|
||||
pb "server/pb_output"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@ -60,36 +61,17 @@ const (
|
||||
MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED = -2
|
||||
)
|
||||
|
||||
// These directions are chosen such that when speed is changed to "(speedX+delta, speedY+delta)" for any of them, the direction is unchanged.
|
||||
var DIRECTION_DECODER = [][]int32{
|
||||
{0, 0},
|
||||
{0, +1},
|
||||
{0, -1},
|
||||
{0, +2},
|
||||
{0, -2},
|
||||
{+2, 0},
|
||||
{-2, 0},
|
||||
{+2, +1},
|
||||
{-2, -1},
|
||||
{+2, -1},
|
||||
{-2, +1},
|
||||
{+2, 0},
|
||||
{-2, 0},
|
||||
{0, +1},
|
||||
{0, -1},
|
||||
}
|
||||
|
||||
var DIRECTION_DECODER_INVERSE_LENGTH = []float64{
|
||||
0.0,
|
||||
1.0,
|
||||
1.0,
|
||||
0.5,
|
||||
0.5,
|
||||
0.4472,
|
||||
0.4472,
|
||||
0.4472,
|
||||
0.4472,
|
||||
0.5,
|
||||
0.5,
|
||||
1.0,
|
||||
1.0,
|
||||
{+1, +1},
|
||||
{-1, -1},
|
||||
{+1, -1},
|
||||
{-1, +1},
|
||||
}
|
||||
|
||||
type RoomBattleState struct {
|
||||
@ -130,7 +112,8 @@ func calRoomScore(inRoomPlayerCount int32, roomPlayerCnt int, currentRoomBattleS
|
||||
type Room struct {
|
||||
Id int32
|
||||
Capacity int
|
||||
playerColliderRadius float64
|
||||
collisionSpaceOffsetX float64
|
||||
collisionSpaceOffsetY float64
|
||||
Players map[int32]*Player
|
||||
PlayersArr []*Player // ordered by joinIndex
|
||||
CollisionSysMap map[int32]*resolv.Object
|
||||
@ -177,11 +160,15 @@ type Room struct {
|
||||
NstDelayFrames int32 // network-single-trip delay in the count of render frames, proposed to be (InputDelayFrames >> 1) because we expect a round-trip delay to be exactly "InputDelayFrames"
|
||||
InputScaleFrames uint32 // inputDelayedAndScaledFrameId = ((originalFrameId - InputDelayFrames) >> InputScaleFrames)
|
||||
JoinIndexBooleanArr []bool
|
||||
RollbackEstimatedDt float64
|
||||
RollbackEstimatedDtMillis float64
|
||||
RollbackEstimatedDtNanos int64
|
||||
LastRenderFrameIdTriggeredAt int64
|
||||
|
||||
WorldToVirtualGridRatio float64
|
||||
VirtualGridToWorldRatio float64
|
||||
|
||||
PlayerDefaultSpeed int32
|
||||
|
||||
StageName string
|
||||
StageDiscreteW int32
|
||||
StageDiscreteH int32
|
||||
@ -192,11 +179,6 @@ type Room struct {
|
||||
BackendDynamicsEnabled bool
|
||||
}
|
||||
|
||||
const (
|
||||
PLAYER_DEFAULT_SPEED = float64(200) // Hardcoded
|
||||
ADD_SPEED = float64(100) // Hardcoded
|
||||
)
|
||||
|
||||
func (pR *Room) updateScore() {
|
||||
pR.Score = calRoomScore(pR.EffectivePlayerCount, pR.Capacity, pR.State)
|
||||
}
|
||||
@ -218,9 +200,8 @@ func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, session *websocke
|
||||
pPlayerFromDbInit.AckingInputFrameId = -1
|
||||
pPlayerFromDbInit.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED
|
||||
pPlayerFromDbInit.BattleState = PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK
|
||||
pPlayerFromDbInit.FrozenAtGmtMillis = -1 // Hardcoded temporarily.
|
||||
pPlayerFromDbInit.Speed = PLAYER_DEFAULT_SPEED // Hardcoded temporarily.
|
||||
pPlayerFromDbInit.AddSpeedAtGmtMillis = -1 // Hardcoded temporarily.
|
||||
pPlayerFromDbInit.Speed = pR.PlayerDefaultSpeed // Hardcoded
|
||||
pPlayerFromDbInit.ColliderRadius = float64(24) // Hardcoded
|
||||
|
||||
pR.Players[playerId] = pPlayerFromDbInit
|
||||
pR.PlayerDownsyncSessionDict[playerId] = session
|
||||
@ -252,6 +233,8 @@ func (pR *Room) ReAddPlayerIfPossible(pTmpPlayerInstance *Player, session *webso
|
||||
pEffectiveInRoomPlayerInstance.AckingInputFrameId = -1
|
||||
pEffectiveInRoomPlayerInstance.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED
|
||||
pEffectiveInRoomPlayerInstance.BattleState = PlayerBattleStateIns.READDED_PENDING_BATTLE_COLLIDER_ACK
|
||||
pEffectiveInRoomPlayerInstance.Speed = pR.PlayerDefaultSpeed // Hardcoded
|
||||
pEffectiveInRoomPlayerInstance.ColliderRadius = float64(16) // Hardcoded
|
||||
|
||||
Logger.Warn("ReAddPlayerIfPossible finished.", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("joinIndex", pEffectiveInRoomPlayerInstance.JoinIndex), zap.Any("playerBattleState", pEffectiveInRoomPlayerInstance.BattleState), zap.Any("roomState", pR.State), zap.Any("roomEffectivePlayerCount", pR.EffectivePlayerCount), zap.Any("AckingFrameId", pEffectiveInRoomPlayerInstance.AckingFrameId), zap.Any("AckingInputFrameId", pEffectiveInRoomPlayerInstance.AckingInputFrameId), zap.Any("LastSentInputFrameId", pEffectiveInRoomPlayerInstance.LastSentInputFrameId))
|
||||
return true
|
||||
@ -269,7 +252,7 @@ func (pR *Room) ChooseStage() error {
|
||||
}
|
||||
|
||||
rand.Seed(time.Now().Unix())
|
||||
stageNameList := []string{ /*"simple" ,*/ "richsoil"}
|
||||
stageNameList := []string{"simple" /* "richsoil" */}
|
||||
chosenStageIndex := rand.Int() % len(stageNameList) // Hardcoded temporarily. -- YFLu
|
||||
|
||||
pR.StageName = stageNameList[chosenStageIndex]
|
||||
@ -329,7 +312,7 @@ func (pR *Room) ChooseStage() error {
|
||||
barrierPolygon2DList := *(toRetStrToPolygon2DListMap["Barrier"])
|
||||
|
||||
var barrierLocalIdInBattle int32 = 0
|
||||
for _, polygon2DUnaligned := range barrierPolygon2DList {
|
||||
for _, polygon2DUnaligned := range barrierPolygon2DList.Eles {
|
||||
polygon2D := AlignPolygon2DToBoundingBox(polygon2DUnaligned)
|
||||
/*
|
||||
// For debug-printing only.
|
||||
@ -362,7 +345,7 @@ func (pR *Room) ConvertToLastUsedRenderFrameId(inputFrameId int32, inputDelayFra
|
||||
return ((inputFrameId << pR.InputScaleFrames) + inputDelayFrames + (1 << pR.InputScaleFrames) - 1)
|
||||
}
|
||||
|
||||
func (pR *Room) EncodeUpsyncCmd(upsyncCmd *pb.InputFrameUpsync) uint64 {
|
||||
func (pR *Room) EncodeUpsyncCmd(upsyncCmd *InputFrameUpsync) uint64 {
|
||||
var ret uint64 = 0
|
||||
// There're 13 possible directions, occupying the first 4 bits, no need to shift
|
||||
ret += uint64(upsyncCmd.EncodedDir)
|
||||
@ -386,7 +369,7 @@ func (pR *Room) InputsBufferString(allDetails bool) string {
|
||||
if nil == tmp {
|
||||
break
|
||||
}
|
||||
f := tmp.(*pb.InputFrameDownsync)
|
||||
f := tmp.(*InputFrameDownsync)
|
||||
s = append(s, fmt.Sprintf("{inputFrameId: %v, inputList: %v, confirmedList: %v}", f.InputFrameId, f.InputList, f.ConfirmedList))
|
||||
}
|
||||
|
||||
@ -408,7 +391,7 @@ func (pR *Room) StartBattle() {
|
||||
|
||||
// Initialize the "collisionSys" as well as "RenderFrameBuffer"
|
||||
pR.CurDynamicsRenderFrameId = 0
|
||||
kickoffFrame := &pb.RoomDownsyncFrame{
|
||||
kickoffFrame := &RoomDownsyncFrame{
|
||||
Id: pR.RenderFrameId,
|
||||
Players: toPbPlayers(pR.Players),
|
||||
CountdownNanos: pR.BattleDurationNanos,
|
||||
@ -419,9 +402,8 @@ func (pR *Room) StartBattle() {
|
||||
spaceW := pR.StageDiscreteW * pR.StageTileW
|
||||
spaceH := pR.StageDiscreteH * pR.StageTileH
|
||||
|
||||
spaceOffsetX := float64(spaceW) * 0.5
|
||||
spaceOffsetY := float64(spaceH) * 0.5
|
||||
pR.refreshColliders(spaceW, spaceH, spaceOffsetX, spaceOffsetY)
|
||||
pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY = float64(spaceW)*0.5, float64(spaceH)*0.5
|
||||
pR.refreshColliders(spaceW, spaceH)
|
||||
|
||||
/**
|
||||
* Will be triggered from a goroutine which executes the critical `Room.AddPlayerIfPossible`, thus the `battleMainLoop` should be detached.
|
||||
@ -465,12 +447,11 @@ func (pR *Room) StartBattle() {
|
||||
pR.prefabInputFrameDownsync(noDelayInputFrameId)
|
||||
}
|
||||
|
||||
pR.markConfirmationIfApplicable()
|
||||
unconfirmedMask := uint64(0)
|
||||
if pR.BackendDynamicsEnabled {
|
||||
// Force setting all-confirmed of buffered inputFrames periodically
|
||||
unconfirmedMask = pR.forceConfirmationIfApplicable()
|
||||
} else {
|
||||
pR.markConfirmationIfApplicable()
|
||||
}
|
||||
|
||||
upperToSendInputFrameId := atomic.LoadInt32(&(pR.LastAllConfirmedInputFrameId))
|
||||
@ -494,7 +475,7 @@ func (pR *Room) StartBattle() {
|
||||
// Apply "all-confirmed inputFrames" to move forward "pR.CurDynamicsRenderFrameId"
|
||||
nextDynamicsRenderFrameId := pR.ConvertToLastUsedRenderFrameId(pR.LastAllConfirmedInputFrameId, pR.InputDelayFrames)
|
||||
Logger.Debug(fmt.Sprintf("roomId=%v, room.RenderFrameId=%v, LastAllConfirmedInputFrameId=%v, InputDelayFrames=%v, nextDynamicsRenderFrameId=%v", pR.Id, pR.RenderFrameId, pR.LastAllConfirmedInputFrameId, pR.InputDelayFrames, nextDynamicsRenderFrameId))
|
||||
pR.applyInputFrameDownsyncDynamics(pR.CurDynamicsRenderFrameId, nextDynamicsRenderFrameId, spaceOffsetX, spaceOffsetY)
|
||||
pR.applyInputFrameDownsyncDynamics(pR.CurDynamicsRenderFrameId, nextDynamicsRenderFrameId, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY)
|
||||
dynamicsDuration = utils.UnixtimeNano() - dynamicsStartedAt
|
||||
}
|
||||
|
||||
@ -503,21 +484,22 @@ func (pR *Room) StartBattle() {
|
||||
refRenderFrameId = pR.CurDynamicsRenderFrameId
|
||||
}
|
||||
}
|
||||
|
||||
for playerId, player := range pR.Players {
|
||||
if swapped := atomic.CompareAndSwapInt32(&player.BattleState, PlayerBattleStateIns.ACTIVE, PlayerBattleStateIns.ACTIVE); !swapped {
|
||||
// [WARNING] DON'T send anything if the player is disconnected, because it could jam the channel and cause significant delay upon "battle recovery for reconnected player".
|
||||
continue
|
||||
}
|
||||
if 0 == pR.RenderFrameId {
|
||||
kickoffFrame := pR.RenderFrameBuffer.GetByFrameId(0).(*pb.RoomDownsyncFrame)
|
||||
kickoffFrame := pR.RenderFrameBuffer.GetByFrameId(0).(*RoomDownsyncFrame)
|
||||
pR.sendSafely(kickoffFrame, nil, DOWNSYNC_MSG_ACT_BATTLE_START, playerId)
|
||||
} else {
|
||||
// [WARNING] Websocket is TCP-based, thus no need to re-send a previously sent inputFrame to a same player!
|
||||
toSendInputFrames := make([]*pb.InputFrameDownsync, 0, pR.InputsBuffer.Cnt)
|
||||
toSendInputFrames := make([]*InputFrameDownsync, 0, pR.InputsBuffer.Cnt)
|
||||
candidateToSendInputFrameId := pR.Players[playerId].LastSentInputFrameId + 1
|
||||
if candidateToSendInputFrameId < pR.InputsBuffer.StFrameId {
|
||||
// [WARNING] As "player.LastSentInputFrameId <= lastAllConfirmedInputFrameIdWithChange" for each iteration, and "lastAllConfirmedInputFrameIdWithChange <= lastAllConfirmedInputFrameId" where the latter is used to "applyInputFrameDownsyncDynamics" and then evict "pR.InputsBuffer", thus there's a very high possibility that "player.LastSentInputFrameId" is already evicted.
|
||||
// Logger.Debug(fmt.Sprintf("LastSentInputFrameId already popped: roomId=%v, playerId=%v, lastSentInputFrameId=%v, playerAckingInputFrameId=%v, InputsBuffer=%v", pR.Id, playerId, candidateToSendInputFrameId-1, player.AckingInputFrameId, pR.InputsBufferString(false)))
|
||||
Logger.Warn(fmt.Sprintf("LastSentInputFrameId already popped: roomId=%v, playerId=%v, lastSentInputFrameId=%v, playerAckingInputFrameId=%v, InputsBuffer=%v", pR.Id, playerId, candidateToSendInputFrameId-1, player.AckingInputFrameId, pR.InputsBufferString(false)))
|
||||
candidateToSendInputFrameId = pR.InputsBuffer.StFrameId
|
||||
}
|
||||
|
||||
@ -533,7 +515,7 @@ func (pR *Room) StartBattle() {
|
||||
if nil == tmp {
|
||||
panic(fmt.Sprintf("Required inputFrameId=%v for roomId=%v, playerId=%v doesn't exist! InputsBuffer=%v", candidateToSendInputFrameId, pR.Id, playerId, pR.InputsBufferString(false)))
|
||||
}
|
||||
f := tmp.(*pb.InputFrameDownsync)
|
||||
f := tmp.(*InputFrameDownsync)
|
||||
if pR.inputFrameIdDebuggable(candidateToSendInputFrameId) {
|
||||
Logger.Debug("inputFrame lifecycle#3[sending]:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("playerAckingInputFrameId", player.AckingInputFrameId), zap.Any("inputFrameId", candidateToSendInputFrameId), zap.Any("inputFrameId-doublecheck", f.InputFrameId), zap.Any("InputsBuffer", pR.InputsBufferString(false)), zap.Any("ConfirmedList", f.ConfirmedList))
|
||||
}
|
||||
@ -543,22 +525,26 @@ func (pR *Room) StartBattle() {
|
||||
|
||||
if 0 >= len(toSendInputFrames) {
|
||||
// [WARNING] When sending DOWNSYNC_MSG_ACT_FORCED_RESYNC, there MUST BE accompanying "toSendInputFrames" for calculating "refRenderFrameId"!
|
||||
|
||||
if MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED == player.LastSentInputFrameId {
|
||||
Logger.Warn(fmt.Sprintf("Not sending due to empty toSendInputFrames: roomId=%v, playerId=%v, refRenderFrameId=%v, upperToSendInputFrameId=%v, lastSentInputFrameId=%v, playerAckingInputFrameId=%v", pR.Id, playerId, refRenderFrameId, upperToSendInputFrameId, player.LastSentInputFrameId, player.AckingInputFrameId))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
indiceInJoinIndexBooleanArr := uint32(player.JoinIndex - 1)
|
||||
var joinMask uint64 = (1 << indiceInJoinIndexBooleanArr)
|
||||
if pR.BackendDynamicsEnabled && (MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED == player.LastSentInputFrameId || 0 < (unconfirmedMask&joinMask)) {
|
||||
// [WARNING] Even upon "MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED", it could be true that "0 == (unconfirmedMask & joinMask)"!
|
||||
/*
|
||||
Resync helps
|
||||
1. when player with a slower frontend clock lags significantly behind and thus wouldn't get its inputUpsync recognized due to faster "forceConfirmation"
|
||||
2. reconnection
|
||||
*/
|
||||
shouldResync1 := (MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED == player.LastSentInputFrameId)
|
||||
// shouldResync2 := (0 < (unconfirmedMask & uint64(1 << uint32(player.JoinIndex-1)))) // This condition is critical, if we don't send resync upon this condition, the "reconnected or slowly-clocking player" might never get its input synced
|
||||
shouldResync2 := (0 < unconfirmedMask) // An easier version of the above, might keep sending "refRenderFrame"s to still connected players when any player is disconnected
|
||||
if pR.BackendDynamicsEnabled && (shouldResync1 || shouldResync2) {
|
||||
tmp := pR.RenderFrameBuffer.GetByFrameId(refRenderFrameId)
|
||||
if nil == tmp {
|
||||
panic(fmt.Sprintf("Required refRenderFrameId=%v for roomId=%v, playerId=%v, candidateToSendInputFrameId=%v doesn't exist! InputsBuffer=%v, RenderFrameBuffer=%v", refRenderFrameId, pR.Id, playerId, candidateToSendInputFrameId, pR.InputsBufferString(false), pR.RenderFrameBufferString()))
|
||||
}
|
||||
refRenderFrame := tmp.(*pb.RoomDownsyncFrame)
|
||||
refRenderFrame := tmp.(*RoomDownsyncFrame)
|
||||
pR.sendSafely(refRenderFrame, toSendInputFrames, DOWNSYNC_MSG_ACT_FORCED_RESYNC, playerId)
|
||||
} else {
|
||||
pR.sendSafely(nil, toSendInputFrames, DOWNSYNC_MSG_ACT_INPUT_BATCH, playerId)
|
||||
@ -567,14 +553,18 @@ func (pR *Room) StartBattle() {
|
||||
}
|
||||
}
|
||||
|
||||
if pR.BackendDynamicsEnabled {
|
||||
// Evict no longer required "RenderFrameBuffer"
|
||||
for pR.RenderFrameBuffer.N < pR.RenderFrameBuffer.Cnt || (0 < pR.RenderFrameBuffer.Cnt && pR.RenderFrameBuffer.StFrameId < refRenderFrameId) {
|
||||
_ = pR.RenderFrameBuffer.Pop()
|
||||
}
|
||||
}
|
||||
|
||||
toApplyInputFrameId := pR.ConvertToInputFrameId(refRenderFrameId, pR.InputDelayFrames)
|
||||
if false == pR.BackendDynamicsEnabled {
|
||||
// When "false == pR.BackendDynamicsEnabled", the variable "refRenderFrameId" is not well defined
|
||||
/*
|
||||
[WARNING]
|
||||
The following updates to "toApplyInputFrameId" is necessary because when "false == pR.BackendDynamicsEnabled", the variable "refRenderFrameId" is not well defined.
|
||||
*/
|
||||
minLastSentInputFrameId := int32(math.MaxInt32)
|
||||
for _, player := range pR.Players {
|
||||
if player.LastSentInputFrameId >= minLastSentInputFrameId {
|
||||
@ -582,20 +572,21 @@ func (pR *Room) StartBattle() {
|
||||
}
|
||||
minLastSentInputFrameId = player.LastSentInputFrameId
|
||||
}
|
||||
if minLastSentInputFrameId < toApplyInputFrameId {
|
||||
toApplyInputFrameId = minLastSentInputFrameId
|
||||
}
|
||||
for pR.InputsBuffer.N < pR.InputsBuffer.Cnt || (0 < pR.InputsBuffer.Cnt && pR.InputsBuffer.StFrameId < toApplyInputFrameId) {
|
||||
f := pR.InputsBuffer.Pop().(*pb.InputFrameDownsync)
|
||||
f := pR.InputsBuffer.Pop().(*InputFrameDownsync)
|
||||
if pR.inputFrameIdDebuggable(f.InputFrameId) {
|
||||
// Popping of an "inputFrame" would be AFTER its being all being confirmed, because it requires the "inputFrame" to be all acked
|
||||
Logger.Debug("inputFrame lifecycle#4[popped]:", zap.Any("roomId", pR.Id), zap.Any("inputFrameId", f.InputFrameId), zap.Any("InputsBuffer", pR.InputsBufferString(false)))
|
||||
Logger.Debug("inputFrame lifecycle#4[popped]:", zap.Any("roomId", pR.Id), zap.Any("inputFrameId", f.InputFrameId), zap.Any("toApplyInputFrameId", toApplyInputFrameId), zap.Any("InputsBuffer", pR.InputsBufferString(false)))
|
||||
}
|
||||
}
|
||||
|
||||
pR.RenderFrameId++
|
||||
elapsedInCalculation := (utils.UnixtimeNano() - stCalculation)
|
||||
if elapsedInCalculation > nanosPerFrame {
|
||||
Logger.Warn(fmt.Sprintf("SLOW FRAME! Elapsed time statistics: roomId=%v, room.RenderFrameId=%v, elapsedInCalculation=%v, dynamicsDuration=%v, nanosPerFrame=%v", pR.Id, pR.RenderFrameId, elapsedInCalculation, dynamicsDuration, nanosPerFrame))
|
||||
Logger.Warn(fmt.Sprintf("SLOW FRAME! Elapsed time statistics: roomId=%v, room.RenderFrameId=%v, elapsedInCalculation=%v ns, dynamicsDuration=%v ns, expected nanosPerFrame=%v", pR.Id, pR.RenderFrameId, elapsedInCalculation, dynamicsDuration, nanosPerFrame))
|
||||
}
|
||||
time.Sleep(time.Duration(nanosPerFrame - elapsedInCalculation))
|
||||
}
|
||||
@ -611,7 +602,7 @@ func (pR *Room) toDiscreteInputsBufferIndex(inputFrameId int32, joinIndex int32)
|
||||
return (inputFrameId << 2) + joinIndex // allowing joinIndex upto 15
|
||||
}
|
||||
|
||||
func (pR *Room) OnBattleCmdReceived(pReq *pb.WsReq) {
|
||||
func (pR *Room) OnBattleCmdReceived(pReq *WsReq) {
|
||||
if swapped := atomic.CompareAndSwapInt32(&pR.State, RoomBattleStateIns.IN_BATTLE, RoomBattleStateIns.IN_BATTLE); !swapped {
|
||||
return
|
||||
}
|
||||
@ -649,7 +640,7 @@ func (pR *Room) OnBattleCmdReceived(pReq *pb.WsReq) {
|
||||
}
|
||||
}
|
||||
|
||||
func (pR *Room) onInputFrameDownsyncAllConfirmed(inputFrameDownsync *pb.InputFrameDownsync, playerId int32) {
|
||||
func (pR *Room) onInputFrameDownsyncAllConfirmed(inputFrameDownsync *InputFrameDownsync, playerId int32) {
|
||||
inputFrameId := inputFrameDownsync.InputFrameId
|
||||
if -1 == pR.LastAllConfirmedInputFrameIdWithChange || false == pR.equalInputLists(inputFrameDownsync.InputList, pR.LastAllConfirmedInputList) {
|
||||
if -1 == playerId {
|
||||
@ -691,7 +682,7 @@ func (pR *Room) StopBattleForSettlement() {
|
||||
Logger.Info("Stopping the `battleMainLoop` for:", zap.Any("roomId", pR.Id))
|
||||
pR.RenderFrameId++
|
||||
for playerId, _ := range pR.Players {
|
||||
assembledFrame := pb.RoomDownsyncFrame{
|
||||
assembledFrame := RoomDownsyncFrame{
|
||||
Id: pR.RenderFrameId,
|
||||
Players: toPbPlayers(pR.Players),
|
||||
CountdownNanos: -1, // TODO: Replace this magic constant!
|
||||
@ -717,18 +708,19 @@ func (pR *Room) onBattlePrepare(cb BattleStartCbType) {
|
||||
pR.State = RoomBattleStateIns.PREPARE
|
||||
Logger.Info("Battle state transitted to RoomBattleStateIns.PREPARE for:", zap.Any("roomId", pR.Id))
|
||||
|
||||
playerMetas := make(map[int32]*pb.PlayerMeta, 0)
|
||||
playerMetas := make(map[int32]*PlayerDownsyncMeta, 0)
|
||||
for _, player := range pR.Players {
|
||||
playerMetas[player.Id] = &pb.PlayerMeta{
|
||||
playerMetas[player.Id] = &PlayerDownsyncMeta{
|
||||
Id: player.Id,
|
||||
Name: player.Name,
|
||||
DisplayName: player.DisplayName,
|
||||
Avatar: player.Avatar,
|
||||
ColliderRadius: player.ColliderRadius, // hardcoded for now
|
||||
JoinIndex: player.JoinIndex,
|
||||
}
|
||||
}
|
||||
|
||||
battleReadyToStartFrame := &pb.RoomDownsyncFrame{
|
||||
battleReadyToStartFrame := &RoomDownsyncFrame{
|
||||
Id: DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START,
|
||||
Players: toPbPlayers(pR.Players),
|
||||
PlayerMetas: playerMetas,
|
||||
@ -798,7 +790,9 @@ func (pR *Room) Dismiss() {
|
||||
func (pR *Room) OnDismissed() {
|
||||
|
||||
// Always instantiates new HeapRAM blocks and let the old blocks die out due to not being retained by any root reference.
|
||||
pR.playerColliderRadius = float64(12) // hardcoded
|
||||
pR.WorldToVirtualGridRatio = float64(1000)
|
||||
pR.VirtualGridToWorldRatio = float64(1.0) / pR.WorldToVirtualGridRatio // this is a one-off computation, should avoid division in iterations
|
||||
pR.PlayerDefaultSpeed = int32(float64(2) * pR.WorldToVirtualGridRatio) // in virtual grids per frame
|
||||
pR.Players = make(map[int32]*Player)
|
||||
pR.PlayersArr = make([]*Player, pR.Capacity)
|
||||
pR.CollisionSysMap = make(map[int32]*resolv.Object)
|
||||
@ -820,13 +814,12 @@ func (pR *Room) OnDismissed() {
|
||||
pR.NstDelayFrames = 8
|
||||
pR.InputScaleFrames = uint32(2)
|
||||
pR.ServerFps = 60
|
||||
pR.RollbackEstimatedDt = 0.016667 // Use fixed-and-low-precision to mitigate the inconsistent floating-point-number issue between Golang and JavaScript
|
||||
pR.RollbackEstimatedDtMillis = 16.667 // Use fixed-and-low-precision to mitigate the inconsistent floating-point-number issue between Golang and JavaScript
|
||||
pR.RollbackEstimatedDtNanos = 16666666 // A little smaller than the actual per frame time, just for preventing FAST FRAME
|
||||
pR.BattleDurationFrames = 30 * pR.ServerFps
|
||||
pR.BattleDurationNanos = int64(pR.BattleDurationFrames) * (pR.RollbackEstimatedDtNanos + 1)
|
||||
pR.InputFrameUpsyncDelayTolerance = 2
|
||||
pR.MaxChasingRenderFramesPerUpdate = 10
|
||||
pR.MaxChasingRenderFramesPerUpdate = 5
|
||||
|
||||
pR.BackendDynamicsEnabled = true // [WARNING] When "false", recovery upon reconnection wouldn't work!
|
||||
|
||||
@ -939,16 +932,15 @@ func (pR *Room) onPlayerAdded(playerId int32) {
|
||||
|
||||
// Lazily assign the initial position of "Player" for "RoomDownsyncFrame".
|
||||
playerPosList := *(pR.RawBattleStrToVec2DListMap["PlayerStartingPos"])
|
||||
if index > len(playerPosList) {
|
||||
if index > len(playerPosList.Eles) {
|
||||
panic(fmt.Sprintf("onPlayerAdded error, index >= len(playerPosList), roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v", pR.Id, playerId, pR.State, pR.EffectivePlayerCount))
|
||||
}
|
||||
playerPos := playerPosList[index]
|
||||
playerPos := playerPosList.Eles[index]
|
||||
|
||||
if nil == playerPos {
|
||||
panic(fmt.Sprintf("onPlayerAdded error, nil == playerPos, roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v", pR.Id, playerId, pR.State, pR.EffectivePlayerCount))
|
||||
}
|
||||
pR.Players[playerId].X = playerPos.X
|
||||
pR.Players[playerId].Y = playerPos.Y
|
||||
pR.Players[playerId].VirtualGridX, pR.Players[playerId].VirtualGridY = WorldToVirtualGridPos(playerPos.X, playerPos.Y, pR.WorldToVirtualGridRatio)
|
||||
|
||||
break
|
||||
}
|
||||
@ -976,14 +968,15 @@ func (pR *Room) OnPlayerBattleColliderAcked(playerId int32) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
playerMetas := make(map[int32]*pb.PlayerMeta, 0)
|
||||
playerMetas := make(map[int32]*PlayerDownsyncMeta, 0)
|
||||
for _, eachPlayer := range pR.Players {
|
||||
playerMetas[eachPlayer.Id] = &pb.PlayerMeta{
|
||||
playerMetas[eachPlayer.Id] = &PlayerDownsyncMeta{
|
||||
Id: eachPlayer.Id,
|
||||
Name: eachPlayer.Name,
|
||||
DisplayName: eachPlayer.DisplayName,
|
||||
Avatar: eachPlayer.Avatar,
|
||||
JoinIndex: eachPlayer.JoinIndex,
|
||||
ColliderRadius: eachPlayer.ColliderRadius,
|
||||
}
|
||||
}
|
||||
|
||||
@ -999,14 +992,14 @@ func (pR *Room) OnPlayerBattleColliderAcked(playerId int32) bool {
|
||||
*/
|
||||
switch targetPlayer.BattleState {
|
||||
case PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK:
|
||||
playerAckedFrame := &pb.RoomDownsyncFrame{
|
||||
playerAckedFrame := &RoomDownsyncFrame{
|
||||
Id: pR.RenderFrameId,
|
||||
Players: toPbPlayers(pR.Players),
|
||||
PlayerMetas: playerMetas,
|
||||
}
|
||||
pR.sendSafely(playerAckedFrame, nil, DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED, eachPlayer.Id)
|
||||
case PlayerBattleStateIns.READDED_PENDING_BATTLE_COLLIDER_ACK:
|
||||
playerAckedFrame := &pb.RoomDownsyncFrame{
|
||||
playerAckedFrame := &RoomDownsyncFrame{
|
||||
Id: pR.RenderFrameId,
|
||||
Players: toPbPlayers(pR.Players),
|
||||
PlayerMetas: playerMetas,
|
||||
@ -1037,14 +1030,14 @@ func (pR *Room) OnPlayerBattleColliderAcked(playerId int32) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (pR *Room) sendSafely(roomDownsyncFrame *pb.RoomDownsyncFrame, toSendFrames []*pb.InputFrameDownsync, act int32, playerId int32) {
|
||||
func (pR *Room) sendSafely(roomDownsyncFrame *RoomDownsyncFrame, toSendFrames []*InputFrameDownsync, act int32, playerId int32) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
pR.PlayerSignalToCloseDict[playerId](Constants.RetCode.UnknownError, fmt.Sprintf("%v", r))
|
||||
}
|
||||
}()
|
||||
|
||||
pResp := &pb.WsResp{
|
||||
pResp := &WsResp{
|
||||
Ret: int32(Constants.RetCode.Ok),
|
||||
Act: act,
|
||||
Rdf: roomDownsyncFrame,
|
||||
@ -1065,16 +1058,16 @@ func (pR *Room) shouldPrefabInputFrameDownsync(renderFrameId int32) bool {
|
||||
return ((renderFrameId & ((1 << pR.InputScaleFrames) - 1)) == 0)
|
||||
}
|
||||
|
||||
func (pR *Room) prefabInputFrameDownsync(inputFrameId int32) *pb.InputFrameDownsync {
|
||||
func (pR *Room) prefabInputFrameDownsync(inputFrameId int32) *InputFrameDownsync {
|
||||
/*
|
||||
Kindly note that on backend the prefab is much simpler than its frontend counterpart, because frontend will upsync its latest command immediately if there's any change w.r.t. its own prev cmd, thus if no upsync received from a frontend,
|
||||
- EITHER it's due to local lag and bad network,
|
||||
- OR there's no change w.r.t. to its prev cmd.
|
||||
*/
|
||||
var currInputFrameDownsync *pb.InputFrameDownsync = nil
|
||||
var currInputFrameDownsync *InputFrameDownsync = nil
|
||||
|
||||
if 0 == inputFrameId && 0 == pR.InputsBuffer.Cnt {
|
||||
currInputFrameDownsync = &pb.InputFrameDownsync{
|
||||
currInputFrameDownsync = &InputFrameDownsync{
|
||||
InputFrameId: 0,
|
||||
InputList: make([]uint64, pR.Capacity),
|
||||
ConfirmedList: uint64(0),
|
||||
@ -1084,9 +1077,9 @@ func (pR *Room) prefabInputFrameDownsync(inputFrameId int32) *pb.InputFrameDowns
|
||||
if nil == tmp {
|
||||
panic(fmt.Sprintf("Error prefabbing inputFrameDownsync: roomId=%v, InputsBuffer=%v", pR.Id, pR.InputsBufferString(false)))
|
||||
}
|
||||
prevInputFrameDownsync := tmp.(*pb.InputFrameDownsync)
|
||||
prevInputFrameDownsync := tmp.(*InputFrameDownsync)
|
||||
currInputList := prevInputFrameDownsync.InputList // Would be a clone of the values
|
||||
currInputFrameDownsync = &pb.InputFrameDownsync{
|
||||
currInputFrameDownsync = &InputFrameDownsync{
|
||||
InputFrameId: inputFrameId,
|
||||
InputList: currInputList,
|
||||
ConfirmedList: uint64(0),
|
||||
@ -1112,20 +1105,19 @@ func (pR *Room) markConfirmationIfApplicable() {
|
||||
if nil == tmp {
|
||||
panic(fmt.Sprintf("inputFrameId=%v doesn't exist for roomId=%v, this is abnormal because the server should prefab inputFrameDownsync in a most advanced pace, check the prefab logic! InputsBuffer=%v", inputFrameId, pR.Id, pR.InputsBufferString(false)))
|
||||
}
|
||||
inputFrameDownsync := tmp.(*pb.InputFrameDownsync)
|
||||
inputFrameDownsync := tmp.(*InputFrameDownsync)
|
||||
for _, player := range pR.Players {
|
||||
bufIndex := pR.toDiscreteInputsBufferIndex(inputFrameId, player.JoinIndex)
|
||||
tmp, loaded := pR.DiscreteInputsBuffer.LoadAndDelete(bufIndex) // It's safe to "LoadAndDelete" here because the "inputFrameUpsync" of this player is already remembered by the corresponding "inputFrameDown".
|
||||
if !loaded {
|
||||
continue
|
||||
}
|
||||
inputFrameUpsync := tmp.(*pb.InputFrameUpsync)
|
||||
inputFrameUpsync := tmp.(*InputFrameUpsync)
|
||||
indiceInJoinIndexBooleanArr := uint32(player.JoinIndex - 1)
|
||||
inputFrameDownsync.InputList[indiceInJoinIndexBooleanArr] = pR.EncodeUpsyncCmd(inputFrameUpsync)
|
||||
inputFrameDownsync.ConfirmedList |= (1 << indiceInJoinIndexBooleanArr)
|
||||
}
|
||||
|
||||
// Force confirmation of "inputFrame2"
|
||||
if allConfirmedMask == inputFrameDownsync.ConfirmedList {
|
||||
pR.onInputFrameDownsyncAllConfirmed(inputFrameDownsync, -1)
|
||||
} else {
|
||||
@ -1156,24 +1148,12 @@ func (pR *Room) forceConfirmationIfApplicable() uint64 {
|
||||
if nil == tmp {
|
||||
panic(fmt.Sprintf("inputFrameId2=%v doesn't exist for roomId=%v, this is abnormal because the server should prefab inputFrameDownsync in a most advanced pace, check the prefab logic! InputsBuffer=%v", inputFrameId2, pR.Id, pR.InputsBufferString(false)))
|
||||
}
|
||||
inputFrame2 := tmp.(*pb.InputFrameDownsync)
|
||||
for _, player := range pR.Players {
|
||||
// Enrich by already arrived player upsync commands
|
||||
bufIndex := pR.toDiscreteInputsBufferIndex(inputFrame2.InputFrameId, player.JoinIndex)
|
||||
tmp, loaded := pR.DiscreteInputsBuffer.LoadAndDelete(bufIndex)
|
||||
if !loaded {
|
||||
continue
|
||||
}
|
||||
inputFrameUpsync := tmp.(*pb.InputFrameUpsync)
|
||||
indiceInJoinIndexBooleanArr := uint32(player.JoinIndex - 1)
|
||||
inputFrame2.InputList[indiceInJoinIndexBooleanArr] = pR.EncodeUpsyncCmd(inputFrameUpsync)
|
||||
inputFrame2.ConfirmedList |= (1 << indiceInJoinIndexBooleanArr)
|
||||
}
|
||||
|
||||
totPlayerCnt := uint32(pR.Capacity)
|
||||
allConfirmedMask := uint64((1 << totPlayerCnt) - 1)
|
||||
|
||||
// Force confirmation of "inputFrame2"
|
||||
inputFrame2 := tmp.(*InputFrameDownsync)
|
||||
oldConfirmedList := inputFrame2.ConfirmedList
|
||||
unconfirmedMask := (oldConfirmedList ^ allConfirmedMask)
|
||||
inputFrame2.ConfirmedList = allConfirmedMask
|
||||
@ -1192,7 +1172,13 @@ func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRende
|
||||
allConfirmedMask := uint64((1 << totPlayerCnt) - 1)
|
||||
|
||||
for collisionSysRenderFrameId := fromRenderFrameId; collisionSysRenderFrameId < toRenderFrameId; collisionSysRenderFrameId++ {
|
||||
currRenderFrameTmp := pR.RenderFrameBuffer.GetByFrameId(collisionSysRenderFrameId)
|
||||
if nil == currRenderFrameTmp {
|
||||
panic(fmt.Sprintf("collisionSysRenderFrameId=%v doesn't exist for roomId=%v, this is abnormal because it's to be used for applying dynamics to [fromRenderFrameId:%v, toRenderFrameId:%v)! RenderFrameBuffer=%v", collisionSysRenderFrameId, pR.Id, fromRenderFrameId, toRenderFrameId, pR.RenderFrameBufferString()))
|
||||
}
|
||||
currRenderFrame := currRenderFrameTmp.(*RoomDownsyncFrame)
|
||||
delayedInputFrameId := pR.ConvertToInputFrameId(collisionSysRenderFrameId, pR.InputDelayFrames)
|
||||
var delayedInputFrame *InputFrameDownsync = nil
|
||||
if 0 <= delayedInputFrameId {
|
||||
if delayedInputFrameId > pR.LastAllConfirmedInputFrameId {
|
||||
panic(fmt.Sprintf("delayedInputFrameId=%v is not yet all-confirmed for roomId=%v, this is abnormal because it's to be used for applying dynamics to [fromRenderFrameId:%v, toRenderFrameId:%v) @ collisionSysRenderFrameId=%v! InputsBuffer=%v", delayedInputFrameId, pR.Id, fromRenderFrameId, toRenderFrameId, collisionSysRenderFrameId, pR.InputsBufferString(false)))
|
||||
@ -1201,73 +1187,126 @@ func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRende
|
||||
if nil == tmp {
|
||||
panic(fmt.Sprintf("delayedInputFrameId=%v doesn't exist for roomId=%v, this is abnormal because it's to be used for applying dynamics to [fromRenderFrameId:%v, toRenderFrameId:%v) @ collisionSysRenderFrameId=%v! InputsBuffer=%v", delayedInputFrameId, pR.Id, fromRenderFrameId, toRenderFrameId, collisionSysRenderFrameId, pR.InputsBufferString(false)))
|
||||
}
|
||||
delayedInputFrame := tmp.(*pb.InputFrameDownsync)
|
||||
delayedInputFrame = tmp.(*InputFrameDownsync)
|
||||
// [WARNING] It's possible that by now "allConfirmedMask != delayedInputFrame.ConfirmedList && delayedInputFrameId <= pR.LastAllConfirmedInputFrameId", we trust "pR.LastAllConfirmedInputFrameId" as the TOP AUTHORITY.
|
||||
atomic.StoreUint64(&(delayedInputFrame.ConfirmedList), allConfirmedMask)
|
||||
}
|
||||
|
||||
nextRenderFrame := pR.applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, currRenderFrame, pR.CollisionSysMap)
|
||||
// Update in the latest player pointers
|
||||
for playerId, playerDownsync := range nextRenderFrame.Players {
|
||||
pR.Players[playerId].VirtualGridX = playerDownsync.VirtualGridX
|
||||
pR.Players[playerId].VirtualGridY = playerDownsync.VirtualGridY
|
||||
pR.Players[playerId].Dir.Dx = playerDownsync.Dir.Dx
|
||||
pR.Players[playerId].Dir.Dy = playerDownsync.Dir.Dy
|
||||
}
|
||||
pR.RenderFrameBuffer.Put(nextRenderFrame)
|
||||
pR.CurDynamicsRenderFrameId++
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Write unit-test for this function to compare with its frontend counter part
|
||||
func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame *InputFrameDownsync, currRenderFrame *RoomDownsyncFrame, collisionSysMap map[int32]*resolv.Object) *RoomDownsyncFrame {
|
||||
nextRenderFramePlayers := make(map[int32]*PlayerDownsync, pR.Capacity)
|
||||
// Make a copy first
|
||||
for playerId, currPlayerDownsync := range currRenderFrame.Players {
|
||||
nextRenderFramePlayers[playerId] = &PlayerDownsync{
|
||||
Id: playerId,
|
||||
VirtualGridX: currPlayerDownsync.VirtualGridX,
|
||||
VirtualGridY: currPlayerDownsync.VirtualGridY,
|
||||
Dir: &Direction{
|
||||
Dx: currPlayerDownsync.Dir.Dx,
|
||||
Dy: currPlayerDownsync.Dir.Dy,
|
||||
},
|
||||
Speed: currPlayerDownsync.Speed,
|
||||
BattleState: currPlayerDownsync.BattleState,
|
||||
Score: currPlayerDownsync.Score,
|
||||
Removed: currPlayerDownsync.Removed,
|
||||
JoinIndex: currPlayerDownsync.JoinIndex,
|
||||
}
|
||||
}
|
||||
|
||||
toRet := &RoomDownsyncFrame{
|
||||
Id: currRenderFrame.Id + 1,
|
||||
Players: nextRenderFramePlayers,
|
||||
CountdownNanos: (pR.BattleDurationNanos - int64(currRenderFrame.Id)*pR.RollbackEstimatedDtNanos),
|
||||
}
|
||||
|
||||
if nil != delayedInputFrame {
|
||||
inputList := delayedInputFrame.InputList
|
||||
// Ordered by joinIndex to guarantee determinism
|
||||
for _, player := range pR.PlayersArr {
|
||||
effPushbacks := make([]Vec2D, pR.Capacity) // Guaranteed determinism regardless of traversal order
|
||||
for playerId, player := range pR.Players {
|
||||
joinIndex := player.JoinIndex
|
||||
effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y = float64(0), float64(0)
|
||||
currPlayerDownsync := currRenderFrame.Players[playerId]
|
||||
encodedInput := inputList[joinIndex-1]
|
||||
decodedInput := DIRECTION_DECODER[encodedInput]
|
||||
decodedInputSpeedFactor := DIRECTION_DECODER_INVERSE_LENGTH[encodedInput]
|
||||
if 0.0 == decodedInputSpeedFactor {
|
||||
continue
|
||||
}
|
||||
baseChange := player.Speed * pR.RollbackEstimatedDt * decodedInputSpeedFactor
|
||||
oldDx, oldDy := baseChange*float64(decodedInput[0]), baseChange*float64(decodedInput[1])
|
||||
dx, dy := oldDx, oldDy
|
||||
|
||||
proposedVirtualGridDx, proposedVirtualGridDy := (decodedInput[0] + decodedInput[0]*currPlayerDownsync.Speed), (decodedInput[1] + decodedInput[1]*currPlayerDownsync.Speed)
|
||||
newVx, newVy := (currPlayerDownsync.VirtualGridX + proposedVirtualGridDx), (currPlayerDownsync.VirtualGridY + proposedVirtualGridDy)
|
||||
// Reset playerCollider position from the "virtual grid position"
|
||||
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
|
||||
playerCollider := pR.CollisionSysMap[collisionPlayerIndex]
|
||||
if collision := playerCollider.Check(oldDx, oldDy, "Barrier"); collision != nil {
|
||||
playerCollider := collisionSysMap[collisionPlayerIndex]
|
||||
playerCollider.X, playerCollider.Y = VirtualGridToPolygonColliderAnchorPos(newVx, newVy, player.ColliderRadius, player.ColliderRadius, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, pR.VirtualGridToWorldRatio)
|
||||
|
||||
// Update in the collision system
|
||||
playerCollider.Update()
|
||||
|
||||
if 0 < encodedInput {
|
||||
Logger.Debug(fmt.Sprintf("Checking collision for playerId=%v: virtual (%d, %d) -> (%d, %d), now playerShape=%v", playerId, currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY, newVx, newVy, ConvexPolygonStr(playerCollider.Shape.(*resolv.ConvexPolygon))))
|
||||
nextRenderFramePlayers[playerId].Dir.Dx = decodedInput[0]
|
||||
nextRenderFramePlayers[playerId].Dir.Dy = decodedInput[1]
|
||||
}
|
||||
}
|
||||
|
||||
// handle pushbacks upon collision after all movements treated as simultaneous
|
||||
for _, player := range pR.Players {
|
||||
joinIndex := player.JoinIndex
|
||||
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
|
||||
playerCollider := collisionSysMap[collisionPlayerIndex]
|
||||
if collision := playerCollider.Check(0, 0); collision != nil {
|
||||
playerShape := playerCollider.Shape.(*resolv.ConvexPolygon)
|
||||
for _, obj := range collision.Objects {
|
||||
barrierShape := obj.Shape.(*resolv.ConvexPolygon)
|
||||
if overlapped, pushbackX, pushbackY := CalcPushbacks(oldDx, oldDy, playerShape, barrierShape); overlapped {
|
||||
Logger.Debug(fmt.Sprintf("Collided & overlapped: player.X=%v, player.Y=%v, oldDx=%v, oldDy=%v, playerShape=%v, toCheckBarrier=%v, pushbackX=%v, pushbackY=%v", playerCollider.X, playerCollider.Y, oldDx, oldDy, ConvexPolygonStr(playerShape), ConvexPolygonStr(barrierShape), pushbackX, pushbackY))
|
||||
dx -= pushbackX
|
||||
dy -= pushbackY
|
||||
if overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, playerShape, barrierShape); overlapped {
|
||||
Logger.Debug(fmt.Sprintf("Overlapped: a=%v, b=%v, pushbackX=%v, pushbackY=%v", ConvexPolygonStr(playerShape), ConvexPolygonStr(barrierShape), pushbackX, pushbackY))
|
||||
effPushbacks[joinIndex-1].X += pushbackX
|
||||
effPushbacks[joinIndex-1].Y += pushbackY
|
||||
} else {
|
||||
Logger.Debug(fmt.Sprintf("Collided BUT not overlapped: player.X=%v, player.Y=%v, oldDx=%v, oldDy=%v, playerShape=%v, toCheckBarrier=%v", playerCollider.X, playerCollider.Y, oldDx, oldDy, ConvexPolygonStr(playerShape), ConvexPolygonStr(barrierShape)))
|
||||
Logger.Debug(fmt.Sprintf("Collided BUT not overlapped: a=%v, b=%v, overlapResult=%v", ConvexPolygonStr(playerShape), ConvexPolygonStr(barrierShape), overlapResult))
|
||||
}
|
||||
}
|
||||
}
|
||||
playerCollider.X += dx
|
||||
playerCollider.Y += dy
|
||||
|
||||
// Update in "collision space"
|
||||
playerCollider.Update()
|
||||
|
||||
player.Dir.Dx = decodedInput[0]
|
||||
player.Dir.Dy = decodedInput[1]
|
||||
player.X = playerCollider.X + pR.playerColliderRadius - spaceOffsetX
|
||||
player.Y = playerCollider.Y + pR.playerColliderRadius - spaceOffsetY
|
||||
}
|
||||
}
|
||||
|
||||
newRenderFrame := pb.RoomDownsyncFrame{
|
||||
Id: collisionSysRenderFrameId + 1,
|
||||
Players: toPbPlayers(pR.Players),
|
||||
CountdownNanos: (pR.BattleDurationNanos - int64(collisionSysRenderFrameId)*pR.RollbackEstimatedDtNanos),
|
||||
for playerId, player := range pR.Players {
|
||||
joinIndex := player.JoinIndex
|
||||
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
|
||||
playerCollider := collisionSysMap[collisionPlayerIndex]
|
||||
|
||||
// Update "virtual grid position"
|
||||
newVx, newVy := PolygonColliderAnchorToVirtualGridPos(playerCollider.X-effPushbacks[joinIndex-1].X, playerCollider.Y-effPushbacks[joinIndex-1].Y, player.ColliderRadius, player.ColliderRadius, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, pR.WorldToVirtualGridRatio)
|
||||
nextRenderFramePlayers[playerId].VirtualGridX = newVx
|
||||
nextRenderFramePlayers[playerId].VirtualGridY = newVy
|
||||
}
|
||||
pR.RenderFrameBuffer.Put(&newRenderFrame)
|
||||
pR.CurDynamicsRenderFrameId++
|
||||
|
||||
Logger.Debug(fmt.Sprintf("After applyInputFrameDownsyncDynamicsOnSingleRenderFrame: currRenderFrame.Id=%v, inputList=%v, currRenderFrame.Players=%v, nextRenderFramePlayers=%v, toRet.Players=%v", currRenderFrame.Id, inputList, currRenderFrame.Players, nextRenderFramePlayers, toRet.Players))
|
||||
}
|
||||
|
||||
return toRet
|
||||
}
|
||||
|
||||
func (pR *Room) inputFrameIdDebuggable(inputFrameId int32) bool {
|
||||
return 0 == (inputFrameId % 10)
|
||||
}
|
||||
|
||||
func (pR *Room) refreshColliders(spaceW, spaceH int32, spaceOffsetX, spaceOffsetY float64) {
|
||||
func (pR *Room) refreshColliders(spaceW, spaceH int32) {
|
||||
// Kindly note that by now, we've already got all the shapes in the tmx file into "pR.(Players | Barriers)" from "ParseTmxLayersAndGroups"
|
||||
|
||||
minStep := int(3) // the approx minimum distance a player can move per frame
|
||||
minStep := (int(float64(pR.PlayerDefaultSpeed)*pR.VirtualGridToWorldRatio) << 2) // the approx minimum distance a player can move per frame in world coordinate
|
||||
space := resolv.NewSpace(int(spaceW), int(spaceH), minStep, minStep) // allocate a new collision space everytime after a battle is settled
|
||||
for _, player := range pR.Players {
|
||||
playerCollider := GenerateRectCollider(player.X, player.Y, pR.playerColliderRadius*2, pR.playerColliderRadius*2, spaceOffsetX, spaceOffsetY, "Player")
|
||||
wx, wy := VirtualGridToWorldPos(player.VirtualGridX, player.VirtualGridY, pR.VirtualGridToWorldRatio)
|
||||
playerCollider := GenerateRectCollider(wx, wy, player.ColliderRadius*2, player.ColliderRadius*2, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "Player")
|
||||
space.Add(playerCollider)
|
||||
// Keep track of the collider in "pR.CollisionSysMap"
|
||||
joinIndex := player.JoinIndex
|
||||
@ -1278,7 +1317,7 @@ func (pR *Room) refreshColliders(spaceW, spaceH int32, spaceOffsetX, spaceOffset
|
||||
|
||||
for _, barrier := range pR.Barriers {
|
||||
boundaryUnaligned := barrier.Boundary
|
||||
barrierCollider := GenerateConvexPolygonCollider(boundaryUnaligned, spaceOffsetX, spaceOffsetY, "Barrier")
|
||||
barrierCollider := GenerateConvexPolygonCollider(boundaryUnaligned, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "Barrier")
|
||||
space.Add(barrierCollider)
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
1264
battle_srv/protos/room_downsync_frame.pb.go
Normal file
1264
battle_srv/protos/room_downsync_frame.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
. "server/common"
|
||||
. "battle_srv/common"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
@ -1,8 +1,8 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
. "battle_srv/common"
|
||||
"fmt"
|
||||
. "server/common"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
|
@ -1,6 +1,9 @@
|
||||
package ws
|
||||
|
||||
import (
|
||||
. "battle_srv/common"
|
||||
"battle_srv/models"
|
||||
pb "battle_srv/protos"
|
||||
"container/heap"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
@ -8,14 +11,12 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
"go.uber.org/zap"
|
||||
"net/http"
|
||||
. "server/common"
|
||||
"server/models"
|
||||
pb "server/pb_output"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
. "dnmshared"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -104,7 +105,7 @@ func Serve(c *gin.Context) {
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
Logger.Warn("Recovered from: ", zap.Any("panic", r))
|
||||
Logger.Error("Recovered from: ", zap.Any("panic", r))
|
||||
}
|
||||
}()
|
||||
/**
|
||||
@ -246,8 +247,8 @@ func Serve(c *gin.Context) {
|
||||
bciFrame := &pb.BattleColliderInfo{
|
||||
BoundRoomId: pRoom.Id,
|
||||
StageName: pRoom.StageName,
|
||||
StrToVec2DListMap: models.ToPbVec2DListMap(pRoom.RawBattleStrToVec2DListMap),
|
||||
StrToPolygon2DListMap: models.ToPbPolygon2DListMap(pRoom.RawBattleStrToPolygon2DListMap),
|
||||
StrToVec2DListMap: pRoom.RawBattleStrToVec2DListMap,
|
||||
StrToPolygon2DListMap: pRoom.RawBattleStrToPolygon2DListMap,
|
||||
StageDiscreteW: pRoom.StageDiscreteW,
|
||||
StageDiscreteH: pRoom.StageDiscreteH,
|
||||
StageTileW: pRoom.StageTileW,
|
||||
@ -263,9 +264,11 @@ func Serve(c *gin.Context) {
|
||||
InputFrameUpsyncDelayTolerance: pRoom.InputFrameUpsyncDelayTolerance,
|
||||
MaxChasingRenderFramesPerUpdate: pRoom.MaxChasingRenderFramesPerUpdate,
|
||||
PlayerBattleState: pThePlayer.BattleState, // For frontend to know whether it's rejoining
|
||||
RollbackEstimatedDt: pRoom.RollbackEstimatedDt,
|
||||
RollbackEstimatedDtMillis: pRoom.RollbackEstimatedDtMillis,
|
||||
RollbackEstimatedDtNanos: pRoom.RollbackEstimatedDtNanos,
|
||||
|
||||
WorldToVirtualGridRatio: pRoom.WorldToVirtualGridRatio,
|
||||
VirtualGridToWorldRatio: pRoom.VirtualGridToWorldRatio,
|
||||
}
|
||||
|
||||
resp := &pb.WsResp{
|
||||
@ -354,7 +357,7 @@ func Serve(c *gin.Context) {
|
||||
receivingLoopAgainstPlayer := func() error {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
Logger.Warn("Goroutine `receivingLoopAgainstPlayer`, recovery spot#1, recovered from: ", zap.Any("panic", r))
|
||||
Logger.Error("Goroutine `receivingLoopAgainstPlayer`, recovery spot#1, recovered from: ", zap.Any("panic", r), zap.Any("callstack", debug.Stack()))
|
||||
}
|
||||
Logger.Info("Goroutine `receivingLoopAgainstPlayer` is stopped for:", zap.Any("playerId", playerId), zap.Any("roomId", pRoom.Id))
|
||||
}()
|
||||
|
BIN
charts/AvoidingFloatingPointAccumulationErr.jpg
Normal file
BIN
charts/AvoidingFloatingPointAccumulationErr.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 144 KiB |
File diff suppressed because one or more lines are too long
@ -3,11 +3,11 @@ module viscol
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
dnmshared v0.0.0
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
github.com/hajimehoshi/ebiten/v2 v2.4.7
|
||||
github.com/solarlune/resolv v0.5.1
|
||||
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69
|
||||
dnmshared v0.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
@ -22,6 +22,7 @@ require (
|
||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect
|
||||
golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105 // indirect
|
||||
golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
)
|
||||
|
||||
replace dnmshared => ../dnmshared
|
||||
|
@ -7,6 +7,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220806181222-55e207c401ad h1:kX51IjbsJP
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220806181222-55e207c401ad/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/hajimehoshi/bitmapfont/v2 v2.2.1 h1:y7zcy02/UgO24IL3COqYtrRZzhRucNBtmCo/SNU648k=
|
||||
github.com/hajimehoshi/bitmapfont/v2 v2.2.1/go.mod h1:wjrYAy8vKgj9JsFgnYAOK346/uvE22TlmqouzdnYIs0=
|
||||
github.com/hajimehoshi/ebiten/v2 v2.4.7 h1:XuvB7R0Rbw/O7g6vNU8gqr5b9e7MNhhAONMSsyreLDI=
|
||||
@ -93,4 +95,8 @@ golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
|
@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
. "dnmshared"
|
||||
. "dnmshared/sharedprotos"
|
||||
"fmt"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/solarlune/resolv"
|
||||
@ -19,7 +20,7 @@ func (world *WorldColliderDisplay) Init() {
|
||||
|
||||
func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTileW, stageTileH int32, playerPosMap StrToVec2DListMap, barrierMap StrToPolygon2DListMap) *WorldColliderDisplay {
|
||||
|
||||
playerList := *(playerPosMap["PlayerStartingPos"])
|
||||
playerPosList := *(playerPosMap["PlayerStartingPos"])
|
||||
barrierList := *(barrierMap["Barrier"])
|
||||
|
||||
world := &WorldColliderDisplay{Game: game}
|
||||
@ -32,20 +33,23 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi
|
||||
spaceOffsetX := float64(spaceW) * 0.5
|
||||
spaceOffsetY := float64(spaceH) * 0.5
|
||||
|
||||
playerColliderRadius := float64(32)
|
||||
playerColliders := make([]*resolv.Object, len(playerList))
|
||||
space := resolv.NewSpace(int(spaceW), int(spaceH), 16, 16)
|
||||
for i, player := range playerList {
|
||||
playerCollider := GenerateRectCollider(player.X, player.Y, playerColliderRadius*2, playerColliderRadius*2, spaceOffsetX, spaceOffsetY, "Player") // [WARNING] Deliberately not using a circle because "resolv v0.5.1" doesn't yet align circle center with space cell center, regardless of the "specified within-object offset"
|
||||
Logger.Info(fmt.Sprintf("Player Collider#%d: player.X=%v, player.Y=%v, radius=%v, spaceOffsetX=%v, spaceOffsetY=%v, shape=%v; calibrationCheckX=player.X-radius+spaceOffsetX=%v", i, player.X, player.Y, playerColliderRadius, spaceOffsetX, spaceOffsetY, playerCollider.Shape, player.X-playerColliderRadius+spaceOffsetX))
|
||||
virtualGridToWorldRatio := 0.1
|
||||
playerDefaultSpeed := 20
|
||||
minStep := (int(float64(playerDefaultSpeed)*virtualGridToWorldRatio) << 2)
|
||||
playerColliderRadius := float64(24)
|
||||
playerColliders := make([]*resolv.Object, len(playerPosList.Eles))
|
||||
space := resolv.NewSpace(int(spaceW), int(spaceH), minStep, minStep)
|
||||
for i, playerPos := range playerPosList.Eles {
|
||||
playerCollider := GenerateRectCollider(playerPos.X, playerPos.Y, playerColliderRadius*2, playerColliderRadius*2, spaceOffsetX, spaceOffsetY, "Player") // [WARNING] Deliberately not using a circle because "resolv v0.5.1" doesn't yet align circle center with space cell center, regardless of the "specified within-object offset"
|
||||
Logger.Info(fmt.Sprintf("Player Collider#%d: player world pos =(%.2f, %.2f), shape=%v", i, playerPos.X, playerPos.Y, ConvexPolygonStr(playerCollider.Shape.(*resolv.ConvexPolygon))))
|
||||
playerColliders[i] = playerCollider
|
||||
space.Add(playerCollider)
|
||||
}
|
||||
|
||||
barrierLocalId := 0
|
||||
for _, barrierUnaligned := range barrierList {
|
||||
for _, barrierUnaligned := range barrierList.Eles {
|
||||
barrierCollider := GenerateConvexPolygonCollider(barrierUnaligned, spaceOffsetX, spaceOffsetY, "Barrier")
|
||||
Logger.Info(fmt.Sprintf("Added barrier: shape=%v", barrierCollider.Shape))
|
||||
Logger.Info(fmt.Sprintf("Added barrier: shape=%v", ConvexPolygonStr(barrierCollider.Shape.(*resolv.ConvexPolygon))))
|
||||
space.Add(barrierCollider)
|
||||
barrierLocalId++
|
||||
}
|
||||
@ -54,24 +58,31 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi
|
||||
|
||||
moveToCollide := true
|
||||
if moveToCollide {
|
||||
newVx, newVy := int32(-2959), int32(-2261)
|
||||
effPushback := Vec2D{X: float64(0), Y: float64(0)}
|
||||
toTestPlayerCollider := playerColliders[0]
|
||||
oldDx, oldDy := -2.98, -50.0
|
||||
dx, dy := oldDx, oldDy
|
||||
if collision := toTestPlayerCollider.Check(oldDx, oldDy, "Barrier"); collision != nil {
|
||||
playerShape := toTestPlayerCollider.Shape.(*resolv.ConvexPolygon)
|
||||
barrierShape := collision.Objects[0].Shape.(*resolv.ConvexPolygon)
|
||||
if overlapped, pushbackX, pushbackY := CalcPushbacks(oldDx, oldDy, playerShape, barrierShape); overlapped {
|
||||
Logger.Info(fmt.Sprintf("Collided & overlapped: player.X=%v, player.Y=%v, oldDx=%v, oldDy=%v, playerShape=%v, toCheckBarrier=%v, pushbackX=%v, pushbackY=%v", toTestPlayerCollider.X, toTestPlayerCollider.Y, oldDx, oldDy, ConvexPolygonStr(playerShape), ConvexPolygonStr(barrierShape), pushbackX, pushbackY))
|
||||
dx -= pushbackX
|
||||
dy -= pushbackY
|
||||
} else {
|
||||
Logger.Info(fmt.Sprintf("Collider BUT not overlapped: player.X=%v, player.Y=%v, oldDx=%v, oldDy=%v, playerShape=%v, toCheckBarrier=%v", toTestPlayerCollider.X, toTestPlayerCollider.Y, oldDx, oldDy, ConvexPolygonStr(playerShape), ConvexPolygonStr(barrierShape)))
|
||||
}
|
||||
}
|
||||
toTestPlayerCollider.X, toTestPlayerCollider.Y = VirtualGridToPolygonColliderAnchorPos(newVx, newVy, playerColliderRadius, playerColliderRadius, spaceOffsetX, spaceOffsetY, virtualGridToWorldRatio)
|
||||
|
||||
Logger.Info(fmt.Sprintf("Checking collision for virtual (%d, %d), now playerShape=%v", newVx, newVy, ConvexPolygonStr(toTestPlayerCollider.Shape.(*resolv.ConvexPolygon))))
|
||||
|
||||
toTestPlayerCollider.X += dx
|
||||
toTestPlayerCollider.Y += dy
|
||||
toTestPlayerCollider.Update()
|
||||
if collision := toTestPlayerCollider.Check(0, 0); collision != nil {
|
||||
playerShape := toTestPlayerCollider.Shape.(*resolv.ConvexPolygon)
|
||||
for _, obj := range collision.Objects {
|
||||
barrierShape := obj.Shape.(*resolv.ConvexPolygon)
|
||||
if overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, playerShape, barrierShape); overlapped {
|
||||
Logger.Warn(fmt.Sprintf("Overlapped: a=%v, b=%v, pushbackX=%v, pushbackY=%v", ConvexPolygonStr(playerShape), ConvexPolygonStr(barrierShape), pushbackX, pushbackY))
|
||||
effPushback.X += pushbackX
|
||||
effPushback.Y += pushbackY
|
||||
} else {
|
||||
Logger.Warn(fmt.Sprintf("Collided BUT not overlapped: a=%v, b=%v, overlapResult=%v", ConvexPolygonStr(playerShape), ConvexPolygonStr(barrierShape), overlapResult))
|
||||
}
|
||||
}
|
||||
toTestPlayerCollider.X -= effPushback.X
|
||||
toTestPlayerCollider.Y -= effPushback.Y
|
||||
toTestPlayerCollider.Update()
|
||||
Logger.Info(fmt.Sprintf("effPushback={%v, %v}", effPushback.X, effPushback.Y))
|
||||
}
|
||||
}
|
||||
|
||||
return world
|
||||
|
@ -1,47 +1,46 @@
|
||||
package dnmshared
|
||||
|
||||
import (
|
||||
. "dnmshared/sharedprotos"
|
||||
"math"
|
||||
)
|
||||
|
||||
// Use type `float64` for json unmarshalling of numbers.
|
||||
type Direction struct {
|
||||
Dx int32 `json:"dx,omitempty"`
|
||||
Dy int32 `json:"dy,omitempty"`
|
||||
}
|
||||
|
||||
type Vec2D struct {
|
||||
X float64 `json:"x,omitempty"`
|
||||
Y float64 `json:"y,omitempty"`
|
||||
}
|
||||
|
||||
func NormVec2D(dx, dy float64) Vec2D {
|
||||
return Vec2D{dy, -dx}
|
||||
return Vec2D{X: dy, Y: -dx}
|
||||
}
|
||||
|
||||
type Polygon2D struct {
|
||||
Anchor *Vec2D `json:"-"` // This "Polygon2D.Anchor" is used to be assigned to "B2BodyDef.Position", which in turn is used as the position of the FIRST POINT of the polygon.
|
||||
Points []*Vec2D `json:"-"`
|
||||
func AlignPolygon2DToBoundingBox(input *Polygon2D) *Polygon2D {
|
||||
// Transform again to put "anchor" at the top-left point of the bounding box for "resolv"
|
||||
boundingBoxTL := &Vec2D{
|
||||
X: math.MaxFloat64,
|
||||
Y: math.MaxFloat64,
|
||||
}
|
||||
for _, p := range input.Points {
|
||||
if p.X < boundingBoxTL.X {
|
||||
boundingBoxTL.X = p.X
|
||||
}
|
||||
if p.Y < boundingBoxTL.Y {
|
||||
boundingBoxTL.Y = p.Y
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
When used to represent a "polyline directly drawn in a `Tmx file`", we can initialize both "Anchor" and "Points" simultaneously.
|
||||
// Now "input.Anchor" should move to "input.Anchor+boundingBoxTL", thus "boundingBoxTL" is also the value of the negative diff for all "input.Points"
|
||||
output := &Polygon2D{
|
||||
Anchor: &Vec2D{
|
||||
X: input.Anchor.X + boundingBoxTL.X,
|
||||
Y: input.Anchor.Y + boundingBoxTL.Y,
|
||||
},
|
||||
Points: make([]*Vec2D, len(input.Points)),
|
||||
}
|
||||
|
||||
Yet when used to represent a "polyline drawn in a `Tsx file`", we have to first initialize "Points w.r.t. center of the tile-rectangle", and then "Anchor(initially nil) of the tile positioned in the `Tmx file`".
|
||||
for i, p := range input.Points {
|
||||
output.Points[i] = &Vec2D{
|
||||
X: p.X - boundingBoxTL.X,
|
||||
Y: p.Y - boundingBoxTL.Y,
|
||||
}
|
||||
}
|
||||
|
||||
Refer to https://shimo.im/docs/SmLJJhXm2C8XMzZT for more information.
|
||||
*/
|
||||
|
||||
/*
|
||||
[WARNING] Used to cache "`TileWidth & TileHeight` of a Tsx file" only.
|
||||
*/
|
||||
TileWidth int
|
||||
TileHeight int
|
||||
|
||||
/*
|
||||
[WARNING] Used to cache "`Width & TileHeight` of an object in Tmx file" only.
|
||||
*/
|
||||
TmxObjectWidth float64
|
||||
TmxObjectHeight float64
|
||||
return output
|
||||
}
|
||||
|
||||
func Distance(pt1 *Vec2D, pt2 *Vec2D) float64 {
|
||||
|
@ -1,3 +1,3 @@
|
||||
module tiled
|
||||
module dnmshared
|
||||
|
||||
go 1.19
|
||||
|
@ -1,6 +1,7 @@
|
||||
package dnmshared
|
||||
|
||||
import (
|
||||
. "dnmshared/sharedprotos"
|
||||
"fmt"
|
||||
"github.com/kvartborg/vector"
|
||||
"github.com/solarlune/resolv"
|
||||
@ -11,14 +12,15 @@ import (
|
||||
func ConvexPolygonStr(body *resolv.ConvexPolygon) string {
|
||||
var s []string = make([]string, len(body.Points))
|
||||
for i, p := range body.Points {
|
||||
s[i] = fmt.Sprintf("[%v, %v]", p[0]+body.X, p[1]+body.Y)
|
||||
s[i] = fmt.Sprintf("[%.2f, %.2f]", p[0]+body.X, p[1]+body.Y)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("[%s]", strings.Join(s, ", "))
|
||||
return fmt.Sprintf("{\n%s\n}", strings.Join(s, ",\n"))
|
||||
}
|
||||
|
||||
func GenerateRectCollider(origX, origY, w, h, spaceOffsetX, spaceOffsetY float64, tag string) *resolv.Object {
|
||||
collider := resolv.NewObject(origX-w*0.5+spaceOffsetX, origY-h*0.5+spaceOffsetY, w, h, tag)
|
||||
cx, cy := WorldToPolygonColliderAnchorPos(origX, origY, w*0.5, h*0.5, spaceOffsetX, spaceOffsetY)
|
||||
collider := resolv.NewObject(cx, cy, w, h, tag)
|
||||
shape := resolv.NewRectangle(0, 0, w, h)
|
||||
collider.SetShape(shape)
|
||||
return collider
|
||||
@ -54,7 +56,7 @@ func GenerateConvexPolygonCollider(unalignedSrc *Polygon2D, spaceOffsetX, spaceO
|
||||
return collider
|
||||
}
|
||||
|
||||
func CalcPushbacks(oldDx, oldDy float64, playerShape, barrierShape *resolv.ConvexPolygon) (bool, float64, float64) {
|
||||
func CalcPushbacks(oldDx, oldDy float64, playerShape, barrierShape *resolv.ConvexPolygon) (bool, float64, float64, *SatResult) {
|
||||
origX, origY := playerShape.Position()
|
||||
defer func() {
|
||||
playerShape.SetPosition(origX, origY)
|
||||
@ -70,9 +72,9 @@ func CalcPushbacks(oldDx, oldDy float64, playerShape, barrierShape *resolv.Conve
|
||||
}
|
||||
if overlapped := IsPolygonPairOverlapped(playerShape, barrierShape, overlapResult); overlapped {
|
||||
pushbackX, pushbackY := overlapResult.Overlap*overlapResult.OverlapX, overlapResult.Overlap*overlapResult.OverlapY
|
||||
return true, pushbackX, pushbackY
|
||||
return true, pushbackX, pushbackY, overlapResult
|
||||
} else {
|
||||
return false, 0, 0
|
||||
return false, 0, 0, overlapResult
|
||||
}
|
||||
}
|
||||
|
||||
@ -218,3 +220,36 @@ func isPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e vector.Vector, re
|
||||
// the specified unit vector "e" doesn't separate "a" and "b", overlap result is generated
|
||||
return false
|
||||
}
|
||||
|
||||
func WorldToVirtualGridPos(wx, wy, worldToVirtualGridRatio float64) (int32, int32) {
|
||||
// [WARNING] Introduces loss of precision!
|
||||
// In JavaScript floating numbers suffer from seemingly non-deterministic arithmetics, and even if certain libs solved this issue by approaches such as fixed-point-number, they might not be used in other libs -- e.g. the "collision libs" we're interested in -- thus couldn't kill all pains.
|
||||
var virtualGridX int32 = int32(math.Round(wx * worldToVirtualGridRatio))
|
||||
var virtualGridY int32 = int32(math.Round(wy * worldToVirtualGridRatio))
|
||||
return virtualGridX, virtualGridY
|
||||
}
|
||||
|
||||
func VirtualGridToWorldPos(vx, vy int32, virtualGridToWorldRatio float64) (float64, float64) {
|
||||
// No loss of precision
|
||||
var wx float64 = float64(vx) * virtualGridToWorldRatio
|
||||
var wy float64 = float64(vy) * virtualGridToWorldRatio
|
||||
return wx, wy
|
||||
}
|
||||
|
||||
func WorldToPolygonColliderAnchorPos(wx, wy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (float64, float64) {
|
||||
return wx - halfBoundingW + collisionSpaceOffsetX, wy - halfBoundingH + collisionSpaceOffsetY
|
||||
}
|
||||
|
||||
func PolygonColliderAnchorToWorldPos(cx, cy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (float64, float64) {
|
||||
return cx + halfBoundingW - collisionSpaceOffsetX, cy + halfBoundingH - collisionSpaceOffsetY
|
||||
}
|
||||
|
||||
func PolygonColliderAnchorToVirtualGridPos(cx, cy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64, worldToVirtualGridRatio float64) (int32, int32) {
|
||||
wx, wy := PolygonColliderAnchorToWorldPos(cx, cy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY)
|
||||
return WorldToVirtualGridPos(wx, wy, worldToVirtualGridRatio)
|
||||
}
|
||||
|
||||
func VirtualGridToPolygonColliderAnchorPos(vx, vy int32, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64, virtualGridToWorldRatio float64) (float64, float64) {
|
||||
wx, wy := VirtualGridToWorldPos(vx, vy, virtualGridToWorldRatio)
|
||||
return WorldToPolygonColliderAnchorPos(wx, wy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY)
|
||||
}
|
||||
|
427
dnmshared/sharedprotos/geometry.pb.go
Normal file
427
dnmshared/sharedprotos/geometry.pb.go
Normal file
@ -0,0 +1,427 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v3.21.4
|
||||
// source: geometry.proto
|
||||
|
||||
package sharedprotos
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type Direction struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Dx int32 `protobuf:"varint,1,opt,name=dx,proto3" json:"dx,omitempty"`
|
||||
Dy int32 `protobuf:"varint,2,opt,name=dy,proto3" json:"dy,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Direction) Reset() {
|
||||
*x = Direction{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_geometry_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Direction) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Direction) ProtoMessage() {}
|
||||
|
||||
func (x *Direction) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_geometry_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Direction.ProtoReflect.Descriptor instead.
|
||||
func (*Direction) Descriptor() ([]byte, []int) {
|
||||
return file_geometry_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Direction) GetDx() int32 {
|
||||
if x != nil {
|
||||
return x.Dx
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Direction) GetDy() int32 {
|
||||
if x != nil {
|
||||
return x.Dy
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type Vec2D struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
X float64 `protobuf:"fixed64,1,opt,name=x,proto3" json:"x,omitempty"`
|
||||
Y float64 `protobuf:"fixed64,2,opt,name=y,proto3" json:"y,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Vec2D) Reset() {
|
||||
*x = Vec2D{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_geometry_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Vec2D) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Vec2D) ProtoMessage() {}
|
||||
|
||||
func (x *Vec2D) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_geometry_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Vec2D.ProtoReflect.Descriptor instead.
|
||||
func (*Vec2D) Descriptor() ([]byte, []int) {
|
||||
return file_geometry_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *Vec2D) GetX() float64 {
|
||||
if x != nil {
|
||||
return x.X
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Vec2D) GetY() float64 {
|
||||
if x != nil {
|
||||
return x.Y
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type Polygon2D struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Anchor *Vec2D `protobuf:"bytes,1,opt,name=anchor,proto3" json:"anchor,omitempty"`
|
||||
Points []*Vec2D `protobuf:"bytes,2,rep,name=points,proto3" json:"points,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Polygon2D) Reset() {
|
||||
*x = Polygon2D{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_geometry_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Polygon2D) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Polygon2D) ProtoMessage() {}
|
||||
|
||||
func (x *Polygon2D) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_geometry_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Polygon2D.ProtoReflect.Descriptor instead.
|
||||
func (*Polygon2D) Descriptor() ([]byte, []int) {
|
||||
return file_geometry_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *Polygon2D) GetAnchor() *Vec2D {
|
||||
if x != nil {
|
||||
return x.Anchor
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Polygon2D) GetPoints() []*Vec2D {
|
||||
if x != nil {
|
||||
return x.Points
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Vec2DList struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Eles []*Vec2D `protobuf:"bytes,1,rep,name=eles,proto3" json:"eles,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Vec2DList) Reset() {
|
||||
*x = Vec2DList{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_geometry_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Vec2DList) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Vec2DList) ProtoMessage() {}
|
||||
|
||||
func (x *Vec2DList) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_geometry_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Vec2DList.ProtoReflect.Descriptor instead.
|
||||
func (*Vec2DList) Descriptor() ([]byte, []int) {
|
||||
return file_geometry_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *Vec2DList) GetEles() []*Vec2D {
|
||||
if x != nil {
|
||||
return x.Eles
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Polygon2DList struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Eles []*Polygon2D `protobuf:"bytes,1,rep,name=eles,proto3" json:"eles,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Polygon2DList) Reset() {
|
||||
*x = Polygon2DList{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_geometry_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Polygon2DList) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Polygon2DList) ProtoMessage() {}
|
||||
|
||||
func (x *Polygon2DList) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_geometry_proto_msgTypes[4]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Polygon2DList.ProtoReflect.Descriptor instead.
|
||||
func (*Polygon2DList) Descriptor() ([]byte, []int) {
|
||||
return file_geometry_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *Polygon2DList) GetEles() []*Polygon2D {
|
||||
if x != nil {
|
||||
return x.Eles
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_geometry_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_geometry_proto_rawDesc = []byte{
|
||||
0x0a, 0x0e, 0x67, 0x65, 0x6f, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x12, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0x2b,
|
||||
0x0a, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x64,
|
||||
0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x64, 0x78, 0x12, 0x0e, 0x0a, 0x02, 0x64,
|
||||
0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x64, 0x79, 0x22, 0x23, 0x0a, 0x05, 0x56,
|
||||
0x65, 0x63, 0x32, 0x44, 0x12, 0x0c, 0x0a, 0x01, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52,
|
||||
0x01, 0x78, 0x12, 0x0c, 0x0a, 0x01, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x01, 0x79,
|
||||
0x22, 0x65, 0x0a, 0x09, 0x50, 0x6f, 0x6c, 0x79, 0x67, 0x6f, 0x6e, 0x32, 0x44, 0x12, 0x2b, 0x0a,
|
||||
0x06, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e,
|
||||
0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x56, 0x65, 0x63,
|
||||
0x32, 0x44, 0x52, 0x06, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x12, 0x2b, 0x0a, 0x06, 0x70, 0x6f,
|
||||
0x69, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x68, 0x61,
|
||||
0x72, 0x65, 0x64, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x56, 0x65, 0x63, 0x32, 0x44, 0x52,
|
||||
0x06, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x22, 0x34, 0x0a, 0x09, 0x56, 0x65, 0x63, 0x32, 0x44,
|
||||
0x4c, 0x69, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x04, 0x65, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03,
|
||||
0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x73, 0x2e, 0x56, 0x65, 0x63, 0x32, 0x44, 0x52, 0x04, 0x65, 0x6c, 0x65, 0x73, 0x22, 0x3c, 0x0a,
|
||||
0x0d, 0x50, 0x6f, 0x6c, 0x79, 0x67, 0x6f, 0x6e, 0x32, 0x44, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x2b,
|
||||
0x0a, 0x04, 0x65, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x73,
|
||||
0x68, 0x61, 0x72, 0x65, 0x64, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x50, 0x6f, 0x6c, 0x79,
|
||||
0x67, 0x6f, 0x6e, 0x32, 0x44, 0x52, 0x04, 0x65, 0x6c, 0x65, 0x73, 0x42, 0x18, 0x5a, 0x16, 0x64,
|
||||
0x6e, 0x6d, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_geometry_proto_rawDescOnce sync.Once
|
||||
file_geometry_proto_rawDescData = file_geometry_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_geometry_proto_rawDescGZIP() []byte {
|
||||
file_geometry_proto_rawDescOnce.Do(func() {
|
||||
file_geometry_proto_rawDescData = protoimpl.X.CompressGZIP(file_geometry_proto_rawDescData)
|
||||
})
|
||||
return file_geometry_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_geometry_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||
var file_geometry_proto_goTypes = []interface{}{
|
||||
(*Direction)(nil), // 0: sharedprotos.Direction
|
||||
(*Vec2D)(nil), // 1: sharedprotos.Vec2D
|
||||
(*Polygon2D)(nil), // 2: sharedprotos.Polygon2D
|
||||
(*Vec2DList)(nil), // 3: sharedprotos.Vec2DList
|
||||
(*Polygon2DList)(nil), // 4: sharedprotos.Polygon2DList
|
||||
}
|
||||
var file_geometry_proto_depIdxs = []int32{
|
||||
1, // 0: sharedprotos.Polygon2D.anchor:type_name -> sharedprotos.Vec2D
|
||||
1, // 1: sharedprotos.Polygon2D.points:type_name -> sharedprotos.Vec2D
|
||||
1, // 2: sharedprotos.Vec2DList.eles:type_name -> sharedprotos.Vec2D
|
||||
2, // 3: sharedprotos.Polygon2DList.eles:type_name -> sharedprotos.Polygon2D
|
||||
4, // [4:4] is the sub-list for method output_type
|
||||
4, // [4:4] is the sub-list for method input_type
|
||||
4, // [4:4] is the sub-list for extension type_name
|
||||
4, // [4:4] is the sub-list for extension extendee
|
||||
0, // [0:4] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_geometry_proto_init() }
|
||||
func file_geometry_proto_init() {
|
||||
if File_geometry_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_geometry_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Direction); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_geometry_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Vec2D); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_geometry_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Polygon2D); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_geometry_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Vec2DList); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_geometry_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Polygon2DList); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_geometry_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 5,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_geometry_proto_goTypes,
|
||||
DependencyIndexes: file_geometry_proto_depIdxs,
|
||||
MessageInfos: file_geometry_proto_msgTypes,
|
||||
}.Build()
|
||||
File_geometry_proto = out.File
|
||||
file_geometry_proto_rawDesc = nil
|
||||
file_geometry_proto_goTypes = nil
|
||||
file_geometry_proto_depIdxs = nil
|
||||
}
|
@ -3,6 +3,7 @@ package dnmshared
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
. "dnmshared/sharedprotos"
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
@ -173,8 +174,6 @@ func (l *TmxLayer) decodeBase64() ([]uint32, error) {
|
||||
return gids, nil
|
||||
}
|
||||
|
||||
type Vec2DList []*Vec2D
|
||||
type Polygon2DList []*Polygon2D
|
||||
type StrToVec2DListMap map[string]*Vec2DList
|
||||
type StrToPolygon2DListMap map[string]*Polygon2DList
|
||||
|
||||
@ -235,8 +234,6 @@ func tsxPolylineToOffsetsWrtTileCenter(pTmxMapIns *TmxMap, singleObjInTsxFile *T
|
||||
thePolygon2DFromPolyline := &Polygon2D{
|
||||
Anchor: nil,
|
||||
Points: make([]*Vec2D, pointsCount),
|
||||
TileWidth: pTsxIns.TileWidth,
|
||||
TileHeight: pTsxIns.TileHeight,
|
||||
}
|
||||
|
||||
/*
|
||||
@ -327,16 +324,17 @@ func DeserializeTsxToColliderDict(pTmxMapIns *TmxMap, byteArrOfTsxFile []byte, f
|
||||
if _, ok := theStrToPolygon2DListMap[key]; ok {
|
||||
pThePolygon2DList = theStrToPolygon2DListMap[key]
|
||||
} else {
|
||||
thePolygon2DList := make(Polygon2DList, 0)
|
||||
theStrToPolygon2DListMap[key] = &thePolygon2DList
|
||||
pThePolygon2DList = theStrToPolygon2DListMap[key]
|
||||
pThePolygon2DList = &Polygon2DList{
|
||||
Eles: make([]*Polygon2D, 0),
|
||||
}
|
||||
theStrToPolygon2DListMap[key] = pThePolygon2DList
|
||||
}
|
||||
|
||||
thePolygon2DFromPolyline, err := tsxPolylineToOffsetsWrtTileCenter(pTmxMapIns, singleObj, singleObj.Polyline, pTsxIns)
|
||||
if nil != err {
|
||||
panic(err)
|
||||
}
|
||||
*pThePolygon2DList = append(*pThePolygon2DList, thePolygon2DFromPolyline)
|
||||
pThePolygon2DList.Eles = append(pThePolygon2DList.Eles, thePolygon2DFromPolyline)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -352,8 +350,10 @@ func ParseTmxLayersAndGroups(pTmxMapIns *TmxMap, gidBoundariesMap map[int]StrToP
|
||||
var pTheVec2DListToCache *Vec2DList
|
||||
_, ok := toRetStrToVec2DListMap[objGroup.Name]
|
||||
if false == ok {
|
||||
theVec2DListToCache := make(Vec2DList, 0)
|
||||
toRetStrToVec2DListMap[objGroup.Name] = &theVec2DListToCache
|
||||
pTheVec2DListToCache = &Vec2DList{
|
||||
Eles: make([]*Vec2D, 0),
|
||||
}
|
||||
toRetStrToVec2DListMap[objGroup.Name] = pTheVec2DListToCache
|
||||
}
|
||||
pTheVec2DListToCache = toRetStrToVec2DListMap[objGroup.Name]
|
||||
for _, singleObjInTmxFile := range objGroup.Objects {
|
||||
@ -362,17 +362,18 @@ func ParseTmxLayersAndGroups(pTmxMapIns *TmxMap, gidBoundariesMap map[int]StrToP
|
||||
Y: singleObjInTmxFile.Y,
|
||||
}
|
||||
thePosInWorld := pTmxMapIns.continuousObjLayerOffsetToContinuousMapNodePos(theUntransformedPos)
|
||||
*pTheVec2DListToCache = append(*pTheVec2DListToCache, &thePosInWorld)
|
||||
pTheVec2DListToCache.Eles = append(pTheVec2DListToCache.Eles, &thePosInWorld)
|
||||
}
|
||||
case "Barrier":
|
||||
// Note that in this case, the "Polygon2D.Anchor" of each "TmxOrTsxObject" is exactly overlapping with "Polygon2D.Points[0]".
|
||||
var pThePolygon2DListToCache *Polygon2DList
|
||||
_, ok := toRetStrToPolygon2DListMap[objGroup.Name]
|
||||
if false == ok {
|
||||
thePolygon2DListToCache := make(Polygon2DList, 0)
|
||||
toRetStrToPolygon2DListMap[objGroup.Name] = &thePolygon2DListToCache
|
||||
pThePolygon2DListToCache = &Polygon2DList{
|
||||
Eles: make([]*Polygon2D, 0),
|
||||
}
|
||||
toRetStrToPolygon2DListMap[objGroup.Name] = pThePolygon2DListToCache
|
||||
}
|
||||
pThePolygon2DListToCache = toRetStrToPolygon2DListMap[objGroup.Name]
|
||||
|
||||
for _, singleObjInTmxFile := range objGroup.Objects {
|
||||
if nil == singleObjInTmxFile.Polyline {
|
||||
@ -386,7 +387,7 @@ func ParseTmxLayersAndGroups(pTmxMapIns *TmxMap, gidBoundariesMap map[int]StrToP
|
||||
if nil != err {
|
||||
panic(err)
|
||||
}
|
||||
*pThePolygon2DListToCache = append(*pThePolygon2DListToCache, thePolygon2DInWorld)
|
||||
pThePolygon2DListToCache.Eles = append(pThePolygon2DListToCache.Eles, thePolygon2DInWorld)
|
||||
}
|
||||
default:
|
||||
}
|
||||
@ -442,40 +443,3 @@ func (pTmxMapIns *TmxMap) continuousObjLayerOffsetToContinuousMapNodePos(continu
|
||||
|
||||
return toRet
|
||||
}
|
||||
|
||||
func AlignPolygon2DToBoundingBox(input *Polygon2D) *Polygon2D {
|
||||
// Transform again to put "anchor" at the top-left point of the bounding box for "resolv"
|
||||
float64Max := float64(99999999999999.9)
|
||||
boundingBoxTL := &Vec2D{
|
||||
X: float64Max,
|
||||
Y: float64Max,
|
||||
}
|
||||
for _, p := range input.Points {
|
||||
if p.X < boundingBoxTL.X {
|
||||
boundingBoxTL.X = p.X
|
||||
}
|
||||
if p.Y < boundingBoxTL.Y {
|
||||
boundingBoxTL.Y = p.Y
|
||||
}
|
||||
}
|
||||
|
||||
// Now "input.Anchor" should move to "input.Anchor+boundingBoxTL", thus "boundingBoxTL" is also the value of the negative diff for all "input.Points"
|
||||
output := &Polygon2D{
|
||||
Anchor: &Vec2D{
|
||||
X: input.Anchor.X + boundingBoxTL.X,
|
||||
Y: input.Anchor.Y + boundingBoxTL.Y,
|
||||
},
|
||||
Points: make([]*Vec2D, len(input.Points)),
|
||||
TileWidth: input.TileWidth,
|
||||
TileHeight: input.TileHeight,
|
||||
}
|
||||
|
||||
for i, p := range input.Points {
|
||||
output.Points[i] = &Vec2D{
|
||||
X: p.X - boundingBoxTL.X,
|
||||
Y: p.Y - boundingBoxTL.Y,
|
||||
}
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,9 +0,0 @@
|
||||
{
|
||||
"ver": "1.0.5",
|
||||
"uuid": "bd514df4-095e-4088-9060-d99397a29a4f",
|
||||
"isPlugin": true,
|
||||
"loadPluginInWeb": true,
|
||||
"loadPluginInNative": true,
|
||||
"loadPluginInEditor": false,
|
||||
"subMetas": {}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.2" tiledversion="1.2.3" orientation="isometric" renderorder="right-down" width="50" height="50" tilewidth="64" tileheight="64" infinite="0" nextlayerid="11" nextobjectid="214">
|
||||
<map version="1.2" tiledversion="1.2.3" orientation="isometric" renderorder="right-down" width="50" height="50" tilewidth="64" tileheight="64" infinite="0" nextlayerid="11" nextobjectid="215">
|
||||
<tileset firstgid="1" source="Tile_W64_H64_S01.tsx"/>
|
||||
<tileset firstgid="17" source="Tile_W300_H300_S01.tsx"/>
|
||||
<layer id="1" name="GroundFloor" width="50" height="50" locked="1">
|
||||
@ -8,7 +8,7 @@
|
||||
</data>
|
||||
</layer>
|
||||
<objectgroup id="2" name="PlayerStartingPos">
|
||||
<object id="135" x="1442.33" y="2063">
|
||||
<object id="135" x="1513.33" y="1996">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="137" x="2270" y="1640">
|
||||
@ -17,7 +17,7 @@
|
||||
</objectgroup>
|
||||
<layer id="3" name="FirsrtFloor" width="50" height="50">
|
||||
<data encoding="base64" compression="zlib">
|
||||
eJztwQENAAAAwqD3T20ON6AAAAAAAAAAAADg3wAnEAAB
|
||||
eJzt1jEKgDAQRNE0GtD739fGaQIhqJHdCf81aSz2oyspBQAAAADWcUYPMIEaaugU37TvwbGl9y05tYz2wWFfaMiBhhyNKzRs99n7lzo0SG1OcWqQtsWxQdSwD57L3CCjO4dDg7zd+Yye7nxmajlCp5jD6Y4OAAAA4H8XE6wBrA==
|
||||
</data>
|
||||
</layer>
|
||||
<objectgroup id="4" name="Barrier">
|
||||
@ -36,5 +36,11 @@
|
||||
</properties>
|
||||
<polyline points="1101.33,-342 470.926,284.848 526.26,339.333 1152.93,-285.091"/>
|
||||
</object>
|
||||
<object id="214" x="988" y="1632">
|
||||
<properties>
|
||||
<property name="boundary_type" value="barrier"/>
|
||||
</properties>
|
||||
<polyline points="-3,1 -70,72 514,640 588,572"/>
|
||||
</object>
|
||||
</objectgroup>
|
||||
</map>
|
||||
|
26
frontend/assets/resources/pbfiles/geometry.proto
Normal file
26
frontend/assets/resources/pbfiles/geometry.proto
Normal file
@ -0,0 +1,26 @@
|
||||
syntax = "proto3";
|
||||
option go_package = "dnmshared/sharedprotos"; // here "./" corresponds to the "--go_out" value in "protoc" command
|
||||
package sharedprotos;
|
||||
|
||||
message Direction {
|
||||
int32 dx = 1;
|
||||
int32 dy = 2;
|
||||
}
|
||||
|
||||
message Vec2D {
|
||||
double x = 1;
|
||||
double y = 2;
|
||||
}
|
||||
|
||||
message Polygon2D {
|
||||
Vec2D anchor = 1;
|
||||
repeated Vec2D points = 2;
|
||||
}
|
||||
|
||||
message Vec2DList {
|
||||
repeated Vec2D eles = 1;
|
||||
}
|
||||
|
||||
message Polygon2DList {
|
||||
repeated Polygon2D eles = 1;
|
||||
}
|
5
frontend/assets/resources/pbfiles/geometry.proto.meta
Normal file
5
frontend/assets/resources/pbfiles/geometry.proto.meta
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"ver": "2.0.0",
|
||||
"uuid": "2ba698f8-1af7-4c47-9d43-b2730b62c692",
|
||||
"subMetas": {}
|
||||
}
|
@ -1,35 +1,13 @@
|
||||
syntax = "proto3";
|
||||
option go_package = "."; // "./" corresponds to the "--go_out" value in "protoc" command
|
||||
option go_package = "battle_srv/protos"; // here "./" corresponds to the "--go_out" value in "protoc" command
|
||||
|
||||
package treasurehunterx;
|
||||
|
||||
message Direction {
|
||||
int32 dx = 1;
|
||||
int32 dy = 2;
|
||||
}
|
||||
|
||||
message Vec2D {
|
||||
double x = 1;
|
||||
double y = 2;
|
||||
}
|
||||
|
||||
message Polygon2D {
|
||||
Vec2D Anchor = 1;
|
||||
repeated Vec2D Points = 2;
|
||||
}
|
||||
|
||||
message Vec2DList {
|
||||
repeated Vec2D vec2DList = 1;
|
||||
}
|
||||
|
||||
message Polygon2DList {
|
||||
repeated Polygon2D polygon2DList = 1;
|
||||
}
|
||||
package protos;
|
||||
import "geometry.proto"; // The import path here is only w.r.t. the proto file, not the Go package.
|
||||
|
||||
message BattleColliderInfo {
|
||||
string stageName = 1;
|
||||
map<string, Vec2DList> strToVec2DListMap = 2;
|
||||
map<string, Polygon2DList> strToPolygon2DListMap = 3;
|
||||
map<string, sharedprotos.Vec2DList> strToVec2DListMap = 2;
|
||||
map<string, sharedprotos.Polygon2DList> strToPolygon2DListMap = 3;
|
||||
int32 stageDiscreteW = 4;
|
||||
int32 stageDiscreteH = 5;
|
||||
int32 stageTileW = 6;
|
||||
@ -46,17 +24,19 @@ message BattleColliderInfo {
|
||||
int32 inputFrameUpsyncDelayTolerance = 16;
|
||||
int32 maxChasingRenderFramesPerUpdate = 17;
|
||||
int32 playerBattleState = 18;
|
||||
double rollbackEstimatedDt = 19;
|
||||
double rollbackEstimatedDtMillis = 20;
|
||||
int64 rollbackEstimatedDtNanos = 21;
|
||||
double rollbackEstimatedDtMillis = 19;
|
||||
int64 rollbackEstimatedDtNanos = 20;
|
||||
|
||||
double worldToVirtualGridRatio = 21;
|
||||
double virtualGridToWorldRatio = 22;
|
||||
}
|
||||
|
||||
message Player {
|
||||
message PlayerDownsync {
|
||||
int32 id = 1;
|
||||
double x = 2;
|
||||
double y = 3;
|
||||
Direction dir = 4;
|
||||
double speed = 5;
|
||||
int32 virtualGridX = 2;
|
||||
int32 virtualGridY = 3;
|
||||
sharedprotos.Direction dir = 4;
|
||||
int32 speed = 5; // in terms of virtual grid units
|
||||
int32 battleState = 6;
|
||||
int32 lastMoveGmtMillis = 7;
|
||||
int32 score = 10;
|
||||
@ -64,12 +44,13 @@ message Player {
|
||||
int32 joinIndex = 12;
|
||||
}
|
||||
|
||||
message PlayerMeta {
|
||||
message PlayerDownsyncMeta {
|
||||
int32 id = 1;
|
||||
string name = 2;
|
||||
string displayName = 3;
|
||||
string avatar = 4;
|
||||
int32 joinIndex = 5;
|
||||
double colliderRadius = 6;
|
||||
}
|
||||
|
||||
message InputFrameUpsync {
|
||||
@ -89,9 +70,9 @@ message HeartbeatUpsync {
|
||||
|
||||
message RoomDownsyncFrame {
|
||||
int32 id = 1;
|
||||
map<int32, Player> players = 2;
|
||||
map<int32, PlayerDownsync> players = 2;
|
||||
int64 countdownNanos = 3;
|
||||
map<int32, PlayerMeta> playerMetas = 4;
|
||||
map<int32, PlayerDownsyncMeta> playerMetas = 4;
|
||||
}
|
||||
|
||||
message WsReq {
|
||||
|
@ -100,7 +100,7 @@
|
||||
"__id__": 1
|
||||
},
|
||||
"_children": [],
|
||||
"_active": false,
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 3
|
||||
@ -119,8 +119,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 93.36,
|
||||
"height": 40
|
||||
"width": 46.68,
|
||||
"height": 27.72
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@ -132,7 +132,7 @@
|
||||
"ctor": "Float64Array",
|
||||
"array": [
|
||||
-5,
|
||||
101,
|
||||
50,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@ -164,12 +164,16 @@
|
||||
"__id__": 2
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [],
|
||||
"_materials": [
|
||||
{
|
||||
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
|
||||
}
|
||||
],
|
||||
"_useOriginalSize": false,
|
||||
"_string": "(0, 0)",
|
||||
"_N$string": "(0, 0)",
|
||||
"_fontSize": 40,
|
||||
"_lineHeight": 40,
|
||||
"_fontSize": 20,
|
||||
"_lineHeight": 22,
|
||||
"_enableWrapText": true,
|
||||
"_N$file": null,
|
||||
"_isSystemFontUsed": true,
|
||||
@ -557,15 +561,13 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"animComp": null,
|
||||
"baseSpeed": 50,
|
||||
"speed": 50,
|
||||
"lastMovedAt": 0,
|
||||
"eps": 0.1,
|
||||
"magicLeanLowerBound": 0.414,
|
||||
"magicLeanUpperBound": 2.414,
|
||||
"arrowTipNode": {
|
||||
"__id__": 8
|
||||
},
|
||||
"coordLabel": {
|
||||
"__id__": 3
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
|
@ -100,7 +100,7 @@
|
||||
"__id__": 1
|
||||
},
|
||||
"_children": [],
|
||||
"_active": false,
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 3
|
||||
@ -119,8 +119,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 93.36,
|
||||
"height": 40
|
||||
"width": 46.68,
|
||||
"height": 27.72
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@ -132,7 +132,7 @@
|
||||
"ctor": "Float64Array",
|
||||
"array": [
|
||||
-5,
|
||||
101,
|
||||
50,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@ -164,12 +164,16 @@
|
||||
"__id__": 2
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [],
|
||||
"_materials": [
|
||||
{
|
||||
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
|
||||
}
|
||||
],
|
||||
"_useOriginalSize": false,
|
||||
"_string": "(0, 0)",
|
||||
"_N$string": "(0, 0)",
|
||||
"_fontSize": 40,
|
||||
"_lineHeight": 40,
|
||||
"_fontSize": 20,
|
||||
"_lineHeight": 22,
|
||||
"_enableWrapText": true,
|
||||
"_N$file": null,
|
||||
"_isSystemFontUsed": true,
|
||||
@ -557,15 +561,13 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"animComp": null,
|
||||
"baseSpeed": 50,
|
||||
"speed": 50,
|
||||
"lastMovedAt": 0,
|
||||
"eps": 0.1,
|
||||
"magicLeanLowerBound": 0.414,
|
||||
"magicLeanUpperBound": 2.414,
|
||||
"arrowTipNode": {
|
||||
"__id__": 8
|
||||
},
|
||||
"coordLabel": {
|
||||
"__id__": 3
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
|
@ -440,7 +440,7 @@
|
||||
"array": [
|
||||
0,
|
||||
0,
|
||||
209.73151519075364,
|
||||
237.35666382819272,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
|
@ -6,30 +6,10 @@ module.export = cc.Class({
|
||||
type: cc.Animation,
|
||||
default: null,
|
||||
},
|
||||
baseSpeed: {
|
||||
type: cc.Float,
|
||||
default: 50,
|
||||
},
|
||||
speed: {
|
||||
type: cc.Float,
|
||||
default: 50
|
||||
},
|
||||
lastMovedAt: {
|
||||
type: cc.Float,
|
||||
default: 0 // In "GMT milliseconds"
|
||||
},
|
||||
eps: {
|
||||
default: 0.10,
|
||||
type: cc.Float
|
||||
},
|
||||
magicLeanLowerBound: {
|
||||
default: 0.414, // Tangent of (PI/8).
|
||||
type: cc.Float
|
||||
},
|
||||
magicLeanUpperBound: {
|
||||
default: 2.414, // Tangent of (3*PI/8).
|
||||
type: cc.Float
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
// LIFE-CYCLE CALLBACKS:
|
||||
@ -44,14 +24,14 @@ module.export = cc.Class({
|
||||
onLoad() {
|
||||
const self = this;
|
||||
self.clips = {
|
||||
'01': 'Top',
|
||||
'0-1': 'Bottom',
|
||||
'02': 'Top',
|
||||
'0-2': 'Bottom',
|
||||
'-20': 'Left',
|
||||
'20': 'Right',
|
||||
'-21': 'TopLeft',
|
||||
'21': 'TopRight',
|
||||
'-2-1': 'BottomLeft',
|
||||
'2-1': 'BottomRight'
|
||||
'-11': 'TopLeft',
|
||||
'11': 'TopRight',
|
||||
'-1-1': 'BottomLeft',
|
||||
'1-1': 'BottomRight'
|
||||
};
|
||||
const canvasNode = self.mapNode.parent;
|
||||
self.mapIns = self.mapNode.getComponent("Map");
|
||||
@ -86,11 +66,9 @@ module.export = cc.Class({
|
||||
}
|
||||
},
|
||||
|
||||
update(dt) {
|
||||
},
|
||||
update(dt) {},
|
||||
|
||||
lateUpdate(dt) {
|
||||
},
|
||||
lateUpdate(dt) {},
|
||||
|
||||
_generateRandomDirection() {
|
||||
return ALL_DISCRETE_DIRECTIONS_CLOCKWISE[Math.floor(Math.random() * ALL_DISCRETE_DIRECTIONS_CLOCKWISE.length)];
|
||||
|
@ -1,6 +1,8 @@
|
||||
const i18n = require('LanguageData');
|
||||
i18n.init(window.language); // languageID should be equal to the one we input in New Language ID input field
|
||||
|
||||
window.pb = require("./modules/room_downsync_frame_proto_bundle.forcemsg");
|
||||
|
||||
cc.Class({
|
||||
extends: cc.Component,
|
||||
|
||||
@ -97,18 +99,6 @@ cc.Class({
|
||||
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
|
||||
}
|
||||
|
||||
cc.loader.loadRes("pbfiles/room_downsync_frame", function(err, textAsset /* cc.TextAsset */ ) {
|
||||
if (err) {
|
||||
cc.error(err.message || err);
|
||||
return;
|
||||
}
|
||||
// Otherwise, `window.RoomDownsyncFrame` is already assigned.
|
||||
let protoRoot = new protobuf.Root;
|
||||
window.protobuf.parse(textAsset.text, protoRoot);
|
||||
window.RoomDownsyncFrame = protoRoot.lookupType("treasurehunterx.RoomDownsyncFrame");
|
||||
window.BattleColliderInfo = protoRoot.lookupType("treasurehunterx.BattleColliderInfo");
|
||||
window.WsReq = protoRoot.lookupType("treasurehunterx.WsReq");
|
||||
window.WsResp = protoRoot.lookupType("treasurehunterx.WsResp");
|
||||
self.checkIntAuthTokenExpire().then(
|
||||
(intAuthToken) => {
|
||||
console.log("Successfully found `intAuthToken` in local cache");
|
||||
@ -119,7 +109,6 @@ cc.Class({
|
||||
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
getRetCodeList() {
|
||||
|
@ -111,7 +111,7 @@ cc.Class({
|
||||
type: cc.Integer,
|
||||
default: 4 // implies (renderFrameIdLagTolerance >> inputScaleFrames) count of inputFrameIds
|
||||
},
|
||||
teleportEps1D: {
|
||||
jigglingEps1D: {
|
||||
type: cc.Float,
|
||||
default: 1e-3
|
||||
},
|
||||
@ -123,12 +123,6 @@ cc.Class({
|
||||
|
||||
dumpToRenderCache: function(rdf) {
|
||||
const self = this;
|
||||
// round player position to lower precision
|
||||
for (let playerId in rdf.players) {
|
||||
const immediatePlayerInfo = rdf.players[playerId];
|
||||
rdf.players[playerId].x = parseFloat(parseInt(immediatePlayerInfo.x * 100)) / 100.0;
|
||||
rdf.players[playerId].y = parseFloat(parseInt(immediatePlayerInfo.y * 100)) / 100.0;
|
||||
}
|
||||
const minToKeepRenderFrameId = self.lastAllConfirmedRenderFrameId;
|
||||
while (0 < self.recentRenderCache.cnt && self.recentRenderCache.stFrameId < minToKeepRenderFrameId) {
|
||||
self.recentRenderCache.pop();
|
||||
@ -180,9 +174,16 @@ cc.Class({
|
||||
}
|
||||
|
||||
const joinIndex = self.selfPlayerInfo.joinIndex;
|
||||
const discreteDir = self.ctrl.getDiscretizedDirection();
|
||||
const previousInputFrameDownsyncWithPrediction = self.getCachedInputFrameDownsyncWithPrediction(inputFrameId);
|
||||
const previousSelfInput = (null == previousInputFrameDownsyncWithPrediction ? null : previousInputFrameDownsyncWithPrediction.inputList[joinIndex - 1]);
|
||||
|
||||
// If "forceConfirmation" is active on backend, we shouldn't override the already downsynced "inputFrameDownsync"s.
|
||||
const existingInputFrame = self.recentInputCache.getByFrameId(inputFrameId);
|
||||
if (null != existingInputFrame && self._allConfirmed(existingInputFrame.confirmedList)) {
|
||||
return [previousSelfInput, existingInputFrame.inputList[joinIndex - 1]];
|
||||
}
|
||||
const prefabbedInputList = (null == previousInputFrameDownsyncWithPrediction ? new Array(self.playerRichInfoDict.size).fill(0) : previousInputFrameDownsyncWithPrediction.inputList.slice());
|
||||
const discreteDir = self.ctrl.getDiscretizedDirection();
|
||||
prefabbedInputList[(joinIndex - 1)] = discreteDir.encodedIdx;
|
||||
const prefabbedInputFrameDownsync = {
|
||||
inputFrameId: inputFrameId,
|
||||
@ -192,7 +193,6 @@ cc.Class({
|
||||
|
||||
self.dumpToInputCache(prefabbedInputFrameDownsync); // A prefabbed inputFrame, would certainly be adding a new inputFrame to the cache, because server only downsyncs "all-confirmed inputFrames"
|
||||
|
||||
const previousSelfInput = (null == previousInputFrameDownsyncWithPrediction ? null : previousInputFrameDownsyncWithPrediction.inputList[joinIndex - 1]);
|
||||
return [previousSelfInput, discreteDir.encodedIdx];
|
||||
},
|
||||
|
||||
@ -229,7 +229,7 @@ cc.Class({
|
||||
inputFrameUpsyncBatch.push(inputFrameUpsync);
|
||||
}
|
||||
}
|
||||
const reqData = window.WsReq.encode({
|
||||
const reqData = window.pb.protos.WsReq.encode({
|
||||
msgId: Date.now(),
|
||||
playerId: self.selfPlayerInfo.id,
|
||||
act: window.UPSYNC_MSG_ACT_PLAYER_CMD,
|
||||
@ -330,12 +330,10 @@ cc.Class({
|
||||
self.selfPlayerInfo = null; // This field is kept for distinguishing "self" and "others".
|
||||
self.recentInputCache = new RingBuffer(1024);
|
||||
|
||||
self.latestCollisionSys = new collisions.Collisions();
|
||||
self.chaserCollisionSys = new collisions.Collisions();
|
||||
self.collisionSys = new collisions.Collisions();
|
||||
|
||||
self.collisionBarrierIndexPrefix = (1 << 16); // For tracking the movements of barriers, though not yet actually used
|
||||
self.latestCollisionSysMap = new Map();
|
||||
self.chaserCollisionSysMap = new Map();
|
||||
self.collisionSysMap = new Map();
|
||||
|
||||
self.transitToState(ALL_MAP_STATES.VISUAL);
|
||||
|
||||
@ -354,6 +352,8 @@ cc.Class({
|
||||
window.mapIns = self;
|
||||
window.forceBigEndianFloatingNumDecoding = self.forceBigEndianFloatingNumDecoding;
|
||||
|
||||
self.showCriticalCoordinateLabels = false;
|
||||
|
||||
console.warn("+++++++ Map onLoad()");
|
||||
window.handleClientSessionError = function() {
|
||||
console.warn('+++++++ Common handleClientSessionError()');
|
||||
@ -428,9 +428,11 @@ cc.Class({
|
||||
self.rollbackEstimatedDt = parsedBattleColliderInfo.rollbackEstimatedDt;
|
||||
self.rollbackEstimatedDtMillis = parsedBattleColliderInfo.rollbackEstimatedDtMillis;
|
||||
self.rollbackEstimatedDtNanos = parsedBattleColliderInfo.rollbackEstimatedDtNanos;
|
||||
self.rollbackEstimatedDtToleranceMillis = self.rollbackEstimatedDtMillis / 1000.0;
|
||||
self.maxChasingRenderFramesPerUpdate = parsedBattleColliderInfo.maxChasingRenderFramesPerUpdate;
|
||||
|
||||
self.worldToVirtualGridRatio = parsedBattleColliderInfo.worldToVirtualGridRatio;
|
||||
self.virtualGridToWorldRatio = parsedBattleColliderInfo.virtualGridToWorldRatio;
|
||||
|
||||
const tiledMapIns = self.node.getComponent(cc.TiledMap);
|
||||
|
||||
const fullPathOfTmxFile = cc.js.formatStr("map/%s/map", parsedBattleColliderInfo.stageName);
|
||||
@ -473,17 +475,44 @@ cc.Class({
|
||||
const x0 = boundaryObj[0].x,
|
||||
y0 = boundaryObj[0].y;
|
||||
let pts = [];
|
||||
// TODO: Simplify this redundant coordinate conversion within "extractBoundaryObjects", but since this routine is only called once per battle, not urgent.
|
||||
for (let i = 0; i < boundaryObj.length; ++i) {
|
||||
pts.push([boundaryObj[i].x - x0, boundaryObj[i].y - y0]);
|
||||
const dx = boundaryObj[i].x - x0;
|
||||
const dy = boundaryObj[i].y - y0;
|
||||
pts.push([dx, dy]);
|
||||
/*
|
||||
if (self.showCriticalCoordinateLabels) {
|
||||
const barrierVertLabelNode = new cc.Node();
|
||||
switch (i % 4) {
|
||||
case 0:
|
||||
barrierVertLabelNode.color = cc.Color.RED;
|
||||
break;
|
||||
case 1:
|
||||
barrierVertLabelNode.color = cc.Color.GRAY;
|
||||
break;
|
||||
case 2:
|
||||
barrierVertLabelNode.color = cc.Color.BLACK;
|
||||
break;
|
||||
default:
|
||||
barrierVertLabelNode.color = cc.Color.MAGENTA;
|
||||
break;
|
||||
}
|
||||
const newBarrierLatest = self.latestCollisionSys.createPolygon(x0, y0, pts);
|
||||
// console.log("Created barrier: ", newBarrierLatest);
|
||||
const newBarrierChaser = self.chaserCollisionSys.createPolygon(x0, y0, pts);
|
||||
barrierVertLabelNode.setPosition(cc.v2(x0+0.95*dx, y0+0.5*dy));
|
||||
const barrierVertLabel = barrierVertLabelNode.addComponent(cc.Label);
|
||||
barrierVertLabel.fontSize = 20;
|
||||
barrierVertLabel.lineHeight = 22;
|
||||
barrierVertLabel.string = `(${boundaryObj[i].x.toFixed(1)}, ${boundaryObj[i].y.toFixed(1)})`;
|
||||
safelyAddChild(self.node, barrierVertLabelNode);
|
||||
setLocalZOrder(barrierVertLabelNode, 5);
|
||||
|
||||
barrierVertLabelNode.active = true;
|
||||
}
|
||||
*/
|
||||
}
|
||||
const newBarrier = self.collisionSys.createPolygon(x0, y0, pts);
|
||||
// console.log("Created barrier: ", newBarrier);
|
||||
++barrierIdCounter;
|
||||
const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter);
|
||||
self.latestCollisionSysMap.set(collisionBarrierIndex, newBarrierLatest);
|
||||
self.chaserCollisionSysMap.set(collisionBarrierIndex, newBarrierChaser);
|
||||
self.collisionSysMap.set(collisionBarrierIndex, newBarrier);
|
||||
}
|
||||
|
||||
self.selfPlayerInfo = JSON.parse(cc.sys.localStorage.getItem('selfPlayer'));
|
||||
@ -506,7 +535,7 @@ cc.Class({
|
||||
self.backgroundMapTiledIns.node.setContentSize(newBackgroundMapSize.width * newBackgroundMapTileSize.width, newBackgroundMapSize.height * newBackgroundMapTileSize.height);
|
||||
self.backgroundMapTiledIns.node.setPosition(cc.v2(0, 0));
|
||||
|
||||
const reqData = window.WsReq.encode({
|
||||
const reqData = window.pb.protos.WsReq.encode({
|
||||
msgId: Date.now(),
|
||||
act: window.UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK,
|
||||
}).finish();
|
||||
@ -589,25 +618,19 @@ cc.Class({
|
||||
if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START < rdf.id && window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet) {
|
||||
/*
|
||||
Don't change
|
||||
- lastAllConfirmedRenderFrameId, it's updated only in "rollbackAndChase > _createRoomDownsyncFrameLocally" (except for when RING_BUFF_NON_CONSECUTIVE_SET)
|
||||
- chaserRenderFrameId, it's updated only in "onInputFrameDownsyncBatch" (except for when RING_BUFF_NON_CONSECUTIVE_SET)
|
||||
- lastAllConfirmedRenderFrameId, it's updated only in "rollbackAndChase" (except for when RING_BUFF_NON_CONSECUTIVE_SET)
|
||||
- chaserRenderFrameId, it's updated only in "rollbackAndChase & onInputFrameDownsyncBatch" (except for when RING_BUFF_NON_CONSECUTIVE_SET)
|
||||
*/
|
||||
return dumpRenderCacheRet;
|
||||
}
|
||||
|
||||
// The logic below applies to ( || window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet)
|
||||
if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id) {
|
||||
console.log('On battle resynced! renderFrameId=', rdf.id);
|
||||
console.log('On battle started! renderFrameId=', rdf.id);
|
||||
} else {
|
||||
console.log('On battle resynced! renderFrameId=', rdf.id);
|
||||
}
|
||||
|
||||
self.renderFrameId = rdf.id;
|
||||
self.lastRenderFrameIdTriggeredAt = performance.now();
|
||||
// In this case it must be true that "rdf.id > chaserRenderFrameId >= lastAllConfirmedRenderFrameId".
|
||||
self.lastAllConfirmedRenderFrameId = rdf.id;
|
||||
self.chaserRenderFrameId = rdf.id;
|
||||
|
||||
const players = rdf.players;
|
||||
const playerMetas = rdf.playerMetas;
|
||||
self._initPlayerRichInfoDict(players, playerMetas);
|
||||
@ -619,6 +642,12 @@ cc.Class({
|
||||
playersInfoScriptIns.updateData(playerMeta);
|
||||
}
|
||||
|
||||
self.renderFrameId = rdf.id;
|
||||
self.lastRenderFrameIdTriggeredAt = performance.now();
|
||||
// In this case it must be true that "rdf.id > chaserRenderFrameId >= lastAllConfirmedRenderFrameId".
|
||||
self.lastAllConfirmedRenderFrameId = rdf.id;
|
||||
self.chaserRenderFrameId = rdf.id;
|
||||
|
||||
if (null != rdf.countdownNanos) {
|
||||
self.countdownNanos = rdf.countdownNanos;
|
||||
}
|
||||
@ -672,6 +701,8 @@ cc.Class({
|
||||
firstPredictedYetIncorrectInputFrameId = inputFrameDownsyncId;
|
||||
}
|
||||
self.lastAllConfirmedInputFrameId = inputFrameDownsyncId;
|
||||
// [WARNING] Take all "inputFrameDownsync" from backend as all-confirmed, it'll be later checked by "rollbackAndChase".
|
||||
inputFrameDownsync.confirmedList = (1 << self.playerRichInfoDict.size) - 1;
|
||||
self.dumpToInputCache(inputFrameDownsync);
|
||||
}
|
||||
|
||||
@ -696,7 +727,7 @@ cc.Class({
|
||||
--------------------------------------------------------
|
||||
*/
|
||||
// The actual rollback-and-chase would later be executed in update(dt).
|
||||
console.warn("Mismatched input detected, resetting chaserRenderFrameId: inputFrameId1:", inputFrameId1, ", renderFrameId1:", renderFrameId1, ", chaserRenderFrameId before reset: ", self.chaserRenderFrameId);
|
||||
console.warn(`Mismatched input detected, resetting chaserRenderFrameId: ${self.chaserRenderFrameId}->${renderFrameId1} by firstPredictedYetIncorrectInputFrameId: ${inputFrameId1}`);
|
||||
self.chaserRenderFrameId = renderFrameId1;
|
||||
},
|
||||
|
||||
@ -713,7 +744,7 @@ cc.Class({
|
||||
logBattleStats() {
|
||||
const self = this;
|
||||
let s = [];
|
||||
s.push("Battle stats: renderFrameId=" + self.renderFrameId + ", lastAllConfirmedRenderFrameId=" + self.lastAllConfirmedRenderFrameId + ", lastUpsyncInputFrameId=" + self.lastUpsyncInputFrameId + ", lastAllConfirmedInputFrameId=" + self.lastAllConfirmedInputFrameId);
|
||||
s.push(`Battle stats: renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastUpsyncInputFrameId=${self.lastUpsyncInputFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, chaserRenderFrameId=${self.chaserRenderFrameId}`);
|
||||
|
||||
for (let i = self.recentInputCache.stFrameId; i < self.recentInputCache.edFrameId; ++i) {
|
||||
const inputFrameDownsync = self.recentInputCache.getByFrameId(i);
|
||||
@ -745,24 +776,22 @@ cc.Class({
|
||||
self.playersInfoNode.getComponent("PlayersInfo").clearInfo();
|
||||
},
|
||||
|
||||
spawnPlayerNode(joinIndex, x, y) {
|
||||
spawnPlayerNode(joinIndex, vx, vy, playerRichInfo) {
|
||||
const self = this;
|
||||
const newPlayerNode = 1 == joinIndex ? cc.instantiate(self.player1Prefab) : cc.instantiate(self.player2Prefab); // hardcoded for now, car color determined solely by joinIndex
|
||||
newPlayerNode.setPosition(cc.v2(x, y));
|
||||
const wpos = self.virtualGridToWorldPos(vx, vy);
|
||||
|
||||
newPlayerNode.setPosition(cc.v2(wpos[0], wpos[1]));
|
||||
newPlayerNode.getComponent("SelfPlayer").mapNode = self.node;
|
||||
const currentSelfColliderCircle = newPlayerNode.getComponent(cc.CircleCollider);
|
||||
const r = currentSelfColliderCircle.radius,
|
||||
d = 2 * r;
|
||||
// The collision box of an individual player is a polygon instead of a circle, because the backend collision engine doesn't handle circle alignment well.
|
||||
const x0 = x - r,
|
||||
y0 = y - r;
|
||||
const cpos = self.virtualGridToPlayerColliderPos(vx, vy, playerRichInfo);
|
||||
const d = playerRichInfo.colliderRadius * 2,
|
||||
x0 = cpos[0],
|
||||
y0 = cpos[1];
|
||||
let pts = [[0, 0], [d, 0], [d, d], [0, d]];
|
||||
|
||||
const newPlayerColliderLatest = self.latestCollisionSys.createPolygon(x0, y0, pts);
|
||||
const newPlayerColliderChaser = self.chaserCollisionSys.createPolygon(x0, y0, pts);
|
||||
const newPlayerCollider = self.collisionSys.createPolygon(x0, y0, pts);
|
||||
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
||||
self.latestCollisionSysMap.set(collisionPlayerIndex, newPlayerColliderLatest);
|
||||
self.chaserCollisionSysMap.set(collisionPlayerIndex, newPlayerColliderChaser);
|
||||
self.collisionSysMap.set(collisionPlayerIndex, newPlayerCollider);
|
||||
|
||||
safelyAddChild(self.node, newPlayerNode);
|
||||
setLocalZOrder(newPlayerNode, 5);
|
||||
@ -770,8 +799,8 @@ cc.Class({
|
||||
newPlayerNode.active = true;
|
||||
const playerScriptIns = newPlayerNode.getComponent("SelfPlayer");
|
||||
playerScriptIns.scheduleNewDirection({
|
||||
dx: 0,
|
||||
dy: 0
|
||||
dx: playerRichInfo.dir.dx,
|
||||
dy: playerRichInfo.dir.dy
|
||||
}, true);
|
||||
|
||||
return [newPlayerNode, playerScriptIns];
|
||||
@ -809,12 +838,11 @@ cc.Class({
|
||||
if (nextChaserRenderFrameId > self.renderFrameId) {
|
||||
nextChaserRenderFrameId = self.renderFrameId;
|
||||
}
|
||||
self.rollbackAndChase(prevChaserRenderFrameId, nextChaserRenderFrameId, self.chaserCollisionSys, self.chaserCollisionSysMap);
|
||||
self.chaserRenderFrameId = nextChaserRenderFrameId; // Move the cursor "self.chaserRenderFrameId", keep in mind that "self.chaserRenderFrameId" is not monotonic!
|
||||
self.rollbackAndChase(prevChaserRenderFrameId, nextChaserRenderFrameId, self.collisionSys, self.collisionSysMap, true);
|
||||
let t2 = performance.now();
|
||||
|
||||
// Inside the following "self.rollbackAndChase" (which actually ROLLS FORWARD), the "self.latestCollisionSys" is ALWAYS "ROLLED BACK" to "self.recentRenderCache.get(self.renderFrameId)" before being applied dynamics from corresponding delayedInputFrame, REGARDLESS OF whether or not "self.chaserRenderFrameId == self.renderFrameId" now.
|
||||
const rdf = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.latestCollisionSys, self.latestCollisionSysMap);
|
||||
// Inside the following "self.rollbackAndChase" actually ROLLS FORWARD w.r.t. the corresponding delayedInputFrame, REGARDLESS OF whether or not "self.chaserRenderFrameId == self.renderFrameId" now.
|
||||
const rdf = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.collisionSys, self.collisionSysMap, false);
|
||||
/*
|
||||
const nonTrivialChaseEnded = (prevChaserRenderFrameId < nextChaserRenderFrameId && nextChaserRenderFrameId == self.renderFrameId);
|
||||
if (nonTrivialChaseEnded) {
|
||||
@ -917,15 +945,28 @@ cc.Class({
|
||||
setLocalZOrder(toShowNode, 10);
|
||||
},
|
||||
|
||||
hideFindingPlayersGUI() {
|
||||
hideFindingPlayersGUI(rdf) {
|
||||
const self = this;
|
||||
if (null == self.findingPlayerNode.parent) return;
|
||||
self.findingPlayerNode.parent.removeChild(self.findingPlayerNode);
|
||||
if (null != rdf) {
|
||||
self._initPlayerRichInfoDict(rdf.players, rdf.playerMetas);
|
||||
}
|
||||
},
|
||||
|
||||
onBattleReadyToStart(playerMetas) {
|
||||
console.log("Calling `onBattleReadyToStart` with:", playerMetas);
|
||||
onBattleReadyToStart(rdf) {
|
||||
const self = this;
|
||||
const players = rdf.players;
|
||||
const playerMetas = rdf.playerMetas;
|
||||
self._initPlayerRichInfoDict(players, playerMetas);
|
||||
|
||||
// Show the top status indicators for IN_BATTLE
|
||||
const playersInfoScriptIns = self.playersInfoNode.getComponent("PlayersInfo");
|
||||
for (let i in playerMetas) {
|
||||
const playerMeta = playerMetas[i];
|
||||
playersInfoScriptIns.updateData(playerMeta);
|
||||
}
|
||||
console.log("Calling `onBattleReadyToStart` with:", playerMetas);
|
||||
const findingPlayerScriptIns = self.findingPlayerNode.getComponent("FindingPlayer");
|
||||
findingPlayerScriptIns.hideExitButton();
|
||||
findingPlayerScriptIns.updatePlayersInfo(playerMetas);
|
||||
@ -939,77 +980,22 @@ cc.Class({
|
||||
}, 1500);
|
||||
},
|
||||
|
||||
_createRoomDownsyncFrameLocally(renderFrameId, collisionSys, collisionSysMap) {
|
||||
const self = this;
|
||||
const prevRenderFrameId = renderFrameId - 1;
|
||||
const inputFrameAppliedOnPrevRenderFrame = (
|
||||
0 > prevRenderFrameId
|
||||
?
|
||||
null
|
||||
:
|
||||
self.getCachedInputFrameDownsyncWithPrediction(self._convertToInputFrameId(prevRenderFrameId, self.inputDelayFrames))
|
||||
);
|
||||
|
||||
// TODO: Find a better way to assign speeds instead of using "speedRefRenderFrameId".
|
||||
const speedRefRenderFrameId = prevRenderFrameId;
|
||||
const speedRefRenderFrame = (
|
||||
0 > speedRefRenderFrameId
|
||||
?
|
||||
null
|
||||
:
|
||||
self.recentRenderCache.getByFrameId(speedRefRenderFrameId)
|
||||
);
|
||||
|
||||
const rdf = {
|
||||
id: renderFrameId,
|
||||
refFrameId: renderFrameId,
|
||||
players: {}
|
||||
};
|
||||
self.playerRichInfoDict.forEach((playerRichInfo, playerId) => {
|
||||
const joinIndex = playerRichInfo.joinIndex;
|
||||
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
||||
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
|
||||
const currentSelfColliderCircle = playerRichInfo.node.getComponent(cc.CircleCollider);
|
||||
const r = currentSelfColliderCircle.radius;
|
||||
rdf.players[playerRichInfo.id] = {
|
||||
id: playerRichInfo.id,
|
||||
x: playerCollider.x + r, // [WARNING] the (x, y) of "playerCollider" is offset to the anchor (i.e. first point of all points) of the polygon shape
|
||||
y: playerCollider.y + r,
|
||||
dir: self.ctrl.decodeDirection(null == inputFrameAppliedOnPrevRenderFrame ? 0 : inputFrameAppliedOnPrevRenderFrame.inputList[joinIndex - 1]),
|
||||
speed: (null == speedRefRenderFrame ? playerRichInfo.speed : speedRefRenderFrame.players[playerRichInfo.id].speed),
|
||||
joinIndex: joinIndex
|
||||
};
|
||||
});
|
||||
if (
|
||||
null != inputFrameAppliedOnPrevRenderFrame && self._allConfirmed(inputFrameAppliedOnPrevRenderFrame.confirmedList)
|
||||
&&
|
||||
rdf.id > self.lastAllConfirmedRenderFrameId
|
||||
) {
|
||||
// We got a more up-to-date "all-confirmed-render-frame".
|
||||
self.lastAllConfirmedRenderFrameId = rdf.id;
|
||||
if (rdf.id > self.chaserRenderFrameId) {
|
||||
// it must be true that "chaserRenderFrameId >= lastAllConfirmedRenderFrameId"
|
||||
self.chaserRenderFrameId = rdf.id;
|
||||
}
|
||||
}
|
||||
self.dumpToRenderCache(rdf);
|
||||
return rdf;
|
||||
},
|
||||
|
||||
applyRoomDownsyncFrameDynamics(rdf) {
|
||||
const self = this;
|
||||
|
||||
self.playerRichInfoDict.forEach((playerRichInfo, playerId) => {
|
||||
const immediatePlayerInfo = rdf.players[playerId];
|
||||
const dx = (immediatePlayerInfo.x - playerRichInfo.node.x);
|
||||
const dy = (immediatePlayerInfo.y - playerRichInfo.node.y);
|
||||
const justJiggling = (self.teleportEps1D >= Math.abs(dx) && self.teleportEps1D >= Math.abs(dy));
|
||||
const wpos = self.virtualGridToWorldPos(immediatePlayerInfo.virtualGridX, immediatePlayerInfo.virtualGridY);
|
||||
const dx = (wpos[0] - playerRichInfo.node.x);
|
||||
const dy = (wpos[1] - playerRichInfo.node.y);
|
||||
const justJiggling = (self.jigglingEps1D >= Math.abs(dx) && self.jigglingEps1D >= Math.abs(dy));
|
||||
if (!justJiggling) {
|
||||
console.log("@renderFrameId=" + self.renderFrameId + ", teleporting playerId=" + playerId + ": '(" + playerRichInfo.node.x + ", " + playerRichInfo.node.y, ")' to '(" + immediatePlayerInfo.x + ", " + immediatePlayerInfo.y + ")'");
|
||||
playerRichInfo.node.setPosition(immediatePlayerInfo.x, immediatePlayerInfo.y);
|
||||
}
|
||||
playerRichInfo.node.setPosition(wpos[0], wpos[1]);
|
||||
playerRichInfo.virtualGridX = immediatePlayerInfo.virtualGridX;
|
||||
playerRichInfo.virtualGridY = immediatePlayerInfo.virtualGridY;
|
||||
playerRichInfo.scriptIns.scheduleNewDirection(immediatePlayerInfo.dir, false);
|
||||
playerRichInfo.scriptIns.updateSpeed(immediatePlayerInfo.speed);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@ -1027,61 +1013,67 @@ cc.Class({
|
||||
return inputFrameDownsync;
|
||||
},
|
||||
|
||||
rollbackAndChase(renderFrameIdSt, renderFrameIdEd, collisionSys, collisionSysMap) {
|
||||
// TODO: Write unit-test for this function to compare with its backend counter part
|
||||
applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, currRenderFrame, collisionSys, collisionSysMap) {
|
||||
const self = this;
|
||||
let latestRdf = self.recentRenderCache.getByFrameId(renderFrameIdSt); // typed "RoomDownsyncFrame"
|
||||
if (null == latestRdf) {
|
||||
console.error("Couldn't find renderFrameId=", renderFrameIdSt, " to rollback, lastAllConfirmedRenderFrameId=", self.lastAllConfirmedRenderFrameId, ", lastAllConfirmedInputFrameId=", self.lastAllConfirmedInputFrameId, ", recentRenderCache=", self._stringifyRecentRenderCache(false), ", recentInputCache=", self._stringifyRecentInputCache(false));
|
||||
const nextRenderFramePlayers = {}
|
||||
for (let playerId in currRenderFrame.players) {
|
||||
const currPlayerDownsync = currRenderFrame.players[playerId];
|
||||
nextRenderFramePlayers[playerId] = {
|
||||
id: playerId,
|
||||
virtualGridX: currPlayerDownsync.virtualGridX,
|
||||
virtualGridY: currPlayerDownsync.virtualGridY,
|
||||
dir: {
|
||||
dx: currPlayerDownsync.dir.dx,
|
||||
dy: currPlayerDownsync.dir.dy,
|
||||
},
|
||||
speed: currPlayerDownsync.speed,
|
||||
battleState: currPlayerDownsync.battleState,
|
||||
score: currPlayerDownsync.score,
|
||||
removed: currPlayerDownsync.removed,
|
||||
joinIndex: currPlayerDownsync.joinIndex,
|
||||
};
|
||||
}
|
||||
|
||||
if (renderFrameIdSt >= renderFrameIdEd) {
|
||||
return latestRdf;
|
||||
}
|
||||
/*
|
||||
Reset "position" of players in "collisionSys" according to "renderFrameIdSt". The easy part is that we don't have path-dependent-integrals to worry about like that of thermal dynamics.
|
||||
*/
|
||||
self.playerRichInfoDict.forEach((playerRichInfo, playerId) => {
|
||||
const joinIndex = playerRichInfo.joinIndex;
|
||||
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
||||
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
|
||||
const player = latestRdf.players[playerId];
|
||||
const toRet = {
|
||||
id: currRenderFrame.id + 1,
|
||||
players: nextRenderFramePlayers,
|
||||
};
|
||||
|
||||
const currentSelfColliderCircle = playerRichInfo.node.getComponent(cc.CircleCollider);
|
||||
const r = currentSelfColliderCircle.radius;
|
||||
playerCollider.x = player.x - r;
|
||||
playerCollider.y = player.y - r;
|
||||
});
|
||||
|
||||
/*
|
||||
This function eventually calculates a "RoomDownsyncFrame" where "RoomDownsyncFrame.id == renderFrameIdEd".
|
||||
*/
|
||||
for (let i = renderFrameIdSt; i < renderFrameIdEd; ++i) {
|
||||
const renderFrame = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"
|
||||
const j = self._convertToInputFrameId(i, self.inputDelayFrames);
|
||||
const inputFrameDownsync = self.getCachedInputFrameDownsyncWithPrediction(j);
|
||||
if (null == inputFrameDownsync) {
|
||||
console.error("Failed to get cached inputFrameDownsync for renderFrameId=", i, ", inputFrameId=", j, "lastAllConfirmedRenderFrameId=", self.lastAllConfirmedRenderFrameId, ", lastAllConfirmedInputFrameId=", self.lastAllConfirmedInputFrameId, ", recentRenderCache=", self._stringifyRecentRenderCache(false), ", recentInputCache=", self._stringifyRecentInputCache(false));
|
||||
}
|
||||
const inputList = inputFrameDownsync.inputList;
|
||||
// [WARNING] Traverse in the order of joinIndices to guarantee determinism.
|
||||
if (null != delayedInputFrame) {
|
||||
const inputList = delayedInputFrame.inputList;
|
||||
const effPushbacks = new Array(self.playerRichInfoArr.length); // Guaranteed determinism regardless of traversal order
|
||||
for (let j in self.playerRichInfoArr) {
|
||||
const joinIndex = parseInt(j) + 1;
|
||||
effPushbacks[joinIndex - 1] = [0.0, 0.0];
|
||||
const playerId = self.playerRichInfoArr[j].id;
|
||||
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
||||
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
|
||||
const player = renderFrame.players[playerId];
|
||||
const player = currRenderFrame.players[playerId];
|
||||
|
||||
const encodedInput = inputList[joinIndex - 1];
|
||||
const decodedInput = self.ctrl.decodeDirection(encodedInput);
|
||||
const baseChange = player.speed * self.rollbackEstimatedDt * decodedInput.speedFactor;
|
||||
playerCollider.x += baseChange * decodedInput.dx;
|
||||
playerCollider.y += baseChange * decodedInput.dy;
|
||||
|
||||
// console.log(`Got non-zero inputs for playerId=${playerId}, decodedInput=${JSON.stringify(decodedInput)} @currRenderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.id}`);
|
||||
/*
|
||||
Reset "position" of players in "collisionSys" according to "virtual grid position". The easy part is that we don't have path-dependent-integrals to worry about like that of thermal dynamics.
|
||||
*/
|
||||
const newVx = player.virtualGridX + (decodedInput.dx + player.speed * decodedInput.dx);
|
||||
const newVy = player.virtualGridY + (decodedInput.dy + player.speed * decodedInput.dy);
|
||||
const newCpos = self.virtualGridToPlayerColliderPos(newVx, newVy, self.playerRichInfoArr[joinIndex - 1]);
|
||||
playerCollider.x = newCpos[0];
|
||||
playerCollider.y = newCpos[1];
|
||||
// Update directions and thus would eventually update moving animation accordingly
|
||||
nextRenderFramePlayers[playerId].dir.dx = decodedInput.dx;
|
||||
nextRenderFramePlayers[playerId].dir.dy = decodedInput.dy;
|
||||
}
|
||||
|
||||
collisionSys.update();
|
||||
const result = collisionSys.createResult(); // Can I reuse a "self.latestCollisionSysResult" object throughout the whole battle?
|
||||
const result = collisionSys.createResult(); // Can I reuse a "self.collisionSysResult" object throughout the whole battle?
|
||||
|
||||
for (let i in self.playerRichInfoArr) {
|
||||
const joinIndex = parseInt(i) + 1;
|
||||
for (let j in self.playerRichInfoArr) {
|
||||
const joinIndex = parseInt(j) + 1;
|
||||
const playerId = self.playerRichInfoArr[j].id;
|
||||
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
||||
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
|
||||
const potentials = playerCollider.potentials();
|
||||
@ -1089,12 +1081,72 @@ cc.Class({
|
||||
// Test if the player collides with the wall
|
||||
if (!playerCollider.collides(potential, result)) continue;
|
||||
// Push the player out of the wall
|
||||
playerCollider.x -= result.overlap * result.overlap_x;
|
||||
playerCollider.y -= result.overlap * result.overlap_y;
|
||||
effPushbacks[joinIndex - 1][0] += result.overlap * result.overlap_x;
|
||||
effPushbacks[joinIndex - 1][1] += result.overlap * result.overlap_y;
|
||||
}
|
||||
}
|
||||
|
||||
latestRdf = self._createRoomDownsyncFrameLocally(i + 1, collisionSys, collisionSysMap);
|
||||
for (let j in self.playerRichInfoArr) {
|
||||
const joinIndex = parseInt(j) + 1;
|
||||
const playerId = self.playerRichInfoArr[j].id;
|
||||
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
||||
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
|
||||
const newVpos = self.playerColliderAnchorToVirtualGridPos(playerCollider.x - effPushbacks[joinIndex - 1][0], playerCollider.y - effPushbacks[joinIndex - 1][1], self.playerRichInfoArr[j]);
|
||||
nextRenderFramePlayers[playerId].virtualGridX = newVpos[0];
|
||||
nextRenderFramePlayers[playerId].virtualGridY = newVpos[1];
|
||||
}
|
||||
}
|
||||
|
||||
return toRet;
|
||||
},
|
||||
|
||||
rollbackAndChase(renderFrameIdSt, renderFrameIdEd, collisionSys, collisionSysMap, isChasing) {
|
||||
/*
|
||||
This function eventually calculates a "RoomDownsyncFrame" where "RoomDownsyncFrame.id == renderFrameIdEd" if not interruptted.
|
||||
*/
|
||||
const self = this;
|
||||
let latestRdf = self.recentRenderCache.getByFrameId(renderFrameIdSt); // typed "RoomDownsyncFrame"
|
||||
if (null == latestRdf) {
|
||||
console.error(`Couldn't find renderFrameId=${renderFrameIdSt}, to rollback, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`);
|
||||
return latestRdf;
|
||||
}
|
||||
|
||||
if (renderFrameIdSt >= renderFrameIdEd) {
|
||||
return latestRdf;
|
||||
}
|
||||
|
||||
for (let i = renderFrameIdSt; i < renderFrameIdEd; ++i) {
|
||||
const currRenderFrame = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing", this function can be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"!
|
||||
if (null == currRenderFrame) {
|
||||
console.warn(`Couldn't find renderFrame for i=${i} to rollback, self.renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, might've been interruptted by onRoomDownsyncFrame`);
|
||||
return latestRdf;
|
||||
}
|
||||
const j = self._convertToInputFrameId(i, self.inputDelayFrames);
|
||||
const delayedInputFrame = self.getCachedInputFrameDownsyncWithPrediction(j);
|
||||
if (null == delayedInputFrame) {
|
||||
console.warn(`Failed to get cached delayedInputFrame for i=${i}, j=${j}, self.renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}`);
|
||||
return latestRdf;
|
||||
}
|
||||
|
||||
latestRdf = self.applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, currRenderFrame, collisionSys, collisionSysMap);
|
||||
if (
|
||||
self._allConfirmed(delayedInputFrame.confirmedList)
|
||||
&&
|
||||
latestRdf.id > self.lastAllConfirmedRenderFrameId
|
||||
) {
|
||||
// We got a more up-to-date "all-confirmed-render-frame".
|
||||
self.lastAllConfirmedRenderFrameId = latestRdf.id;
|
||||
if (latestRdf.id > self.chaserRenderFrameId) {
|
||||
// it must be true that "chaserRenderFrameId >= lastAllConfirmedRenderFrameId", regardeless of the "isChasing" param
|
||||
self.chaserRenderFrameId = latestRdf.id;
|
||||
}
|
||||
}
|
||||
|
||||
if (true == isChasing) {
|
||||
// Move the cursor "self.chaserRenderFrameId", keep in mind that "self.chaserRenderFrameId" is not monotonic!
|
||||
self.chaserRenderFrameId = latestRdf.id;
|
||||
}
|
||||
self.dumpToRenderCache(latestRdf);
|
||||
}
|
||||
|
||||
return latestRdf;
|
||||
@ -1107,8 +1159,10 @@ cc.Class({
|
||||
if (self.playerRichInfoDict.has(playerId)) continue; // Skip already put keys
|
||||
const immediatePlayerInfo = players[playerId];
|
||||
const immediatePlayerMeta = playerMetas[playerId];
|
||||
const nodeAndScriptIns = self.spawnPlayerNode(immediatePlayerInfo.joinIndex, immediatePlayerInfo.x, immediatePlayerInfo.y);
|
||||
self.playerRichInfoDict.set(playerId, immediatePlayerInfo);
|
||||
Object.assign(self.playerRichInfoDict.get(playerId), immediatePlayerMeta);
|
||||
|
||||
const nodeAndScriptIns = self.spawnPlayerNode(immediatePlayerInfo.joinIndex, immediatePlayerInfo.virtualGridX, immediatePlayerInfo.virtualGridY, self.playerRichInfoDict.get(playerId));
|
||||
|
||||
Object.assign(self.playerRichInfoDict.get(playerId), {
|
||||
node: nodeAndScriptIns[0],
|
||||
@ -1136,7 +1190,7 @@ cc.Class({
|
||||
|
||||
return s.join('\n');
|
||||
}
|
||||
return "[stInputFrameId=" + self.recentInputCache.stFrameId + ", edInputFrameId=" + self.recentInputCache.edFrameId + ")";
|
||||
return `[stInputFrameId=${self.recentInputCache.stFrameId}, edInputFrameId=${self.recentInputCache.edFrameId})`;
|
||||
},
|
||||
|
||||
_stringifyRecentRenderCache(usefullOutput) {
|
||||
@ -1149,7 +1203,43 @@ cc.Class({
|
||||
|
||||
return s.join('\n');
|
||||
}
|
||||
return "[stRenderFrameId=" + self.recentRenderCache.stFrameId + ", edRenderFrameId=" + self.recentRenderCache.edFrameId + ")";
|
||||
return `[stRenderFrameId=${self.recentRenderCache.stFrameId}, edRenderFrameId=${self.recentRenderCache.edFrameId})`;
|
||||
},
|
||||
|
||||
worldToVirtualGridPos(x, y) {
|
||||
// [WARNING] Introduces loss of precision!
|
||||
const self = this;
|
||||
// In JavaScript floating numbers suffer from seemingly non-deterministic arithmetics, and even if certain libs solved this issue by approaches such as fixed-point-number, they might not be used in other libs -- e.g. the "collision libs" we're interested in -- thus couldn't kill all pains.
|
||||
let virtualGridX = Math.round(x * self.worldToVirtualGridRatio);
|
||||
let virtualGridY = Math.round(y * self.worldToVirtualGridRatio);
|
||||
return [virtualGridX, virtualGridY];
|
||||
},
|
||||
|
||||
virtualGridToWorldPos(vx, vy) {
|
||||
// No loss of precision
|
||||
const self = this;
|
||||
let wx = parseFloat(vx) * self.virtualGridToWorldRatio;
|
||||
let wy = parseFloat(vy) * self.virtualGridToWorldRatio;
|
||||
return [wx, wy];
|
||||
},
|
||||
|
||||
playerWorldToCollisionPos(wx, wy, playerRichInfo) {
|
||||
return [wx - playerRichInfo.colliderRadius, wy - playerRichInfo.colliderRadius];
|
||||
},
|
||||
|
||||
playerColliderAnchorToWorldPos(cx, cy, playerRichInfo) {
|
||||
return [cx + playerRichInfo.colliderRadius, cy + playerRichInfo.colliderRadius];
|
||||
},
|
||||
|
||||
playerColliderAnchorToVirtualGridPos(cx, cy, playerRichInfo) {
|
||||
const self = this;
|
||||
const wpos = self.playerColliderAnchorToWorldPos(cx, cy, playerRichInfo);
|
||||
return self.worldToVirtualGridPos(wpos[0], wpos[1])
|
||||
},
|
||||
|
||||
virtualGridToPlayerColliderPos(vx, vy, playerRichInfo) {
|
||||
const self = this;
|
||||
const wpos = self.virtualGridToWorldPos(vx, vy);
|
||||
return self.playerWorldToCollisionPos(wpos[0], wpos[1], playerRichInfo)
|
||||
},
|
||||
});
|
||||
|
@ -7,6 +7,10 @@ cc.Class({
|
||||
arrowTipNode: {
|
||||
type: cc.Node,
|
||||
default: null
|
||||
},
|
||||
coordLabel: {
|
||||
type: cc.Label,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
start() {
|
||||
@ -26,6 +30,10 @@ cc.Class({
|
||||
'2-1': 'attackedRight'
|
||||
};
|
||||
this.arrowTipNode.active = false;
|
||||
|
||||
if (!this.mapIns.showCriticalCoordinateLabels) {
|
||||
this.coordLabel.node.active = false;
|
||||
}
|
||||
},
|
||||
|
||||
showArrowTipNode() {
|
||||
@ -34,7 +42,7 @@ cc.Class({
|
||||
return;
|
||||
}
|
||||
self.arrowTipNode.active = true;
|
||||
window.setTimeout(function(){
|
||||
window.setTimeout(function() {
|
||||
if (null == self.arrowTipNode) {
|
||||
return;
|
||||
}
|
||||
@ -44,6 +52,9 @@ cc.Class({
|
||||
|
||||
update(dt) {
|
||||
BasePlayer.prototype.update.call(this, dt);
|
||||
if (this.mapIns.showCriticalCoordinateLabels) {
|
||||
this.coordLabel.string = `(${this.node.x.toFixed(2)}, ${this.node.y.toFixed(2)})`;
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
@ -372,7 +372,7 @@ TileCollisionManager.prototype.extractBoundaryObjects = function (withTiledMapNo
|
||||
for (let tsxFilenameIdx = 0; tsxFilenameIdx < tsxFileNames.length; ++tsxFilenameIdx) {
|
||||
const tsxOrientation = tileSets[tsxFilenameIdx].orientation;
|
||||
if (cc.TiledMap.Orientation.ORTHO == tsxOrientation) {
|
||||
cc.error("Error at tileset %s: We proceed with ONLY tilesets in ORTHO orientation for all map orientations by now.", tsxFileNames[tsxFilenameIdx]);
|
||||
cc.error("Error at tileset %s: We don't proceed with tilesets in ORTHO orientation by now.", tsxFileNames[tsxFilenameIdx]);
|
||||
continue;
|
||||
};
|
||||
|
||||
|
@ -1,18 +1,14 @@
|
||||
window.DIRECTION_DECODER = [
|
||||
// The 3rd value matches low-precision constants in backend.
|
||||
[0, 0, 0.0],
|
||||
[0, +1, 1.0],
|
||||
[0, -1, 1.0],
|
||||
[+2, 0, 0.5],
|
||||
[-2, 0, 0.5],
|
||||
[+2, +1, 0.4472],
|
||||
[-2, -1, 0.4472],
|
||||
[+2, -1, 0.4472],
|
||||
[-2, +1, 0.4472],
|
||||
[+2, 0, 0.5],
|
||||
[-2, 0, 0.5],
|
||||
[0, +1, 1.0],
|
||||
[0, -1, 1.0],
|
||||
[0, 0],
|
||||
[0, +2],
|
||||
[0, -2],
|
||||
[+2, 0],
|
||||
[-2, 0],
|
||||
[+1, +1],
|
||||
[-1, -1],
|
||||
[+1, -1],
|
||||
[-1, +1],
|
||||
];
|
||||
|
||||
cc.Class({
|
||||
@ -40,11 +36,11 @@ cc.Class({
|
||||
type: cc.Float
|
||||
},
|
||||
magicLeanLowerBound: {
|
||||
default: 0.414, // Tangent of (PI/8).
|
||||
default: 0.1,
|
||||
type: cc.Float
|
||||
},
|
||||
magicLeanUpperBound: {
|
||||
default: 2.414, // Tangent of (3*PI/8).
|
||||
default: 0.9,
|
||||
type: cc.Float
|
||||
},
|
||||
// For joystick ends.
|
||||
@ -219,7 +215,7 @@ cc.Class({
|
||||
}
|
||||
this.mainCamera.zoomRatio = targetScale;
|
||||
for (let child of this.mainCameraNode.children) {
|
||||
child.setScale(1/targetScale);
|
||||
child.setScale(1 / targetScale);
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -269,16 +265,8 @@ cc.Class({
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (Math.abs(continuousDx) < eps) {
|
||||
ret.dx = 0;
|
||||
if (0 < continuousDy) {
|
||||
ret.dy = +1; // up
|
||||
ret.encodedIdx = 1;
|
||||
} else {
|
||||
ret.dy = -1; // down
|
||||
ret.encodedIdx = 2;
|
||||
}
|
||||
} else if (Math.abs(continuousDy) < eps) {
|
||||
const criticalRatio = continuousDy / continuousDx;
|
||||
if (Math.abs(criticalRatio) < this.magicLeanLowerBound) {
|
||||
ret.dy = 0;
|
||||
if (0 < continuousDx) {
|
||||
ret.dx = +2; // right
|
||||
@ -287,50 +275,40 @@ cc.Class({
|
||||
ret.dx = -2; // left
|
||||
ret.encodedIdx = 4;
|
||||
}
|
||||
} else if (Math.abs(criticalRatio) > this.magicLeanUpperBound) {
|
||||
ret.dx = 0;
|
||||
if (0 < continuousDy) {
|
||||
ret.dy = +2; // up
|
||||
ret.encodedIdx = 1;
|
||||
} else {
|
||||
ret.dy = -2; // down
|
||||
ret.encodedIdx = 2;
|
||||
}
|
||||
} else {
|
||||
const criticalRatio = continuousDy / continuousDx;
|
||||
if (criticalRatio > this.magicLeanLowerBound && criticalRatio < this.magicLeanUpperBound) {
|
||||
if (0 < continuousDx) {
|
||||
ret.dx = +2;
|
||||
if (0 < continuousDy) {
|
||||
ret.dx = +1;
|
||||
ret.dy = +1;
|
||||
ret.encodedIdx = 5;
|
||||
} else {
|
||||
ret.dx = -2;
|
||||
ret.dx = +1;
|
||||
ret.dy = -1;
|
||||
ret.encodedIdx = 7;
|
||||
}
|
||||
} else {
|
||||
// 0 >= continuousDx
|
||||
if (0 < continuousDy) {
|
||||
ret.dx = -1;
|
||||
ret.dy = +1;
|
||||
ret.encodedIdx = 8;
|
||||
} else {
|
||||
ret.dx = -1;
|
||||
ret.dy = -1;
|
||||
ret.encodedIdx = 6;
|
||||
}
|
||||
} else if (criticalRatio > -this.magicLeanUpperBound && criticalRatio < -this.magicLeanLowerBound) {
|
||||
if (0 < continuousDx) {
|
||||
ret.dx = +2;
|
||||
ret.dy = -1;
|
||||
ret.encodedIdx = 7;
|
||||
} else {
|
||||
ret.dx = -2;
|
||||
ret.dy = +1;
|
||||
ret.encodedIdx = 8;
|
||||
}
|
||||
} else {
|
||||
if (Math.abs(criticalRatio) < 1) {
|
||||
ret.dy = 0;
|
||||
if (0 < continuousDx) {
|
||||
ret.dx = +2;
|
||||
ret.encodedIdx = 9;
|
||||
} else {
|
||||
ret.dx = -2;
|
||||
ret.encodedIdx = 10;
|
||||
}
|
||||
} else {
|
||||
ret.dx = 0;
|
||||
if (0 < continuousDy) {
|
||||
ret.dy = +1;
|
||||
ret.encodedIdx = 11;
|
||||
} else {
|
||||
ret.dy = -1;
|
||||
ret.encodedIdx = 12;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
@ -342,8 +320,7 @@ cc.Class({
|
||||
return {
|
||||
dx: mapped[0],
|
||||
dy: mapped[1],
|
||||
speedFactor: mapped[2],
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
getDiscretizedDirection() {
|
||||
|
@ -146,7 +146,7 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const resp = window.WsResp.decode(new Uint8Array(evt.data));
|
||||
const resp = window.pb.protos.WsResp.decode(new Uint8Array(evt.data));
|
||||
switch (resp.act) {
|
||||
case window.DOWNSYNC_MSG_ACT_HB_REQ:
|
||||
window.handleHbRequirements(resp); // 获取boundRoomId并存储到localStorage
|
||||
@ -156,10 +156,10 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
|
||||
break;
|
||||
case window.DOWNSYNC_MSG_ACT_PLAYER_READDED_AND_ACKED:
|
||||
// Deliberately left blank for now
|
||||
mapIns.hideFindingPlayersGUI();
|
||||
mapIns.hideFindingPlayersGUI(resp.rdf);
|
||||
break;
|
||||
case window.DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START:
|
||||
mapIns.onBattleReadyToStart(resp.rdf.playerMetas);
|
||||
mapIns.onBattleReadyToStart(resp.rdf);
|
||||
break;
|
||||
case window.DOWNSYNC_MSG_ACT_BATTLE_START:
|
||||
mapIns.onRoomDownsyncFrame(resp.rdf);
|
||||
@ -172,22 +172,16 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
|
||||
break;
|
||||
case window.DOWNSYNC_MSG_ACT_FORCED_RESYNC:
|
||||
if (null == resp.inputFrameDownsyncBatch || 0 >= resp.inputFrameDownsyncBatch.length) {
|
||||
console.error("Got empty inputFrameDownsyncBatch upon resync@localRenderFrameId=", mapIns.renderFrameId, ", @lastAllConfirmedRenderFrameId=", mapIns.lastAllConfirmedRenderFrameId, "@lastAllConfirmedInputFrameId=", mapIns.lastAllConfirmedInputFrameId, ", @localRecentInputCache=", mapIns._stringifyRecentInputCache(false), ", the incoming resp=\n", JSON.stringify(resp, null, 2));
|
||||
console.error(`Got empty inputFrameDownsyncBatch upon resync@localRenderFrameId=${mapIns.renderFrameId}, @lastAllConfirmedRenderFrameId=${mapIns.lastAllConfirmedRenderFrameId}, @lastAllConfirmedInputFrameId=${mapIns.lastAllConfirmedInputFrameId}, @chaserRenderFrameId=${mapIns.chaserRenderFrameId}, @localRecentInputCache=${mapIns._stringifyRecentInputCache(false)}, the incoming resp=
|
||||
${JSON.stringify(resp, null, 2)}`);
|
||||
return;
|
||||
}
|
||||
// Unless upon ws session lost and reconnected, it's maintained true that "inputFrameDownsyncBatch[0].inputFrameId == frontend.lastAllConfirmedInputFrameId+1", and in this case we should try to keep frontend moving only by "frontend.recentInputCache" to avoid jiggling of synced positions
|
||||
const inputFrameIdConsecutive = (resp.inputFrameDownsyncBatch[0].inputFrameId == mapIns.lastAllConfirmedInputFrameId + 1);
|
||||
const renderFrameIdConsecutive = (resp.rdf.id <= mapIns.renderFrameId + mapIns.renderFrameIdLagTolerance);
|
||||
if (inputFrameIdConsecutive && renderFrameIdConsecutive) {
|
||||
// console.log("Got consecutive resync@localRenderFrameId=", mapIns.renderFrameId, ", @lastAllConfirmedRenderFrameId=", mapIns.lastAllConfirmedRenderFrameId, "@lastAllConfirmedInputFrameId=", mapIns.lastAllConfirmedInputFrameId, ", @localRecentInputCache=", mapIns._stringifyRecentInputCache(false), ", the incoming resp=\n", JSON.stringify(resp));
|
||||
mapIns.onInputFrameDownsyncBatch(resp.inputFrameDownsyncBatch);
|
||||
} else {
|
||||
// console.warn("Got forced resync@localRenderFrameId=", mapIns.renderFrameId, ", @lastAllConfirmedRenderFrameId=", mapIns.lastAllConfirmedRenderFrameId, "@lastAllConfirmedInputFrameId=", mapIns.lastAllConfirmedInputFrameId, ", @localRecentInputCache=", mapIns._stringifyRecentInputCache(false), ", the incoming resp=\n", JSON.stringify(resp, null, 2));
|
||||
console.warn("Got forced resync@localRenderFrameId=", mapIns.renderFrameId, ", @lastAllConfirmedRenderFrameId=", mapIns.lastAllConfirmedRenderFrameId, "@lastAllConfirmedInputFrameId=", mapIns.lastAllConfirmedInputFrameId, ", @localRecentInputCache=", mapIns._stringifyRecentInputCache(false), ", inputFrameIdConsecutive=", inputFrameIdConsecutive, ", renderFrameIdConsecutive=", renderFrameIdConsecutive);
|
||||
console.warn(`Got resync@localRenderFrameId=${mapIns.renderFrameId}, @lastAllConfirmedRenderFrameId=${mapIns.lastAllConfirmedRenderFrameId}, @lastAllConfirmedInputFrameId=${mapIns.lastAllConfirmedInputFrameId}, @chaserRenderFrameId=${mapIns.chaserRenderFrameId}, @localRecentInputCache=${mapIns._stringifyRecentInputCache(false)}, inputFrameIdConsecutive=${inputFrameIdConsecutive}, renderFrameIdConsecutive=${renderFrameIdConsecutive}`);
|
||||
// The following order of execution is important
|
||||
mapIns.onRoomDownsyncFrame(resp.rdf);
|
||||
mapIns.onInputFrameDownsyncBatch(resp.inputFrameDownsyncBatch);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -1,39 +0,0 @@
|
||||
const collisions = require('./modules/Collisions');
|
||||
|
||||
const collisionSys = new collisions.Collisions();
|
||||
|
||||
/*
|
||||
Backend result reference
|
||||
|
||||
2022-10-22T12:11:25.156+0800 INFO collider_visualizer/worldColliderDisplay.go:77 Collided: player.X=1257.665, player.Y=1415.335, oldDx=-2.98, oldDy=-50, playerShape=&{[[0 0] [64 0] [64 64] [0 64]] 1254.685 1365.335 true}, toCheckBarrier=&{[[628.626 54.254500000000064] [0 56.03250000000003] [0.42449999999999477 1.1229999999999905] [625.9715000000001 0]] 1289.039 1318.0805 true}, pushbackX=-0.15848054013127655, pushbackY=-56.03205175509715, result=&{56.03227587710039 -0.0028283794946841584 -0.9999960001267175 false false [0.9988052279193613 -0.04886836073527201]}
|
||||
*/
|
||||
function polygonStr(body) {
|
||||
let coords = [];
|
||||
let cnt = body._coords.length;
|
||||
for (let ix = 0, iy = 1; ix < cnt; ix += 2, iy += 2) {
|
||||
coords.push([body._coords[ix], body._coords[iy]]);
|
||||
}
|
||||
return JSON.stringify(coords);
|
||||
}
|
||||
|
||||
const playerCollider = collisionSys.createPolygon(1257.665, 1415.335, [[0, 0], [64, 0], [64, 64], [0, 64]]);
|
||||
const barrierCollider = collisionSys.createPolygon(1289.039, 1318.0805, [[628.626, 54.254500000000064], [0, 56.03250000000003], [0.42449999999999477, 1.1229999999999905], [625.9715000000001, 0]]);
|
||||
|
||||
const oldDx = -2.98;
|
||||
const oldDy = -50.0;
|
||||
|
||||
playerCollider.x += oldDx;
|
||||
playerCollider.y += oldDy;
|
||||
|
||||
collisionSys.update();
|
||||
const result = collisionSys.createResult();
|
||||
|
||||
const potentials = playerCollider.potentials();
|
||||
|
||||
let overlapCheckId = 0;
|
||||
for (const barrier of potentials) {
|
||||
if (!playerCollider.collides(barrier, result)) continue;
|
||||
const pushbackX = result.overlap * result.overlap_x;
|
||||
const pushbackY = result.overlap * result.overlap_y;
|
||||
console.log("For overlapCheckId=" + overlapCheckId + ", the overlap: a=", polygonStr(result.a), ", b=", polygonStr(result.b), ", pushbackX=", pushbackX, ", pushbackY=", pushbackY);
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"ver": "1.0.5",
|
||||
"uuid": "fce86138-76fc-44d5-8eac-2731b3b0cefd",
|
||||
"isPlugin": false,
|
||||
"loadPluginInWeb": true,
|
||||
"loadPluginInNative": true,
|
||||
"loadPluginInEditor": false,
|
||||
"subMetas": {}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"ver": "1.0.5",
|
||||
"uuid": "f9cd97f6-3533-4a27-afc8-cf302da003b2",
|
||||
"uuid": "1ef4a156-5c54-45b9-85ac-86734985e13a",
|
||||
"isPlugin": false,
|
||||
"loadPluginInWeb": true,
|
||||
"loadPluginInNative": true,
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,5 +0,0 @@
|
||||
{
|
||||
"ver": "1.0.1",
|
||||
"uuid": "e646fbd9-7821-4567-9846-fb6e45aeb921",
|
||||
"subMetas": {}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
39
frontend/collision_test_nodejs.js
Normal file
39
frontend/collision_test_nodejs.js
Normal file
@ -0,0 +1,39 @@
|
||||
const collisions = require('./assets/scripts/modules/Collisions');
|
||||
|
||||
const collisionSys = new collisions.Collisions();
|
||||
|
||||
function polygonStr(body) {
|
||||
let coords = [];
|
||||
let cnt = body._coords.length;
|
||||
for (let ix = 0, iy = 1; ix < cnt; ix += 2, iy += 2) {
|
||||
coords.push([body._coords[ix], body._coords[iy]]);
|
||||
}
|
||||
return JSON.stringify(coords);
|
||||
}
|
||||
|
||||
const playerCollider = collisionSys.createPolygon(1269.665, 1353.335, [[0, 0], [64, 0], [64, 64], [0, 64]]);
|
||||
|
||||
const barrierCollider1 = collisionSys.createPolygon(1277.7159000000001, 1570.5575, [[642.5696, 319.159], [0, 319.15680000000003], [5.7286, 0], [643.7451, 0.9014999999999986]]);
|
||||
const barrierCollider2 = collisionSys.createPolygon(1289.039, 1318.0805, [[628.626, 54.254500000000064], [0, 56.03250000000003], [0.42449999999999477, 1.1229999999999905], [625.9715000000001, 0]]);
|
||||
const barrierCollider3 = collisionSys.createPolygon(1207, 1310, [[69, 581], [0, 579], [8, 3], [79, 0]]);
|
||||
|
||||
playerCollider.x += -2.98;
|
||||
playerCollider.y += -50.0;
|
||||
collisionSys.update();
|
||||
|
||||
const effPushback = [0.0, 0.0];
|
||||
|
||||
const result = collisionSys.createResult();
|
||||
|
||||
const potentials = playerCollider.potentials();
|
||||
|
||||
for (const barrier of potentials) {
|
||||
if (!playerCollider.collides(barrier, result)) continue;
|
||||
const pushbackX = result.overlap * result.overlap_x;
|
||||
const pushbackY = result.overlap * result.overlap_y;
|
||||
console.log(`Overlapped: a=${polygonStr(result.a)}, b=${polygonStr(result.b)}, pushbackX=${pushbackX}, pushbackY=${pushbackY}`);
|
||||
effPushback[0] += pushbackX;
|
||||
effPushback[1] += pushbackY;
|
||||
}
|
||||
|
||||
console.log(`effPushback=${effPushback}`);
|
@ -2,11 +2,16 @@
|
||||
|
||||
# GOLANG part
|
||||
# You have to download the OS binary "protoc" from `https://developers.google.com/protocol-buffers/docs/downloads` and set it to $PATH appropriately.
|
||||
# You have to install `proto-gen-go` by `go get -u github.com/golang/protobuf/protoc-gen-go` as instructed in https://developers.google.com/protocol-buffers/docs/gotutorial too.
|
||||
# You have to install `proto-gen-go` by `go install google.golang.org/protobuf/cmd/protoc-gen-go@latest` as instructed in https://developers.google.com/protocol-buffers/docs/gotutorial#compiling-your-protocol-buffers too.
|
||||
|
||||
golang_basedir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/battle_srv
|
||||
mkdir -p $golang_basedir/pb_output
|
||||
protoc -I=$golang_basedir/../frontend/assets/resources/pbfiles/ --go_out=$golang_basedir/pb_output room_downsync_frame.proto
|
||||
golang_basedir_1=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/dnmshared
|
||||
protoc -I=$golang_basedir_1/../frontend/assets/resources/pbfiles/ --go_out=. geometry.proto
|
||||
echo "GOLANG part 1 done"
|
||||
|
||||
# [WARNING] The following "room_downsync_frame.proto" is generated in another Go package than "geometry.proto", but the generated Go codes are also required to work with imports correctly!
|
||||
golang_basedir_2=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/battle_srv
|
||||
protoc -I=$golang_basedir_2/../frontend/assets/resources/pbfiles/ --go_out=. room_downsync_frame.proto
|
||||
echo "GOLANG part 2 done"
|
||||
|
||||
# JS part
|
||||
js_basedir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/frontend
|
||||
@ -17,6 +22,8 @@ js_outdir=$js_basedir/assets/scripts/modules
|
||||
# npm install -g protobufjs-cli
|
||||
|
||||
# The specific filename is respected by "frontend/build-templates/wechatgame/game.js".
|
||||
pbjs -t static-module -w commonjs --keep-case --force-message -o $js_outdir/room_downsync_frame_proto_bundle.forcemsg.js $js_basedir/assets/resources/pbfiles/room_downsync_frame.proto
|
||||
pbjs -t static-module -w commonjs --keep-case --force-message -o $js_outdir/room_downsync_frame_proto_bundle.forcemsg.js $js_basedir/assets/resources/pbfiles/geometry.proto $js_basedir/assets/resources/pbfiles/room_downsync_frame.proto
|
||||
|
||||
sed -i 's#require("protobufjs/minimal")#require("./protobuf-with-floating-num-decoding-endianess-toggle")#g' $js_outdir/room_downsync_frame_proto_bundle.forcemsg.js
|
||||
sed -i 's#require("protobufjs/minimal")#require("./protobuf-with-floating-num-decoding-endianess-toggle")#g' $js_outdir/room_downsync_frame_proto_bundle.forcemsg.js # Not working in OSX, needs further investigation
|
||||
|
||||
echo "JavaScript part done"
|
||||
|
Loading…
Reference in New Issue
Block a user