Compare commits
47 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
3e7718ed04 | ||
|
b78dd54431 | ||
|
22fb72afbc | ||
|
7b9172c27b | ||
|
7e12853a73 | ||
|
95dcc2ef17 | ||
|
63164569b1 | ||
|
8a4efd023b | ||
|
b031fc1c61 | ||
|
4369729d9c | ||
|
2d080ad134 | ||
|
bd9beec5e5 | ||
|
89a54211e1 | ||
|
a4ebde3e07 | ||
|
41967b11f7 | ||
|
98daeff408 | ||
|
320e98361e | ||
|
15a062af10 | ||
|
3f4e49656a | ||
|
f97ce22cef | ||
|
901b189c5a | ||
|
e5ed8124e8 | ||
|
885443c2b1 | ||
|
aa795fcee5 | ||
|
cb3c19a339 | ||
|
d3d3629618 | ||
|
f37f4337de | ||
|
1a3b3a0a7a | ||
|
4f1ce0d71a | ||
|
1f728071a9 | ||
|
4b68917337 | ||
|
0cbf968228 | ||
|
ec2a21dbe7 | ||
|
dc6402c2b7 | ||
|
8038b393e0 | ||
|
4e0f7b52d4 | ||
|
486c46f608 | ||
|
6d075877ec | ||
|
fe826b393b | ||
|
c69aa25353 | ||
|
0f4d067c06 | ||
|
cff31d295c | ||
|
150e30db2a | ||
|
bc8989a0e6 | ||
|
1959a7fd9a | ||
|
3baaf1d52c | ||
|
62f10e0877 |
25
README.md
@@ -1,11 +1,24 @@
|
|||||||
# Preface
|
# Preface
|
||||||
|
|
||||||
This project is a demo for a websocket-based input synchronization method inspired by [GGPO](https://www.ggpo.net/).
|
This project is a demo for a websocket-based rollback netcode inspired by [GGPO](https://github.com/pond3r/ggpo/blob/master/doc/README.md).
|
||||||

|
|
||||||
|
|
||||||
Please checkout [this demo video](https://pan.baidu.com/s/1aM6e8IWaJszFCYAsRjt19g?pwd=z02c) to see whether the source codes are doing what you expect for synchronization.
|

|
||||||
|
|
||||||
The video mainly shows the following feature (yet I'm not surprised if they're not obvious): when a player didn't have its input arrived at the backend in time (e.g. due to local lag, network delay or reconnection), backend forces confirmation of a prediction of its own and sends the confirmed input together w/ a reference render frame to that player.
|
Please also checkout [this demo video](https://pan.baidu.com/s/1YkfuHjNLzlFVnKiEj6wrDQ?pwd=tkr5) to see how this demo carries out a full 60fps synchronization with the help of _batched input upsync/downsync_ for satisfying network I/O performance.
|
||||||
|
|
||||||
|
The video mainly shows the following features.
|
||||||
|
- The backend receives inputs from frontend peers and broadcasts back for synchronization.
|
||||||
|
- The game is recovered for a player upon reconnection.
|
||||||
|
- Both backend(Golang) and frontend(JavaScript) execute collision detection and handle collision contacts by the same algorithm. The backend dynamics is togglable by [Room.BackendDynamicsEnabled](https://github.com/genxium/DelayNoMore/blob/v0.5.2/battle_srv/models/room.go#L813), but **when turned off the game couldn't support recovery upon reconnection**.
|
||||||
|
|
||||||
|
_(how input delay roughly works)_
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
_(how rollback-and-chase in this project roughly works)_
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
# 1. Building & running
|
# 1. Building & running
|
||||||
|
|
||||||
@@ -19,7 +32,7 @@ The video mainly shows the following feature (yet I'm not surprised if they're n
|
|||||||
- [protobuf CLI](https://developers.google.com/protocol-buffers/docs/downloads) (optional, only for development)
|
- [protobuf CLI](https://developers.google.com/protocol-buffers/docs/downloads) (optional, only for development)
|
||||||
|
|
||||||
### Frontend
|
### Frontend
|
||||||
- [CocosCreator v2.2.1](https://www.cocos.com/en/cocos-creator-2-2-1-released-with-performance-improvements) (mandatory, **ONLY AVAILABLE on Windows or OSX and should be exactly this version**, DON'T use any other version because CocosCreator is well-known for new versions not being backward incompatible)
|
- [CocosCreator v2.2.1](https://www.cocos.com/en/cocos-creator-2-2-1-released-with-performance-improvements) (mandatory, **ONLY AVAILABLE on Windows or OSX and should be exactly this version**, DON'T use any other version because CocosCreator is well-known for new versions not being backward compatible)
|
||||||
- [protojs](https://www.npmjs.com/package/protojs) (optional, only for development)
|
- [protojs](https://www.npmjs.com/package/protojs) (optional, only for development)
|
||||||
|
|
||||||
## 1.2 Provisioning
|
## 1.2 Provisioning
|
||||||
@@ -64,7 +77,7 @@ The easy way is to try out 2 players with test accounts on a same machine.
|
|||||||
- Open one browser instance, visit _http://localhost:7456?expectedRoomId=1_, input `add`on the username box and click to request a captcha, this is a test account so a captcha would be returned by the backend and filled automatically (as shown in the figure below), then click and click to proceed to a matching scene.
|
- Open one browser instance, visit _http://localhost:7456?expectedRoomId=1_, input `add`on the username box and click to request a captcha, this is a test account so a captcha would be returned by the backend and filled automatically (as shown in the figure below), then click and click to proceed to a matching scene.
|
||||||
- Open another browser instance, visit _http://localhost:7456?expectedRoomId=1_, input `bdd`on the username box and click to request a captcha, this is another test account so a captcha would be returned by the backend and filled automatically, then click and click to proceed, when matched a `battle`(but no competition rule yet) would start.
|
- Open another browser instance, visit _http://localhost:7456?expectedRoomId=1_, input `bdd`on the username box and click to request a captcha, this is another test account so a captcha would be returned by the backend and filled automatically, then click and click to proceed, when matched a `battle`(but no competition rule yet) would start.
|
||||||
- Try out the onscreen virtual joysticks to move the cars and see if their movements are in-sync.
|
- Try out the onscreen virtual joysticks to move the cars and see if their movements are in-sync.
|
||||||

|

|
||||||
|
|
||||||
## 2 Troubleshooting
|
## 2 Troubleshooting
|
||||||
|
|
||||||
|
@@ -1,6 +1,11 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"battle_srv/api"
|
||||||
|
. "battle_srv/common"
|
||||||
|
"battle_srv/common/utils"
|
||||||
|
"battle_srv/models"
|
||||||
|
"battle_srv/storage"
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
@@ -10,11 +15,6 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"server/api"
|
|
||||||
. "server/common"
|
|
||||||
"server/common/utils"
|
|
||||||
"server/models"
|
|
||||||
"server/storage"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
. "dnmshared"
|
. "dnmshared"
|
||||||
@@ -79,7 +79,6 @@ func (p *playerController) SMSCaptchaGet(c *gin.Context) {
|
|||||||
c.Set(api.RET, Constants.RetCode.UnknownError)
|
c.Set(api.RET, Constants.RetCode.UnknownError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Redis剩余时长校验
|
|
||||||
if ttl >= ConstVals.Player.CaptchaMaxTTL {
|
if ttl >= ConstVals.Player.CaptchaMaxTTL {
|
||||||
Logger.Info("There's an existing SmsCaptcha record in Redis-server: ", zap.String("key", redisKey), zap.Duration("ttl", ttl))
|
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)
|
c.Set(api.RET, Constants.RetCode.SmsCaptchaRequestedTooFrequently)
|
||||||
@@ -89,7 +88,6 @@ func (p *playerController) SMSCaptchaGet(c *gin.Context) {
|
|||||||
pass := false
|
pass := false
|
||||||
var succRet int
|
var succRet int
|
||||||
if Conf.General.ServerEnv == SERVER_ENV_TEST {
|
if Conf.General.ServerEnv == SERVER_ENV_TEST {
|
||||||
// 测试环境,优先从数据库校验`player.name`,不通过再走机器人magic name校验
|
|
||||||
player, err := models.GetPlayerByName(req.Num)
|
player, err := models.GetPlayerByName(req.Num)
|
||||||
if nil == err && nil != player {
|
if nil == err && nil != player {
|
||||||
pass = true
|
pass = true
|
||||||
@@ -98,7 +96,6 @@ func (p *playerController) SMSCaptchaGet(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !pass {
|
if !pass {
|
||||||
// 机器人magic name校验,不通过再走手机号校验
|
|
||||||
player, err := models.GetPlayerByName(req.Num)
|
player, err := models.GetPlayerByName(req.Num)
|
||||||
if nil == err && nil != player {
|
if nil == err && nil != player {
|
||||||
pass = true
|
pass = true
|
||||||
@@ -111,7 +108,6 @@ func (p *playerController) SMSCaptchaGet(c *gin.Context) {
|
|||||||
succRet = Constants.RetCode.Ok
|
succRet = Constants.RetCode.Ok
|
||||||
pass = true
|
pass = true
|
||||||
}
|
}
|
||||||
// Hardecoded 只验证国内手机号格式
|
|
||||||
if req.CountryCode == "86" {
|
if req.CountryCode == "86" {
|
||||||
if RE_CHINA_PHONE_NUM.MatchString(req.Num) {
|
if RE_CHINA_PHONE_NUM.MatchString(req.Num) {
|
||||||
succRet = Constants.RetCode.Ok
|
succRet = Constants.RetCode.Ok
|
||||||
@@ -133,7 +129,6 @@ func (p *playerController) SMSCaptchaGet(c *gin.Context) {
|
|||||||
}{Ret: succRet}
|
}{Ret: succRet}
|
||||||
var captcha string
|
var captcha string
|
||||||
if ttl >= 0 {
|
if ttl >= 0 {
|
||||||
// 已有未过期的旧验证码记录,续验证码有效期。
|
|
||||||
storage.RedisManagerIns.Expire(redisKey, ConstVals.Player.CaptchaExpire)
|
storage.RedisManagerIns.Expire(redisKey, ConstVals.Player.CaptchaExpire)
|
||||||
captcha = storage.RedisManagerIns.Get(redisKey).Val()
|
captcha = storage.RedisManagerIns.Get(redisKey).Val()
|
||||||
if ttl >= ConstVals.Player.CaptchaExpire/4 {
|
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))
|
Logger.Info("Extended ttl of existing SMSCaptcha record in Redis:", zap.String("key", redisKey), zap.String("captcha", captcha))
|
||||||
} else {
|
} else {
|
||||||
// 校验通过,进行验证码生成处理
|
|
||||||
captcha = strconv.Itoa(utils.Rand.Number(1000, 9999))
|
captcha = strconv.Itoa(utils.Rand.Number(1000, 9999))
|
||||||
if succRet == Constants.RetCode.Ok {
|
if succRet == Constants.RetCode.Ok {
|
||||||
getSmsCaptchaRespErrorCode := sendSMSViaVendor(req.Num, req.CountryCode, captcha)
|
getSmsCaptchaRespErrorCode := sendSMSViaVendor(req.Num, req.CountryCode, captcha)
|
||||||
@@ -234,7 +228,6 @@ func (p *playerController) WechatLogin(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//baseInfo ResAccessToken 获取用户授权access_token的返回结果
|
|
||||||
baseInfo, err := utils.WechatIns.GetOauth2Basic(req.Authcode)
|
baseInfo, err := utils.WechatIns.GetOauth2Basic(req.Authcode)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -250,7 +243,6 @@ func (p *playerController) WechatLogin(c *gin.Context) {
|
|||||||
c.Set(api.RET, Constants.RetCode.WechatServerError)
|
c.Set(api.RET, Constants.RetCode.WechatServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//fserver不会返回openId
|
|
||||||
userInfo.OpenID = baseInfo.OpenID
|
userInfo.OpenID = baseInfo.OpenID
|
||||||
|
|
||||||
player, err := p.maybeCreatePlayerWechatAuthBinding(userInfo)
|
player, err := p.maybeCreatePlayerWechatAuthBinding(userInfo)
|
||||||
@@ -316,7 +308,6 @@ func (p *playerController) WechatGameLogin(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//baseInfo ResAccessToken 获取用户授权access_token的返回结果
|
|
||||||
baseInfo, err := utils.WechatGameIns.GetOauth2Basic(req.Authcode)
|
baseInfo, err := utils.WechatGameIns.GetOauth2Basic(req.Authcode)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -337,7 +328,6 @@ func (p *playerController) WechatGameLogin(c *gin.Context) {
|
|||||||
c.Set(api.RET, Constants.RetCode.WechatServerError)
|
c.Set(api.RET, Constants.RetCode.WechatServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//fserver不会返回openId
|
|
||||||
userInfo.OpenID = baseInfo.OpenID
|
userInfo.OpenID = baseInfo.OpenID
|
||||||
|
|
||||||
player, err := p.maybeCreatePlayerWechatGameAuthBinding(userInfo)
|
player, err := p.maybeCreatePlayerWechatGameAuthBinding(userInfo)
|
||||||
@@ -395,7 +385,6 @@ func (p *playerController) IntAuthTokenLogin(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//kobako: 从player获取display name等
|
|
||||||
player, err := models.GetPlayerById(playerLogin.PlayerID)
|
player, err := models.GetPlayerById(playerLogin.PlayerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Logger.Error("Get player by id in IntAuthTokenLogin function error: ", zap.Error(err))
|
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()
|
c.Abort()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 以下是内部私有函数
|
|
||||||
func (p *playerController) maybeCreateNewPlayer(req smsCaptchaReq) (*models.Player, error) {
|
func (p *playerController) maybeCreateNewPlayer(req smsCaptchaReq) (*models.Player, error) {
|
||||||
extAuthID := req.extAuthID()
|
extAuthID := req.extAuthID()
|
||||||
if Conf.General.ServerEnv == SERVER_ENV_TEST {
|
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))
|
Logger.Info("Got a test env player:", zap.Any("phonenum", req.Num), zap.Any("playerId", player.Id))
|
||||||
return player, nil
|
return player, nil
|
||||||
}
|
}
|
||||||
} else { //正式环境检查是否为bot用户
|
} else {
|
||||||
botPlayer, err := models.GetPlayerByName(req.Num)
|
botPlayer, err := models.GetPlayerByName(req.Num)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Logger.Error("Seeking bot player error:", zap.Error(err))
|
Logger.Error("Seeking bot player error:", zap.Error(err))
|
||||||
@@ -537,19 +525,17 @@ func (p *playerController) maybeCreatePlayerWechatAuthBinding(userInfo utils.Use
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if player != nil {
|
if player != nil {
|
||||||
{ //更新玩家姓名及头像
|
updateInfo := models.Player{
|
||||||
updateInfo := models.Player{
|
Avatar: userInfo.HeadImgURL,
|
||||||
Avatar: userInfo.HeadImgURL,
|
DisplayName: userInfo.Nickname,
|
||||||
DisplayName: userInfo.Nickname,
|
}
|
||||||
}
|
tx := storage.MySQLManagerIns.MustBegin()
|
||||||
tx := storage.MySQLManagerIns.MustBegin()
|
defer tx.Rollback()
|
||||||
defer tx.Rollback()
|
ok, err := models.Update(tx, player.Id, &updateInfo)
|
||||||
ok, err := models.Update(tx, player.Id, &updateInfo)
|
if err != nil && ok != true {
|
||||||
if err != nil && ok != true {
|
return nil, err
|
||||||
return nil, err
|
} else {
|
||||||
} else {
|
tx.Commit()
|
||||||
tx.Commit()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return player, nil
|
return player, nil
|
||||||
}
|
}
|
||||||
@@ -575,19 +561,17 @@ func (p *playerController) maybeCreatePlayerWechatGameAuthBinding(userInfo utils
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if player != nil {
|
if player != nil {
|
||||||
{ //更新玩家姓名及头像
|
updateInfo := models.Player{
|
||||||
updateInfo := models.Player{
|
Avatar: userInfo.HeadImgURL,
|
||||||
Avatar: userInfo.HeadImgURL,
|
DisplayName: userInfo.Nickname,
|
||||||
DisplayName: userInfo.Nickname,
|
}
|
||||||
}
|
tx := storage.MySQLManagerIns.MustBegin()
|
||||||
tx := storage.MySQLManagerIns.MustBegin()
|
defer tx.Rollback()
|
||||||
defer tx.Rollback()
|
ok, err := models.Update(tx, player.Id, &updateInfo)
|
||||||
ok, err := models.Update(tx, player.Id, &updateInfo)
|
if err != nil && ok != true {
|
||||||
if err != nil && ok != true {
|
return nil, err
|
||||||
return nil, err
|
} else {
|
||||||
} else {
|
tx.Commit()
|
||||||
tx.Commit()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return player, nil
|
return player, nil
|
||||||
}
|
}
|
||||||
@@ -672,15 +656,13 @@ func sendSMSViaVendor(mobile string, nationcode string, captchaCode string) int
|
|||||||
Nationcode: nationcode,
|
Nationcode: nationcode,
|
||||||
}
|
}
|
||||||
var captchaExpireMin string
|
var captchaExpireMin string
|
||||||
//短信有效期hardcode
|
|
||||||
if Conf.General.ServerEnv == SERVER_ENV_TEST {
|
if Conf.General.ServerEnv == SERVER_ENV_TEST {
|
||||||
//测试环境下有效期为20秒 先hardcode了
|
captchaExpireMin = "0.5" // Hardcoded
|
||||||
captchaExpireMin = "0.5"
|
|
||||||
} else {
|
} else {
|
||||||
captchaExpireMin = strconv.Itoa(int(ConstVals.Player.CaptchaExpire) / 60000000000)
|
captchaExpireMin = strconv.Itoa(int(ConstVals.Player.CaptchaExpire) / 60000000000)
|
||||||
}
|
}
|
||||||
params := [2]string{captchaCode, captchaExpireMin}
|
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))
|
rand := strconv.Itoa(utils.Rand.Number(1000, 9999))
|
||||||
now := utils.UnixtimeSec()
|
now := utils.UnixtimeSec()
|
||||||
|
|
||||||
@@ -694,7 +676,7 @@ func sendSMSViaVendor(mobile string, nationcode string, captchaCode string) int
|
|||||||
Extend: "",
|
Extend: "",
|
||||||
Params: ¶ms,
|
Params: ¶ms,
|
||||||
Sig: sig,
|
Sig: sig,
|
||||||
Sign: "洛克互娱",
|
Sign: "YYYYYYYYYYYYYYYYY",
|
||||||
Tel: tel,
|
Tel: tel,
|
||||||
Time: now,
|
Time: now,
|
||||||
Tpl_id: 207399,
|
Tpl_id: 207399,
|
||||||
@@ -705,7 +687,7 @@ func sendSMSViaVendor(mobile string, nationcode string, captchaCode string) int
|
|||||||
Logger.Info("json marshal", zap.Any("err:", err))
|
Logger.Info("json marshal", zap.Any("err:", err))
|
||||||
return -1
|
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",
|
"application/json",
|
||||||
req)
|
req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
. "battle_srv/common"
|
||||||
|
. "battle_srv/configs"
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
. "dnmshared"
|
. "dnmshared"
|
||||||
@@ -11,8 +13,6 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
. "server/common"
|
|
||||||
. "server/configs"
|
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -250,10 +250,6 @@ func (w *wechat) getTicketFromServer() (ticket resTicket, err error) {
|
|||||||
return
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,9 +272,6 @@ func (w *wechat) getAccessTokenFromServer() (accessToken string, err error) {
|
|||||||
return
|
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
|
accessToken = r.AccessToken
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -1,15 +1,15 @@
|
|||||||
package env_tools
|
package env_tools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
. "battle_srv/common"
|
||||||
|
"battle_srv/common/utils"
|
||||||
|
"battle_srv/models"
|
||||||
|
"battle_srv/storage"
|
||||||
. "dnmshared"
|
. "dnmshared"
|
||||||
sq "github.com/Masterminds/squirrel"
|
sq "github.com/Masterminds/squirrel"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
. "server/common"
|
|
||||||
"server/common/utils"
|
|
||||||
"server/models"
|
|
||||||
"server/storage"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func LoadPreConf() {
|
func LoadPreConf() {
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
package env_tools
|
package env_tools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
. "battle_srv/common"
|
||||||
|
"battle_srv/common/utils"
|
||||||
|
"battle_srv/models"
|
||||||
|
"battle_srv/storage"
|
||||||
. "dnmshared"
|
. "dnmshared"
|
||||||
. "server/common"
|
|
||||||
"server/common/utils"
|
|
||||||
"server/models"
|
|
||||||
"server/storage"
|
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
module server
|
module battle_srv
|
||||||
|
|
||||||
go 1.19
|
go 1.19
|
||||||
|
|
||||||
|
@@ -1,19 +1,19 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
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"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"server/api"
|
|
||||||
"server/api/v1"
|
|
||||||
. "server/common"
|
|
||||||
"server/env_tools"
|
|
||||||
"server/models"
|
|
||||||
"server/storage"
|
|
||||||
"server/ws"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "dnmshared"
|
. "dnmshared/sharedprotos"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Barrier struct {
|
type Barrier struct {
|
||||||
|
@@ -1,77 +1,22 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "dnmshared"
|
. "battle_srv/protos"
|
||||||
pb "server/pb_output"
|
. "dnmshared/sharedprotos"
|
||||||
)
|
)
|
||||||
|
|
||||||
func toPbVec2D(modelInstance *Vec2D) *pb.Vec2D {
|
func toPbPlayers(modelInstances map[int32]*Player) map[int32]*PlayerDownsync {
|
||||||
toRet := &pb.Vec2D{
|
toRet := make(map[int32]*PlayerDownsync, 0)
|
||||||
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)
|
|
||||||
if nil == modelInstances {
|
if nil == modelInstances {
|
||||||
return toRet
|
return toRet
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, last := range modelInstances {
|
for k, last := range modelInstances {
|
||||||
toRet[k] = &pb.Player{
|
toRet[k] = &PlayerDownsync{
|
||||||
Id: last.Id,
|
Id: last.Id,
|
||||||
X: last.X,
|
VirtualGridX: last.VirtualGridX,
|
||||||
Y: last.Y,
|
VirtualGridY: last.VirtualGridY,
|
||||||
Dir: &pb.Direction{
|
Dir: &Direction{
|
||||||
Dx: last.Dir.Dx,
|
Dx: last.Dir.Dx,
|
||||||
Dy: last.Dir.Dy,
|
Dy: last.Dir.Dy,
|
||||||
},
|
},
|
||||||
|
@@ -2,7 +2,7 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
. "dnmshared"
|
. "dnmshared/sharedprotos"
|
||||||
"fmt"
|
"fmt"
|
||||||
sq "github.com/Masterminds/squirrel"
|
sq "github.com/Masterminds/squirrel"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
@@ -33,30 +33,32 @@ func InitPlayerBattleStateIns() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Player struct {
|
type Player struct {
|
||||||
Id int32 `json:"id,omitempty" db:"id"`
|
// Meta info fields
|
||||||
X float64 `json:"x,omitempty"`
|
Id int32 `json:"id,omitempty" db:"id"`
|
||||||
Y float64 `json:"y,omitempty"`
|
Name string `json:"name,omitempty" db:"name"`
|
||||||
Dir *Direction `json:"dir,omitempty"`
|
DisplayName string `json:"displayName,omitempty" db:"display_name"`
|
||||||
Speed float64 `json:"speed,omitempty"`
|
Avatar string `json:"avatar,omitempty"`
|
||||||
BattleState int32 `json:"battleState,omitempty"`
|
ColliderRadius float64 `json:"-"`
|
||||||
LastMoveGmtMillis int32 `json:"lastMoveGmtMillis,omitempty"`
|
|
||||||
Score int32 `json:"score,omitempty"`
|
|
||||||
Removed bool `json:"removed,omitempty"`
|
|
||||||
JoinIndex int32
|
|
||||||
|
|
||||||
Name string `json:"name,omitempty" db:"name"`
|
// DB only fields
|
||||||
DisplayName string `json:"displayName,omitempty" db:"display_name"`
|
CreatedAt int64 `db:"created_at"`
|
||||||
Avatar string `json:"avatar,omitempty"`
|
UpdatedAt int64 `db:"updated_at"`
|
||||||
|
DeletedAt NullInt64 `db:"deleted_at"`
|
||||||
|
TutorialStage int `db:"tutorial_stage"`
|
||||||
|
|
||||||
FrozenAtGmtMillis int64 `json:"-" db:"-"`
|
// in-battle info fields
|
||||||
AddSpeedAtGmtMillis int64 `json:"-" db:"-"`
|
VirtualGridX int32
|
||||||
CreatedAt int64 `json:"-" db:"created_at"`
|
VirtualGridY int32
|
||||||
UpdatedAt int64 `json:"-" db:"updated_at"`
|
Dir *Direction
|
||||||
DeletedAt NullInt64 `json:"-" db:"deleted_at"`
|
Speed int32
|
||||||
TutorialStage int `json:"-" db:"tutorial_stage"`
|
BattleState int32
|
||||||
AckingFrameId int32 `json:"ackingFrameId"`
|
LastMoveGmtMillis int32
|
||||||
AckingInputFrameId int32 `json:"-"`
|
Score int32
|
||||||
LastSentInputFrameId int32 `json:"-"`
|
Removed bool
|
||||||
|
JoinIndex int32
|
||||||
|
AckingFrameId int32
|
||||||
|
AckingInputFrameId int32
|
||||||
|
LastSentInputFrameId int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExistPlayerByName(name string) (bool, error) {
|
func ExistPlayerByName(name string) (bool, error) {
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"battle_srv/storage"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
. "dnmshared"
|
. "dnmshared"
|
||||||
"server/storage"
|
|
||||||
|
|
||||||
sq "github.com/Masterminds/squirrel"
|
sq "github.com/Masterminds/squirrel"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
. "battle_srv/common"
|
||||||
|
"battle_srv/common/utils"
|
||||||
|
"battle_srv/storage"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
. "server/common"
|
|
||||||
"server/common/utils"
|
|
||||||
"server/storage"
|
|
||||||
|
|
||||||
sq "github.com/Masterminds/squirrel"
|
sq "github.com/Masterminds/squirrel"
|
||||||
)
|
)
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
. "battle_srv/common"
|
||||||
|
"battle_srv/common/utils"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
. "dnmshared"
|
. "dnmshared"
|
||||||
"errors"
|
"errors"
|
||||||
. "server/common"
|
|
||||||
"server/common/utils"
|
|
||||||
|
|
||||||
sq "github.com/Masterminds/squirrel"
|
sq "github.com/Masterminds/squirrel"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
@@ -1,26 +1,26 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
. "battle_srv/common"
|
||||||
|
"battle_srv/common/utils"
|
||||||
|
. "battle_srv/protos"
|
||||||
. "dnmshared"
|
. "dnmshared"
|
||||||
|
. "dnmshared/sharedprotos"
|
||||||
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/solarlune/resolv"
|
"github.com/solarlune/resolv"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"io/ioutil"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
. "server/common"
|
"os"
|
||||||
"server/common/utils"
|
"path/filepath"
|
||||||
pb "server/pb_output"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"encoding/xml"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -61,36 +61,17 @@ const (
|
|||||||
MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED = -2
|
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{
|
var DIRECTION_DECODER = [][]int32{
|
||||||
{0, 0},
|
{0, 0},
|
||||||
{0, +1},
|
{0, +2},
|
||||||
{0, -1},
|
{0, -2},
|
||||||
{+2, 0},
|
{+2, 0},
|
||||||
{-2, 0},
|
{-2, 0},
|
||||||
{+2, +1},
|
{+1, +1},
|
||||||
{-2, -1},
|
{-1, -1},
|
||||||
{+2, -1},
|
{+1, -1},
|
||||||
{-2, +1},
|
{-1, +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,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RoomBattleState struct {
|
type RoomBattleState struct {
|
||||||
@@ -129,11 +110,13 @@ func calRoomScore(inRoomPlayerCount int32, roomPlayerCnt int, currentRoomBattleS
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Room struct {
|
type Room struct {
|
||||||
Id int32
|
Id int32
|
||||||
Capacity int
|
Capacity int
|
||||||
Players map[int32]*Player
|
collisionSpaceOffsetX float64
|
||||||
PlayersArr []*Player // ordered by joinIndex
|
collisionSpaceOffsetY float64
|
||||||
CollisionSysMap map[int32]*resolv.Object
|
Players map[int32]*Player
|
||||||
|
PlayersArr []*Player // ordered by joinIndex
|
||||||
|
CollisionSysMap map[int32]*resolv.Object
|
||||||
/**
|
/**
|
||||||
* The following `PlayerDownsyncSessionDict` is NOT individually put
|
* The following `PlayerDownsyncSessionDict` is NOT individually put
|
||||||
* under `type Player struct` for a reason.
|
* under `type Player struct` for a reason.
|
||||||
@@ -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"
|
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)
|
InputScaleFrames uint32 // inputDelayedAndScaledFrameId = ((originalFrameId - InputDelayFrames) >> InputScaleFrames)
|
||||||
JoinIndexBooleanArr []bool
|
JoinIndexBooleanArr []bool
|
||||||
RollbackEstimatedDt float64
|
|
||||||
RollbackEstimatedDtMillis float64
|
RollbackEstimatedDtMillis float64
|
||||||
RollbackEstimatedDtNanos int64
|
RollbackEstimatedDtNanos int64
|
||||||
LastRenderFrameIdTriggeredAt int64
|
LastRenderFrameIdTriggeredAt int64
|
||||||
|
|
||||||
|
WorldToVirtualGridRatio float64
|
||||||
|
VirtualGridToWorldRatio float64
|
||||||
|
|
||||||
|
PlayerDefaultSpeed int32
|
||||||
|
|
||||||
StageName string
|
StageName string
|
||||||
StageDiscreteW int32
|
StageDiscreteW int32
|
||||||
StageDiscreteH int32
|
StageDiscreteH int32
|
||||||
@@ -189,13 +176,9 @@ type Room struct {
|
|||||||
StageTileH int32
|
StageTileH int32
|
||||||
RawBattleStrToVec2DListMap StrToVec2DListMap
|
RawBattleStrToVec2DListMap StrToVec2DListMap
|
||||||
RawBattleStrToPolygon2DListMap StrToPolygon2DListMap
|
RawBattleStrToPolygon2DListMap StrToPolygon2DListMap
|
||||||
|
BackendDynamicsEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
PLAYER_DEFAULT_SPEED = float64(200) // Hardcoded
|
|
||||||
ADD_SPEED = float64(100) // Hardcoded
|
|
||||||
)
|
|
||||||
|
|
||||||
func (pR *Room) updateScore() {
|
func (pR *Room) updateScore() {
|
||||||
pR.Score = calRoomScore(pR.EffectivePlayerCount, pR.Capacity, pR.State)
|
pR.Score = calRoomScore(pR.EffectivePlayerCount, pR.Capacity, pR.State)
|
||||||
}
|
}
|
||||||
@@ -217,9 +200,8 @@ func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, session *websocke
|
|||||||
pPlayerFromDbInit.AckingInputFrameId = -1
|
pPlayerFromDbInit.AckingInputFrameId = -1
|
||||||
pPlayerFromDbInit.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED
|
pPlayerFromDbInit.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED
|
||||||
pPlayerFromDbInit.BattleState = PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK
|
pPlayerFromDbInit.BattleState = PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK
|
||||||
pPlayerFromDbInit.FrozenAtGmtMillis = -1 // Hardcoded temporarily.
|
pPlayerFromDbInit.Speed = pR.PlayerDefaultSpeed // Hardcoded
|
||||||
pPlayerFromDbInit.Speed = PLAYER_DEFAULT_SPEED // Hardcoded temporarily.
|
pPlayerFromDbInit.ColliderRadius = float64(24) // Hardcoded
|
||||||
pPlayerFromDbInit.AddSpeedAtGmtMillis = -1 // Hardcoded temporarily.
|
|
||||||
|
|
||||||
pR.Players[playerId] = pPlayerFromDbInit
|
pR.Players[playerId] = pPlayerFromDbInit
|
||||||
pR.PlayerDownsyncSessionDict[playerId] = session
|
pR.PlayerDownsyncSessionDict[playerId] = session
|
||||||
@@ -251,6 +233,8 @@ func (pR *Room) ReAddPlayerIfPossible(pTmpPlayerInstance *Player, session *webso
|
|||||||
pEffectiveInRoomPlayerInstance.AckingInputFrameId = -1
|
pEffectiveInRoomPlayerInstance.AckingInputFrameId = -1
|
||||||
pEffectiveInRoomPlayerInstance.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED
|
pEffectiveInRoomPlayerInstance.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED
|
||||||
pEffectiveInRoomPlayerInstance.BattleState = PlayerBattleStateIns.READDED_PENDING_BATTLE_COLLIDER_ACK
|
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))
|
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
|
return true
|
||||||
@@ -268,7 +252,7 @@ func (pR *Room) ChooseStage() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rand.Seed(time.Now().Unix())
|
rand.Seed(time.Now().Unix())
|
||||||
stageNameList := []string{ /*"simple" ,*/ "richsoil"}
|
stageNameList := []string{"dungeon" /*"dungeon", "simple", "richsoil" */}
|
||||||
chosenStageIndex := rand.Int() % len(stageNameList) // Hardcoded temporarily. -- YFLu
|
chosenStageIndex := rand.Int() % len(stageNameList) // Hardcoded temporarily. -- YFLu
|
||||||
|
|
||||||
pR.StageName = stageNameList[chosenStageIndex]
|
pR.StageName = stageNameList[chosenStageIndex]
|
||||||
@@ -328,8 +312,8 @@ func (pR *Room) ChooseStage() error {
|
|||||||
barrierPolygon2DList := *(toRetStrToPolygon2DListMap["Barrier"])
|
barrierPolygon2DList := *(toRetStrToPolygon2DListMap["Barrier"])
|
||||||
|
|
||||||
var barrierLocalIdInBattle int32 = 0
|
var barrierLocalIdInBattle int32 = 0
|
||||||
for _, polygon2DUnaligned := range barrierPolygon2DList {
|
for _, polygon2DUnaligned := range barrierPolygon2DList.Eles {
|
||||||
polygon2D := AlignPolygon2DToBoundingBox(polygon2DUnaligned)
|
polygon2D := AlignPolygon2DToBoundingBox(polygon2DUnaligned)
|
||||||
/*
|
/*
|
||||||
// For debug-printing only.
|
// For debug-printing only.
|
||||||
Logger.Info("ChooseStage printing polygon2D for barrierPolygon2DList", zap.Any("barrierLocalIdInBattle", barrierLocalIdInBattle), zap.Any("polygon2D.Anchor", polygon2D.Anchor), zap.Any("polygon2D.Points", polygon2D.Points))
|
Logger.Info("ChooseStage printing polygon2D for barrierPolygon2DList", zap.Any("barrierLocalIdInBattle", barrierLocalIdInBattle), zap.Any("polygon2D.Anchor", polygon2D.Anchor), zap.Any("polygon2D.Points", polygon2D.Points))
|
||||||
@@ -361,7 +345,7 @@ func (pR *Room) ConvertToLastUsedRenderFrameId(inputFrameId int32, inputDelayFra
|
|||||||
return ((inputFrameId << pR.InputScaleFrames) + inputDelayFrames + (1 << pR.InputScaleFrames) - 1)
|
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
|
var ret uint64 = 0
|
||||||
// There're 13 possible directions, occupying the first 4 bits, no need to shift
|
// There're 13 possible directions, occupying the first 4 bits, no need to shift
|
||||||
ret += uint64(upsyncCmd.EncodedDir)
|
ret += uint64(upsyncCmd.EncodedDir)
|
||||||
@@ -385,7 +369,7 @@ func (pR *Room) InputsBufferString(allDetails bool) string {
|
|||||||
if nil == tmp {
|
if nil == tmp {
|
||||||
break
|
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))
|
s = append(s, fmt.Sprintf("{inputFrameId: %v, inputList: %v, confirmedList: %v}", f.InputFrameId, f.InputList, f.ConfirmedList))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,7 +391,7 @@ func (pR *Room) StartBattle() {
|
|||||||
|
|
||||||
// Initialize the "collisionSys" as well as "RenderFrameBuffer"
|
// Initialize the "collisionSys" as well as "RenderFrameBuffer"
|
||||||
pR.CurDynamicsRenderFrameId = 0
|
pR.CurDynamicsRenderFrameId = 0
|
||||||
kickoffFrame := &pb.RoomDownsyncFrame{
|
kickoffFrame := &RoomDownsyncFrame{
|
||||||
Id: pR.RenderFrameId,
|
Id: pR.RenderFrameId,
|
||||||
Players: toPbPlayers(pR.Players),
|
Players: toPbPlayers(pR.Players),
|
||||||
CountdownNanos: pR.BattleDurationNanos,
|
CountdownNanos: pR.BattleDurationNanos,
|
||||||
@@ -415,7 +399,11 @@ func (pR *Room) StartBattle() {
|
|||||||
pR.RenderFrameBuffer.Put(kickoffFrame)
|
pR.RenderFrameBuffer.Put(kickoffFrame)
|
||||||
|
|
||||||
// Refresh "Colliders"
|
// Refresh "Colliders"
|
||||||
pR.refreshColliders()
|
spaceW := pR.StageDiscreteW * pR.StageTileW
|
||||||
|
spaceH := pR.StageDiscreteH * pR.StageTileH
|
||||||
|
|
||||||
|
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.
|
* Will be triggered from a goroutine which executes the critical `Room.AddPlayerIfPossible`, thus the `battleMainLoop` should be detached.
|
||||||
@@ -459,17 +447,11 @@ func (pR *Room) StartBattle() {
|
|||||||
pR.prefabInputFrameDownsync(noDelayInputFrameId)
|
pR.prefabInputFrameDownsync(noDelayInputFrameId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force setting all-confirmed of buffered inputFrames periodically
|
pR.markConfirmationIfApplicable()
|
||||||
unconfirmedMask := pR.forceConfirmationIfApplicable()
|
unconfirmedMask := uint64(0)
|
||||||
|
if pR.BackendDynamicsEnabled {
|
||||||
dynamicsDuration := int64(0)
|
// Force setting all-confirmed of buffered inputFrames periodically
|
||||||
if 0 <= pR.LastAllConfirmedInputFrameId {
|
unconfirmedMask = pR.forceConfirmationIfApplicable()
|
||||||
dynamicsStartedAt := utils.UnixtimeNano()
|
|
||||||
// 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)
|
|
||||||
dynamicsDuration = utils.UnixtimeNano() - dynamicsStartedAt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
upperToSendInputFrameId := atomic.LoadInt32(&(pR.LastAllConfirmedInputFrameId))
|
upperToSendInputFrameId := atomic.LoadInt32(&(pR.LastAllConfirmedInputFrameId))
|
||||||
@@ -479,31 +461,45 @@ func (pR *Room) StartBattle() {
|
|||||||
|
|
||||||
If "NstDelayFrames" becomes larger, "pR.RenderFrameId - refRenderFrameId" possibly becomes larger because the force confirmation is delayed more.
|
If "NstDelayFrames" becomes larger, "pR.RenderFrameId - refRenderFrameId" possibly becomes larger because the force confirmation is delayed more.
|
||||||
|
|
||||||
Hence even upon resync, it's still possible that "refRenderFrameId < frontend.chaserRenderFrameId".
|
Upon resync, it's still possible that "refRenderFrameId < frontend.chaserRenderFrameId" -- and this is allowed.
|
||||||
*/
|
*/
|
||||||
refRenderFrameId := pR.ConvertToGeneratingRenderFrameId(upperToSendInputFrameId) + (1 << pR.InputScaleFrames) - 1
|
refRenderFrameId := pR.ConvertToGeneratingRenderFrameId(upperToSendInputFrameId) + (1 << pR.InputScaleFrames) - 1
|
||||||
// [WARNING] The following inequalities are seldom true, but just to avoid that in good network condition the frontend resyncs itself to a "too advanced frontend.renderFrameId", and then starts upsyncing "too advanced inputFrameId".
|
|
||||||
if refRenderFrameId > pR.RenderFrameId {
|
if refRenderFrameId > pR.RenderFrameId {
|
||||||
refRenderFrameId = pR.RenderFrameId
|
refRenderFrameId = pR.RenderFrameId
|
||||||
}
|
}
|
||||||
if refRenderFrameId > pR.CurDynamicsRenderFrameId {
|
|
||||||
refRenderFrameId = pR.CurDynamicsRenderFrameId
|
dynamicsDuration := int64(0)
|
||||||
|
if pR.BackendDynamicsEnabled {
|
||||||
|
if 0 <= pR.LastAllConfirmedInputFrameId {
|
||||||
|
dynamicsStartedAt := utils.UnixtimeNano()
|
||||||
|
// 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, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY)
|
||||||
|
dynamicsDuration = utils.UnixtimeNano() - dynamicsStartedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
// [WARNING] The following inequality are seldom true, but just to avoid that in good network condition the frontend resyncs itself to a "too advanced frontend.renderFrameId", and then starts upsyncing "too advanced inputFrameId".
|
||||||
|
if refRenderFrameId > pR.CurDynamicsRenderFrameId {
|
||||||
|
refRenderFrameId = pR.CurDynamicsRenderFrameId
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for playerId, player := range pR.Players {
|
for playerId, player := range pR.Players {
|
||||||
if swapped := atomic.CompareAndSwapInt32(&player.BattleState, PlayerBattleStateIns.ACTIVE, PlayerBattleStateIns.ACTIVE); !swapped {
|
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".
|
// [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
|
continue
|
||||||
}
|
}
|
||||||
if 0 == pR.RenderFrameId {
|
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)
|
pR.sendSafely(kickoffFrame, nil, DOWNSYNC_MSG_ACT_BATTLE_START, playerId)
|
||||||
} else {
|
} else {
|
||||||
// [WARNING] Websocket is TCP-based, thus no need to re-send a previously sent inputFrame to a same player!
|
// [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
|
candidateToSendInputFrameId := pR.Players[playerId].LastSentInputFrameId + 1
|
||||||
if candidateToSendInputFrameId < pR.InputsBuffer.StFrameId {
|
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.
|
// [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
|
candidateToSendInputFrameId = pR.InputsBuffer.StFrameId
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,7 +515,7 @@ func (pR *Room) StartBattle() {
|
|||||||
if nil == tmp {
|
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)))
|
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) {
|
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))
|
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))
|
||||||
}
|
}
|
||||||
@@ -529,22 +525,26 @@ func (pR *Room) StartBattle() {
|
|||||||
|
|
||||||
if 0 >= len(toSendInputFrames) {
|
if 0 >= len(toSendInputFrames) {
|
||||||
// [WARNING] When sending DOWNSYNC_MSG_ACT_FORCED_RESYNC, there MUST BE accompanying "toSendInputFrames" for calculating "refRenderFrameId"!
|
// [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 {
|
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, candidateToSendInputFrameId=%v, upperToSendInputFrameId=%v, lastSentInputFrameId=%v, playerAckingInputFrameId=%v", pR.Id, playerId, refRenderFrameId, candidateToSendInputFrameId, upperToSendInputFrameId, player.LastSentInputFrameId, player.AckingInputFrameId))
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
indiceInJoinIndexBooleanArr := uint32(player.JoinIndex - 1)
|
/*
|
||||||
var joinMask uint64 = (1 << indiceInJoinIndexBooleanArr)
|
Resync helps
|
||||||
if MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED == player.LastSentInputFrameId || 0 < (unconfirmedMask&joinMask) {
|
1. when player with a slower frontend clock lags significantly behind and thus wouldn't get its inputUpsync recognized due to faster "forceConfirmation"
|
||||||
// [WARNING] Even upon "MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED", it could be true that "0 == (unconfirmedMask & joinMask)"!
|
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)
|
tmp := pR.RenderFrameBuffer.GetByFrameId(refRenderFrameId)
|
||||||
if nil == tmp {
|
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()))
|
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)
|
pR.sendSafely(refRenderFrame, toSendInputFrames, DOWNSYNC_MSG_ACT_FORCED_RESYNC, playerId)
|
||||||
} else {
|
} else {
|
||||||
pR.sendSafely(nil, toSendInputFrames, DOWNSYNC_MSG_ACT_INPUT_BATCH, playerId)
|
pR.sendSafely(nil, toSendInputFrames, DOWNSYNC_MSG_ACT_INPUT_BATCH, playerId)
|
||||||
@@ -553,24 +553,40 @@ func (pR *Room) StartBattle() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evict no longer required "RenderFrameBuffer"
|
if pR.BackendDynamicsEnabled {
|
||||||
for pR.RenderFrameBuffer.N < pR.RenderFrameBuffer.Cnt || (0 < pR.RenderFrameBuffer.Cnt && pR.RenderFrameBuffer.StFrameId < refRenderFrameId) {
|
// Evict no longer required "RenderFrameBuffer"
|
||||||
_ = pR.RenderFrameBuffer.Pop()
|
for pR.RenderFrameBuffer.N < pR.RenderFrameBuffer.Cnt || (0 < pR.RenderFrameBuffer.Cnt && pR.RenderFrameBuffer.StFrameId < refRenderFrameId) {
|
||||||
|
_ = pR.RenderFrameBuffer.Pop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toApplyInputFrameId := pR.ConvertToInputFrameId(refRenderFrameId, pR.InputDelayFrames)
|
toApplyInputFrameId := pR.ConvertToInputFrameId(refRenderFrameId, pR.InputDelayFrames)
|
||||||
|
/*
|
||||||
|
[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 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
minLastSentInputFrameId = player.LastSentInputFrameId
|
||||||
|
}
|
||||||
|
if minLastSentInputFrameId < toApplyInputFrameId {
|
||||||
|
toApplyInputFrameId = minLastSentInputFrameId
|
||||||
|
}
|
||||||
for pR.InputsBuffer.N < pR.InputsBuffer.Cnt || (0 < pR.InputsBuffer.Cnt && pR.InputsBuffer.StFrameId < toApplyInputFrameId) {
|
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) {
|
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
|
// 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++
|
pR.RenderFrameId++
|
||||||
elapsedInCalculation := (utils.UnixtimeNano() - stCalculation)
|
elapsedInCalculation := (utils.UnixtimeNano() - stCalculation)
|
||||||
if elapsedInCalculation > nanosPerFrame {
|
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))
|
time.Sleep(time.Duration(nanosPerFrame - elapsedInCalculation))
|
||||||
}
|
}
|
||||||
@@ -586,7 +602,7 @@ func (pR *Room) toDiscreteInputsBufferIndex(inputFrameId int32, joinIndex int32)
|
|||||||
return (inputFrameId << 2) + joinIndex // allowing joinIndex upto 15
|
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 {
|
if swapped := atomic.CompareAndSwapInt32(&pR.State, RoomBattleStateIns.IN_BATTLE, RoomBattleStateIns.IN_BATTLE); !swapped {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -624,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
|
inputFrameId := inputFrameDownsync.InputFrameId
|
||||||
if -1 == pR.LastAllConfirmedInputFrameIdWithChange || false == pR.equalInputLists(inputFrameDownsync.InputList, pR.LastAllConfirmedInputList) {
|
if -1 == pR.LastAllConfirmedInputFrameIdWithChange || false == pR.equalInputLists(inputFrameDownsync.InputList, pR.LastAllConfirmedInputList) {
|
||||||
if -1 == playerId {
|
if -1 == playerId {
|
||||||
@@ -666,7 +682,7 @@ func (pR *Room) StopBattleForSettlement() {
|
|||||||
Logger.Info("Stopping the `battleMainLoop` for:", zap.Any("roomId", pR.Id))
|
Logger.Info("Stopping the `battleMainLoop` for:", zap.Any("roomId", pR.Id))
|
||||||
pR.RenderFrameId++
|
pR.RenderFrameId++
|
||||||
for playerId, _ := range pR.Players {
|
for playerId, _ := range pR.Players {
|
||||||
assembledFrame := pb.RoomDownsyncFrame{
|
assembledFrame := RoomDownsyncFrame{
|
||||||
Id: pR.RenderFrameId,
|
Id: pR.RenderFrameId,
|
||||||
Players: toPbPlayers(pR.Players),
|
Players: toPbPlayers(pR.Players),
|
||||||
CountdownNanos: -1, // TODO: Replace this magic constant!
|
CountdownNanos: -1, // TODO: Replace this magic constant!
|
||||||
@@ -692,18 +708,19 @@ func (pR *Room) onBattlePrepare(cb BattleStartCbType) {
|
|||||||
pR.State = RoomBattleStateIns.PREPARE
|
pR.State = RoomBattleStateIns.PREPARE
|
||||||
Logger.Info("Battle state transitted to RoomBattleStateIns.PREPARE for:", zap.Any("roomId", pR.Id))
|
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 {
|
for _, player := range pR.Players {
|
||||||
playerMetas[player.Id] = &pb.PlayerMeta{
|
playerMetas[player.Id] = &PlayerDownsyncMeta{
|
||||||
Id: player.Id,
|
Id: player.Id,
|
||||||
Name: player.Name,
|
Name: player.Name,
|
||||||
DisplayName: player.DisplayName,
|
DisplayName: player.DisplayName,
|
||||||
Avatar: player.Avatar,
|
Avatar: player.Avatar,
|
||||||
JoinIndex: player.JoinIndex,
|
ColliderRadius: player.ColliderRadius, // hardcoded for now
|
||||||
|
JoinIndex: player.JoinIndex,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
battleReadyToStartFrame := &pb.RoomDownsyncFrame{
|
battleReadyToStartFrame := &RoomDownsyncFrame{
|
||||||
Id: DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START,
|
Id: DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START,
|
||||||
Players: toPbPlayers(pR.Players),
|
Players: toPbPlayers(pR.Players),
|
||||||
PlayerMetas: playerMetas,
|
PlayerMetas: playerMetas,
|
||||||
@@ -773,6 +790,9 @@ func (pR *Room) Dismiss() {
|
|||||||
func (pR *Room) OnDismissed() {
|
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.
|
// Always instantiates new HeapRAM blocks and let the old blocks die out due to not being retained by any root reference.
|
||||||
|
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.Players = make(map[int32]*Player)
|
||||||
pR.PlayersArr = make([]*Player, pR.Capacity)
|
pR.PlayersArr = make([]*Player, pR.Capacity)
|
||||||
pR.CollisionSysMap = make(map[int32]*resolv.Object)
|
pR.CollisionSysMap = make(map[int32]*resolv.Object)
|
||||||
@@ -794,13 +814,14 @@ func (pR *Room) OnDismissed() {
|
|||||||
pR.NstDelayFrames = 8
|
pR.NstDelayFrames = 8
|
||||||
pR.InputScaleFrames = uint32(2)
|
pR.InputScaleFrames = uint32(2)
|
||||||
pR.ServerFps = 60
|
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.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.RollbackEstimatedDtNanos = 16666666 // A little smaller than the actual per frame time, just for preventing FAST FRAME
|
||||||
pR.BattleDurationFrames = 30 * pR.ServerFps
|
pR.BattleDurationFrames = 30 * pR.ServerFps
|
||||||
pR.BattleDurationNanos = int64(pR.BattleDurationFrames) * (pR.RollbackEstimatedDtNanos + 1)
|
pR.BattleDurationNanos = int64(pR.BattleDurationFrames) * (pR.RollbackEstimatedDtNanos + 1)
|
||||||
pR.InputFrameUpsyncDelayTolerance = 2
|
pR.InputFrameUpsyncDelayTolerance = 2
|
||||||
pR.MaxChasingRenderFramesPerUpdate = 10
|
pR.MaxChasingRenderFramesPerUpdate = 5
|
||||||
|
|
||||||
|
pR.BackendDynamicsEnabled = true // [WARNING] When "false", recovery upon reconnection wouldn't work!
|
||||||
|
|
||||||
pR.ChooseStage()
|
pR.ChooseStage()
|
||||||
pR.EffectivePlayerCount = 0
|
pR.EffectivePlayerCount = 0
|
||||||
@@ -911,16 +932,15 @@ func (pR *Room) onPlayerAdded(playerId int32) {
|
|||||||
|
|
||||||
// Lazily assign the initial position of "Player" for "RoomDownsyncFrame".
|
// Lazily assign the initial position of "Player" for "RoomDownsyncFrame".
|
||||||
playerPosList := *(pR.RawBattleStrToVec2DListMap["PlayerStartingPos"])
|
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))
|
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 {
|
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))
|
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].VirtualGridX, pR.Players[playerId].VirtualGridY = WorldToVirtualGridPos(playerPos.X, playerPos.Y, pR.WorldToVirtualGridRatio)
|
||||||
pR.Players[playerId].Y = playerPos.Y
|
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -948,14 +968,15 @@ func (pR *Room) OnPlayerBattleColliderAcked(playerId int32) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
playerMetas := make(map[int32]*pb.PlayerMeta, 0)
|
playerMetas := make(map[int32]*PlayerDownsyncMeta, 0)
|
||||||
for _, eachPlayer := range pR.Players {
|
for _, eachPlayer := range pR.Players {
|
||||||
playerMetas[eachPlayer.Id] = &pb.PlayerMeta{
|
playerMetas[eachPlayer.Id] = &PlayerDownsyncMeta{
|
||||||
Id: eachPlayer.Id,
|
Id: eachPlayer.Id,
|
||||||
Name: eachPlayer.Name,
|
Name: eachPlayer.Name,
|
||||||
DisplayName: eachPlayer.DisplayName,
|
DisplayName: eachPlayer.DisplayName,
|
||||||
Avatar: eachPlayer.Avatar,
|
Avatar: eachPlayer.Avatar,
|
||||||
JoinIndex: eachPlayer.JoinIndex,
|
JoinIndex: eachPlayer.JoinIndex,
|
||||||
|
ColliderRadius: eachPlayer.ColliderRadius,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -971,14 +992,14 @@ func (pR *Room) OnPlayerBattleColliderAcked(playerId int32) bool {
|
|||||||
*/
|
*/
|
||||||
switch targetPlayer.BattleState {
|
switch targetPlayer.BattleState {
|
||||||
case PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK:
|
case PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK:
|
||||||
playerAckedFrame := &pb.RoomDownsyncFrame{
|
playerAckedFrame := &RoomDownsyncFrame{
|
||||||
Id: pR.RenderFrameId,
|
Id: pR.RenderFrameId,
|
||||||
Players: toPbPlayers(pR.Players),
|
Players: toPbPlayers(pR.Players),
|
||||||
PlayerMetas: playerMetas,
|
PlayerMetas: playerMetas,
|
||||||
}
|
}
|
||||||
pR.sendSafely(playerAckedFrame, nil, DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED, eachPlayer.Id)
|
pR.sendSafely(playerAckedFrame, nil, DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED, eachPlayer.Id)
|
||||||
case PlayerBattleStateIns.READDED_PENDING_BATTLE_COLLIDER_ACK:
|
case PlayerBattleStateIns.READDED_PENDING_BATTLE_COLLIDER_ACK:
|
||||||
playerAckedFrame := &pb.RoomDownsyncFrame{
|
playerAckedFrame := &RoomDownsyncFrame{
|
||||||
Id: pR.RenderFrameId,
|
Id: pR.RenderFrameId,
|
||||||
Players: toPbPlayers(pR.Players),
|
Players: toPbPlayers(pR.Players),
|
||||||
PlayerMetas: playerMetas,
|
PlayerMetas: playerMetas,
|
||||||
@@ -1009,14 +1030,14 @@ func (pR *Room) OnPlayerBattleColliderAcked(playerId int32) bool {
|
|||||||
return true
|
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() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
pR.PlayerSignalToCloseDict[playerId](Constants.RetCode.UnknownError, fmt.Sprintf("%v", r))
|
pR.PlayerSignalToCloseDict[playerId](Constants.RetCode.UnknownError, fmt.Sprintf("%v", r))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
pResp := &pb.WsResp{
|
pResp := &WsResp{
|
||||||
Ret: int32(Constants.RetCode.Ok),
|
Ret: int32(Constants.RetCode.Ok),
|
||||||
Act: act,
|
Act: act,
|
||||||
Rdf: roomDownsyncFrame,
|
Rdf: roomDownsyncFrame,
|
||||||
@@ -1037,16 +1058,16 @@ func (pR *Room) shouldPrefabInputFrameDownsync(renderFrameId int32) bool {
|
|||||||
return ((renderFrameId & ((1 << pR.InputScaleFrames) - 1)) == 0)
|
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,
|
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,
|
- EITHER it's due to local lag and bad network,
|
||||||
- OR there's no change w.r.t. to its prev cmd.
|
- 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 {
|
if 0 == inputFrameId && 0 == pR.InputsBuffer.Cnt {
|
||||||
currInputFrameDownsync = &pb.InputFrameDownsync{
|
currInputFrameDownsync = &InputFrameDownsync{
|
||||||
InputFrameId: 0,
|
InputFrameId: 0,
|
||||||
InputList: make([]uint64, pR.Capacity),
|
InputList: make([]uint64, pR.Capacity),
|
||||||
ConfirmedList: uint64(0),
|
ConfirmedList: uint64(0),
|
||||||
@@ -1056,9 +1077,9 @@ func (pR *Room) prefabInputFrameDownsync(inputFrameId int32) *pb.InputFrameDowns
|
|||||||
if nil == tmp {
|
if nil == tmp {
|
||||||
panic(fmt.Sprintf("Error prefabbing inputFrameDownsync: roomId=%v, InputsBuffer=%v", pR.Id, pR.InputsBufferString(false)))
|
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
|
currInputList := prevInputFrameDownsync.InputList // Would be a clone of the values
|
||||||
currInputFrameDownsync = &pb.InputFrameDownsync{
|
currInputFrameDownsync = &InputFrameDownsync{
|
||||||
InputFrameId: inputFrameId,
|
InputFrameId: inputFrameId,
|
||||||
InputList: currInputList,
|
InputList: currInputList,
|
||||||
ConfirmedList: uint64(0),
|
ConfirmedList: uint64(0),
|
||||||
@@ -1069,6 +1090,42 @@ func (pR *Room) prefabInputFrameDownsync(inputFrameId int32) *pb.InputFrameDowns
|
|||||||
return currInputFrameDownsync
|
return currInputFrameDownsync
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pR *Room) markConfirmationIfApplicable() {
|
||||||
|
inputFrameId1 := pR.LastAllConfirmedInputFrameId + 1
|
||||||
|
gap := int32(4) // This value is hardcoded and doesn't need be much bigger, because the backend side is supposed to never lag when "false == BackendDynamicsEnabled".
|
||||||
|
inputFrameId2 := inputFrameId1 + gap
|
||||||
|
if inputFrameId2 > pR.InputsBuffer.EdFrameId {
|
||||||
|
inputFrameId2 = pR.InputsBuffer.EdFrameId
|
||||||
|
}
|
||||||
|
|
||||||
|
totPlayerCnt := uint32(pR.Capacity)
|
||||||
|
allConfirmedMask := uint64((1 << totPlayerCnt) - 1)
|
||||||
|
for inputFrameId := inputFrameId1; inputFrameId < inputFrameId2; inputFrameId++ {
|
||||||
|
tmp := pR.InputsBuffer.GetByFrameId(inputFrameId)
|
||||||
|
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.(*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.(*InputFrameUpsync)
|
||||||
|
indiceInJoinIndexBooleanArr := uint32(player.JoinIndex - 1)
|
||||||
|
inputFrameDownsync.InputList[indiceInJoinIndexBooleanArr] = pR.EncodeUpsyncCmd(inputFrameUpsync)
|
||||||
|
inputFrameDownsync.ConfirmedList |= (1 << indiceInJoinIndexBooleanArr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if allConfirmedMask == inputFrameDownsync.ConfirmedList {
|
||||||
|
pR.onInputFrameDownsyncAllConfirmed(inputFrameDownsync, -1)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (pR *Room) forceConfirmationIfApplicable() uint64 {
|
func (pR *Room) forceConfirmationIfApplicable() uint64 {
|
||||||
// Force confirmation of non-all-confirmed inputFrame EXACTLY ONE AT A TIME, returns the non-confirmed mask of players, e.g. in a 4-player-battle returning 1001 means that players with JoinIndex=1 and JoinIndex=4 are non-confirmed for inputFrameId2
|
// Force confirmation of non-all-confirmed inputFrame EXACTLY ONE AT A TIME, returns the non-confirmed mask of players, e.g. in a 4-player-battle returning 1001 means that players with JoinIndex=1 and JoinIndex=4 are non-confirmed for inputFrameId2
|
||||||
renderFrameId1 := (pR.RenderFrameId - pR.NstDelayFrames) // the renderFrameId which should've been rendered on frontend
|
renderFrameId1 := (pR.RenderFrameId - pR.NstDelayFrames) // the renderFrameId which should've been rendered on frontend
|
||||||
@@ -1091,24 +1148,12 @@ func (pR *Room) forceConfirmationIfApplicable() uint64 {
|
|||||||
if nil == tmp {
|
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)))
|
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)
|
totPlayerCnt := uint32(pR.Capacity)
|
||||||
allConfirmedMask := uint64((1 << totPlayerCnt) - 1)
|
allConfirmedMask := uint64((1 << totPlayerCnt) - 1)
|
||||||
|
|
||||||
// Force confirmation of "inputFrame2"
|
// Force confirmation of "inputFrame2"
|
||||||
|
inputFrame2 := tmp.(*InputFrameDownsync)
|
||||||
oldConfirmedList := inputFrame2.ConfirmedList
|
oldConfirmedList := inputFrame2.ConfirmedList
|
||||||
unconfirmedMask := (oldConfirmedList ^ allConfirmedMask)
|
unconfirmedMask := (oldConfirmedList ^ allConfirmedMask)
|
||||||
inputFrame2.ConfirmedList = allConfirmedMask
|
inputFrame2.ConfirmedList = allConfirmedMask
|
||||||
@@ -1117,7 +1162,7 @@ func (pR *Room) forceConfirmationIfApplicable() uint64 {
|
|||||||
return unconfirmedMask
|
return unconfirmedMask
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRenderFrameId int32) {
|
func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRenderFrameId int32, spaceOffsetX, spaceOffsetY float64) {
|
||||||
if fromRenderFrameId >= toRenderFrameId {
|
if fromRenderFrameId >= toRenderFrameId {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1127,7 +1172,13 @@ func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRende
|
|||||||
allConfirmedMask := uint64((1 << totPlayerCnt) - 1)
|
allConfirmedMask := uint64((1 << totPlayerCnt) - 1)
|
||||||
|
|
||||||
for collisionSysRenderFrameId := fromRenderFrameId; collisionSysRenderFrameId < toRenderFrameId; collisionSysRenderFrameId++ {
|
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)
|
delayedInputFrameId := pR.ConvertToInputFrameId(collisionSysRenderFrameId, pR.InputDelayFrames)
|
||||||
|
var delayedInputFrame *InputFrameDownsync = nil
|
||||||
if 0 <= delayedInputFrameId {
|
if 0 <= delayedInputFrameId {
|
||||||
if delayedInputFrameId > pR.LastAllConfirmedInputFrameId {
|
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)))
|
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)))
|
||||||
@@ -1136,76 +1187,126 @@ func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRende
|
|||||||
if nil == tmp {
|
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)))
|
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.
|
// [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)
|
atomic.StoreUint64(&(delayedInputFrame.ConfirmedList), allConfirmedMask)
|
||||||
|
}
|
||||||
|
|
||||||
inputList := delayedInputFrame.InputList
|
nextRenderFrame := pR.applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, currRenderFrame, pR.CollisionSysMap)
|
||||||
// Ordered by joinIndex to guarantee determinism
|
// Update in the latest player pointers
|
||||||
for _, player := range pR.PlayersArr {
|
for playerId, playerDownsync := range nextRenderFrame.Players {
|
||||||
joinIndex := player.JoinIndex
|
pR.Players[playerId].VirtualGridX = playerDownsync.VirtualGridX
|
||||||
encodedInput := inputList[joinIndex-1]
|
pR.Players[playerId].VirtualGridY = playerDownsync.VirtualGridY
|
||||||
decodedInput := DIRECTION_DECODER[encodedInput]
|
pR.Players[playerId].Dir.Dx = playerDownsync.Dir.Dx
|
||||||
decodedInputSpeedFactor := DIRECTION_DECODER_INVERSE_LENGTH[encodedInput]
|
pR.Players[playerId].Dir.Dy = playerDownsync.Dir.Dy
|
||||||
if 0.0 == decodedInputSpeedFactor {
|
}
|
||||||
continue
|
pR.RenderFrameBuffer.Put(nextRenderFrame)
|
||||||
}
|
pR.CurDynamicsRenderFrameId++
|
||||||
baseChange := player.Speed * pR.RollbackEstimatedDt * decodedInputSpeedFactor
|
}
|
||||||
dx := baseChange * float64(decodedInput[0])
|
}
|
||||||
dy := baseChange * float64(decodedInput[1])
|
|
||||||
|
|
||||||
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
|
// TODO: Write unit-test for this function to compare with its frontend counter part
|
||||||
playerCollider := pR.CollisionSysMap[collisionPlayerIndex]
|
func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame *InputFrameDownsync, currRenderFrame *RoomDownsyncFrame, collisionSysMap map[int32]*resolv.Object) *RoomDownsyncFrame {
|
||||||
if collision := playerCollider.Check(dx, dy, "Barrier"); collision != nil {
|
nextRenderFramePlayers := make(map[int32]*PlayerDownsync, pR.Capacity)
|
||||||
changeWithCollision := collision.ContactWithObject(collision.Objects[0])
|
// Make a copy first
|
||||||
Logger.Info(fmt.Sprintf("Collided: roomId=%v, playerId=%v, orig dx=%v, orig dy=%v, proposed new dx =%v, proposed new dy=%v", pR.Id, player.Id, dx, dy, changeWithCollision.X(), changeWithCollision.Y()))
|
for playerId, currPlayerDownsync := range currRenderFrame.Players {
|
||||||
// FIXME: Use a mechanism equivalent to that of the frontend!
|
nextRenderFramePlayers[playerId] = &PlayerDownsync{
|
||||||
// dx = changeWithCollision.X()
|
Id: playerId,
|
||||||
// dy = changeWithCollision.Y()
|
VirtualGridX: currPlayerDownsync.VirtualGridX,
|
||||||
dx = 0
|
VirtualGridY: currPlayerDownsync.VirtualGridY,
|
||||||
dy = 0
|
Dir: &Direction{
|
||||||
}
|
Dx: currPlayerDownsync.Dir.Dx,
|
||||||
playerCollider.X += dx
|
Dy: currPlayerDownsync.Dir.Dy,
|
||||||
playerCollider.Y += dy
|
},
|
||||||
// Update in "collision space"
|
Speed: currPlayerDownsync.Speed,
|
||||||
playerCollider.Update()
|
BattleState: currPlayerDownsync.BattleState,
|
||||||
|
Score: currPlayerDownsync.Score,
|
||||||
|
Removed: currPlayerDownsync.Removed,
|
||||||
|
JoinIndex: currPlayerDownsync.JoinIndex,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
player.Dir.Dx = decodedInput[0]
|
toRet := &RoomDownsyncFrame{
|
||||||
player.Dir.Dy = decodedInput[1]
|
Id: currRenderFrame.Id + 1,
|
||||||
player.X += dx
|
Players: nextRenderFramePlayers,
|
||||||
player.Y += dy
|
CountdownNanos: (pR.BattleDurationNanos - int64(currRenderFrame.Id)*pR.RollbackEstimatedDtNanos),
|
||||||
|
}
|
||||||
|
|
||||||
|
if nil != delayedInputFrame {
|
||||||
|
inputList := delayedInputFrame.InputList
|
||||||
|
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]
|
||||||
|
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 := 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]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newRenderFrame := pb.RoomDownsyncFrame{
|
// handle pushbacks upon collision after all movements treated as simultaneous
|
||||||
Id: collisionSysRenderFrameId + 1,
|
for _, player := range pR.Players {
|
||||||
Players: toPbPlayers(pR.Players),
|
joinIndex := player.JoinIndex
|
||||||
CountdownNanos: (pR.BattleDurationNanos - int64(collisionSysRenderFrameId)*pR.RollbackEstimatedDtNanos),
|
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, 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: a=%v, b=%v, overlapResult=%v", ConvexPolygonStr(playerShape), ConvexPolygonStr(barrierShape), overlapResult))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pR.RenderFrameBuffer.Put(&newRenderFrame)
|
|
||||||
pR.CurDynamicsRenderFrameId++
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
func (pR *Room) inputFrameIdDebuggable(inputFrameId int32) bool {
|
||||||
return 0 == (inputFrameId % 10)
|
return 0 == (inputFrameId % 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pR *Room) refreshColliders() {
|
func (pR *Room) refreshColliders(spaceW, spaceH int32) {
|
||||||
playerColliderRadius := float64(12) // hardcoded
|
|
||||||
// Kindly note that by now, we've already got all the shapes in the tmx file into "pR.(Players | Barriers)" from "ParseTmxLayersAndGroups"
|
// Kindly note that by now, we've already got all the shapes in the tmx file into "pR.(Players | Barriers)" from "ParseTmxLayersAndGroups"
|
||||||
spaceW := pR.StageDiscreteW * pR.StageTileW
|
|
||||||
spaceH := pR.StageDiscreteH * pR.StageTileH
|
|
||||||
|
|
||||||
spaceOffsetX := float64(spaceW) * 0.5
|
minStep := (int(float64(pR.PlayerDefaultSpeed)*pR.VirtualGridToWorldRatio) << 2) // the approx minimum distance a player can move per frame in world coordinate
|
||||||
spaceOffsetY := float64(spaceH) * 0.5
|
space := resolv.NewSpace(int(spaceW), int(spaceH), minStep, minStep) // allocate a new collision space everytime after a battle is settled
|
||||||
|
|
||||||
minStep := int(3) // the approx minimum distance a player can move per frame
|
|
||||||
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 {
|
for _, player := range pR.Players {
|
||||||
playerCollider := resolv.NewObject(player.X+spaceOffsetX, player.Y+spaceOffsetY, playerColliderRadius*2, playerColliderRadius*2)
|
wx, wy := VirtualGridToWorldPos(player.VirtualGridX, player.VirtualGridY, pR.VirtualGridToWorldRatio)
|
||||||
playerColliderShape := resolv.NewCircle(0, 0, playerColliderRadius*2)
|
playerCollider := GenerateRectCollider(wx, wy, player.ColliderRadius*2, player.ColliderRadius*2, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "Player")
|
||||||
playerCollider.SetShape(playerColliderShape)
|
|
||||||
space.Add(playerCollider)
|
space.Add(playerCollider)
|
||||||
// Keep track of the collider in "pR.CollisionSysMap"
|
// Keep track of the collider in "pR.CollisionSysMap"
|
||||||
joinIndex := player.JoinIndex
|
joinIndex := player.JoinIndex
|
||||||
@@ -1215,31 +1316,8 @@ func (pR *Room) refreshColliders() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, barrier := range pR.Barriers {
|
for _, barrier := range pR.Barriers {
|
||||||
|
boundaryUnaligned := barrier.Boundary
|
||||||
var w float64 = 0
|
barrierCollider := GenerateConvexPolygonCollider(boundaryUnaligned, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "Barrier")
|
||||||
var h float64 = 0
|
|
||||||
|
|
||||||
for i, pi := range barrier.Boundary.Points {
|
|
||||||
for j, pj := range barrier.Boundary.Points {
|
|
||||||
if i == j {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if math.Abs(pj.X-pi.X) > w {
|
|
||||||
w = math.Abs(pj.X - pi.X)
|
|
||||||
}
|
|
||||||
if math.Abs(pj.Y-pi.Y) > h {
|
|
||||||
h = math.Abs(pj.Y - pi.Y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
barrierColliderShape := resolv.NewConvexPolygon()
|
|
||||||
for _, p := range barrier.Boundary.Points {
|
|
||||||
barrierColliderShape.AddPoints(p.X, p.Y)
|
|
||||||
}
|
|
||||||
|
|
||||||
barrierCollider := resolv.NewObject(barrier.Boundary.Anchor.X+spaceOffsetX, barrier.Boundary.Anchor.Y+spaceOffsetY, w, h, "Barrier")
|
|
||||||
barrierCollider.SetShape(barrierColliderShape)
|
|
||||||
space.Add(barrierCollider)
|
space.Add(barrierCollider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1264
battle_srv/protos/room_downsync_frame.pb.go
Normal file
@@ -1,7 +1,7 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "server/common"
|
. "battle_srv/common"
|
||||||
|
|
||||||
_ "github.com/go-sql-driver/mysql"
|
_ "github.com/go-sql-driver/mysql"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
. "battle_srv/common"
|
||||||
"fmt"
|
"fmt"
|
||||||
. "server/common"
|
|
||||||
|
|
||||||
"github.com/go-redis/redis"
|
"github.com/go-redis/redis"
|
||||||
_ "github.com/go-sql-driver/mysql"
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
package ws
|
package ws
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
. "battle_srv/common"
|
||||||
|
"battle_srv/models"
|
||||||
|
pb "battle_srv/protos"
|
||||||
"container/heap"
|
"container/heap"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -8,14 +11,12 @@ import (
|
|||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"net/http"
|
"net/http"
|
||||||
. "server/common"
|
|
||||||
"server/models"
|
|
||||||
pb "server/pb_output"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
. "dnmshared"
|
. "dnmshared"
|
||||||
|
"runtime/debug"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -104,7 +105,7 @@ func Serve(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
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{
|
bciFrame := &pb.BattleColliderInfo{
|
||||||
BoundRoomId: pRoom.Id,
|
BoundRoomId: pRoom.Id,
|
||||||
StageName: pRoom.StageName,
|
StageName: pRoom.StageName,
|
||||||
StrToVec2DListMap: models.ToPbVec2DListMap(pRoom.RawBattleStrToVec2DListMap),
|
StrToVec2DListMap: pRoom.RawBattleStrToVec2DListMap,
|
||||||
StrToPolygon2DListMap: models.ToPbPolygon2DListMap(pRoom.RawBattleStrToPolygon2DListMap),
|
StrToPolygon2DListMap: pRoom.RawBattleStrToPolygon2DListMap,
|
||||||
StageDiscreteW: pRoom.StageDiscreteW,
|
StageDiscreteW: pRoom.StageDiscreteW,
|
||||||
StageDiscreteH: pRoom.StageDiscreteH,
|
StageDiscreteH: pRoom.StageDiscreteH,
|
||||||
StageTileW: pRoom.StageTileW,
|
StageTileW: pRoom.StageTileW,
|
||||||
@@ -263,9 +264,11 @@ func Serve(c *gin.Context) {
|
|||||||
InputFrameUpsyncDelayTolerance: pRoom.InputFrameUpsyncDelayTolerance,
|
InputFrameUpsyncDelayTolerance: pRoom.InputFrameUpsyncDelayTolerance,
|
||||||
MaxChasingRenderFramesPerUpdate: pRoom.MaxChasingRenderFramesPerUpdate,
|
MaxChasingRenderFramesPerUpdate: pRoom.MaxChasingRenderFramesPerUpdate,
|
||||||
PlayerBattleState: pThePlayer.BattleState, // For frontend to know whether it's rejoining
|
PlayerBattleState: pThePlayer.BattleState, // For frontend to know whether it's rejoining
|
||||||
RollbackEstimatedDt: pRoom.RollbackEstimatedDt,
|
|
||||||
RollbackEstimatedDtMillis: pRoom.RollbackEstimatedDtMillis,
|
RollbackEstimatedDtMillis: pRoom.RollbackEstimatedDtMillis,
|
||||||
RollbackEstimatedDtNanos: pRoom.RollbackEstimatedDtNanos,
|
RollbackEstimatedDtNanos: pRoom.RollbackEstimatedDtNanos,
|
||||||
|
|
||||||
|
WorldToVirtualGridRatio: pRoom.WorldToVirtualGridRatio,
|
||||||
|
VirtualGridToWorldRatio: pRoom.VirtualGridToWorldRatio,
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := &pb.WsResp{
|
resp := &pb.WsResp{
|
||||||
@@ -354,7 +357,7 @@ func Serve(c *gin.Context) {
|
|||||||
receivingLoopAgainstPlayer := func() error {
|
receivingLoopAgainstPlayer := func() error {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
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))
|
Logger.Info("Goroutine `receivingLoopAgainstPlayer` is stopped for:", zap.Any("playerId", playerId), zap.Any("roomId", pRoom.Id))
|
||||||
}()
|
}()
|
||||||
|
BIN
charts/AvoidingFloatingPointAccumulationErr.jpg
Normal file
After Width: | Height: | Size: 144 KiB |
1
charts/DelayNoMore.drawio
Normal file
BIN
charts/InputDelayIntro.jpg
Normal file
After Width: | Height: | Size: 123 KiB |
4
charts/README.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# GIF creation cmd reference
|
||||||
|
```
|
||||||
|
ffmpeg -ss 12 -t 13 -i input.mp4 -vf "fps=10,scale=480:-1" -loop 0 output.gif
|
||||||
|
```
|
BIN
charts/RollbackAndChase.jpg
Normal file
After Width: | Height: | Size: 190 KiB |
BIN
charts/along_wall_interaction_with_reconnection.gif
Normal file
After Width: | Height: | Size: 2.7 MiB |
Before Width: | Height: | Size: 684 KiB After Width: | Height: | Size: 684 KiB |
@@ -3,11 +3,11 @@ module viscol
|
|||||||
go 1.19
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
dnmshared v0.0.0
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||||
github.com/hajimehoshi/ebiten/v2 v2.4.7
|
github.com/hajimehoshi/ebiten/v2 v2.4.7
|
||||||
github.com/solarlune/resolv v0.5.1
|
github.com/solarlune/resolv v0.5.1
|
||||||
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69
|
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69
|
||||||
dnmshared v0.0.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -22,6 +22,7 @@ require (
|
|||||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect
|
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect
|
||||||
golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105 // indirect
|
golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 // indirect
|
golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 // indirect
|
||||||
|
google.golang.org/protobuf v1.28.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace dnmshared => ../dnmshared
|
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/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 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
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 h1:y7zcy02/UgO24IL3COqYtrRZzhRucNBtmCo/SNU648k=
|
||||||
github.com/hajimehoshi/bitmapfont/v2 v2.2.1/go.mod h1:wjrYAy8vKgj9JsFgnYAOK346/uvE22TlmqouzdnYIs0=
|
github.com/hajimehoshi/bitmapfont/v2 v2.2.1/go.mod h1:wjrYAy8vKgj9JsFgnYAOK346/uvE22TlmqouzdnYIs0=
|
||||||
github.com/hajimehoshi/ebiten/v2 v2.4.7 h1:XuvB7R0Rbw/O7g6vNU8gqr5b9e7MNhhAONMSsyreLDI=
|
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/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-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-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=
|
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=
|
||||||
|
@@ -85,8 +85,9 @@ type Game struct {
|
|||||||
|
|
||||||
func NewGame() *Game {
|
func NewGame() *Game {
|
||||||
|
|
||||||
// stageName := "simple" // Use this for calibration
|
// stageName := "simple" // Use this for calibration in isometric orientation
|
||||||
stageName := "richsoil"
|
// stageName := "richsoil"
|
||||||
|
stageName := "dungeon"
|
||||||
stageDiscreteW, stageDiscreteH, stageTileW, stageTileH, playerPosMap, barrierMap, err := parseStage(stageName)
|
stageDiscreteW, stageDiscreteH, stageTileW, stageTileH, playerPosMap, barrierMap, err := parseStage(stageName)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -160,7 +161,6 @@ func (g *Game) DebugDraw(screen *ebiten.Image, space *resolv.Space) {
|
|||||||
ebitenutil.DrawLine(screen, cx, cy+ch, cx, cy, drawColor)
|
ebitenutil.DrawLine(screen, cx, cy+ch, cx, cy, drawColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) Layout(w, h int) (int, int) {
|
func (g *Game) Layout(w, h int) (int, int) {
|
||||||
|
@@ -2,14 +2,12 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
. "dnmshared"
|
. "dnmshared"
|
||||||
|
. "dnmshared/sharedprotos"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
|
||||||
"github.com/solarlune/resolv"
|
"github.com/solarlune/resolv"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"math"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type WorldColliderDisplay struct {
|
type WorldColliderDisplay struct {
|
||||||
@@ -22,7 +20,7 @@ func (world *WorldColliderDisplay) Init() {
|
|||||||
|
|
||||||
func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTileW, stageTileH int32, playerPosMap StrToVec2DListMap, barrierMap StrToPolygon2DListMap) *WorldColliderDisplay {
|
func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTileW, stageTileH int32, playerPosMap StrToVec2DListMap, barrierMap StrToPolygon2DListMap) *WorldColliderDisplay {
|
||||||
|
|
||||||
playerList := *(playerPosMap["PlayerStartingPos"])
|
playerPosList := *(playerPosMap["PlayerStartingPos"])
|
||||||
barrierList := *(barrierMap["Barrier"])
|
barrierList := *(barrierMap["Barrier"])
|
||||||
|
|
||||||
world := &WorldColliderDisplay{Game: game}
|
world := &WorldColliderDisplay{Game: game}
|
||||||
@@ -35,52 +33,58 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi
|
|||||||
spaceOffsetX := float64(spaceW) * 0.5
|
spaceOffsetX := float64(spaceW) * 0.5
|
||||||
spaceOffsetY := float64(spaceH) * 0.5
|
spaceOffsetY := float64(spaceH) * 0.5
|
||||||
|
|
||||||
// TODO: Move collider y-axis transformation to a "dnmshared"
|
virtualGridToWorldRatio := 0.1
|
||||||
playerColliderRadius := float64(12) // hardcoded
|
playerDefaultSpeed := 20
|
||||||
space := resolv.NewSpace(int(spaceW), int(spaceH), 16, 16)
|
minStep := (int(float64(playerDefaultSpeed)*virtualGridToWorldRatio) << 2)
|
||||||
for _, player := range playerList {
|
playerColliderRadius := float64(16)
|
||||||
playerCollider := resolv.NewObject(player.X+spaceOffsetX, player.Y+spaceOffsetY, playerColliderRadius*2, playerColliderRadius*2, "Player")
|
playerColliders := make([]*resolv.Object, len(playerPosList.Eles))
|
||||||
playerColliderShape := resolv.NewCircle(0, 0, playerColliderRadius*2)
|
space := resolv.NewSpace(int(spaceW), int(spaceH), minStep, minStep)
|
||||||
playerCollider.SetShape(playerColliderShape)
|
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)
|
space.Add(playerCollider)
|
||||||
}
|
}
|
||||||
|
|
||||||
barrierLocalId := 0
|
barrierLocalId := 0
|
||||||
for _, barrierUnaligned := range barrierList {
|
for _, barrierUnaligned := range barrierList.Eles {
|
||||||
barrier := AlignPolygon2DToBoundingBox(barrierUnaligned)
|
barrierCollider := GenerateConvexPolygonCollider(barrierUnaligned, spaceOffsetX, spaceOffsetY, "Barrier")
|
||||||
|
Logger.Info(fmt.Sprintf("Added barrier: shape=%v", ConvexPolygonStr(barrierCollider.Shape.(*resolv.ConvexPolygon))))
|
||||||
var w float64 = 0
|
|
||||||
var h float64 = 0
|
|
||||||
|
|
||||||
for i, pi := range barrier.Points {
|
|
||||||
for j, pj := range barrier.Points {
|
|
||||||
if i == j {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if math.Abs(pj.X-pi.X) > w {
|
|
||||||
w = math.Abs(pj.X - pi.X)
|
|
||||||
}
|
|
||||||
if math.Abs(pj.Y-pi.Y) > h {
|
|
||||||
h = math.Abs(pj.Y - pi.Y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
barrierColliderShape := resolv.NewConvexPolygon()
|
|
||||||
for i := 0; i < len(barrier.Points); i++ {
|
|
||||||
p := barrier.Points[i]
|
|
||||||
barrierColliderShape.AddPoints(p.X, p.Y)
|
|
||||||
}
|
|
||||||
|
|
||||||
barrierCollider := resolv.NewObject(barrier.Anchor.X+spaceOffsetX, barrier.Anchor.Y+spaceOffsetY, w, h, "Barrier")
|
|
||||||
barrierCollider.SetShape(barrierColliderShape)
|
|
||||||
|
|
||||||
space.Add(barrierCollider)
|
space.Add(barrierCollider)
|
||||||
|
|
||||||
barrierLocalId++
|
barrierLocalId++
|
||||||
}
|
}
|
||||||
|
|
||||||
world.Space = space
|
world.Space = space
|
||||||
|
|
||||||
|
moveToCollide := false
|
||||||
|
if moveToCollide {
|
||||||
|
newVx, newVy := int32(-2959), int32(-2261)
|
||||||
|
effPushback := Vec2D{X: float64(0), Y: float64(0)}
|
||||||
|
toTestPlayerCollider := playerColliders[0]
|
||||||
|
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.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
|
return world
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,9 +96,8 @@ func (world *WorldColliderDisplay) Draw(screen *ebiten.Image) {
|
|||||||
|
|
||||||
for _, o := range world.Space.Objects() {
|
for _, o := range world.Space.Objects() {
|
||||||
if o.HasTags("Player") {
|
if o.HasTags("Player") {
|
||||||
circle := o.Shape.(*resolv.Circle)
|
|
||||||
drawColor := color.RGBA{0, 255, 0, 255}
|
drawColor := color.RGBA{0, 255, 0, 255}
|
||||||
ebitenutil.DrawCircle(screen, circle.X, circle.Y, circle.Radius, drawColor)
|
DrawPolygon(screen, o.Shape.(*resolv.ConvexPolygon), drawColor)
|
||||||
} else {
|
} else {
|
||||||
drawColor := color.RGBA{60, 60, 60, 255}
|
drawColor := color.RGBA{60, 60, 60, 255}
|
||||||
DrawPolygon(screen, o.Shape.(*resolv.ConvexPolygon), drawColor)
|
DrawPolygon(screen, o.Shape.(*resolv.ConvexPolygon), drawColor)
|
||||||
|
@@ -1,43 +1,46 @@
|
|||||||
package dnmshared
|
package dnmshared
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
. "dnmshared/sharedprotos"
|
||||||
"math"
|
"math"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Use type `float64` for json unmarshalling of numbers.
|
func NormVec2D(dx, dy float64) Vec2D {
|
||||||
type Direction struct {
|
return Vec2D{X: dy, Y: -dx}
|
||||||
Dx int32 `json:"dx,omitempty"`
|
|
||||||
Dy int32 `json:"dy,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Vec2D struct {
|
func AlignPolygon2DToBoundingBox(input *Polygon2D) *Polygon2D {
|
||||||
X float64 `json:"x,omitempty"`
|
// Transform again to put "anchor" at the top-left point of the bounding box for "resolv"
|
||||||
Y float64 `json:"y,omitempty"`
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Polygon2D struct {
|
// Now "input.Anchor" should move to "input.Anchor+boundingBoxTL", thus "boundingBoxTL" is also the value of the negative diff for all "input.Points"
|
||||||
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.
|
output := &Polygon2D{
|
||||||
Points []*Vec2D `json:"-"`
|
Anchor: &Vec2D{
|
||||||
|
X: input.Anchor.X + boundingBoxTL.X,
|
||||||
|
Y: input.Anchor.Y + boundingBoxTL.Y,
|
||||||
|
},
|
||||||
|
Points: make([]*Vec2D, len(input.Points)),
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
for i, p := range input.Points {
|
||||||
When used to represent a "polyline directly drawn in a `Tmx file`", we can initialize both "Anchor" and "Points" simultaneously.
|
output.Points[i] = &Vec2D{
|
||||||
|
X: p.X - boundingBoxTL.X,
|
||||||
|
Y: p.Y - boundingBoxTL.Y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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`".
|
return output
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Distance(pt1 *Vec2D, pt2 *Vec2D) float64 {
|
func Distance(pt1 *Vec2D, pt2 *Vec2D) float64 {
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
module tiled
|
module dnmshared
|
||||||
|
|
||||||
go 1.19
|
go 1.19
|
||||||
|
255
dnmshared/resolv_helper.go
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
package dnmshared
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "dnmshared/sharedprotos"
|
||||||
|
"fmt"
|
||||||
|
"github.com/kvartborg/vector"
|
||||||
|
"github.com/solarlune/resolv"
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ConvexPolygonStr(body *resolv.ConvexPolygon) string {
|
||||||
|
var s []string = make([]string, len(body.Points))
|
||||||
|
for i, p := range body.Points {
|
||||||
|
s[i] = fmt.Sprintf("[%.2f, %.2f]", p[0]+body.X, p[1]+body.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("{\n%s\n}", strings.Join(s, ",\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateRectCollider(origX, origY, w, h, spaceOffsetX, spaceOffsetY float64, tag string) *resolv.Object {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateConvexPolygonCollider(unalignedSrc *Polygon2D, spaceOffsetX, spaceOffsetY float64, tag string) *resolv.Object {
|
||||||
|
aligned := AlignPolygon2DToBoundingBox(unalignedSrc)
|
||||||
|
var w, h float64 = 0, 0
|
||||||
|
|
||||||
|
shape := resolv.NewConvexPolygon()
|
||||||
|
for i, pi := range aligned.Points {
|
||||||
|
for j, pj := range aligned.Points {
|
||||||
|
if i == j {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if math.Abs(pj.X-pi.X) > w {
|
||||||
|
w = math.Abs(pj.X - pi.X)
|
||||||
|
}
|
||||||
|
if math.Abs(pj.Y-pi.Y) > h {
|
||||||
|
h = math.Abs(pj.Y - pi.Y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(aligned.Points); i++ {
|
||||||
|
p := aligned.Points[i]
|
||||||
|
shape.AddPoints(p.X, p.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
collider := resolv.NewObject(aligned.Anchor.X+spaceOffsetX, aligned.Anchor.Y+spaceOffsetY, w, h, tag)
|
||||||
|
collider.SetShape(shape)
|
||||||
|
|
||||||
|
return collider
|
||||||
|
}
|
||||||
|
|
||||||
|
func CalcPushbacks(oldDx, oldDy float64, playerShape, barrierShape *resolv.ConvexPolygon) (bool, float64, float64, *SatResult) {
|
||||||
|
origX, origY := playerShape.Position()
|
||||||
|
defer func() {
|
||||||
|
playerShape.SetPosition(origX, origY)
|
||||||
|
}()
|
||||||
|
playerShape.SetPosition(origX+oldDx, origY+oldDy)
|
||||||
|
overlapResult := &SatResult{
|
||||||
|
Overlap: 0,
|
||||||
|
OverlapX: 0,
|
||||||
|
OverlapY: 0,
|
||||||
|
AContainedInB: true,
|
||||||
|
BContainedInA: true,
|
||||||
|
Axis: vector.Vector{0, 0},
|
||||||
|
}
|
||||||
|
if overlapped := IsPolygonPairOverlapped(playerShape, barrierShape, overlapResult); overlapped {
|
||||||
|
pushbackX, pushbackY := overlapResult.Overlap*overlapResult.OverlapX, overlapResult.Overlap*overlapResult.OverlapY
|
||||||
|
return true, pushbackX, pushbackY, overlapResult
|
||||||
|
} else {
|
||||||
|
return false, 0, 0, overlapResult
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SatResult struct {
|
||||||
|
Overlap float64
|
||||||
|
OverlapX float64
|
||||||
|
OverlapY float64
|
||||||
|
AContainedInB bool
|
||||||
|
BContainedInA bool
|
||||||
|
Axis vector.Vector
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsPolygonPairOverlapped(a, b *resolv.ConvexPolygon, result *SatResult) bool {
|
||||||
|
aCnt, bCnt := len(a.Points), len(b.Points)
|
||||||
|
// Single point case
|
||||||
|
if 1 == aCnt && 1 == bCnt {
|
||||||
|
if nil != result {
|
||||||
|
result.Overlap = 0
|
||||||
|
}
|
||||||
|
return a.Points[0].X() == b.Points[0].X() && a.Points[0].Y() == b.Points[0].Y()
|
||||||
|
}
|
||||||
|
|
||||||
|
if 1 < aCnt {
|
||||||
|
for _, axis := range a.SATAxes() {
|
||||||
|
if isPolygonPairSeparatedByDir(a, b, axis.Unit(), result) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if 1 < bCnt {
|
||||||
|
for _, axis := range b.SATAxes() {
|
||||||
|
if isPolygonPairSeparatedByDir(a, b, axis.Unit(), result) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e vector.Vector, result *SatResult) bool {
|
||||||
|
/*
|
||||||
|
[WARNING] This function is deliberately made private, it shouldn't be used alone (i.e. not along the norms of a polygon), otherwise the pushbacks calculated would be meaningless.
|
||||||
|
|
||||||
|
Consider the following example
|
||||||
|
a: {
|
||||||
|
anchor: [1337.19 1696.74]
|
||||||
|
points: [[0 0] [24 0] [24 24] [0 24]]
|
||||||
|
},
|
||||||
|
b: {
|
||||||
|
anchor: [1277.72 1570.56]
|
||||||
|
points: [[642.57 319.16] [0 319.16] [5.73 0] [643.75 0.90]]
|
||||||
|
}
|
||||||
|
|
||||||
|
e = (-2.98, 1.49).Unit()
|
||||||
|
*/
|
||||||
|
|
||||||
|
var aStart, aEnd, bStart, bEnd float64 = math.MaxFloat64, -math.MaxFloat64, math.MaxFloat64, -math.MaxFloat64
|
||||||
|
for _, p := range a.Points {
|
||||||
|
dot := (p.X()+a.X)*e.X() + (p.Y()+a.Y)*e.Y()
|
||||||
|
|
||||||
|
if aStart > dot {
|
||||||
|
aStart = dot
|
||||||
|
}
|
||||||
|
|
||||||
|
if aEnd < dot {
|
||||||
|
aEnd = dot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range b.Points {
|
||||||
|
dot := (p.X()+b.X)*e.X() + (p.Y()+b.Y)*e.Y()
|
||||||
|
|
||||||
|
if bStart > dot {
|
||||||
|
bStart = dot
|
||||||
|
}
|
||||||
|
|
||||||
|
if bEnd < dot {
|
||||||
|
bEnd = dot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if aStart > bEnd || aEnd < bStart {
|
||||||
|
// Separated by unit vector "e"
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if nil != result {
|
||||||
|
result.Axis = e
|
||||||
|
overlap := float64(0)
|
||||||
|
|
||||||
|
if aStart < bStart {
|
||||||
|
result.AContainedInB = false
|
||||||
|
|
||||||
|
if aEnd < bEnd {
|
||||||
|
overlap = aEnd - bStart
|
||||||
|
result.BContainedInA = false
|
||||||
|
} else {
|
||||||
|
option1 := aEnd - bStart
|
||||||
|
option2 := bEnd - aStart
|
||||||
|
if option1 < option2 {
|
||||||
|
overlap = option1
|
||||||
|
} else {
|
||||||
|
overlap = -option2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.BContainedInA = false
|
||||||
|
|
||||||
|
if aEnd > bEnd {
|
||||||
|
overlap = aStart - bEnd
|
||||||
|
result.AContainedInB = false
|
||||||
|
} else {
|
||||||
|
option1 := aEnd - bStart
|
||||||
|
option2 := bEnd - aStart
|
||||||
|
if option1 < option2 {
|
||||||
|
overlap = option1
|
||||||
|
} else {
|
||||||
|
overlap = -option2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentOverlap := result.Overlap
|
||||||
|
absoluteOverlap := overlap
|
||||||
|
if overlap < 0 {
|
||||||
|
absoluteOverlap = -overlap
|
||||||
|
}
|
||||||
|
|
||||||
|
if 0 == currentOverlap || currentOverlap > absoluteOverlap {
|
||||||
|
var sign float64 = 1
|
||||||
|
if overlap < 0 {
|
||||||
|
sign = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Overlap = absoluteOverlap
|
||||||
|
result.OverlapX = e.X() * sign
|
||||||
|
result.OverlapY = e.Y() * sign
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
@@ -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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/zlib"
|
"compress/zlib"
|
||||||
|
. "dnmshared/sharedprotos"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -173,8 +174,6 @@ func (l *TmxLayer) decodeBase64() ([]uint32, error) {
|
|||||||
return gids, nil
|
return gids, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Vec2DList []*Vec2D
|
|
||||||
type Polygon2DList []*Polygon2D
|
|
||||||
type StrToVec2DListMap map[string]*Vec2DList
|
type StrToVec2DListMap map[string]*Vec2DList
|
||||||
type StrToPolygon2DListMap map[string]*Polygon2DList
|
type StrToPolygon2DListMap map[string]*Polygon2DList
|
||||||
|
|
||||||
@@ -233,10 +232,8 @@ func tsxPolylineToOffsetsWrtTileCenter(pTmxMapIns *TmxMap, singleObjInTsxFile *T
|
|||||||
pointsCount := len(singleValueArray)
|
pointsCount := len(singleValueArray)
|
||||||
|
|
||||||
thePolygon2DFromPolyline := &Polygon2D{
|
thePolygon2DFromPolyline := &Polygon2D{
|
||||||
Anchor: nil,
|
Anchor: nil,
|
||||||
Points: make([]*Vec2D, pointsCount),
|
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 {
|
if _, ok := theStrToPolygon2DListMap[key]; ok {
|
||||||
pThePolygon2DList = theStrToPolygon2DListMap[key]
|
pThePolygon2DList = theStrToPolygon2DListMap[key]
|
||||||
} else {
|
} else {
|
||||||
thePolygon2DList := make(Polygon2DList, 0)
|
pThePolygon2DList = &Polygon2DList{
|
||||||
theStrToPolygon2DListMap[key] = &thePolygon2DList
|
Eles: make([]*Polygon2D, 0),
|
||||||
pThePolygon2DList = theStrToPolygon2DListMap[key]
|
}
|
||||||
|
theStrToPolygon2DListMap[key] = pThePolygon2DList
|
||||||
}
|
}
|
||||||
|
|
||||||
thePolygon2DFromPolyline, err := tsxPolylineToOffsetsWrtTileCenter(pTmxMapIns, singleObj, singleObj.Polyline, pTsxIns)
|
thePolygon2DFromPolyline, err := tsxPolylineToOffsetsWrtTileCenter(pTmxMapIns, singleObj, singleObj.Polyline, pTsxIns)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
*pThePolygon2DList = append(*pThePolygon2DList, thePolygon2DFromPolyline)
|
pThePolygon2DList.Eles = append(pThePolygon2DList.Eles, thePolygon2DFromPolyline)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -352,8 +350,10 @@ func ParseTmxLayersAndGroups(pTmxMapIns *TmxMap, gidBoundariesMap map[int]StrToP
|
|||||||
var pTheVec2DListToCache *Vec2DList
|
var pTheVec2DListToCache *Vec2DList
|
||||||
_, ok := toRetStrToVec2DListMap[objGroup.Name]
|
_, ok := toRetStrToVec2DListMap[objGroup.Name]
|
||||||
if false == ok {
|
if false == ok {
|
||||||
theVec2DListToCache := make(Vec2DList, 0)
|
pTheVec2DListToCache = &Vec2DList{
|
||||||
toRetStrToVec2DListMap[objGroup.Name] = &theVec2DListToCache
|
Eles: make([]*Vec2D, 0),
|
||||||
|
}
|
||||||
|
toRetStrToVec2DListMap[objGroup.Name] = pTheVec2DListToCache
|
||||||
}
|
}
|
||||||
pTheVec2DListToCache = toRetStrToVec2DListMap[objGroup.Name]
|
pTheVec2DListToCache = toRetStrToVec2DListMap[objGroup.Name]
|
||||||
for _, singleObjInTmxFile := range objGroup.Objects {
|
for _, singleObjInTmxFile := range objGroup.Objects {
|
||||||
@@ -362,17 +362,18 @@ func ParseTmxLayersAndGroups(pTmxMapIns *TmxMap, gidBoundariesMap map[int]StrToP
|
|||||||
Y: singleObjInTmxFile.Y,
|
Y: singleObjInTmxFile.Y,
|
||||||
}
|
}
|
||||||
thePosInWorld := pTmxMapIns.continuousObjLayerOffsetToContinuousMapNodePos(theUntransformedPos)
|
thePosInWorld := pTmxMapIns.continuousObjLayerOffsetToContinuousMapNodePos(theUntransformedPos)
|
||||||
*pTheVec2DListToCache = append(*pTheVec2DListToCache, &thePosInWorld)
|
pTheVec2DListToCache.Eles = append(pTheVec2DListToCache.Eles, &thePosInWorld)
|
||||||
}
|
}
|
||||||
case "Barrier":
|
case "Barrier":
|
||||||
// Note that in this case, the "Polygon2D.Anchor" of each "TmxOrTsxObject" is exactly overlapping with "Polygon2D.Points[0]".
|
// Note that in this case, the "Polygon2D.Anchor" of each "TmxOrTsxObject" is exactly overlapping with "Polygon2D.Points[0]".
|
||||||
var pThePolygon2DListToCache *Polygon2DList
|
var pThePolygon2DListToCache *Polygon2DList
|
||||||
_, ok := toRetStrToPolygon2DListMap[objGroup.Name]
|
_, ok := toRetStrToPolygon2DListMap[objGroup.Name]
|
||||||
if false == ok {
|
if false == ok {
|
||||||
thePolygon2DListToCache := make(Polygon2DList, 0)
|
pThePolygon2DListToCache = &Polygon2DList{
|
||||||
toRetStrToPolygon2DListMap[objGroup.Name] = &thePolygon2DListToCache
|
Eles: make([]*Polygon2D, 0),
|
||||||
|
}
|
||||||
|
toRetStrToPolygon2DListMap[objGroup.Name] = pThePolygon2DListToCache
|
||||||
}
|
}
|
||||||
pThePolygon2DListToCache = toRetStrToPolygon2DListMap[objGroup.Name]
|
|
||||||
|
|
||||||
for _, singleObjInTmxFile := range objGroup.Objects {
|
for _, singleObjInTmxFile := range objGroup.Objects {
|
||||||
if nil == singleObjInTmxFile.Polyline {
|
if nil == singleObjInTmxFile.Polyline {
|
||||||
@@ -386,7 +387,7 @@ func ParseTmxLayersAndGroups(pTmxMapIns *TmxMap, gidBoundariesMap map[int]StrToP
|
|||||||
if nil != err {
|
if nil != err {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
*pThePolygon2DListToCache = append(*pThePolygon2DListToCache, thePolygon2DInWorld)
|
pThePolygon2DListToCache.Eles = append(pThePolygon2DListToCache.Eles, thePolygon2DInWorld)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
@@ -405,6 +406,12 @@ type TileRectilinearSize struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pTmxMapIns *TmxMap) continuousObjLayerVecToContinuousMapNodeVec(continuousObjLayerVec *Vec2D) Vec2D {
|
func (pTmxMapIns *TmxMap) continuousObjLayerVecToContinuousMapNodeVec(continuousObjLayerVec *Vec2D) Vec2D {
|
||||||
|
if "orthogonal" == pTmxMapIns.Orientation {
|
||||||
|
return Vec2D{
|
||||||
|
X: continuousObjLayerVec.X,
|
||||||
|
Y: -continuousObjLayerVec.Y,
|
||||||
|
}
|
||||||
|
}
|
||||||
var tileRectilinearSize TileRectilinearSize
|
var tileRectilinearSize TileRectilinearSize
|
||||||
tileRectilinearSize.Width = float64(pTmxMapIns.TileWidth)
|
tileRectilinearSize.Width = float64(pTmxMapIns.TileWidth)
|
||||||
tileRectilinearSize.Height = float64(pTmxMapIns.TileHeight)
|
tileRectilinearSize.Height = float64(pTmxMapIns.TileHeight)
|
||||||
@@ -427,55 +434,24 @@ func (pTmxMapIns *TmxMap) continuousObjLayerVecToContinuousMapNodeVec(continuous
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pTmxMapIns *TmxMap) continuousObjLayerOffsetToContinuousMapNodePos(continuousObjLayerOffset *Vec2D) Vec2D {
|
func (pTmxMapIns *TmxMap) continuousObjLayerOffsetToContinuousMapNodePos(continuousObjLayerOffset *Vec2D) Vec2D {
|
||||||
layerOffset := Vec2D{
|
var layerOffset Vec2D
|
||||||
X: 0,
|
if "orthogonal" == pTmxMapIns.Orientation {
|
||||||
Y: float64(pTmxMapIns.Height*pTmxMapIns.TileHeight) * 0.5,
|
layerOffset = Vec2D{
|
||||||
|
X: -float64(pTmxMapIns.Width*pTmxMapIns.TileWidth) * 0.5,
|
||||||
|
Y: float64(pTmxMapIns.Height*pTmxMapIns.TileHeight) * 0.5,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// "isometric" == pTmxMapIns.Orientation
|
||||||
|
layerOffset = Vec2D{
|
||||||
|
X: 0,
|
||||||
|
Y: float64(pTmxMapIns.Height*pTmxMapIns.TileHeight) * 0.5,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
calibratedVec := continuousObjLayerOffset
|
convertedVec := pTmxMapIns.continuousObjLayerVecToContinuousMapNodeVec(continuousObjLayerOffset)
|
||||||
convertedVec := pTmxMapIns.continuousObjLayerVecToContinuousMapNodeVec(calibratedVec)
|
|
||||||
|
|
||||||
toRet := Vec2D{
|
return Vec2D{
|
||||||
X: layerOffset.X + convertedVec.X,
|
X: layerOffset.X + convertedVec.X,
|
||||||
Y: layerOffset.Y + convertedVec.Y,
|
Y: layerOffset.Y + convertedVec.Y,
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.0.5",
|
|
||||||
"uuid": "bd514df4-095e-4088-9060-d99397a29a4f",
|
|
||||||
"isPlugin": true,
|
|
||||||
"loadPluginInWeb": true,
|
|
||||||
"loadPluginInNative": true,
|
|
||||||
"loadPluginInEditor": false,
|
|
||||||
"subMetas": {}
|
|
||||||
}
|
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"ver": "1.0.1",
|
"ver": "1.0.1",
|
||||||
"uuid": "51c54820-d753-4be8-a855-5760eed8f7ef",
|
"uuid": "5650b341-a420-4d79-a969-39a461e13378",
|
||||||
"isSubpackage": false,
|
"isSubpackage": false,
|
||||||
"subpackageName": "",
|
"subpackageName": "",
|
||||||
"subMetas": {}
|
"subMetas": {}
|
65
frontend/assets/resources/map/dungeon/map.tmx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<map version="1.2" tiledversion="1.2.3" orientation="orthogonal" renderorder="right-down" width="64" height="64" tilewidth="16" tileheight="16" infinite="0" nextlayerid="2" nextobjectid="8">
|
||||||
|
<tileset firstgid="1" source="tiles0.tsx"/>
|
||||||
|
<tileset firstgid="65" source="tiles1.tsx"/>
|
||||||
|
<layer id="1" name="Ground" width="64" height="64">
|
||||||
|
<data encoding="base64" compression="zlib">
|
||||||
|
eJzt0jEOwjAUREGLkiLmALRU3In7HwMKkFCUCCwiNrKnmCbVvh8fSikHAAAA/qruYEOr84Zd+vM9re0v+vXr1z9S/7f0l3KZ9dXZ91r67QeAXxyf0jv0Z/tvO9iS7F+T3ucG+Xuk96RvkN4CMJLaIL0VttLy7r19euHdA+zX6YP0vlR3z/3ax2tfarw+TAO0L/VPb3ruXvv3o7z7lv70zuQN0vtgz+6DDSsZ
|
||||||
|
</data>
|
||||||
|
</layer>
|
||||||
|
<objectgroup id="1" name="PlayerStartingPos">
|
||||||
|
<object id="135" x="512" y="512">
|
||||||
|
<point/>
|
||||||
|
</object>
|
||||||
|
<object id="137" x="640" y="640">
|
||||||
|
<point/>
|
||||||
|
</object>
|
||||||
|
</objectgroup>
|
||||||
|
<objectgroup id="2" name="Barrier">
|
||||||
|
<properties>
|
||||||
|
<property name="type" value="barrier_and_shelter"/>
|
||||||
|
</properties>
|
||||||
|
<object id="1" x="400" y="224.5">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 0,141.5 17,141.5 17,-1"/>
|
||||||
|
</object>
|
||||||
|
<object id="2" x="559.5" y="207.5">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 1,158.5 17,158.5 17,0"/>
|
||||||
|
</object>
|
||||||
|
<object id="3" x="448.5" y="353">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 0,14.5 -49,14.5 -49,-1.5"/>
|
||||||
|
</object>
|
||||||
|
<object id="4" x="577.5" y="351.5">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 -98,1 -98,16 -1,15.5"/>
|
||||||
|
</object>
|
||||||
|
<object id="5" x="449.333" y="654.667">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 -178.667,0.666667 -178.667,17.3333 -0.666667,17.3333"/>
|
||||||
|
</object>
|
||||||
|
<object id="6" x="432.5" y="703.5">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 -191.667,0.666667 -191.667,17.3333 -0.715174,17.3333"/>
|
||||||
|
</object>
|
||||||
|
<object id="7" x="400" y="751">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 -191.667,0.666667 -191.667,17.3333 -0.715174,17.3333"/>
|
||||||
|
</object>
|
||||||
|
</objectgroup>
|
||||||
|
</map>
|
5
frontend/assets/resources/map/dungeon/map.tmx.meta
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.0.2",
|
||||||
|
"uuid": "1b802c87-1978-4c6a-bd0b-1f6b8526b3ad",
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
4
frontend/assets/resources/map/dungeon/tiles0.tsx
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<tileset version="1.2" tiledversion="1.2.3" name="tiles0" tilewidth="16" tileheight="16" tilecount="64" columns="16">
|
||||||
|
<image source="watabou_pixel_dungeon_orig_files/tiles0.png" width="256" height="64"/>
|
||||||
|
</tileset>
|
5
frontend/assets/resources/map/dungeon/tiles0.tsx.meta
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.0.0",
|
||||||
|
"uuid": "d4cf0e72-7454-4310-b975-beb5e81a63ae",
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
4
frontend/assets/resources/map/dungeon/tiles1.tsx
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<tileset version="1.2" tiledversion="1.2.3" name="tiles1" tilewidth="16" tileheight="16" tilecount="64" columns="16">
|
||||||
|
<image source="watabou_pixel_dungeon_orig_files/tiles1.png" width="256" height="64"/>
|
||||||
|
</tileset>
|
5
frontend/assets/resources/map/dungeon/tiles1.tsx.meta
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.0.0",
|
||||||
|
"uuid": "0bcabaac-a406-4b3d-9285-814e00c5b09d",
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"ver": "1.0.1",
|
||||||
|
"uuid": "2a231040-0e69-42b4-a35b-9690e976e71a",
|
||||||
|
"isSubpackage": false,
|
||||||
|
"subpackageName": "",
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.0.0",
|
||||||
|
"uuid": "acb40b5d-372b-4502-a6bf-f756908f8221",
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
After Width: | Height: | Size: 1.0 KiB |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"ver": "2.3.3",
|
"ver": "2.3.3",
|
||||||
"uuid": "d8e6c175-1f17-48df-a0aa-cdd9785f4d3a",
|
"uuid": "94f42afa-43e1-4936-b5ac-7bcfe252f9e1",
|
||||||
"type": "sprite",
|
"type": "sprite",
|
||||||
"wrapMode": "clamp",
|
"wrapMode": "clamp",
|
||||||
"filterMode": "bilinear",
|
"filterMode": "bilinear",
|
||||||
@@ -9,21 +9,21 @@
|
|||||||
"packable": true,
|
"packable": true,
|
||||||
"platformSettings": {},
|
"platformSettings": {},
|
||||||
"subMetas": {
|
"subMetas": {
|
||||||
"Tile_W256_H256_S01": {
|
"amulet": {
|
||||||
"ver": "1.0.4",
|
"ver": "1.0.4",
|
||||||
"uuid": "4a23290b-bf5a-4849-ac19-6ebd4b7daa59",
|
"uuid": "52580025-ae7a-4c48-878f-f685bc90e737",
|
||||||
"rawTextureUuid": "d8e6c175-1f17-48df-a0aa-cdd9785f4d3a",
|
"rawTextureUuid": "94f42afa-43e1-4936-b5ac-7bcfe252f9e1",
|
||||||
"trimType": "auto",
|
"trimType": "auto",
|
||||||
"trimThreshold": 1,
|
"trimThreshold": 1,
|
||||||
"rotated": false,
|
"rotated": false,
|
||||||
"offsetX": 0,
|
"offsetX": 0,
|
||||||
"offsetY": 0,
|
"offsetY": 0,
|
||||||
"trimX": 0,
|
"trimX": 0,
|
||||||
"trimY": 64,
|
"trimY": 0,
|
||||||
"width": 1280,
|
"width": 32,
|
||||||
"height": 896,
|
"height": 32,
|
||||||
"rawWidth": 1280,
|
"rawWidth": 32,
|
||||||
"rawHeight": 1024,
|
"rawHeight": 32,
|
||||||
"borderTop": 0,
|
"borderTop": 0,
|
||||||
"borderBottom": 0,
|
"borderBottom": 0,
|
||||||
"borderLeft": 0,
|
"borderLeft": 0,
|
After Width: | Height: | Size: 189 B |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"ver": "2.3.3",
|
"ver": "2.3.3",
|
||||||
"uuid": "136d09e9-c33c-45dc-abb7-e367d730c814",
|
"uuid": "a9781031-1dba-4214-9167-080fc1920f92",
|
||||||
"type": "sprite",
|
"type": "sprite",
|
||||||
"wrapMode": "clamp",
|
"wrapMode": "clamp",
|
||||||
"filterMode": "bilinear",
|
"filterMode": "bilinear",
|
||||||
@@ -9,21 +9,21 @@
|
|||||||
"packable": true,
|
"packable": true,
|
||||||
"platformSettings": {},
|
"platformSettings": {},
|
||||||
"subMetas": {
|
"subMetas": {
|
||||||
"Tile_W256_H128_S01": {
|
"arcs1": {
|
||||||
"ver": "1.0.4",
|
"ver": "1.0.4",
|
||||||
"uuid": "7acc48f5-d9c9-4438-8794-57a85590bd97",
|
"uuid": "5430edd4-3e0b-4509-a575-d7c7f8db4e31",
|
||||||
"rawTextureUuid": "136d09e9-c33c-45dc-abb7-e367d730c814",
|
"rawTextureUuid": "a9781031-1dba-4214-9167-080fc1920f92",
|
||||||
"trimType": "auto",
|
"trimType": "auto",
|
||||||
"trimThreshold": 1,
|
"trimThreshold": 1,
|
||||||
"rotated": false,
|
"rotated": false,
|
||||||
"offsetX": 0,
|
"offsetX": 0,
|
||||||
"offsetY": 831,
|
"offsetY": 0,
|
||||||
"trimX": 0,
|
"trimX": 0,
|
||||||
"trimY": 0,
|
"trimY": 0,
|
||||||
"width": 2048,
|
"width": 32,
|
||||||
"height": 386,
|
"height": 32,
|
||||||
"rawWidth": 2048,
|
"rawWidth": 32,
|
||||||
"rawHeight": 2048,
|
"rawHeight": 32,
|
||||||
"borderTop": 0,
|
"borderTop": 0,
|
||||||
"borderBottom": 0,
|
"borderBottom": 0,
|
||||||
"borderLeft": 0,
|
"borderLeft": 0,
|
After Width: | Height: | Size: 288 B |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"ver": "2.3.3",
|
"ver": "2.3.3",
|
||||||
"uuid": "74245e28-6cec-4960-ac41-5b482ad8fd13",
|
"uuid": "940f2fcc-df03-46ed-ad32-d92880efe501",
|
||||||
"type": "sprite",
|
"type": "sprite",
|
||||||
"wrapMode": "clamp",
|
"wrapMode": "clamp",
|
||||||
"filterMode": "bilinear",
|
"filterMode": "bilinear",
|
||||||
@@ -9,21 +9,21 @@
|
|||||||
"packable": true,
|
"packable": true,
|
||||||
"platformSettings": {},
|
"platformSettings": {},
|
||||||
"subMetas": {
|
"subMetas": {
|
||||||
"Tile_W256_H256_S02": {
|
"arcs2": {
|
||||||
"ver": "1.0.4",
|
"ver": "1.0.4",
|
||||||
"uuid": "8fc46c1f-6fb4-4290-99f3-b773b92312b7",
|
"uuid": "11915948-c7bf-41db-b6eb-74b364df7688",
|
||||||
"rawTextureUuid": "74245e28-6cec-4960-ac41-5b482ad8fd13",
|
"rawTextureUuid": "940f2fcc-df03-46ed-ad32-d92880efe501",
|
||||||
"trimType": "auto",
|
"trimType": "auto",
|
||||||
"trimThreshold": 1,
|
"trimThreshold": 1,
|
||||||
"rotated": false,
|
"rotated": false,
|
||||||
"offsetX": 19.5,
|
"offsetX": 0,
|
||||||
"offsetY": 3.5,
|
"offsetY": 0,
|
||||||
"trimX": 89,
|
"trimX": 0,
|
||||||
"trimY": 0,
|
"trimY": 0,
|
||||||
"width": 1409,
|
"width": 64,
|
||||||
"height": 251,
|
"height": 64,
|
||||||
"rawWidth": 1548,
|
"rawWidth": 64,
|
||||||
"rawHeight": 258,
|
"rawHeight": 64,
|
||||||
"borderTop": 0,
|
"borderTop": 0,
|
||||||
"borderBottom": 0,
|
"borderBottom": 0,
|
||||||
"borderLeft": 0,
|
"borderLeft": 0,
|
After Width: | Height: | Size: 2.5 KiB |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"ver": "2.3.3",
|
"ver": "2.3.3",
|
||||||
"uuid": "c30bd4d7-efdc-410c-8bdf-4a3dfc77bebd",
|
"uuid": "ac193c85-fb5d-4d66-b911-81e2254e0009",
|
||||||
"type": "sprite",
|
"type": "sprite",
|
||||||
"wrapMode": "clamp",
|
"wrapMode": "clamp",
|
||||||
"filterMode": "bilinear",
|
"filterMode": "bilinear",
|
||||||
@@ -9,21 +9,21 @@
|
|||||||
"packable": true,
|
"packable": true,
|
||||||
"platformSettings": {},
|
"platformSettings": {},
|
||||||
"subMetas": {
|
"subMetas": {
|
||||||
"Tile_W300_H300_S01": {
|
"avatars": {
|
||||||
"ver": "1.0.4",
|
"ver": "1.0.4",
|
||||||
"uuid": "66b49304-7b5b-442c-92a5-d2b368abf659",
|
"uuid": "b472e0d1-c866-4a4d-b8f9-4daca275b8db",
|
||||||
"rawTextureUuid": "c30bd4d7-efdc-410c-8bdf-4a3dfc77bebd",
|
"rawTextureUuid": "ac193c85-fb5d-4d66-b911-81e2254e0009",
|
||||||
"trimType": "auto",
|
"trimType": "auto",
|
||||||
"trimThreshold": 1,
|
"trimThreshold": 1,
|
||||||
"rotated": false,
|
"rotated": false,
|
||||||
"offsetX": 4,
|
"offsetX": -16.5,
|
||||||
"offsetY": -24.5,
|
"offsetY": 2,
|
||||||
"trimX": 97,
|
"trimX": 2,
|
||||||
"trimY": 85,
|
"trimY": 0,
|
||||||
"width": 114,
|
"width": 91,
|
||||||
"height": 179,
|
"height": 28,
|
||||||
"rawWidth": 300,
|
"rawWidth": 128,
|
||||||
"rawHeight": 300,
|
"rawHeight": 32,
|
||||||
"borderTop": 0,
|
"borderTop": 0,
|
||||||
"borderBottom": 0,
|
"borderBottom": 0,
|
||||||
"borderLeft": 0,
|
"borderLeft": 0,
|
After Width: | Height: | Size: 7.1 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.3.3",
|
||||||
|
"uuid": "f204f568-cf6e-462e-834e-ef6961dae26e",
|
||||||
|
"type": "sprite",
|
||||||
|
"wrapMode": "clamp",
|
||||||
|
"filterMode": "bilinear",
|
||||||
|
"premultiplyAlpha": false,
|
||||||
|
"genMipmaps": false,
|
||||||
|
"packable": true,
|
||||||
|
"platformSettings": {},
|
||||||
|
"subMetas": {
|
||||||
|
"badges": {
|
||||||
|
"ver": "1.0.4",
|
||||||
|
"uuid": "fec2a102-8c77-4528-8130-488184e8bf38",
|
||||||
|
"rawTextureUuid": "f204f568-cf6e-462e-834e-ef6961dae26e",
|
||||||
|
"trimType": "auto",
|
||||||
|
"trimThreshold": 1,
|
||||||
|
"rotated": false,
|
||||||
|
"offsetX": 0,
|
||||||
|
"offsetY": 0,
|
||||||
|
"trimX": 0,
|
||||||
|
"trimY": 0,
|
||||||
|
"width": 128,
|
||||||
|
"height": 128,
|
||||||
|
"rawWidth": 128,
|
||||||
|
"rawHeight": 128,
|
||||||
|
"borderTop": 0,
|
||||||
|
"borderBottom": 0,
|
||||||
|
"borderLeft": 0,
|
||||||
|
"borderRight": 0,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.3.3",
|
||||||
|
"uuid": "90cc6214-a196-4c59-885e-86c4449d426b",
|
||||||
|
"type": "sprite",
|
||||||
|
"wrapMode": "clamp",
|
||||||
|
"filterMode": "bilinear",
|
||||||
|
"premultiplyAlpha": false,
|
||||||
|
"genMipmaps": false,
|
||||||
|
"packable": true,
|
||||||
|
"platformSettings": {},
|
||||||
|
"subMetas": {
|
||||||
|
"banners": {
|
||||||
|
"ver": "1.0.4",
|
||||||
|
"uuid": "ce394cd8-e574-4170-8c9c-24072e7d6428",
|
||||||
|
"rawTextureUuid": "90cc6214-a196-4c59-885e-86c4449d426b",
|
||||||
|
"trimType": "auto",
|
||||||
|
"trimThreshold": 1,
|
||||||
|
"rotated": false,
|
||||||
|
"offsetX": 2.5,
|
||||||
|
"offsetY": 16.5,
|
||||||
|
"trimX": 5,
|
||||||
|
"trimY": 4,
|
||||||
|
"width": 123,
|
||||||
|
"height": 215,
|
||||||
|
"rawWidth": 128,
|
||||||
|
"rawHeight": 256,
|
||||||
|
"borderTop": 0,
|
||||||
|
"borderBottom": 0,
|
||||||
|
"borderLeft": 0,
|
||||||
|
"borderRight": 0,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 3.4 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.3.3",
|
||||||
|
"uuid": "e0a44b92-2564-401c-8276-b47e279c0039",
|
||||||
|
"type": "sprite",
|
||||||
|
"wrapMode": "clamp",
|
||||||
|
"filterMode": "bilinear",
|
||||||
|
"premultiplyAlpha": false,
|
||||||
|
"genMipmaps": false,
|
||||||
|
"packable": true,
|
||||||
|
"platformSettings": {},
|
||||||
|
"subMetas": {
|
||||||
|
"bat": {
|
||||||
|
"ver": "1.0.4",
|
||||||
|
"uuid": "5ad7b260-8239-4eb0-a436-7bc1c3743433",
|
||||||
|
"rawTextureUuid": "e0a44b92-2564-401c-8276-b47e279c0039",
|
||||||
|
"trimType": "auto",
|
||||||
|
"trimThreshold": 1,
|
||||||
|
"rotated": false,
|
||||||
|
"offsetX": -12,
|
||||||
|
"offsetY": 0.5,
|
||||||
|
"trimX": 0,
|
||||||
|
"trimY": 0,
|
||||||
|
"width": 104,
|
||||||
|
"height": 15,
|
||||||
|
"rawWidth": 128,
|
||||||
|
"rawHeight": 16,
|
||||||
|
"borderTop": 0,
|
||||||
|
"borderBottom": 0,
|
||||||
|
"borderLeft": 0,
|
||||||
|
"borderRight": 0,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.3.3",
|
||||||
|
"uuid": "a83b9bc0-78f0-41ed-acc2-6f88020d4828",
|
||||||
|
"type": "sprite",
|
||||||
|
"wrapMode": "clamp",
|
||||||
|
"filterMode": "bilinear",
|
||||||
|
"premultiplyAlpha": false,
|
||||||
|
"genMipmaps": false,
|
||||||
|
"packable": true,
|
||||||
|
"platformSettings": {},
|
||||||
|
"subMetas": {
|
||||||
|
"bee": {
|
||||||
|
"ver": "1.0.4",
|
||||||
|
"uuid": "a5d1dc1d-5f46-430c-96d3-3e2d2584be13",
|
||||||
|
"rawTextureUuid": "a83b9bc0-78f0-41ed-acc2-6f88020d4828",
|
||||||
|
"trimType": "auto",
|
||||||
|
"trimThreshold": 1,
|
||||||
|
"rotated": false,
|
||||||
|
"offsetX": -40.5,
|
||||||
|
"offsetY": 0,
|
||||||
|
"trimX": 2,
|
||||||
|
"trimY": 0,
|
||||||
|
"width": 171,
|
||||||
|
"height": 16,
|
||||||
|
"rawWidth": 256,
|
||||||
|
"rawHeight": 16,
|
||||||
|
"borderTop": 0,
|
||||||
|
"borderBottom": 0,
|
||||||
|
"borderLeft": 0,
|
||||||
|
"borderRight": 0,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 918 B |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.3.3",
|
||||||
|
"uuid": "4ba0be3a-32d0-43f1-9640-5fb0f65ea16a",
|
||||||
|
"type": "sprite",
|
||||||
|
"wrapMode": "clamp",
|
||||||
|
"filterMode": "bilinear",
|
||||||
|
"premultiplyAlpha": false,
|
||||||
|
"genMipmaps": false,
|
||||||
|
"packable": true,
|
||||||
|
"platformSettings": {},
|
||||||
|
"subMetas": {
|
||||||
|
"blacksmith": {
|
||||||
|
"ver": "1.0.4",
|
||||||
|
"uuid": "cc238a21-5d38-496a-86c0-3c223cb0977a",
|
||||||
|
"rawTextureUuid": "4ba0be3a-32d0-43f1-9640-5fb0f65ea16a",
|
||||||
|
"trimType": "auto",
|
||||||
|
"trimThreshold": 1,
|
||||||
|
"rotated": false,
|
||||||
|
"offsetX": -6.5,
|
||||||
|
"offsetY": 0,
|
||||||
|
"trimX": 0,
|
||||||
|
"trimY": 0,
|
||||||
|
"width": 51,
|
||||||
|
"height": 16,
|
||||||
|
"rawWidth": 64,
|
||||||
|
"rawHeight": 16,
|
||||||
|
"borderTop": 0,
|
||||||
|
"borderBottom": 0,
|
||||||
|
"borderLeft": 0,
|
||||||
|
"borderRight": 0,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 5.6 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.3.3",
|
||||||
|
"uuid": "2ed4fd6c-7c83-49e8-baba-01145a109cc8",
|
||||||
|
"type": "sprite",
|
||||||
|
"wrapMode": "clamp",
|
||||||
|
"filterMode": "bilinear",
|
||||||
|
"premultiplyAlpha": false,
|
||||||
|
"genMipmaps": false,
|
||||||
|
"packable": true,
|
||||||
|
"platformSettings": {},
|
||||||
|
"subMetas": {
|
||||||
|
"brute": {
|
||||||
|
"ver": "1.0.4",
|
||||||
|
"uuid": "eb04bd2a-243f-4ca8-8e58-3bc6cc5e61c1",
|
||||||
|
"rawTextureUuid": "2ed4fd6c-7c83-49e8-baba-01145a109cc8",
|
||||||
|
"trimType": "auto",
|
||||||
|
"trimThreshold": 1,
|
||||||
|
"rotated": false,
|
||||||
|
"offsetX": -61.5,
|
||||||
|
"offsetY": 0,
|
||||||
|
"trimX": 1,
|
||||||
|
"trimY": 0,
|
||||||
|
"width": 131,
|
||||||
|
"height": 32,
|
||||||
|
"rawWidth": 256,
|
||||||
|
"rawHeight": 32,
|
||||||
|
"borderTop": 0,
|
||||||
|
"borderBottom": 0,
|
||||||
|
"borderLeft": 0,
|
||||||
|
"borderRight": 0,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.3.3",
|
||||||
|
"uuid": "f38d420a-e1d6-4b6f-8863-e35c52cf4f8b",
|
||||||
|
"type": "sprite",
|
||||||
|
"wrapMode": "clamp",
|
||||||
|
"filterMode": "bilinear",
|
||||||
|
"premultiplyAlpha": false,
|
||||||
|
"genMipmaps": false,
|
||||||
|
"packable": true,
|
||||||
|
"platformSettings": {},
|
||||||
|
"subMetas": {
|
||||||
|
"buffs": {
|
||||||
|
"ver": "1.0.4",
|
||||||
|
"uuid": "5a9620a1-2da1-4870-94e9-096e5e323a43",
|
||||||
|
"rawTextureUuid": "f38d420a-e1d6-4b6f-8863-e35c52cf4f8b",
|
||||||
|
"trimType": "auto",
|
||||||
|
"trimThreshold": 1,
|
||||||
|
"rotated": false,
|
||||||
|
"offsetX": 0,
|
||||||
|
"offsetY": 0,
|
||||||
|
"trimX": 0,
|
||||||
|
"trimY": 0,
|
||||||
|
"width": 128,
|
||||||
|
"height": 16,
|
||||||
|
"rawWidth": 128,
|
||||||
|
"rawHeight": 16,
|
||||||
|
"borderTop": 0,
|
||||||
|
"borderBottom": 0,
|
||||||
|
"borderLeft": 0,
|
||||||
|
"borderRight": 0,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.3.3",
|
||||||
|
"uuid": "4ce8b218-cd3d-40d8-9102-ba1e4cf415eb",
|
||||||
|
"type": "sprite",
|
||||||
|
"wrapMode": "clamp",
|
||||||
|
"filterMode": "bilinear",
|
||||||
|
"premultiplyAlpha": false,
|
||||||
|
"genMipmaps": false,
|
||||||
|
"packable": true,
|
||||||
|
"platformSettings": {},
|
||||||
|
"subMetas": {
|
||||||
|
"burning_fist": {
|
||||||
|
"ver": "1.0.4",
|
||||||
|
"uuid": "c62848ac-4eec-46f9-b684-b5365d507212",
|
||||||
|
"rawTextureUuid": "4ce8b218-cd3d-40d8-9102-ba1e4cf415eb",
|
||||||
|
"trimType": "auto",
|
||||||
|
"trimThreshold": 1,
|
||||||
|
"rotated": false,
|
||||||
|
"offsetX": -42.5,
|
||||||
|
"offsetY": 7.5,
|
||||||
|
"trimX": 3,
|
||||||
|
"trimY": 0,
|
||||||
|
"width": 165,
|
||||||
|
"height": 17,
|
||||||
|
"rawWidth": 256,
|
||||||
|
"rawHeight": 32,
|
||||||
|
"borderTop": 0,
|
||||||
|
"borderBottom": 0,
|
||||||
|
"borderLeft": 0,
|
||||||
|
"borderRight": 0,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.3.3",
|
||||||
|
"uuid": "21e86a32-9ad1-4d02-877b-37e65b874697",
|
||||||
|
"type": "sprite",
|
||||||
|
"wrapMode": "clamp",
|
||||||
|
"filterMode": "bilinear",
|
||||||
|
"premultiplyAlpha": false,
|
||||||
|
"genMipmaps": false,
|
||||||
|
"packable": true,
|
||||||
|
"platformSettings": {},
|
||||||
|
"subMetas": {
|
||||||
|
"chrome": {
|
||||||
|
"ver": "1.0.4",
|
||||||
|
"uuid": "ba373151-5747-4c0d-8457-5543fefd50d2",
|
||||||
|
"rawTextureUuid": "21e86a32-9ad1-4d02-877b-37e65b874697",
|
||||||
|
"trimType": "auto",
|
||||||
|
"trimThreshold": 1,
|
||||||
|
"rotated": false,
|
||||||
|
"offsetX": -21,
|
||||||
|
"offsetY": 0.5,
|
||||||
|
"trimX": 1,
|
||||||
|
"trimY": 0,
|
||||||
|
"width": 84,
|
||||||
|
"height": 63,
|
||||||
|
"rawWidth": 128,
|
||||||
|
"rawHeight": 64,
|
||||||
|
"borderTop": 0,
|
||||||
|
"borderBottom": 0,
|
||||||
|
"borderLeft": 0,
|
||||||
|
"borderRight": 0,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 4.7 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.3.3",
|
||||||
|
"uuid": "cde66edb-eafb-4e46-b5a9-0b21851f4974",
|
||||||
|
"type": "sprite",
|
||||||
|
"wrapMode": "clamp",
|
||||||
|
"filterMode": "bilinear",
|
||||||
|
"premultiplyAlpha": false,
|
||||||
|
"genMipmaps": false,
|
||||||
|
"packable": true,
|
||||||
|
"platformSettings": {},
|
||||||
|
"subMetas": {
|
||||||
|
"crab": {
|
||||||
|
"ver": "1.0.4",
|
||||||
|
"uuid": "fc11e3df-a336-4ee6-b9f7-1dbabb1c7b1b",
|
||||||
|
"rawTextureUuid": "cde66edb-eafb-4e46-b5a9-0b21851f4974",
|
||||||
|
"trimType": "auto",
|
||||||
|
"trimThreshold": 1,
|
||||||
|
"rotated": false,
|
||||||
|
"offsetX": -15.5,
|
||||||
|
"offsetY": 0,
|
||||||
|
"trimX": 1,
|
||||||
|
"trimY": 0,
|
||||||
|
"width": 223,
|
||||||
|
"height": 16,
|
||||||
|
"rawWidth": 256,
|
||||||
|
"rawHeight": 16,
|
||||||
|
"borderTop": 0,
|
||||||
|
"borderBottom": 0,
|
||||||
|
"borderLeft": 0,
|
||||||
|
"borderRight": 0,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.3.3",
|
||||||
|
"uuid": "b146d7f8-393b-42d1-bb53-f0f4ad379dfc",
|
||||||
|
"type": "sprite",
|
||||||
|
"wrapMode": "clamp",
|
||||||
|
"filterMode": "bilinear",
|
||||||
|
"premultiplyAlpha": false,
|
||||||
|
"genMipmaps": false,
|
||||||
|
"packable": true,
|
||||||
|
"platformSettings": {},
|
||||||
|
"subMetas": {
|
||||||
|
"dashboard": {
|
||||||
|
"ver": "1.0.4",
|
||||||
|
"uuid": "4d4eb3aa-5d9e-4f78-9c0c-dcb99f479c2a",
|
||||||
|
"rawTextureUuid": "b146d7f8-393b-42d1-bb53-f0f4ad379dfc",
|
||||||
|
"trimType": "auto",
|
||||||
|
"trimThreshold": 1,
|
||||||
|
"rotated": false,
|
||||||
|
"offsetX": -0.5,
|
||||||
|
"offsetY": 0,
|
||||||
|
"trimX": 0,
|
||||||
|
"trimY": 0,
|
||||||
|
"width": 127,
|
||||||
|
"height": 32,
|
||||||
|
"rawWidth": 128,
|
||||||
|
"rawHeight": 32,
|
||||||
|
"borderTop": 0,
|
||||||
|
"borderBottom": 0,
|
||||||
|
"borderLeft": 0,
|
||||||
|
"borderRight": 0,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.3.3",
|
||||||
|
"uuid": "6abab25f-0d0a-4994-8001-bc37e7632abe",
|
||||||
|
"type": "sprite",
|
||||||
|
"wrapMode": "clamp",
|
||||||
|
"filterMode": "bilinear",
|
||||||
|
"premultiplyAlpha": false,
|
||||||
|
"genMipmaps": false,
|
||||||
|
"packable": true,
|
||||||
|
"platformSettings": {},
|
||||||
|
"subMetas": {
|
||||||
|
"demon": {
|
||||||
|
"ver": "1.0.4",
|
||||||
|
"uuid": "31fcc6ba-8e07-47f7-a933-0f45384dc511",
|
||||||
|
"rawTextureUuid": "6abab25f-0d0a-4994-8001-bc37e7632abe",
|
||||||
|
"trimType": "auto",
|
||||||
|
"trimThreshold": 1,
|
||||||
|
"rotated": false,
|
||||||
|
"offsetX": -2,
|
||||||
|
"offsetY": 1,
|
||||||
|
"trimX": 1,
|
||||||
|
"trimY": 0,
|
||||||
|
"width": 58,
|
||||||
|
"height": 14,
|
||||||
|
"rawWidth": 64,
|
||||||
|
"rawHeight": 16,
|
||||||
|
"borderTop": 0,
|
||||||
|
"borderBottom": 0,
|
||||||
|
"borderLeft": 0,
|
||||||
|
"borderRight": 0,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 4.4 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.3.3",
|
||||||
|
"uuid": "902e544c-494e-4d24-989c-8101b081d516",
|
||||||
|
"type": "sprite",
|
||||||
|
"wrapMode": "clamp",
|
||||||
|
"filterMode": "bilinear",
|
||||||
|
"premultiplyAlpha": false,
|
||||||
|
"genMipmaps": false,
|
||||||
|
"packable": true,
|
||||||
|
"platformSettings": {},
|
||||||
|
"subMetas": {
|
||||||
|
"dm300": {
|
||||||
|
"ver": "1.0.4",
|
||||||
|
"uuid": "e18e981b-7790-45be-a877-fa1ffccd9cfd",
|
||||||
|
"rawTextureUuid": "902e544c-494e-4d24-989c-8101b081d516",
|
||||||
|
"trimType": "auto",
|
||||||
|
"trimThreshold": 1,
|
||||||
|
"rotated": false,
|
||||||
|
"offsetX": -29,
|
||||||
|
"offsetY": 6,
|
||||||
|
"trimX": 0,
|
||||||
|
"trimY": 0,
|
||||||
|
"width": 198,
|
||||||
|
"height": 20,
|
||||||
|
"rawWidth": 256,
|
||||||
|
"rawHeight": 32,
|
||||||
|
"borderTop": 0,
|
||||||
|
"borderBottom": 0,
|
||||||
|
"borderLeft": 0,
|
||||||
|
"borderRight": 0,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.3.3",
|
||||||
|
"uuid": "84e847c4-c2d1-4d77-8b62-439c7cc10d16",
|
||||||
|
"type": "sprite",
|
||||||
|
"wrapMode": "clamp",
|
||||||
|
"filterMode": "bilinear",
|
||||||
|
"premultiplyAlpha": false,
|
||||||
|
"genMipmaps": false,
|
||||||
|
"packable": true,
|
||||||
|
"platformSettings": {},
|
||||||
|
"subMetas": {
|
||||||
|
"effects": {
|
||||||
|
"ver": "1.0.4",
|
||||||
|
"uuid": "c0abc05f-e714-47bf-8c8a-95a26b09cadb",
|
||||||
|
"rawTextureUuid": "84e847c4-c2d1-4d77-8b62-439c7cc10d16",
|
||||||
|
"trimType": "auto",
|
||||||
|
"trimThreshold": 1,
|
||||||
|
"rotated": false,
|
||||||
|
"offsetX": 0,
|
||||||
|
"offsetY": 4.5,
|
||||||
|
"trimX": 0,
|
||||||
|
"trimY": 0,
|
||||||
|
"width": 32,
|
||||||
|
"height": 23,
|
||||||
|
"rawWidth": 32,
|
||||||
|
"rawHeight": 32,
|
||||||
|
"borderTop": 0,
|
||||||
|
"borderBottom": 0,
|
||||||
|
"borderLeft": 0,
|
||||||
|
"borderRight": 0,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 3.6 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.3.3",
|
||||||
|
"uuid": "8c4dbca2-14e2-4ef6-9544-ee04f69bdaaa",
|
||||||
|
"type": "sprite",
|
||||||
|
"wrapMode": "clamp",
|
||||||
|
"filterMode": "bilinear",
|
||||||
|
"premultiplyAlpha": false,
|
||||||
|
"genMipmaps": false,
|
||||||
|
"packable": true,
|
||||||
|
"platformSettings": {},
|
||||||
|
"subMetas": {
|
||||||
|
"elemental": {
|
||||||
|
"ver": "1.0.4",
|
||||||
|
"uuid": "1da68822-1fd7-49fc-b855-6c5fbf8a9af3",
|
||||||
|
"rawTextureUuid": "8c4dbca2-14e2-4ef6-9544-ee04f69bdaaa",
|
||||||
|
"trimType": "auto",
|
||||||
|
"trimThreshold": 1,
|
||||||
|
"rotated": false,
|
||||||
|
"offsetX": -45.5,
|
||||||
|
"offsetY": 1,
|
||||||
|
"trimX": 1,
|
||||||
|
"trimY": 0,
|
||||||
|
"width": 163,
|
||||||
|
"height": 14,
|
||||||
|
"rawWidth": 256,
|
||||||
|
"rawHeight": 16,
|
||||||
|
"borderTop": 0,
|
||||||
|
"borderBottom": 0,
|
||||||
|
"borderLeft": 0,
|
||||||
|
"borderRight": 0,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 123 B |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.3.3",
|
||||||
|
"uuid": "9883dd54-d76a-49bb-b343-422da4f78853",
|
||||||
|
"type": "sprite",
|
||||||
|
"wrapMode": "clamp",
|
||||||
|
"filterMode": "bilinear",
|
||||||
|
"premultiplyAlpha": false,
|
||||||
|
"genMipmaps": false,
|
||||||
|
"packable": true,
|
||||||
|
"platformSettings": {},
|
||||||
|
"subMetas": {
|
||||||
|
"exp_bar": {
|
||||||
|
"ver": "1.0.4",
|
||||||
|
"uuid": "c5c8aca4-800c-49d4-9ee3-fc15b1f738d3",
|
||||||
|
"rawTextureUuid": "9883dd54-d76a-49bb-b343-422da4f78853",
|
||||||
|
"trimType": "auto",
|
||||||
|
"trimThreshold": 1,
|
||||||
|
"rotated": false,
|
||||||
|
"offsetX": 0,
|
||||||
|
"offsetY": 0,
|
||||||
|
"trimX": 0,
|
||||||
|
"trimY": 0,
|
||||||
|
"width": 16,
|
||||||
|
"height": 1,
|
||||||
|
"rawWidth": 16,
|
||||||
|
"rawHeight": 1,
|
||||||
|
"borderTop": 0,
|
||||||
|
"borderBottom": 0,
|
||||||
|
"borderLeft": 0,
|
||||||
|
"borderRight": 0,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.3.3",
|
||||||
|
"uuid": "2ae831f7-5ac0-487f-91e6-659a279ab8ab",
|
||||||
|
"type": "sprite",
|
||||||
|
"wrapMode": "clamp",
|
||||||
|
"filterMode": "bilinear",
|
||||||
|
"premultiplyAlpha": false,
|
||||||
|
"genMipmaps": false,
|
||||||
|
"packable": true,
|
||||||
|
"platformSettings": {},
|
||||||
|
"subMetas": {
|
||||||
|
"eye": {
|
||||||
|
"ver": "1.0.4",
|
||||||
|
"uuid": "e1476f8f-750c-454a-842b-d9197eee5e6d",
|
||||||
|
"rawTextureUuid": "2ae831f7-5ac0-487f-91e6-659a279ab8ab",
|
||||||
|
"trimType": "auto",
|
||||||
|
"trimThreshold": 1,
|
||||||
|
"rotated": false,
|
||||||
|
"offsetX": -48,
|
||||||
|
"offsetY": 7,
|
||||||
|
"trimX": 0,
|
||||||
|
"trimY": 0,
|
||||||
|
"width": 160,
|
||||||
|
"height": 18,
|
||||||
|
"rawWidth": 256,
|
||||||
|
"rawHeight": 32,
|
||||||
|
"borderTop": 0,
|
||||||
|
"borderBottom": 0,
|
||||||
|
"borderLeft": 0,
|
||||||
|
"borderRight": 0,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 4.5 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.3.3",
|
||||||
|
"uuid": "6e7e65bb-322a-42ba-97a4-e7d9115e87d2",
|
||||||
|
"type": "sprite",
|
||||||
|
"wrapMode": "clamp",
|
||||||
|
"filterMode": "bilinear",
|
||||||
|
"premultiplyAlpha": false,
|
||||||
|
"genMipmaps": false,
|
||||||
|
"packable": true,
|
||||||
|
"platformSettings": {},
|
||||||
|
"subMetas": {
|
||||||
|
"fireball": {
|
||||||
|
"ver": "1.0.4",
|
||||||
|
"uuid": "141a794c-5dea-4d33-80a7-b5efdf08ade6",
|
||||||
|
"rawTextureUuid": "6e7e65bb-322a-42ba-97a4-e7d9115e87d2",
|
||||||
|
"trimType": "auto",
|
||||||
|
"trimThreshold": 1,
|
||||||
|
"rotated": false,
|
||||||
|
"offsetX": -0.5,
|
||||||
|
"offsetY": 0.5,
|
||||||
|
"trimX": 4,
|
||||||
|
"trimY": 0,
|
||||||
|
"width": 119,
|
||||||
|
"height": 31,
|
||||||
|
"rawWidth": 128,
|
||||||
|
"rawHeight": 32,
|
||||||
|
"borderTop": 0,
|
||||||
|
"borderBottom": 0,
|
||||||
|
"borderLeft": 0,
|
||||||
|
"borderRight": 0,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.3.3",
|
||||||
|
"uuid": "faaa1229-6f2e-4127-b884-962f6a104ffb",
|
||||||
|
"type": "sprite",
|
||||||
|
"wrapMode": "clamp",
|
||||||
|
"filterMode": "bilinear",
|
||||||
|
"premultiplyAlpha": false,
|
||||||
|
"genMipmaps": false,
|
||||||
|
"packable": true,
|
||||||
|
"platformSettings": {},
|
||||||
|
"subMetas": {
|
||||||
|
"font15x": {
|
||||||
|
"ver": "1.0.4",
|
||||||
|
"uuid": "da1bd7b3-b4c9-4cde-9def-258b1fcd80eb",
|
||||||
|
"rawTextureUuid": "faaa1229-6f2e-4127-b884-962f6a104ffb",
|
||||||
|
"trimType": "auto",
|
||||||
|
"trimThreshold": 1,
|
||||||
|
"rotated": false,
|
||||||
|
"offsetX": -171,
|
||||||
|
"offsetY": 2,
|
||||||
|
"trimX": 5,
|
||||||
|
"trimY": 0,
|
||||||
|
"width": 672,
|
||||||
|
"height": 12,
|
||||||
|
"rawWidth": 1024,
|
||||||
|
"rawHeight": 16,
|
||||||
|
"borderTop": 0,
|
||||||
|
"borderBottom": 0,
|
||||||
|
"borderLeft": 0,
|
||||||
|
"borderRight": 0,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.3.3",
|
||||||
|
"uuid": "a0072e35-a681-4fc1-9c11-b8717990bbcc",
|
||||||
|
"type": "sprite",
|
||||||
|
"wrapMode": "clamp",
|
||||||
|
"filterMode": "bilinear",
|
||||||
|
"premultiplyAlpha": false,
|
||||||
|
"genMipmaps": false,
|
||||||
|
"packable": true,
|
||||||
|
"platformSettings": {},
|
||||||
|
"subMetas": {
|
||||||
|
"font1x": {
|
||||||
|
"ver": "1.0.4",
|
||||||
|
"uuid": "7c0a9e28-00f7-44e5-8199-a5b291df02c5",
|
||||||
|
"rawTextureUuid": "a0072e35-a681-4fc1-9c11-b8717990bbcc",
|
||||||
|
"trimType": "auto",
|
||||||
|
"trimThreshold": 1,
|
||||||
|
"rotated": false,
|
||||||
|
"offsetX": -231,
|
||||||
|
"offsetY": 0,
|
||||||
|
"trimX": 3,
|
||||||
|
"trimY": 0,
|
||||||
|
"width": 556,
|
||||||
|
"height": 8,
|
||||||
|
"rawWidth": 1024,
|
||||||
|
"rawHeight": 8,
|
||||||
|
"borderTop": 0,
|
||||||
|
"borderBottom": 0,
|
||||||
|
"borderLeft": 0,
|
||||||
|
"borderRight": 0,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 3.6 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.3.3",
|
||||||
|
"uuid": "d9805b59-f11e-4c47-ac44-61b701c5eb46",
|
||||||
|
"type": "sprite",
|
||||||
|
"wrapMode": "clamp",
|
||||||
|
"filterMode": "bilinear",
|
||||||
|
"premultiplyAlpha": false,
|
||||||
|
"genMipmaps": false,
|
||||||
|
"packable": true,
|
||||||
|
"platformSettings": {},
|
||||||
|
"subMetas": {
|
||||||
|
"font25x": {
|
||||||
|
"ver": "1.0.4",
|
||||||
|
"uuid": "652701b3-3b87-4095-9e6c-a21e4b138f83",
|
||||||
|
"rawTextureUuid": "d9805b59-f11e-4c47-ac44-61b701c5eb46",
|
||||||
|
"trimType": "auto",
|
||||||
|
"trimThreshold": 1,
|
||||||
|
"rotated": false,
|
||||||
|
"offsetX": -67.5,
|
||||||
|
"offsetY": 7.5,
|
||||||
|
"trimX": 7,
|
||||||
|
"trimY": 0,
|
||||||
|
"width": 875,
|
||||||
|
"height": 17,
|
||||||
|
"rawWidth": 1024,
|
||||||
|
"rawHeight": 32,
|
||||||
|
"borderTop": 0,
|
||||||
|
"borderBottom": 0,
|
||||||
|
"borderLeft": 0,
|
||||||
|
"borderRight": 0,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 5.3 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.3.3",
|
||||||
|
"uuid": "0c3f3c09-e2ac-4073-9e4d-5c3b3be91bd5",
|
||||||
|
"type": "sprite",
|
||||||
|
"wrapMode": "clamp",
|
||||||
|
"filterMode": "bilinear",
|
||||||
|
"premultiplyAlpha": false,
|
||||||
|
"genMipmaps": false,
|
||||||
|
"packable": true,
|
||||||
|
"platformSettings": {},
|
||||||
|
"subMetas": {
|
||||||
|
"font2x": {
|
||||||
|
"ver": "1.0.4",
|
||||||
|
"uuid": "1b899d40-9891-41d0-95ff-858334262984",
|
||||||
|
"rawTextureUuid": "0c3f3c09-e2ac-4073-9e4d-5c3b3be91bd5",
|
||||||
|
"trimType": "auto",
|
||||||
|
"trimThreshold": 1,
|
||||||
|
"rotated": false,
|
||||||
|
"offsetX": -133.5,
|
||||||
|
"offsetY": 1,
|
||||||
|
"trimX": 6,
|
||||||
|
"trimY": 0,
|
||||||
|
"width": 745,
|
||||||
|
"height": 14,
|
||||||
|
"rawWidth": 1024,
|
||||||
|
"rawHeight": 16,
|
||||||
|
"borderTop": 0,
|
||||||
|
"borderBottom": 0,
|
||||||
|
"borderLeft": 0,
|
||||||
|
"borderRight": 0,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 7.3 KiB |