mirror of
https://github.com/genxium/DelayNoMore
synced 2025-10-17 04:29:00 +00:00
Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
70e552f5f0 | ||
|
c58e690a47 | ||
|
1593965950 | ||
|
2a1105efa4 | ||
|
04de4666d5 | ||
|
2290c57c1c | ||
|
24d5ad9dc8 | ||
|
fdc296531a | ||
|
becc56f672 | ||
|
58c18ab7ae | ||
|
024d527f3d | ||
|
9b29edaaa1 | ||
|
360f2fc22b |
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
This project is a demo for a websocket-based rollback netcode inspired by [GGPO](https://github.com/pond3r/ggpo/blob/master/doc/README.md).
|
This project is a demo for a websocket-based rollback netcode inspired by [GGPO](https://github.com/pond3r/ggpo/blob/master/doc/README.md).
|
||||||
|
|
||||||

|
_(the following gif is sped up to 2x for file size reduction)_
|
||||||
|

|
||||||
|
|
||||||
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.
|
Please also checkout [this demo video](https://pan.baidu.com/s/1fy0CuFKnVP_Gn2cDfrj6yg?pwd=q5uc) 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 video mainly shows the following features.
|
||||||
- The backend receives inputs from frontend peers and broadcasts back for synchronization.
|
- The backend receives inputs from frontend peers and broadcasts back for synchronization.
|
||||||
|
@@ -5,6 +5,7 @@ import (
|
|||||||
. "battle_srv/common"
|
. "battle_srv/common"
|
||||||
"battle_srv/common/utils"
|
"battle_srv/common/utils"
|
||||||
"battle_srv/models"
|
"battle_srv/models"
|
||||||
|
. "battle_srv/protos"
|
||||||
"battle_srv/storage"
|
"battle_srv/storage"
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
@@ -526,8 +527,10 @@ func (p *playerController) maybeCreatePlayerWechatAuthBinding(userInfo utils.Use
|
|||||||
}
|
}
|
||||||
if player != nil {
|
if player != nil {
|
||||||
updateInfo := models.Player{
|
updateInfo := models.Player{
|
||||||
Avatar: userInfo.HeadImgURL,
|
PlayerDownsync: PlayerDownsync{
|
||||||
DisplayName: userInfo.Nickname,
|
Avatar: userInfo.HeadImgURL,
|
||||||
|
DisplayName: userInfo.Nickname,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
tx := storage.MySQLManagerIns.MustBegin()
|
tx := storage.MySQLManagerIns.MustBegin()
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
@@ -542,10 +545,12 @@ func (p *playerController) maybeCreatePlayerWechatAuthBinding(userInfo utils.Use
|
|||||||
}
|
}
|
||||||
now := utils.UnixtimeMilli()
|
now := utils.UnixtimeMilli()
|
||||||
player := models.Player{
|
player := models.Player{
|
||||||
CreatedAt: now,
|
PlayerDownsync: PlayerDownsync{
|
||||||
UpdatedAt: now,
|
DisplayName: userInfo.Nickname,
|
||||||
DisplayName: userInfo.Nickname,
|
Avatar: userInfo.HeadImgURL,
|
||||||
Avatar: userInfo.HeadImgURL,
|
},
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
return p.createNewPlayer(player, userInfo.OpenID, int(Constants.AuthChannel.Wechat))
|
return p.createNewPlayer(player, userInfo.OpenID, int(Constants.AuthChannel.Wechat))
|
||||||
}
|
}
|
||||||
@@ -562,8 +567,10 @@ func (p *playerController) maybeCreatePlayerWechatGameAuthBinding(userInfo utils
|
|||||||
}
|
}
|
||||||
if player != nil {
|
if player != nil {
|
||||||
updateInfo := models.Player{
|
updateInfo := models.Player{
|
||||||
Avatar: userInfo.HeadImgURL,
|
PlayerDownsync: PlayerDownsync{
|
||||||
DisplayName: userInfo.Nickname,
|
Avatar: userInfo.HeadImgURL,
|
||||||
|
DisplayName: userInfo.Nickname,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
tx := storage.MySQLManagerIns.MustBegin()
|
tx := storage.MySQLManagerIns.MustBegin()
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
@@ -578,10 +585,12 @@ func (p *playerController) maybeCreatePlayerWechatGameAuthBinding(userInfo utils
|
|||||||
}
|
}
|
||||||
now := utils.UnixtimeMilli()
|
now := utils.UnixtimeMilli()
|
||||||
player := models.Player{
|
player := models.Player{
|
||||||
CreatedAt: now,
|
PlayerDownsync: PlayerDownsync{
|
||||||
UpdatedAt: now,
|
DisplayName: userInfo.Nickname,
|
||||||
DisplayName: userInfo.Nickname,
|
Avatar: userInfo.HeadImgURL,
|
||||||
Avatar: userInfo.HeadImgURL,
|
},
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
return p.createNewPlayer(player, userInfo.OpenID, int(Constants.AuthChannel.WechatGame))
|
return p.createNewPlayer(player, userInfo.OpenID, int(Constants.AuthChannel.WechatGame))
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ import (
|
|||||||
. "battle_srv/common"
|
. "battle_srv/common"
|
||||||
"battle_srv/common/utils"
|
"battle_srv/common/utils"
|
||||||
"battle_srv/models"
|
"battle_srv/models"
|
||||||
|
. "battle_srv/protos"
|
||||||
"battle_srv/storage"
|
"battle_srv/storage"
|
||||||
. "dnmshared"
|
. "dnmshared"
|
||||||
sq "github.com/Masterminds/squirrel"
|
sq "github.com/Masterminds/squirrel"
|
||||||
@@ -71,7 +72,6 @@ func createMysqlData(rows *sqlx.Rows, v string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加上tableName参数, 用于pre_conf_data.sqlite里bot_player表的复用 --kobako
|
|
||||||
func maybeCreateNewPlayerFromBotTable(db *sqlx.DB, tableName string) {
|
func maybeCreateNewPlayerFromBotTable(db *sqlx.DB, tableName string) {
|
||||||
var ls []*dbBotPlayer
|
var ls []*dbBotPlayer
|
||||||
err := db.Select(&ls, "SELECT name, magic_phone_country_code, magic_phone_num, display_name FROM "+tableName)
|
err := db.Select(&ls, "SELECT name, magic_phone_country_code, magic_phone_num, display_name FROM "+tableName)
|
||||||
@@ -88,7 +88,6 @@ func maybeCreateNewPlayerFromBotTable(db *sqlx.DB, tableName string) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
query = storage.MySQLManagerIns.Rebind(query)
|
query = storage.MySQLManagerIns.Rebind(query)
|
||||||
// existNames := make([]string, len(ls), len(ls))
|
|
||||||
var existPlayers []*models.Player
|
var existPlayers []*models.Player
|
||||||
err = storage.MySQLManagerIns.Select(&existPlayers, query, args...)
|
err = storage.MySQLManagerIns.Select(&existPlayers, query, args...)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
@@ -99,13 +98,11 @@ func maybeCreateNewPlayerFromBotTable(db *sqlx.DB, tableName string) {
|
|||||||
var flag bool
|
var flag bool
|
||||||
for _, v := range existPlayers {
|
for _, v := range existPlayers {
|
||||||
if botPlayer.Name == v.Name {
|
if botPlayer.Name == v.Name {
|
||||||
// 已有数据,合并处理
|
|
||||||
flag = true
|
flag = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !flag {
|
if !flag {
|
||||||
// 找不到,新增
|
|
||||||
Logger.Debug("create", zap.Any(tableName, botPlayer))
|
Logger.Debug("create", zap.Any(tableName, botPlayer))
|
||||||
err := createNewBotPlayer(botPlayer)
|
err := createNewBotPlayer(botPlayer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -120,11 +117,14 @@ func createNewBotPlayer(p *dbBotPlayer) error {
|
|||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
now := utils.UnixtimeMilli()
|
now := utils.UnixtimeMilli()
|
||||||
player := models.Player{
|
player := models.Player{
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
Name: p.Name,
|
PlayerDownsync: PlayerDownsync{
|
||||||
DisplayName: p.DisplayName,
|
Name: p.Name,
|
||||||
|
DisplayName: p.DisplayName,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := player.Insert(tx)
|
err := player.Insert(tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@@ -4,6 +4,7 @@ import (
|
|||||||
. "battle_srv/common"
|
. "battle_srv/common"
|
||||||
"battle_srv/common/utils"
|
"battle_srv/common/utils"
|
||||||
"battle_srv/models"
|
"battle_srv/models"
|
||||||
|
. "battle_srv/protos"
|
||||||
"battle_srv/storage"
|
"battle_srv/storage"
|
||||||
. "dnmshared"
|
. "dnmshared"
|
||||||
|
|
||||||
@@ -75,9 +76,11 @@ func createNewPlayer(p *dbTestPlayer) error {
|
|||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
now := utils.UnixtimeMilli()
|
now := utils.UnixtimeMilli()
|
||||||
player := models.Player{
|
player := models.Player{
|
||||||
|
PlayerDownsync: PlayerDownsync{
|
||||||
|
Name: p.Name,
|
||||||
|
},
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
Name: p.Name,
|
|
||||||
}
|
}
|
||||||
err := player.Insert(tx)
|
err := player.Insert(tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -2,10 +2,9 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
. "battle_srv/protos"
|
. "battle_srv/protos"
|
||||||
. "dnmshared/sharedprotos"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func toPbPlayers(modelInstances map[int32]*Player) map[int32]*PlayerDownsync {
|
func toPbPlayers(modelInstances map[int32]*Player, withMetaInfo bool) map[int32]*PlayerDownsync {
|
||||||
toRet := make(map[int32]*PlayerDownsync, 0)
|
toRet := make(map[int32]*PlayerDownsync, 0)
|
||||||
if nil == modelInstances {
|
if nil == modelInstances {
|
||||||
return toRet
|
return toRet
|
||||||
@@ -13,18 +12,22 @@ func toPbPlayers(modelInstances map[int32]*Player) map[int32]*PlayerDownsync {
|
|||||||
|
|
||||||
for k, last := range modelInstances {
|
for k, last := range modelInstances {
|
||||||
toRet[k] = &PlayerDownsync{
|
toRet[k] = &PlayerDownsync{
|
||||||
Id: last.Id,
|
Id: last.Id,
|
||||||
VirtualGridX: last.VirtualGridX,
|
VirtualGridX: last.VirtualGridX,
|
||||||
VirtualGridY: last.VirtualGridY,
|
VirtualGridY: last.VirtualGridY,
|
||||||
Dir: &Direction{
|
DirX: last.DirX,
|
||||||
Dx: last.Dir.Dx,
|
DirY: last.DirY,
|
||||||
Dy: last.Dir.Dy,
|
ColliderRadius: last.ColliderRadius,
|
||||||
},
|
Speed: last.Speed,
|
||||||
Speed: last.Speed,
|
BattleState: last.BattleState,
|
||||||
BattleState: last.BattleState,
|
Score: last.Score,
|
||||||
Score: last.Score,
|
Removed: last.Removed,
|
||||||
Removed: last.Removed,
|
JoinIndex: last.JoinIndex,
|
||||||
JoinIndex: last.JoinIndex,
|
}
|
||||||
|
if withMetaInfo {
|
||||||
|
toRet[k].Name = last.Name
|
||||||
|
toRet[k].DisplayName = last.DisplayName
|
||||||
|
toRet[k].Avatar = last.Avatar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,11 +1,13 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
. "battle_srv/protos"
|
||||||
. "dnmshared/sharedprotos"
|
"battle_srv/storage"
|
||||||
|
. "dnmshared"
|
||||||
"fmt"
|
"fmt"
|
||||||
sq "github.com/Masterminds/squirrel"
|
sq "github.com/Masterminds/squirrel"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PlayerBattleState struct {
|
type PlayerBattleState struct {
|
||||||
@@ -33,12 +35,7 @@ func InitPlayerBattleStateIns() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Player struct {
|
type Player struct {
|
||||||
// Meta info fields
|
PlayerDownsync
|
||||||
Id int32 `json:"id,omitempty" db:"id"`
|
|
||||||
Name string `json:"name,omitempty" db:"name"`
|
|
||||||
DisplayName string `json:"displayName,omitempty" db:"display_name"`
|
|
||||||
Avatar string `json:"avatar,omitempty"`
|
|
||||||
ColliderRadius float64 `json:"-"`
|
|
||||||
|
|
||||||
// DB only fields
|
// DB only fields
|
||||||
CreatedAt int64 `db:"created_at"`
|
CreatedAt int64 `db:"created_at"`
|
||||||
@@ -46,19 +43,10 @@ type Player struct {
|
|||||||
DeletedAt NullInt64 `db:"deleted_at"`
|
DeletedAt NullInt64 `db:"deleted_at"`
|
||||||
TutorialStage int `db:"tutorial_stage"`
|
TutorialStage int `db:"tutorial_stage"`
|
||||||
|
|
||||||
// in-battle info fields
|
// other in-battle info fields
|
||||||
VirtualGridX int32
|
LastSentInputFrameId int32
|
||||||
VirtualGridY int32
|
|
||||||
Dir *Direction
|
|
||||||
Speed int32
|
|
||||||
BattleState int32
|
|
||||||
LastMoveGmtMillis int32
|
|
||||||
Score int32
|
|
||||||
Removed bool
|
|
||||||
JoinIndex int32
|
|
||||||
AckingFrameId int32
|
AckingFrameId int32
|
||||||
AckingInputFrameId int32
|
AckingInputFrameId int32
|
||||||
LastSentInputFrameId int32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExistPlayerByName(name string) (bool, error) {
|
func ExistPlayerByName(name string) (bool, error) {
|
||||||
@@ -74,15 +62,43 @@ func GetPlayerById(id int) (*Player, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getPlayer(cond sq.Eq) (*Player, error) {
|
func getPlayer(cond sq.Eq) (*Player, error) {
|
||||||
var p Player
|
p := Player{}
|
||||||
err := getObj("player", cond, &p)
|
pd := PlayerDownsync{}
|
||||||
if err == sql.ErrNoRows {
|
query, args, err := sq.Select("*").From("player").Where(cond).Limit(1).ToSql()
|
||||||
return nil, nil
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
p.Dir = &Direction{
|
rows, err := storage.MySQLManagerIns.Queryx(query, args...)
|
||||||
Dx: 0,
|
if err != nil {
|
||||||
Dy: 0,
|
return nil, err
|
||||||
}
|
}
|
||||||
|
cols, err := rows.Columns()
|
||||||
|
if nil != err {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
for rows.Next() {
|
||||||
|
// TODO: Do it more elegantly, but by now I don't have time to learn reflection of Golang
|
||||||
|
vals := rowValues(rows, cols)
|
||||||
|
for i, col := range cols {
|
||||||
|
val := *vals[i].(*interface{})
|
||||||
|
if "id" == col {
|
||||||
|
pd.Id = int32(val.(int64))
|
||||||
|
}
|
||||||
|
if "name" == col {
|
||||||
|
switch v := val.(type) {
|
||||||
|
case []byte:
|
||||||
|
pd.Name = string(v)
|
||||||
|
default:
|
||||||
|
pd.Name = fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if "created_at" == col {
|
||||||
|
p.CreatedAt = int64(val.(int64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Logger.Info("Queried player from db", zap.Any("cond", cond), zap.Any("p", p), zap.Any("pd", pd), zap.Any("cols", cols), zap.Any("rowValues", vals))
|
||||||
|
}
|
||||||
|
p.PlayerDownsync = pd
|
||||||
return &p, nil
|
return &p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,8 +129,6 @@ func Update(tx *sqlx.Tx, id int32, p *Player) (bool, error) {
|
|||||||
}
|
}
|
||||||
result, err := tx.Exec(query, args...)
|
result, err := tx.Exec(query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("ERRRRRRR: ")
|
|
||||||
fmt.Println(err)
|
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
rowsAffected, err := result.RowsAffected()
|
rowsAffected, err := result.RowsAffected()
|
||||||
|
@@ -10,6 +10,17 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func rowValues(rows *sqlx.Rows, cols []string) []interface{} {
|
||||||
|
results := make([]interface{}, len(cols))
|
||||||
|
for i := range results {
|
||||||
|
results[i] = new(interface{})
|
||||||
|
}
|
||||||
|
if err := rows.Scan(results[:]...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
func exist(t string, cond sq.Eq) (bool, error) {
|
func exist(t string, cond sq.Eq) (bool, error) {
|
||||||
c, err := getCount(t, cond)
|
c, err := getCount(t, cond)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -54,6 +54,7 @@ const (
|
|||||||
|
|
||||||
COLLISION_PLAYER_INDEX_PREFIX = (1 << 17)
|
COLLISION_PLAYER_INDEX_PREFIX = (1 << 17)
|
||||||
COLLISION_BARRIER_INDEX_PREFIX = (1 << 16)
|
COLLISION_BARRIER_INDEX_PREFIX = (1 << 16)
|
||||||
|
COLLISION_BULLET_INDEX_PREFIX = (1 << 15)
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -61,6 +62,17 @@ const (
|
|||||||
MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED = -2
|
MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED = -2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ATK_CHARACTER_STATE_IDLE1 = 0
|
||||||
|
ATK_CHARACTER_STATE_WALKING = 1
|
||||||
|
ATK_CHARACTER_STATE_ATK1 = 2
|
||||||
|
ATK_CHARACTER_STATE_ATKED1 = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DEFAULT_PLAYER_RADIUS = float64(16)
|
||||||
|
)
|
||||||
|
|
||||||
// These directions are chosen such that when speed is changed to "(speedX+delta, speedY+delta)" for any of them, the direction is unchanged.
|
// 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},
|
||||||
@@ -116,6 +128,7 @@ type Room struct {
|
|||||||
collisionSpaceOffsetY float64
|
collisionSpaceOffsetY float64
|
||||||
Players map[int32]*Player
|
Players map[int32]*Player
|
||||||
PlayersArr []*Player // ordered by joinIndex
|
PlayersArr []*Player // ordered by joinIndex
|
||||||
|
Space *resolv.Space
|
||||||
CollisionSysMap map[int32]*resolv.Object
|
CollisionSysMap map[int32]*resolv.Object
|
||||||
/**
|
/**
|
||||||
* The following `PlayerDownsyncSessionDict` is NOT individually put
|
* The following `PlayerDownsyncSessionDict` is NOT individually put
|
||||||
@@ -142,11 +155,6 @@ type Room struct {
|
|||||||
Index int
|
Index int
|
||||||
RenderFrameId int32
|
RenderFrameId int32
|
||||||
CurDynamicsRenderFrameId int32 // [WARNING] The dynamics of backend is ALWAYS MOVING FORWARD BY ALL-CONFIRMED INPUTFRAMES (either by upsync or forced), i.e. no rollback
|
CurDynamicsRenderFrameId int32 // [WARNING] The dynamics of backend is ALWAYS MOVING FORWARD BY ALL-CONFIRMED INPUTFRAMES (either by upsync or forced), i.e. no rollback
|
||||||
ServerFps int32
|
|
||||||
BattleDurationFrames int32
|
|
||||||
BattleDurationNanos int64
|
|
||||||
InputFrameUpsyncDelayTolerance int32
|
|
||||||
MaxChasingRenderFramesPerUpdate int32
|
|
||||||
EffectivePlayerCount int32
|
EffectivePlayerCount int32
|
||||||
DismissalWaitGroup sync.WaitGroup
|
DismissalWaitGroup sync.WaitGroup
|
||||||
Barriers map[int32]*Barrier
|
Barriers map[int32]*Barrier
|
||||||
@@ -156,16 +164,15 @@ type Room struct {
|
|||||||
LastAllConfirmedInputFrameId int32
|
LastAllConfirmedInputFrameId int32
|
||||||
LastAllConfirmedInputFrameIdWithChange int32
|
LastAllConfirmedInputFrameIdWithChange int32
|
||||||
LastAllConfirmedInputList []uint64
|
LastAllConfirmedInputList []uint64
|
||||||
InputDelayFrames int32 // in the count of render frames
|
|
||||||
NstDelayFrames int32 // network-single-trip delay in the count of render frames, proposed to be (InputDelayFrames >> 1) because we expect a round-trip delay to be exactly "InputDelayFrames"
|
|
||||||
InputScaleFrames uint32 // inputDelayedAndScaledFrameId = ((originalFrameId - InputDelayFrames) >> InputScaleFrames)
|
|
||||||
JoinIndexBooleanArr []bool
|
JoinIndexBooleanArr []bool
|
||||||
|
|
||||||
BackendDynamicsEnabled bool
|
BackendDynamicsEnabled bool
|
||||||
LastRenderFrameIdTriggeredAt int64
|
LastRenderFrameIdTriggeredAt int64
|
||||||
PlayerDefaultSpeed int32
|
PlayerDefaultSpeed int32
|
||||||
|
|
||||||
BattleColliderInfo // Compositing to send centralized magic numbers
|
BulletBattleLocalIdCounter int32
|
||||||
|
dilutedRollbackEstimatedDtNanos int64
|
||||||
|
BattleColliderInfo // Compositing to send centralized magic numbers
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pR *Room) updateScore() {
|
func (pR *Room) updateScore() {
|
||||||
@@ -189,8 +196,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.Speed = pR.PlayerDefaultSpeed // Hardcoded
|
pPlayerFromDbInit.Speed = pR.PlayerDefaultSpeed // Hardcoded
|
||||||
pPlayerFromDbInit.ColliderRadius = float64(24) // Hardcoded
|
pPlayerFromDbInit.ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded
|
||||||
|
|
||||||
pR.Players[playerId] = pPlayerFromDbInit
|
pR.Players[playerId] = pPlayerFromDbInit
|
||||||
pR.PlayerDownsyncSessionDict[playerId] = session
|
pR.PlayerDownsyncSessionDict[playerId] = session
|
||||||
@@ -215,15 +222,16 @@ func (pR *Room) ReAddPlayerIfPossible(pTmpPlayerInstance *Player, session *webso
|
|||||||
* -- YFLu
|
* -- YFLu
|
||||||
*/
|
*/
|
||||||
defer pR.onPlayerReAdded(playerId)
|
defer pR.onPlayerReAdded(playerId)
|
||||||
pR.PlayerDownsyncSessionDict[playerId] = session
|
|
||||||
pR.PlayerSignalToCloseDict[playerId] = signalToCloseConnOfThisPlayer
|
|
||||||
pEffectiveInRoomPlayerInstance := pR.Players[playerId]
|
pEffectiveInRoomPlayerInstance := pR.Players[playerId]
|
||||||
pEffectiveInRoomPlayerInstance.AckingFrameId = -1
|
pEffectiveInRoomPlayerInstance.AckingFrameId = -1
|
||||||
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.Speed = pR.PlayerDefaultSpeed // Hardcoded
|
||||||
pEffectiveInRoomPlayerInstance.ColliderRadius = float64(16) // Hardcoded
|
pEffectiveInRoomPlayerInstance.ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded
|
||||||
|
|
||||||
|
pR.PlayerDownsyncSessionDict[playerId] = session
|
||||||
|
pR.PlayerSignalToCloseDict[playerId] = signalToCloseConnOfThisPlayer
|
||||||
|
|
||||||
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
|
||||||
@@ -266,8 +274,8 @@ func (pR *Room) ChooseStage() error {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtain the content of `gidBoundariesMapInB2World`.
|
// Obtain the content of `gidBoundariesMap`.
|
||||||
gidBoundariesMapInB2World := make(map[int]StrToPolygon2DListMap, 0)
|
gidBoundariesMap := make(map[int]StrToPolygon2DListMap, 0)
|
||||||
for _, tileset := range pTmxMapIns.Tilesets {
|
for _, tileset := range pTmxMapIns.Tilesets {
|
||||||
relativeTsxFilePath := fmt.Sprintf("%s/%s", filepath.Join(pwd, relativePathForChosenStage), tileset.Source) // Note that "TmxTileset.Source" can be a string of "relative path".
|
relativeTsxFilePath := fmt.Sprintf("%s/%s", filepath.Join(pwd, relativePathForChosenStage), tileset.Source) // Note that "TmxTileset.Source" can be a string of "relative path".
|
||||||
absTsxFilePath, err := filepath.Abs(relativeTsxFilePath)
|
absTsxFilePath, err := filepath.Abs(relativeTsxFilePath)
|
||||||
@@ -283,10 +291,10 @@ func (pR *Room) ChooseStage() error {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
DeserializeTsxToColliderDict(pTmxMapIns, byteArrOfTsxFile, int(tileset.FirstGid), gidBoundariesMapInB2World)
|
DeserializeTsxToColliderDict(pTmxMapIns, byteArrOfTsxFile, int(tileset.FirstGid), gidBoundariesMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
stageDiscreteW, stageDiscreteH, stageTileW, stageTileH, strToVec2DListMap, strToPolygon2DListMap, err := ParseTmxLayersAndGroups(pTmxMapIns, gidBoundariesMapInB2World)
|
stageDiscreteW, stageDiscreteH, stageTileW, stageTileH, strToVec2DListMap, strToPolygon2DListMap, err := ParseTmxLayersAndGroups(pTmxMapIns, gidBoundariesMap)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -352,6 +360,7 @@ func (pR *Room) InputsBufferString(allDetails bool) string {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
f := tmp.(*InputFrameDownsync)
|
f := tmp.(*InputFrameDownsync)
|
||||||
|
//s = append(s, fmt.Sprintf("{inputFrameId: %v, inputList: %v, &inputList: %p, confirmedList: %v}", f.InputFrameId, f.InputList, &(f.InputList), f.ConfirmedList))
|
||||||
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))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,15 +376,13 @@ func (pR *Room) StartBattle() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always instantiates a new channel and let the old one die out due to not being retained by any root reference.
|
|
||||||
nanosPerFrame := 1000000000 / int64(pR.ServerFps)
|
|
||||||
pR.RenderFrameId = 0
|
pR.RenderFrameId = 0
|
||||||
|
|
||||||
// Initialize the "collisionSys" as well as "RenderFrameBuffer"
|
// Initialize the "collisionSys" as well as "RenderFrameBuffer"
|
||||||
pR.CurDynamicsRenderFrameId = 0
|
pR.CurDynamicsRenderFrameId = 0
|
||||||
kickoffFrame := &RoomDownsyncFrame{
|
kickoffFrame := &RoomDownsyncFrame{
|
||||||
Id: pR.RenderFrameId,
|
Id: pR.RenderFrameId,
|
||||||
Players: toPbPlayers(pR.Players),
|
Players: toPbPlayers(pR.Players, false),
|
||||||
CountdownNanos: pR.BattleDurationNanos,
|
CountdownNanos: pR.BattleDurationNanos,
|
||||||
}
|
}
|
||||||
pR.RenderFrameBuffer.Put(kickoffFrame)
|
pR.RenderFrameBuffer.Put(kickoffFrame)
|
||||||
@@ -408,7 +415,7 @@ func (pR *Room) StartBattle() {
|
|||||||
stCalculation := utils.UnixtimeNano()
|
stCalculation := utils.UnixtimeNano()
|
||||||
|
|
||||||
elapsedNanosSinceLastFrameIdTriggered := stCalculation - pR.LastRenderFrameIdTriggeredAt
|
elapsedNanosSinceLastFrameIdTriggered := stCalculation - pR.LastRenderFrameIdTriggeredAt
|
||||||
if elapsedNanosSinceLastFrameIdTriggered < pR.RollbackEstimatedDtNanos {
|
if elapsedNanosSinceLastFrameIdTriggered < pR.dilutedRollbackEstimatedDtNanos {
|
||||||
Logger.Debug(fmt.Sprintf("Avoiding too fast frame@roomId=%v, renderFrameId=%v: elapsedNanosSinceLastFrameIdTriggered=%v", pR.Id, pR.RenderFrameId, elapsedNanosSinceLastFrameIdTriggered))
|
Logger.Debug(fmt.Sprintf("Avoiding too fast frame@roomId=%v, renderFrameId=%v: elapsedNanosSinceLastFrameIdTriggered=%v", pR.Id, pR.RenderFrameId, elapsedNanosSinceLastFrameIdTriggered))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -570,10 +577,10 @@ func (pR *Room) StartBattle() {
|
|||||||
|
|
||||||
pR.RenderFrameId++
|
pR.RenderFrameId++
|
||||||
elapsedInCalculation := (utils.UnixtimeNano() - stCalculation)
|
elapsedInCalculation := (utils.UnixtimeNano() - stCalculation)
|
||||||
if elapsedInCalculation > nanosPerFrame {
|
if elapsedInCalculation > pR.dilutedRollbackEstimatedDtNanos {
|
||||||
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))
|
Logger.Warn(fmt.Sprintf("SLOW FRAME! Elapsed time statistics: roomId=%v, room.RenderFrameId=%v, elapsedInCalculation=%v ns, dynamicsDuration=%v ns, dilutedRollbackEstimatedDtNanos=%v", pR.Id, pR.RenderFrameId, elapsedInCalculation, dynamicsDuration, pR.dilutedRollbackEstimatedDtNanos))
|
||||||
}
|
}
|
||||||
time.Sleep(time.Duration(nanosPerFrame - elapsedInCalculation))
|
time.Sleep(time.Duration(pR.dilutedRollbackEstimatedDtNanos - elapsedInCalculation))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,7 +624,6 @@ func (pR *Room) OnBattleCmdReceived(pReq *WsReq) {
|
|||||||
Logger.Debug(fmt.Sprintf("Omitting obsolete inputFrameUpsync: roomId=%v, playerId=%v, clientInputFrameId=%v, InputsBuffer=%v", pR.Id, playerId, clientInputFrameId, pR.InputsBufferString(false)))
|
Logger.Debug(fmt.Sprintf("Omitting obsolete inputFrameUpsync: roomId=%v, playerId=%v, clientInputFrameId=%v, InputsBuffer=%v", pR.Id, playerId, clientInputFrameId, pR.InputsBufferString(false)))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
bufIndex := pR.toDiscreteInputsBufferIndex(clientInputFrameId, pReq.JoinIndex)
|
bufIndex := pR.toDiscreteInputsBufferIndex(clientInputFrameId, pReq.JoinIndex)
|
||||||
pR.DiscreteInputsBuffer.Store(bufIndex, inputFrameUpsync)
|
pR.DiscreteInputsBuffer.Store(bufIndex, inputFrameUpsync)
|
||||||
|
|
||||||
@@ -669,7 +675,7 @@ func (pR *Room) StopBattleForSettlement() {
|
|||||||
for playerId, _ := range pR.Players {
|
for playerId, _ := range pR.Players {
|
||||||
assembledFrame := RoomDownsyncFrame{
|
assembledFrame := RoomDownsyncFrame{
|
||||||
Id: pR.RenderFrameId,
|
Id: pR.RenderFrameId,
|
||||||
Players: toPbPlayers(pR.Players),
|
Players: toPbPlayers(pR.Players, false),
|
||||||
CountdownNanos: -1, // TODO: Replace this magic constant!
|
CountdownNanos: -1, // TODO: Replace this magic constant!
|
||||||
}
|
}
|
||||||
pR.sendSafely(&assembledFrame, nil, DOWNSYNC_MSG_ACT_BATTLE_STOPPED, playerId)
|
pR.sendSafely(&assembledFrame, nil, DOWNSYNC_MSG_ACT_BATTLE_STOPPED, playerId)
|
||||||
@@ -693,22 +699,9 @@ 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]*PlayerDownsyncMeta, 0)
|
|
||||||
for _, player := range pR.Players {
|
|
||||||
playerMetas[player.Id] = &PlayerDownsyncMeta{
|
|
||||||
Id: player.Id,
|
|
||||||
Name: player.Name,
|
|
||||||
DisplayName: player.DisplayName,
|
|
||||||
Avatar: player.Avatar,
|
|
||||||
ColliderRadius: player.ColliderRadius, // hardcoded for now
|
|
||||||
JoinIndex: player.JoinIndex,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
battleReadyToStartFrame := &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, true),
|
||||||
PlayerMetas: playerMetas,
|
|
||||||
CountdownNanos: pR.BattleDurationNanos,
|
CountdownNanos: pR.BattleDurationNanos,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -775,6 +768,7 @@ 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.BulletBattleLocalIdCounter = 0
|
||||||
pR.WorldToVirtualGridRatio = float64(1000)
|
pR.WorldToVirtualGridRatio = float64(1000)
|
||||||
pR.VirtualGridToWorldRatio = float64(1.0) / pR.WorldToVirtualGridRatio // this is a one-off computation, should avoid division in iterations
|
pR.VirtualGridToWorldRatio = float64(1.0) / pR.WorldToVirtualGridRatio // this is a one-off computation, should avoid division in iterations
|
||||||
pR.SpAtkLookupFrames = 5
|
pR.SpAtkLookupFrames = 5
|
||||||
@@ -786,9 +780,10 @@ func (pR *Room) OnDismissed() {
|
|||||||
pR.PlayerSignalToCloseDict = make(map[int32]SignalToCloseConnCbType)
|
pR.PlayerSignalToCloseDict = make(map[int32]SignalToCloseConnCbType)
|
||||||
pR.JoinIndexBooleanArr = make([]bool, pR.Capacity)
|
pR.JoinIndexBooleanArr = make([]bool, pR.Capacity)
|
||||||
pR.Barriers = make(map[int32]*Barrier)
|
pR.Barriers = make(map[int32]*Barrier)
|
||||||
pR.InputsBuffer = NewRingBuffer(1024)
|
pR.RenderCacheSize = 1024
|
||||||
|
pR.RenderFrameBuffer = NewRingBuffer(pR.RenderCacheSize)
|
||||||
pR.DiscreteInputsBuffer = sync.Map{}
|
pR.DiscreteInputsBuffer = sync.Map{}
|
||||||
pR.RenderFrameBuffer = NewRingBuffer(1024)
|
pR.InputsBuffer = NewRingBuffer((pR.RenderCacheSize >> 2) + 1)
|
||||||
|
|
||||||
pR.LastAllConfirmedInputFrameId = -1
|
pR.LastAllConfirmedInputFrameId = -1
|
||||||
pR.LastAllConfirmedInputFrameIdWithChange = -1
|
pR.LastAllConfirmedInputFrameIdWithChange = -1
|
||||||
@@ -797,17 +792,45 @@ func (pR *Room) OnDismissed() {
|
|||||||
pR.RenderFrameId = 0
|
pR.RenderFrameId = 0
|
||||||
pR.CurDynamicsRenderFrameId = 0
|
pR.CurDynamicsRenderFrameId = 0
|
||||||
pR.InputDelayFrames = 8
|
pR.InputDelayFrames = 8
|
||||||
pR.NstDelayFrames = 8
|
pR.NstDelayFrames = 4
|
||||||
pR.InputScaleFrames = uint32(2)
|
pR.InputScaleFrames = uint32(2)
|
||||||
pR.ServerFps = 60
|
pR.ServerFps = 60
|
||||||
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
|
||||||
|
dilutionFactor := 12
|
||||||
|
pR.dilutedRollbackEstimatedDtNanos = int64(16666666 * (dilutionFactor) / (dilutionFactor - 1)) // [WARNING] Only used in controlling "battleMainLoop" to be keep a frame rate lower than that of the frontends, such that upon resync(i.e. BackendDynamicsEnabled=true), the frontends would have bigger chances to keep up with or even surpass the backend calculation
|
||||||
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 = 8
|
pR.MaxChasingRenderFramesPerUpdate = 5
|
||||||
|
|
||||||
pR.BackendDynamicsEnabled = true // [WARNING] When "false", recovery upon reconnection wouldn't work!
|
pR.BackendDynamicsEnabled = true // [WARNING] When "false", recovery upon reconnection wouldn't work!
|
||||||
|
punchSkillId := int32(1)
|
||||||
|
pR.MeleeSkillConfig = make(map[int32]*MeleeBullet, 0)
|
||||||
|
pR.MeleeSkillConfig[punchSkillId] = &MeleeBullet{
|
||||||
|
// for offender
|
||||||
|
StartupFrames: int32(23),
|
||||||
|
ActiveFrames: int32(3),
|
||||||
|
RecoveryFrames: int32(61), // I hereby set it to be 1 frame more than the actual animation to avoid critical transition, i.e. when the animation is 1 frame from ending but "rdfPlayer.framesToRecover" is already counted 0 and the player triggers an other same attack, making an effective bullet trigger but no animation is played due to same animName is still playing
|
||||||
|
RecoveryFramesOnBlock: int32(61),
|
||||||
|
RecoveryFramesOnHit: int32(61),
|
||||||
|
Moveforward: &Vec2D{
|
||||||
|
X: 0,
|
||||||
|
Y: 0,
|
||||||
|
},
|
||||||
|
HitboxOffset: float64(24.0), // should be about the radius of the PlayerCollider
|
||||||
|
HitboxSize: &Vec2D{
|
||||||
|
X: float64(45.0),
|
||||||
|
Y: float64(32.0),
|
||||||
|
},
|
||||||
|
|
||||||
|
// for defender
|
||||||
|
HitStunFrames: int32(18),
|
||||||
|
BlockStunFrames: int32(9),
|
||||||
|
Pushback: float64(11.0),
|
||||||
|
ReleaseTriggerType: int32(1), // 1: rising-edge, 2: falling-edge
|
||||||
|
Damage: int32(5),
|
||||||
|
}
|
||||||
|
|
||||||
pR.ChooseStage()
|
pR.ChooseStage()
|
||||||
pR.EffectivePlayerCount = 0
|
pR.EffectivePlayerCount = 0
|
||||||
@@ -927,6 +950,14 @@ func (pR *Room) onPlayerAdded(playerId int32) {
|
|||||||
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].VirtualGridX, pR.Players[playerId].VirtualGridY = WorldToVirtualGridPos(playerPos.X, playerPos.Y, pR.WorldToVirtualGridRatio)
|
pR.Players[playerId].VirtualGridX, pR.Players[playerId].VirtualGridY = WorldToVirtualGridPos(playerPos.X, playerPos.Y, pR.WorldToVirtualGridRatio)
|
||||||
|
// Hardcoded initial character orientation/facing
|
||||||
|
if 0 == (pR.Players[playerId].JoinIndex % 2) {
|
||||||
|
pR.Players[playerId].DirX = -2
|
||||||
|
pR.Players[playerId].DirY = 0
|
||||||
|
} else {
|
||||||
|
pR.Players[playerId].DirX = +2
|
||||||
|
pR.Players[playerId].DirY = 0
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -954,18 +985,6 @@ func (pR *Room) OnPlayerBattleColliderAcked(playerId int32) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
playerMetas := make(map[int32]*PlayerDownsyncMeta, 0)
|
|
||||||
for _, eachPlayer := range pR.Players {
|
|
||||||
playerMetas[eachPlayer.Id] = &PlayerDownsyncMeta{
|
|
||||||
Id: eachPlayer.Id,
|
|
||||||
Name: eachPlayer.Name,
|
|
||||||
DisplayName: eachPlayer.DisplayName,
|
|
||||||
Avatar: eachPlayer.Avatar,
|
|
||||||
JoinIndex: eachPlayer.JoinIndex,
|
|
||||||
ColliderRadius: eachPlayer.ColliderRadius,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Broadcast added or readded player info to all players in the same room
|
// Broadcast added or readded player info to all players in the same room
|
||||||
for _, eachPlayer := range pR.Players {
|
for _, eachPlayer := range pR.Players {
|
||||||
/*
|
/*
|
||||||
@@ -979,16 +998,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 := &RoomDownsyncFrame{
|
playerAckedFrame := &RoomDownsyncFrame{
|
||||||
Id: pR.RenderFrameId,
|
Id: pR.RenderFrameId,
|
||||||
Players: toPbPlayers(pR.Players),
|
Players: toPbPlayers(pR.Players, true),
|
||||||
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 := &RoomDownsyncFrame{
|
playerAckedFrame := &RoomDownsyncFrame{
|
||||||
Id: pR.RenderFrameId,
|
Id: pR.RenderFrameId,
|
||||||
Players: toPbPlayers(pR.Players),
|
Players: toPbPlayers(pR.Players, true),
|
||||||
PlayerMetas: playerMetas,
|
|
||||||
}
|
}
|
||||||
pR.sendSafely(playerAckedFrame, nil, DOWNSYNC_MSG_ACT_PLAYER_READDED_AND_ACKED, eachPlayer.Id)
|
pR.sendSafely(playerAckedFrame, nil, DOWNSYNC_MSG_ACT_PLAYER_READDED_AND_ACKED, eachPlayer.Id)
|
||||||
default:
|
default:
|
||||||
@@ -1064,7 +1081,10 @@ func (pR *Room) prefabInputFrameDownsync(inputFrameId int32) *InputFrameDownsync
|
|||||||
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.(*InputFrameDownsync)
|
prevInputFrameDownsync := tmp.(*InputFrameDownsync)
|
||||||
currInputList := prevInputFrameDownsync.InputList // Would be a clone of the values
|
currInputList := make([]uint64, pR.Capacity) // Would be a clone of the values
|
||||||
|
for i, _ := range currInputList {
|
||||||
|
currInputList[i] = (prevInputFrameDownsync.InputList[i] & uint64(15)) // Don't predict attack input!
|
||||||
|
}
|
||||||
currInputFrameDownsync = &InputFrameDownsync{
|
currInputFrameDownsync = &InputFrameDownsync{
|
||||||
InputFrameId: inputFrameId,
|
InputFrameId: inputFrameId,
|
||||||
InputList: currInputList,
|
InputList: currInputList,
|
||||||
@@ -1083,7 +1103,7 @@ func (pR *Room) markConfirmationIfApplicable() {
|
|||||||
for inputFrameId := inputFrameId1; inputFrameId < pR.InputsBuffer.EdFrameId; inputFrameId++ {
|
for inputFrameId := inputFrameId1; inputFrameId < pR.InputsBuffer.EdFrameId; inputFrameId++ {
|
||||||
tmp := pR.InputsBuffer.GetByFrameId(inputFrameId)
|
tmp := pR.InputsBuffer.GetByFrameId(inputFrameId)
|
||||||
if nil == tmp {
|
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)))
|
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 (Or maybe you're having a 'Room.RenderCacheSize' too small)! InputsBuffer=%v", inputFrameId, pR.Id, pR.InputsBufferString(false)))
|
||||||
}
|
}
|
||||||
inputFrameDownsync := tmp.(*InputFrameDownsync)
|
inputFrameDownsync := tmp.(*InputFrameDownsync)
|
||||||
for _, player := range pR.Players {
|
for _, player := range pR.Players {
|
||||||
@@ -1173,13 +1193,6 @@ func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRende
|
|||||||
}
|
}
|
||||||
|
|
||||||
nextRenderFrame := pR.applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, currRenderFrame, pR.CollisionSysMap)
|
nextRenderFrame := pR.applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, currRenderFrame, pR.CollisionSysMap)
|
||||||
// Update in the latest player pointers
|
|
||||||
for playerId, playerDownsync := range nextRenderFrame.Players {
|
|
||||||
pR.Players[playerId].VirtualGridX = playerDownsync.VirtualGridX
|
|
||||||
pR.Players[playerId].VirtualGridY = playerDownsync.VirtualGridY
|
|
||||||
pR.Players[playerId].Dir.Dx = playerDownsync.Dir.Dx
|
|
||||||
pR.Players[playerId].Dir.Dy = playerDownsync.Dir.Dy
|
|
||||||
}
|
|
||||||
pR.RenderFrameBuffer.Put(nextRenderFrame)
|
pR.RenderFrameBuffer.Put(nextRenderFrame)
|
||||||
pR.CurDynamicsRenderFrameId++
|
pR.CurDynamicsRenderFrameId++
|
||||||
}
|
}
|
||||||
@@ -1187,22 +1200,28 @@ func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRende
|
|||||||
|
|
||||||
// TODO: Write unit-test for this function to compare with its frontend counter part
|
// TODO: Write unit-test for this function to compare with its frontend counter part
|
||||||
func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame *InputFrameDownsync, currRenderFrame *RoomDownsyncFrame, collisionSysMap map[int32]*resolv.Object) *RoomDownsyncFrame {
|
func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame *InputFrameDownsync, currRenderFrame *RoomDownsyncFrame, collisionSysMap map[int32]*resolv.Object) *RoomDownsyncFrame {
|
||||||
|
// TODO: Derive "nextRenderFramePlayers[*].CharacterState" as the frontend counter-part!
|
||||||
nextRenderFramePlayers := make(map[int32]*PlayerDownsync, pR.Capacity)
|
nextRenderFramePlayers := make(map[int32]*PlayerDownsync, pR.Capacity)
|
||||||
// Make a copy first
|
// Make a copy first
|
||||||
for playerId, currPlayerDownsync := range currRenderFrame.Players {
|
for playerId, currPlayerDownsync := range currRenderFrame.Players {
|
||||||
nextRenderFramePlayers[playerId] = &PlayerDownsync{
|
nextRenderFramePlayers[playerId] = &PlayerDownsync{
|
||||||
Id: playerId,
|
Id: playerId,
|
||||||
VirtualGridX: currPlayerDownsync.VirtualGridX,
|
VirtualGridX: currPlayerDownsync.VirtualGridX,
|
||||||
VirtualGridY: currPlayerDownsync.VirtualGridY,
|
VirtualGridY: currPlayerDownsync.VirtualGridY,
|
||||||
Dir: &Direction{
|
DirX: currPlayerDownsync.DirX,
|
||||||
Dx: currPlayerDownsync.Dir.Dx,
|
DirY: currPlayerDownsync.DirY,
|
||||||
Dy: currPlayerDownsync.Dir.Dy,
|
CharacterState: currPlayerDownsync.CharacterState,
|
||||||
},
|
Speed: currPlayerDownsync.Speed,
|
||||||
Speed: currPlayerDownsync.Speed,
|
BattleState: currPlayerDownsync.BattleState,
|
||||||
BattleState: currPlayerDownsync.BattleState,
|
Score: currPlayerDownsync.Score,
|
||||||
Score: currPlayerDownsync.Score,
|
Removed: currPlayerDownsync.Removed,
|
||||||
Removed: currPlayerDownsync.Removed,
|
JoinIndex: currPlayerDownsync.JoinIndex,
|
||||||
JoinIndex: currPlayerDownsync.JoinIndex,
|
FramesToRecover: currPlayerDownsync.FramesToRecover - 1,
|
||||||
|
Hp: currPlayerDownsync.Hp,
|
||||||
|
MaxHp: currPlayerDownsync.MaxHp,
|
||||||
|
}
|
||||||
|
if nextRenderFramePlayers[playerId].FramesToRecover < 0 {
|
||||||
|
nextRenderFramePlayers[playerId].FramesToRecover = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1210,28 +1229,165 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
|
|||||||
Id: currRenderFrame.Id + 1,
|
Id: currRenderFrame.Id + 1,
|
||||||
Players: nextRenderFramePlayers,
|
Players: nextRenderFramePlayers,
|
||||||
CountdownNanos: (pR.BattleDurationNanos - int64(currRenderFrame.Id)*pR.RollbackEstimatedDtNanos),
|
CountdownNanos: (pR.BattleDurationNanos - int64(currRenderFrame.Id)*pR.RollbackEstimatedDtNanos),
|
||||||
|
MeleeBullets: make([]*MeleeBullet, 0), // Is there any better way to reduce malloc/free impact, e.g. smart prediction for fixed memory allocation?
|
||||||
|
}
|
||||||
|
|
||||||
|
bulletPushbacks := make([]Vec2D, pR.Capacity) // Guaranteed determinism regardless of traversal order
|
||||||
|
effPushbacks := make([]Vec2D, pR.Capacity) // Guaranteed determinism regardless of traversal order
|
||||||
|
|
||||||
|
// Reset playerCollider position from the "virtual grid position"
|
||||||
|
for playerId, player := range pR.Players {
|
||||||
|
joinIndex := player.JoinIndex
|
||||||
|
bulletPushbacks[joinIndex-1].X, bulletPushbacks[joinIndex-1].Y = float64(0), float64(0)
|
||||||
|
effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y = float64(0), float64(0)
|
||||||
|
currPlayerDownsync := currRenderFrame.Players[playerId]
|
||||||
|
newVx, newVy := currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check bullet-anything collisions first, because the pushbacks caused by bullets might later be reverted by player-barrier collision
|
||||||
|
bulletColliders := make(map[int32]*resolv.Object, 0) // Will all be removed at the end of `applyInputFrameDownsyncDynamicsOnSingleRenderFrame` due to the need for being rollback-compatible
|
||||||
|
removedBulletsAtCurrFrame := make(map[int32]int32, 0)
|
||||||
|
for _, meleeBullet := range currRenderFrame.MeleeBullets {
|
||||||
|
if (meleeBullet.OriginatedRenderFrameId+meleeBullet.StartupFrames <= currRenderFrame.Id) && (meleeBullet.OriginatedRenderFrameId+meleeBullet.StartupFrames+meleeBullet.ActiveFrames > currRenderFrame.Id) {
|
||||||
|
collisionBulletIndex := COLLISION_BULLET_INDEX_PREFIX + meleeBullet.BattleLocalId
|
||||||
|
collisionOffenderIndex := COLLISION_PLAYER_INDEX_PREFIX + meleeBullet.OffenderJoinIndex
|
||||||
|
offenderCollider := collisionSysMap[collisionOffenderIndex]
|
||||||
|
offender := currRenderFrame.Players[meleeBullet.OffenderPlayerId]
|
||||||
|
|
||||||
|
xfac := float64(1.0) // By now, straight Punch offset doesn't respect "y-axis"
|
||||||
|
if 0 > offender.DirX {
|
||||||
|
xfac = float64(-1.0)
|
||||||
|
}
|
||||||
|
offenderWx, offenderWy := VirtualGridToWorldPos(offender.VirtualGridX, offender.VirtualGridY, pR.VirtualGridToWorldRatio)
|
||||||
|
bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy
|
||||||
|
|
||||||
|
newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "MeleeBullet")
|
||||||
|
newBulletCollider.Data = meleeBullet
|
||||||
|
pR.Space.Add(newBulletCollider)
|
||||||
|
collisionSysMap[collisionBulletIndex] = newBulletCollider
|
||||||
|
bulletColliders[collisionBulletIndex] = newBulletCollider
|
||||||
|
|
||||||
|
Logger.Debug(fmt.Sprintf("roomId=%v, a meleeBullet is added to collisionSys at currRenderFrame.id=%v as start-up frames ended and active frame is not yet ended: %v, from offenderCollider=%v, xfac=%v", pR.Id, currRenderFrame.Id, ConvexPolygonStr(newBulletCollider.Shape.(*resolv.ConvexPolygon)), ConvexPolygonStr(offenderCollider.Shape.(*resolv.ConvexPolygon)), xfac))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, bulletCollider := range bulletColliders {
|
||||||
|
shouldRemove := false
|
||||||
|
meleeBullet := bulletCollider.Data.(*MeleeBullet)
|
||||||
|
collisionBulletIndex := COLLISION_BULLET_INDEX_PREFIX + meleeBullet.BattleLocalId
|
||||||
|
bulletShape := bulletCollider.Shape.(*resolv.ConvexPolygon)
|
||||||
|
if collision := bulletCollider.Check(0, 0); collision != nil {
|
||||||
|
offender := currRenderFrame.Players[meleeBullet.OffenderPlayerId]
|
||||||
|
for _, obj := range collision.Objects {
|
||||||
|
defenderShape := obj.Shape.(*resolv.ConvexPolygon)
|
||||||
|
switch t := obj.Data.(type) {
|
||||||
|
case *Player:
|
||||||
|
if meleeBullet.OffenderPlayerId != t.Id {
|
||||||
|
if overlapped, _, _, _ := CalcPushbacks(0, 0, bulletShape, defenderShape); overlapped {
|
||||||
|
xfac := float64(1.0) // By now, straight Punch offset doesn't respect "y-axis"
|
||||||
|
if 0 > offender.DirX {
|
||||||
|
xfac = float64(-1.0)
|
||||||
|
}
|
||||||
|
bulletPushbacks[t.JoinIndex-1].X += xfac * meleeBullet.Pushback
|
||||||
|
nextRenderFramePlayers[t.Id].CharacterState = ATK_CHARACTER_STATE_ATKED1
|
||||||
|
oldFramesToRecover := nextRenderFramePlayers[t.Id].FramesToRecover
|
||||||
|
if meleeBullet.HitStunFrames > oldFramesToRecover {
|
||||||
|
nextRenderFramePlayers[t.Id].FramesToRecover = meleeBullet.HitStunFrames
|
||||||
|
}
|
||||||
|
Logger.Debug(fmt.Sprintf("roomId=%v, a meleeBullet collides w/ player at currRenderFrame.id=%v: b=%v, p=%v", pR.Id, currRenderFrame.Id, ConvexPolygonStr(bulletShape), ConvexPolygonStr(defenderShape)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
Logger.Debug(fmt.Sprintf("Bullet %v collided with non-player %v: roomId=%v, currRenderFrame.Id=%v, delayedInputFrame.Id=%v, objDataType=%t, objData=%v", ConvexPolygonStr(bulletShape), ConvexPolygonStr(defenderShape), pR.Id, currRenderFrame.Id, delayedInputFrame.InputFrameId, obj.Data, obj.Data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shouldRemove = true
|
||||||
|
}
|
||||||
|
if shouldRemove {
|
||||||
|
removedBulletsAtCurrFrame[collisionBulletIndex] = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, meleeBullet := range currRenderFrame.MeleeBullets {
|
||||||
|
collisionBulletIndex := COLLISION_BULLET_INDEX_PREFIX + meleeBullet.BattleLocalId
|
||||||
|
if bulletCollider, existent := collisionSysMap[collisionBulletIndex]; existent {
|
||||||
|
bulletCollider.Space.Remove(bulletCollider)
|
||||||
|
delete(collisionSysMap, collisionBulletIndex)
|
||||||
|
}
|
||||||
|
if _, existent := removedBulletsAtCurrFrame[collisionBulletIndex]; existent {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
toRet.MeleeBullets = append(toRet.MeleeBullets, meleeBullet)
|
||||||
}
|
}
|
||||||
|
|
||||||
if nil != delayedInputFrame {
|
if nil != delayedInputFrame {
|
||||||
|
var delayedInputFrameForPrevRenderFrame *InputFrameDownsync = nil
|
||||||
|
tmp := pR.InputsBuffer.GetByFrameId(pR.ConvertToInputFrameId(currRenderFrame.Id-1, pR.InputDelayFrames))
|
||||||
|
if nil != tmp {
|
||||||
|
delayedInputFrameForPrevRenderFrame = tmp.(*InputFrameDownsync)
|
||||||
|
}
|
||||||
inputList := delayedInputFrame.InputList
|
inputList := delayedInputFrame.InputList
|
||||||
effPushbacks := make([]Vec2D, pR.Capacity) // Guaranteed determinism regardless of traversal order
|
// Process player inputs
|
||||||
for playerId, player := range pR.Players {
|
for playerId, player := range pR.Players {
|
||||||
joinIndex := player.JoinIndex
|
joinIndex := player.JoinIndex
|
||||||
effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y = float64(0), float64(0)
|
|
||||||
currPlayerDownsync := currRenderFrame.Players[playerId]
|
|
||||||
decodedInput := pR.decodeInput(inputList[joinIndex-1])
|
|
||||||
proposedVirtualGridDx, proposedVirtualGridDy := (decodedInput.Dx + decodedInput.Dx*currPlayerDownsync.Speed), (decodedInput.Dy + decodedInput.Dy*currPlayerDownsync.Speed)
|
|
||||||
newVx, newVy := (currPlayerDownsync.VirtualGridX + proposedVirtualGridDx), (currPlayerDownsync.VirtualGridY + proposedVirtualGridDy)
|
|
||||||
// Reset playerCollider position from the "virtual grid position"
|
|
||||||
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
|
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
|
||||||
playerCollider := collisionSysMap[collisionPlayerIndex]
|
playerCollider := collisionSysMap[collisionPlayerIndex]
|
||||||
playerCollider.X, playerCollider.Y = VirtualGridToPolygonColliderAnchorPos(newVx, newVy, player.ColliderRadius, player.ColliderRadius, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, pR.VirtualGridToWorldRatio)
|
thatPlayerInNextFrame := nextRenderFramePlayers[playerId]
|
||||||
|
if 0 < thatPlayerInNextFrame.FramesToRecover {
|
||||||
|
// No need to process inputs for this player, but there might be bullet pushbacks on this player
|
||||||
|
playerCollider.X += bulletPushbacks[joinIndex-1].X
|
||||||
|
playerCollider.Y += bulletPushbacks[joinIndex-1].Y
|
||||||
|
// Update in the collision system
|
||||||
|
playerCollider.Update()
|
||||||
|
if 0 != bulletPushbacks[joinIndex-1].X || 0 != bulletPushbacks[joinIndex-1].Y {
|
||||||
|
Logger.Info(fmt.Sprintf("roomId=%v, playerId=%v is pushed back by (%.2f, %.2f) by bullet impacts, now its framesToRecover is %d at currRenderFrame.id=%v", pR.Id, playerId, bulletPushbacks[joinIndex-1].X, bulletPushbacks[joinIndex-1].Y, thatPlayerInNextFrame.FramesToRecover, currRenderFrame.Id))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
currPlayerDownsync := currRenderFrame.Players[playerId]
|
||||||
|
decodedInput := pR.decodeInput(inputList[joinIndex-1])
|
||||||
|
prevBtnALevel := int32(0)
|
||||||
|
if nil != delayedInputFrameForPrevRenderFrame {
|
||||||
|
prevDecodedInput := pR.decodeInput(delayedInputFrameForPrevRenderFrame.InputList[joinIndex-1])
|
||||||
|
prevBtnALevel = prevDecodedInput.BtnALevel
|
||||||
|
}
|
||||||
|
|
||||||
|
if decodedInput.BtnALevel > prevBtnALevel {
|
||||||
|
punchSkillId := int32(1)
|
||||||
|
punchConfig := pR.MeleeSkillConfig[punchSkillId]
|
||||||
|
var newMeleeBullet MeleeBullet = *punchConfig
|
||||||
|
newMeleeBullet.BattleLocalId = pR.BulletBattleLocalIdCounter
|
||||||
|
pR.BulletBattleLocalIdCounter += 1
|
||||||
|
newMeleeBullet.OffenderJoinIndex = joinIndex
|
||||||
|
newMeleeBullet.OffenderPlayerId = playerId
|
||||||
|
newMeleeBullet.OriginatedRenderFrameId = currRenderFrame.Id
|
||||||
|
toRet.MeleeBullets = append(toRet.MeleeBullets, &newMeleeBullet)
|
||||||
|
thatPlayerInNextFrame.FramesToRecover = newMeleeBullet.RecoveryFrames
|
||||||
|
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATK1
|
||||||
|
Logger.Info(fmt.Sprintf("roomId=%v, playerId=%v triggered a rising-edge of btnA at currRenderFrame.id=%v, delayedInputFrame.id=%v", pR.Id, playerId, currRenderFrame.Id, delayedInputFrame.InputFrameId))
|
||||||
|
|
||||||
|
} else if decodedInput.BtnALevel < prevBtnALevel {
|
||||||
|
Logger.Debug(fmt.Sprintf("roomId=%v, playerId=%v triggered a falling-edge of btnA at currRenderFrame.id=%v, delayedInputFrame.id=%v", pR.Id, playerId, currRenderFrame.Id, delayedInputFrame.InputFrameId))
|
||||||
|
} else {
|
||||||
|
// No bullet trigger, process movement inputs
|
||||||
|
if 0 != decodedInput.Dx || 0 != decodedInput.Dy {
|
||||||
|
thatPlayerInNextFrame.DirX = decodedInput.Dx
|
||||||
|
thatPlayerInNextFrame.DirY = decodedInput.Dy
|
||||||
|
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_WALKING
|
||||||
|
} else {
|
||||||
|
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
movementX, movementY := VirtualGridToWorldPos(decodedInput.Dx+decodedInput.Dx*currPlayerDownsync.Speed, decodedInput.Dy+decodedInput.Dy*currPlayerDownsync.Speed, pR.VirtualGridToWorldRatio)
|
||||||
|
playerCollider.X += movementX
|
||||||
|
playerCollider.Y += movementY
|
||||||
|
|
||||||
// Update in the collision system
|
// Update in the collision system
|
||||||
playerCollider.Update()
|
playerCollider.Update()
|
||||||
|
|
||||||
nextRenderFramePlayers[playerId].Dir.Dx = decodedInput.Dx
|
|
||||||
nextRenderFramePlayers[playerId].Dir.Dy = decodedInput.Dy
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle pushbacks upon collision after all movements treated as simultaneous
|
// handle pushbacks upon collision after all movements treated as simultaneous
|
||||||
@@ -1261,8 +1417,8 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
|
|||||||
|
|
||||||
// Update "virtual grid position"
|
// 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)
|
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
|
thatPlayerInNextFrame := nextRenderFramePlayers[playerId]
|
||||||
nextRenderFramePlayers[playerId].VirtualGridY = newVy
|
thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY = newVx, newVy
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Debug(fmt.Sprintf("After applyInputFrameDownsyncDynamicsOnSingleRenderFrame: currRenderFrame.Id=%v, inputList=%v, currRenderFrame.Players=%v, nextRenderFramePlayers=%v", currRenderFrame.Id, inputList, currRenderFrame.Players, nextRenderFramePlayers))
|
Logger.Debug(fmt.Sprintf("After applyInputFrameDownsyncDynamicsOnSingleRenderFrame: currRenderFrame.Id=%v, inputList=%v, currRenderFrame.Players=%v, nextRenderFramePlayers=%v", currRenderFrame.Id, inputList, currRenderFrame.Players, nextRenderFramePlayers))
|
||||||
@@ -1288,12 +1444,13 @@ func (pR *Room) inputFrameIdDebuggable(inputFrameId int32) bool {
|
|||||||
func (pR *Room) refreshColliders(spaceW, spaceH int32) {
|
func (pR *Room) refreshColliders(spaceW, spaceH int32) {
|
||||||
// Kindly note that by now, we've already got all the shapes in the tmx file into "pR.(Players | Barriers)" from "ParseTmxLayersAndGroups"
|
// Kindly note that by now, we've already got all the shapes in the tmx file into "pR.(Players | Barriers)" from "ParseTmxLayersAndGroups"
|
||||||
|
|
||||||
minStep := (int(float64(pR.PlayerDefaultSpeed)*pR.VirtualGridToWorldRatio) << 2) // the approx minimum distance a player can move per frame in world coordinate
|
minStep := (int(float64(pR.PlayerDefaultSpeed)*pR.VirtualGridToWorldRatio) << 1) // the approx minimum distance a player can move per frame in world coordinate
|
||||||
space := resolv.NewSpace(int(spaceW), int(spaceH), minStep, minStep) // allocate a new collision space everytime after a battle is settled
|
pR.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 {
|
||||||
wx, wy := VirtualGridToWorldPos(player.VirtualGridX, player.VirtualGridY, pR.VirtualGridToWorldRatio)
|
wx, wy := VirtualGridToWorldPos(player.VirtualGridX, player.VirtualGridY, pR.VirtualGridToWorldRatio)
|
||||||
playerCollider := GenerateRectCollider(wx, wy, player.ColliderRadius*2, player.ColliderRadius*2, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "Player")
|
playerCollider := GenerateRectCollider(wx, wy, player.ColliderRadius*2, player.ColliderRadius*2, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "Player")
|
||||||
space.Add(playerCollider)
|
playerCollider.Data = player
|
||||||
|
pR.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
|
||||||
pR.PlayersArr[joinIndex-1] = player
|
pR.PlayersArr[joinIndex-1] = player
|
||||||
@@ -1304,7 +1461,7 @@ func (pR *Room) refreshColliders(spaceW, spaceH int32) {
|
|||||||
for _, barrier := range pR.Barriers {
|
for _, barrier := range pR.Barriers {
|
||||||
boundaryUnaligned := barrier.Boundary
|
boundaryUnaligned := barrier.Boundary
|
||||||
barrierCollider := GenerateConvexPolygonCollider(boundaryUnaligned, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "Barrier")
|
barrierCollider := GenerateConvexPolygonCollider(boundaryUnaligned, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "Barrier")
|
||||||
space.Add(barrierCollider)
|
pR.Space.Add(barrierCollider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -271,6 +271,8 @@ func Serve(c *gin.Context) {
|
|||||||
VirtualGridToWorldRatio: pRoom.VirtualGridToWorldRatio,
|
VirtualGridToWorldRatio: pRoom.VirtualGridToWorldRatio,
|
||||||
|
|
||||||
SpAtkLookupFrames: pRoom.SpAtkLookupFrames,
|
SpAtkLookupFrames: pRoom.SpAtkLookupFrames,
|
||||||
|
RenderCacheSize: pRoom.RenderCacheSize,
|
||||||
|
MeleeSkillConfig: pRoom.MeleeSkillConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := &pb.WsResp{
|
resp := &pb.WsResp{
|
||||||
|
@@ -1,3 +1,8 @@
|
|||||||
|
# Double playback speed of a video
|
||||||
|
```
|
||||||
|
ffmpeg -i input.mp4 -filter:v "setpts=0.5*PTS" output.mp4
|
||||||
|
```
|
||||||
|
|
||||||
# GIF creation cmd reference
|
# GIF creation cmd reference
|
||||||
```
|
```
|
||||||
ffmpeg -ss 12 -t 13 -i input.mp4 -vf "fps=10,scale=480:-1" -loop 0 output.gif
|
ffmpeg -ss 12 -t 13 -i input.mp4 -vf "fps=10,scale=480:-1" -loop 0 output.gif
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 2.7 MiB |
BIN
charts/melee_attack_2.gif
Normal file
BIN
charts/melee_attack_2.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 MiB |
@@ -4,6 +4,7 @@ go 1.19
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
dnmshared v0.0.0
|
dnmshared v0.0.0
|
||||||
|
battle_srv 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
|
||||||
@@ -26,3 +27,4 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
replace dnmshared => ../dnmshared
|
replace dnmshared => ../dnmshared
|
||||||
|
replace battle_srv => ../battle_srv
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
. "battle_srv/protos"
|
||||||
. "dnmshared"
|
. "dnmshared"
|
||||||
. "dnmshared/sharedprotos"
|
. "dnmshared/sharedprotos"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -36,7 +37,7 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi
|
|||||||
virtualGridToWorldRatio := 0.1
|
virtualGridToWorldRatio := 0.1
|
||||||
playerDefaultSpeed := 20
|
playerDefaultSpeed := 20
|
||||||
minStep := (int(float64(playerDefaultSpeed)*virtualGridToWorldRatio) << 2)
|
minStep := (int(float64(playerDefaultSpeed)*virtualGridToWorldRatio) << 2)
|
||||||
playerColliderRadius := float64(16)
|
playerColliderRadius := float64(24)
|
||||||
playerColliders := make([]*resolv.Object, len(playerPosList.Eles))
|
playerColliders := make([]*resolv.Object, len(playerPosList.Eles))
|
||||||
space := resolv.NewSpace(int(spaceW), int(spaceH), minStep, minStep)
|
space := resolv.NewSpace(int(spaceW), int(spaceH), minStep, minStep)
|
||||||
for i, playerPos := range playerPosList.Eles {
|
for i, playerPos := range playerPosList.Eles {
|
||||||
@@ -84,6 +85,75 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi
|
|||||||
Logger.Info(fmt.Sprintf("effPushback={%v, %v}", effPushback.X, effPushback.Y))
|
Logger.Info(fmt.Sprintf("effPushback={%v, %v}", effPushback.X, effPushback.Y))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
meleeBullet := &MeleeBullet{
|
||||||
|
// for offender
|
||||||
|
StartupFrames: int32(18),
|
||||||
|
ActiveFrames: int32(1),
|
||||||
|
RecoveryFrames: int32(61),
|
||||||
|
RecoveryFramesOnBlock: int32(61),
|
||||||
|
RecoveryFramesOnHit: int32(61),
|
||||||
|
Moveforward: &Vec2D{
|
||||||
|
X: 0,
|
||||||
|
Y: 0,
|
||||||
|
},
|
||||||
|
HitboxOffset: float64(24.0),
|
||||||
|
HitboxSize: &Vec2D{
|
||||||
|
X: float64(45.0),
|
||||||
|
Y: float64(32.0),
|
||||||
|
},
|
||||||
|
|
||||||
|
// for defender
|
||||||
|
HitStunFrames: int32(18),
|
||||||
|
BlockStunFrames: int32(9),
|
||||||
|
Pushback: float64(22.0),
|
||||||
|
ReleaseTriggerType: int32(1), // 1: rising-edge, 2: falling-edge
|
||||||
|
Damage: int32(5),
|
||||||
|
}
|
||||||
|
bulletLeftToRight := true
|
||||||
|
if bulletLeftToRight {
|
||||||
|
xfac := float64(1.0)
|
||||||
|
offenderWx, offenderWy := playerPosList.Eles[0].X, playerPosList.Eles[0].Y
|
||||||
|
bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy
|
||||||
|
|
||||||
|
newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, spaceOffsetX, spaceOffsetY, "MeleeBullet")
|
||||||
|
space.Add(newBulletCollider)
|
||||||
|
bulletShape := newBulletCollider.Shape.(*resolv.ConvexPolygon)
|
||||||
|
Logger.Warn(fmt.Sprintf("bullet ->: Added bullet collider to space: a=%v", ConvexPolygonStr(bulletShape)))
|
||||||
|
|
||||||
|
if collision := newBulletCollider.Check(0, 0); collision != nil {
|
||||||
|
for _, obj := range collision.Objects {
|
||||||
|
objShape := obj.Shape.(*resolv.ConvexPolygon)
|
||||||
|
if overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, bulletShape, objShape); overlapped {
|
||||||
|
Logger.Warn(fmt.Sprintf("bullet ->: Overlapped: a=%v, b=%v, pushbackX=%v, pushbackY=%v", ConvexPolygonStr(bulletShape), ConvexPolygonStr(objShape), pushbackX, pushbackY))
|
||||||
|
} else {
|
||||||
|
Logger.Warn(fmt.Sprintf("bullet ->: Collided BUT not overlapped: a=%v, b=%v, overlapResult=%v", ConvexPolygonStr(bulletShape), ConvexPolygonStr(objShape), overlapResult))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bulletRightToLeft := true
|
||||||
|
if bulletRightToLeft {
|
||||||
|
xfac := float64(-1.0)
|
||||||
|
offenderWx, offenderWy := playerPosList.Eles[1].X, playerPosList.Eles[1].Y
|
||||||
|
bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy
|
||||||
|
|
||||||
|
newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, spaceOffsetX, spaceOffsetY, "MeleeBullet")
|
||||||
|
space.Add(newBulletCollider)
|
||||||
|
bulletShape := newBulletCollider.Shape.(*resolv.ConvexPolygon)
|
||||||
|
Logger.Warn(fmt.Sprintf("bullet <-: Added bullet collider to space: a=%v", ConvexPolygonStr(bulletShape)))
|
||||||
|
|
||||||
|
if collision := newBulletCollider.Check(0, 0); collision != nil {
|
||||||
|
for _, obj := range collision.Objects {
|
||||||
|
objShape := obj.Shape.(*resolv.ConvexPolygon)
|
||||||
|
if overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, bulletShape, objShape); overlapped {
|
||||||
|
Logger.Warn(fmt.Sprintf("bullet <-: Overlapped: a=%v, b=%v, pushbackX=%v, pushbackY=%v", ConvexPolygonStr(bulletShape), ConvexPolygonStr(objShape), pushbackX, pushbackY))
|
||||||
|
} else {
|
||||||
|
Logger.Warn(fmt.Sprintf("bullet <-: Collided BUT not overlapped: a=%v, b=%v, overlapResult=%v", ConvexPolygonStr(bulletShape), ConvexPolygonStr(objShape), overlapResult))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return world
|
return world
|
||||||
}
|
}
|
||||||
@@ -98,6 +168,9 @@ func (world *WorldColliderDisplay) Draw(screen *ebiten.Image) {
|
|||||||
if o.HasTags("Player") {
|
if o.HasTags("Player") {
|
||||||
drawColor := color.RGBA{0, 255, 0, 255}
|
drawColor := color.RGBA{0, 255, 0, 255}
|
||||||
DrawPolygon(screen, o.Shape.(*resolv.ConvexPolygon), drawColor)
|
DrawPolygon(screen, o.Shape.(*resolv.ConvexPolygon), drawColor)
|
||||||
|
} else if o.HasTags("MeleeBullet") {
|
||||||
|
drawColor := color.RGBA{0, 0, 255, 255}
|
||||||
|
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)
|
||||||
|
@@ -20,6 +20,10 @@ func ConvexPolygonStr(body *resolv.ConvexPolygon) string {
|
|||||||
|
|
||||||
func GenerateRectCollider(origX, origY, w, h, spaceOffsetX, spaceOffsetY float64, tag string) *resolv.Object {
|
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)
|
cx, cy := WorldToPolygonColliderAnchorPos(origX, origY, w*0.5, h*0.5, spaceOffsetX, spaceOffsetY)
|
||||||
|
return GenerateRectColliderInCollisionSpace(cx, cy, w, h, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateRectColliderInCollisionSpace(cx, cy, w, h float64, tag string) *resolv.Object {
|
||||||
collider := resolv.NewObject(cx, cy, w, h, tag)
|
collider := resolv.NewObject(cx, cy, w, h, tag)
|
||||||
shape := resolv.NewRectangle(0, 0, w, h)
|
shape := resolv.NewRectangle(0, 0, w, h)
|
||||||
collider.SetShape(shape)
|
collider.SetShape(shape)
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.28.1
|
// protoc-gen-go v1.28.1
|
||||||
// protoc v3.7.1
|
// protoc v3.21.4
|
||||||
// source: geometry.proto
|
// source: geometry.proto
|
||||||
|
|
||||||
package sharedprotos
|
package sharedprotos
|
||||||
|
BIN
dragonBonesAnimProjects/SoldierWaterGhost.dbproj
Normal file
BIN
dragonBonesAnimProjects/SoldierWaterGhost.dbproj
Normal file
Binary file not shown.
1
dragonBonesAnimProjects/library/SoldierWaterGhost.json
Normal file
1
dragonBonesAnimProjects/library/SoldierWaterGhost.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"SubTexture":[{"width":23,"y":44,"height":22,"name":"biu","x":53},{"width":9,"y":68,"height":14,"name":"rightArm","x":76},{"y":35,"frameX":-1,"frameY":0,"width":27,"frameWidth":29,"height":32,"name":"yinmoqe00","frameHeight":32,"x":89},{"width":34,"y":1,"height":41,"name":"body","x":53},{"width":9,"y":44,"height":13,"name":"rightShoulder","x":78},{"y":50,"frameX":0,"frameY":0,"width":19,"frameWidth":19,"height":18,"name":"rightFrontArm","frameHeight":18,"x":23},{"width":14,"y":70,"height":14,"name":"rightHand","x":23},{"y":68,"frameX":0,"frameY":0,"width":12,"frameWidth":12,"height":12,"name":"leftArm","frameHeight":12,"x":62},{"width":13,"y":73,"height":12,"name":"leftShoulder","x":1},{"width":20,"y":50,"height":21,"name":"leftFrontArm","x":1},{"width":33,"y":1,"height":32,"name":"head","x":89},{"width":50,"y":1,"height":47,"name":"head2","x":1},{"width":16,"y":68,"height":14,"name":"leftHand","x":44},{"y":59,"frameX":-1,"frameY":-2,"width":4,"frameWidth":8,"height":4,"name":"huomiao01","frameHeight":8,"x":78}],"width":128,"height":128,"name":"SoldierWaterGhost","imagePath":"SoldierWaterGhost_tex.png"}
|
BIN
dragonBonesAnimProjects/library/SoldierWaterGhost.png
Normal file
BIN
dragonBonesAnimProjects/library/SoldierWaterGhost.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"ver": "1.0.1",
|
||||||
|
"uuid": "8f2f76c7-649c-414a-80be-b2daef4ed580",
|
||||||
|
"isSubpackage": false,
|
||||||
|
"subpackageName": "",
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
{"SubTexture":[{"y":50,"frameX":-2,"frameY":-2,"width":19,"frameWidth":23,"height":19,"name":"biu","frameHeight":22,"x":1},{"width":9,"y":50,"height":14,"name":"rightArm","x":42},{"y":34,"frameX":-6,"frameY":0,"width":20,"frameWidth":29,"height":32,"name":"yinmoqe00","frameHeight":32,"x":88},{"y":1,"frameX":0,"frameY":0,"width":33,"frameWidth":34,"height":39,"name":"body","frameHeight":41,"x":53},{"width":9,"y":56,"height":13,"name":"rightShoulder","x":74},{"y":50,"frameX":0,"frameY":0,"width":18,"frameWidth":19,"height":17,"name":"rightFrontArm","frameHeight":18,"x":22},{"width":14,"y":50,"height":14,"name":"rightHand","x":110},{"width":12,"y":42,"height":12,"name":"leftArm","x":74},{"width":13,"y":66,"height":12,"name":"leftShoulder","x":110},{"y":42,"frameX":-1,"frameY":0,"width":19,"frameWidth":20,"height":21,"name":"leftFrontArm","frameHeight":21,"x":53},{"width":50,"y":1,"height":47,"name":"head2","x":1},{"y":1,"frameX":-1,"frameY":0,"width":32,"frameWidth":33,"height":31,"name":"head","frameHeight":32,"x":88},{"width":16,"y":34,"height":14,"name":"leftHand","x":110},{"y":1,"frameX":-2,"frameY":-3,"width":2,"frameWidth":8,"height":2,"name":"huomiao01","frameHeight":8,"x":122}],"width":128,"height":128,"name":"SoldierWaterGhost","imagePath":"SoldierWaterGhost_tex.png"}
|
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"ver": "1.0.0",
|
||||||
|
"uuid": "e9e703e9-3589-4713-b889-28b23406d220",
|
||||||
|
"atlasJson": "{\"SubTexture\":[{\"y\":50,\"frameX\":-2,\"frameY\":-2,\"width\":19,\"frameWidth\":23,\"height\":19,\"name\":\"biu\",\"frameHeight\":22,\"x\":1},{\"width\":9,\"y\":50,\"height\":14,\"name\":\"rightArm\",\"x\":42},{\"y\":34,\"frameX\":-6,\"frameY\":0,\"width\":20,\"frameWidth\":29,\"height\":32,\"name\":\"yinmoqe00\",\"frameHeight\":32,\"x\":88},{\"y\":1,\"frameX\":0,\"frameY\":0,\"width\":33,\"frameWidth\":34,\"height\":39,\"name\":\"body\",\"frameHeight\":41,\"x\":53},{\"width\":9,\"y\":56,\"height\":13,\"name\":\"rightShoulder\",\"x\":74},{\"y\":50,\"frameX\":0,\"frameY\":0,\"width\":18,\"frameWidth\":19,\"height\":17,\"name\":\"rightFrontArm\",\"frameHeight\":18,\"x\":22},{\"width\":14,\"y\":50,\"height\":14,\"name\":\"rightHand\",\"x\":110},{\"width\":12,\"y\":42,\"height\":12,\"name\":\"leftArm\",\"x\":74},{\"width\":13,\"y\":66,\"height\":12,\"name\":\"leftShoulder\",\"x\":110},{\"y\":42,\"frameX\":-1,\"frameY\":0,\"width\":19,\"frameWidth\":20,\"height\":21,\"name\":\"leftFrontArm\",\"frameHeight\":21,\"x\":53},{\"width\":50,\"y\":1,\"height\":47,\"name\":\"head2\",\"x\":1},{\"y\":1,\"frameX\":-1,\"frameY\":0,\"width\":32,\"frameWidth\":33,\"height\":31,\"name\":\"head\",\"frameHeight\":32,\"x\":88},{\"width\":16,\"y\":34,\"height\":14,\"name\":\"leftHand\",\"x\":110},{\"y\":1,\"frameX\":-2,\"frameY\":-3,\"width\":2,\"frameWidth\":8,\"height\":2,\"name\":\"huomiao01\",\"frameHeight\":8,\"x\":122}],\"width\":128,\"height\":128,\"name\":\"SoldierWaterGhost\",\"imagePath\":\"SoldierWaterGhost_tex.png\"}",
|
||||||
|
"texture": "def168c3-3f07-43f9-a460-36b397c70a57",
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 6.8 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.3.3",
|
||||||
|
"uuid": "def168c3-3f07-43f9-a460-36b397c70a57",
|
||||||
|
"type": "sprite",
|
||||||
|
"wrapMode": "clamp",
|
||||||
|
"filterMode": "bilinear",
|
||||||
|
"premultiplyAlpha": false,
|
||||||
|
"genMipmaps": false,
|
||||||
|
"packable": true,
|
||||||
|
"platformSettings": {},
|
||||||
|
"subMetas": {
|
||||||
|
"SoldierWaterGhost_tex": {
|
||||||
|
"ver": "1.0.4",
|
||||||
|
"uuid": "52fb0606-bbea-433c-803b-bf5ce936a0df",
|
||||||
|
"rawTextureUuid": "def168c3-3f07-43f9-a460-36b397c70a57",
|
||||||
|
"trimType": "auto",
|
||||||
|
"trimThreshold": 1,
|
||||||
|
"rotated": false,
|
||||||
|
"offsetX": -0.5,
|
||||||
|
"offsetY": 24.5,
|
||||||
|
"trimX": 1,
|
||||||
|
"trimY": 1,
|
||||||
|
"width": 125,
|
||||||
|
"height": 77,
|
||||||
|
"rawWidth": 128,
|
||||||
|
"rawHeight": 128,
|
||||||
|
"borderTop": 0,
|
||||||
|
"borderBottom": 0,
|
||||||
|
"borderLeft": 0,
|
||||||
|
"borderRight": 0,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,17 +1,18 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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="12">
|
<map version="1.2" tiledversion="1.2.3" orientation="orthogonal" renderorder="right-down" width="128" height="128" tilewidth="16" tileheight="16" infinite="0" nextlayerid="3" nextobjectid="39">
|
||||||
<tileset firstgid="1" source="tiles0.tsx"/>
|
<tileset firstgid="1" source="tiles0.tsx"/>
|
||||||
<tileset firstgid="65" source="tiles1.tsx"/>
|
<tileset firstgid="65" source="tiles1.tsx"/>
|
||||||
<layer id="1" name="Ground" width="64" height="64">
|
<tileset firstgid="129" source="tiles2.tsx"/>
|
||||||
|
<layer id="2" name="Ground" width="128" height="128">
|
||||||
<data encoding="base64" compression="zlib">
|
<data encoding="base64" compression="zlib">
|
||||||
eJzt0jEOwjAUREGLkiLmALRU3In7HwMKkFCUCCwiNrKnmCbVvh8fSikHAAAA/qruYEOr84Zd+vM9re0v+vXr1z9S/7f0l3KZ9dXZ91r67QeAXxyf0jv0Z/tvO9iS7F+T3ucG+Xuk96RvkN4CMJLaIL0VttLy7r19euHdA+zX6YP0vlR3z/3ax2tfarw+TAO0L/VPb3ruXvv3o7z7lv70zuQN0vtgz+6DDSsZ
|
eJzt2q1u22AYhuEo1UhBtYFWKh/bmRRMRYNFPZPxwcKi7jznSInkWk7teG5ex88FLhQD570d5/PP1WazuQIAAAAAAAAAAAAAAAAAgAmu96r3g/N5bjn0fw5U3aGy/3bv0P+p8XDEdoX0f9//GP3Xp93/YaD/9QJa6f95/fvov26773671zeb7jmhupX+5+2fQH/9qztU9v8yQXUz/fXXX3/9a/ovgf7666+//vrrf1n9/4zc7tue/uvq/9Z4PfLZ7vnz38ZP/cv7T53p4Z7i7/+kv/7667/bl/sO/dff/77T3+9ff/3r+38dMGf/Nv1r+w917+v/0vh1okN/67/l9B/b/nFj/X8pxvYf236r/0UZ6t/X+Efj5kj7bv+uxxPpX9e/r/1NS7f7Z/S/a3zXfzH9Pzrvt/vPTf/l9p+yRjyV/ufvP+YY6M5yzr5D5uy/XUCLJfafa8YfmdJe//X0r6Z/9jGgv/7661/doqp/9fyrpV//Vc+/mv7Z9M+mfzb9s+mfTf9s+mfTP5v+2fTPpn82/bPpn03/bPpn0z+b/tn0z6Z/Nv2z6Z/N+9/Z9M+mfzb//9n0z6Z/Nv2z6Z9N/2z6Z9M/m/7Z0vtT3wEAAAAAAAAAAAAAAAA4v39IY4NC
|
||||||
</data>
|
</data>
|
||||||
</layer>
|
</layer>
|
||||||
<objectgroup id="1" name="PlayerStartingPos">
|
<objectgroup id="1" name="PlayerStartingPos">
|
||||||
<object id="135" x="512" y="512">
|
<object id="135" x="1090" y="833.667">
|
||||||
<point/>
|
<point/>
|
||||||
</object>
|
</object>
|
||||||
<object id="137" x="640" y="640">
|
<object id="137" x="1215" y="830.5">
|
||||||
<point/>
|
<point/>
|
||||||
</object>
|
</object>
|
||||||
</objectgroup>
|
</objectgroup>
|
||||||
@@ -19,71 +20,149 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<property name="type" value="barrier_and_shelter"/>
|
<property name="type" value="barrier_and_shelter"/>
|
||||||
</properties>
|
</properties>
|
||||||
<object id="1" x="400" y="224.5">
|
<object id="8" x="648.242" y="480.606">
|
||||||
<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>
|
|
||||||
<object id="8" x="-9.33333" y="-13.3333">
|
|
||||||
<properties>
|
<properties>
|
||||||
<property name="boundary_type" value="barrier"/>
|
<property name="boundary_type" value="barrier"/>
|
||||||
</properties>
|
</properties>
|
||||||
<polyline points="0,0 0,18.6667 1041.33,21.3333 1041.33,-1.33333"/>
|
<polyline points="0,0 0,18.6667 1041.33,21.3333 1041.33,-1.33333"/>
|
||||||
</object>
|
</object>
|
||||||
<object id="9" x="-9.33333" y="1014.67">
|
<object id="9" x="650.667" y="1604.67">
|
||||||
<properties>
|
<properties>
|
||||||
<property name="boundary_type" value="barrier"/>
|
<property name="boundary_type" value="barrier"/>
|
||||||
</properties>
|
</properties>
|
||||||
<polyline points="0,0 0,18.6667 1041.33,21.3333 1041.33,-1.33333"/>
|
<polyline points="0,0 0,18.6667 1041.33,21.3333 1041.33,-1.33333"/>
|
||||||
</object>
|
</object>
|
||||||
<object id="10" x="-14" y="-40">
|
<object id="10" x="634.485" y="505.455">
|
||||||
<properties>
|
<properties>
|
||||||
<property name="boundary_type" value="barrier"/>
|
<property name="boundary_type" value="barrier"/>
|
||||||
</properties>
|
</properties>
|
||||||
<polyline points="0,0 4,1110 24,1110 24,-8"/>
|
<polyline points="0,0 4,1110 24,1110 24,-8"/>
|
||||||
</object>
|
</object>
|
||||||
<object id="11" x="1014" y="-42">
|
<object id="11" x="1677.64" y="501.333">
|
||||||
<properties>
|
<properties>
|
||||||
<property name="boundary_type" value="barrier"/>
|
<property name="boundary_type" value="barrier"/>
|
||||||
</properties>
|
</properties>
|
||||||
<polyline points="0,0 4,1110 24,1110 24,-8"/>
|
<polyline points="0,0 4,1110 24,1110 24,-8"/>
|
||||||
</object>
|
</object>
|
||||||
|
<object id="14" x="688.667" y="464">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 -0.666667,78 33.3333,78 32,-0.666667"/>
|
||||||
|
</object>
|
||||||
|
<object id="15" x="833.333" y="495.333">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 -112,1.33333 -111.333,44.6667 -1.33333,44.6667"/>
|
||||||
|
</object>
|
||||||
|
<object id="17" x="832" y="574">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 -67.3333,0 -67.3333,-76.6667 0.666667,-76"/>
|
||||||
|
</object>
|
||||||
|
<object id="18" x="865.333" y="606.667">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 -210,-0.666667 -210,143.333 0,142.667"/>
|
||||||
|
</object>
|
||||||
|
<object id="19" x="754.667" y="1055.33">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="-2,1.33333 -100,0.666667 -97.3333,-454 -9.33333,-451.333"/>
|
||||||
|
</object>
|
||||||
|
<object id="20" x="769.333" y="747.333">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 -115.333,0.666667 -114.667,160.667 -2,162"/>
|
||||||
|
</object>
|
||||||
|
<object id="21" x="768" y="960.667">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 -18,0.666667 -18.6667,95.3333 0,95.3333"/>
|
||||||
|
</object>
|
||||||
|
<object id="23" x="786" y="1058.67">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 0,-52.6667 -20,-52 -19.3333,-1.33333"/>
|
||||||
|
</object>
|
||||||
|
<object id="24" x="1118.67" y="749.333">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 -0.666667,-94 -254,-93.3333 -256.667,1.33333"/>
|
||||||
|
</object>
|
||||||
|
<object id="25" x="1168" y="975.333">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 0,16.6667 224.667,17.3333 225.333,-2"/>
|
||||||
|
</object>
|
||||||
|
<object id="28" x="1394.67" y="958">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 -210.667,1.33333 -210.667,17.3333 -2,18"/>
|
||||||
|
</object>
|
||||||
|
<object id="29" x="1119" y="654.5">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 0,63.5 272.5,65 273,-1"/>
|
||||||
|
</object>
|
||||||
|
<object id="30" x="1136.5" y="717.5">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 -0.5,17 255,17.5 255,1.5"/>
|
||||||
|
</object>
|
||||||
|
<object id="31" x="1152" y="735.667">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 0,15 80.3333,15.3333 80.3333,-2"/>
|
||||||
|
</object>
|
||||||
|
<object id="32" x="1280.67" y="734.667">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 -0.666667,64.3333 48,65 48,0"/>
|
||||||
|
</object>
|
||||||
|
<object id="34" x="1329" y="783">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 63.3333,0.333333 63,-48.3333 -0.666667,-48.3333"/>
|
||||||
|
</object>
|
||||||
|
<object id="35" x="1296.67" y="799">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 -0.666667,31.3333 31.3333,31.6667 31.3333,-0.333333"/>
|
||||||
|
</object>
|
||||||
|
<object id="36" x="1280" y="848">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 0.333333,62 112,63 111.667,-1.33333"/>
|
||||||
|
</object>
|
||||||
|
<object id="37" x="1392.33" y="911.333">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 -81.3333,-0.666667 -81,45 -0.666667,46.3333"/>
|
||||||
|
</object>
|
||||||
|
<object id="38" x="1344.33" y="800.667">
|
||||||
|
<properties>
|
||||||
|
<property name="boundary_type" value="barrier"/>
|
||||||
|
</properties>
|
||||||
|
<polyline points="0,0 0,46.3333 47,46.3333 47,-1"/>
|
||||||
|
</object>
|
||||||
</objectgroup>
|
</objectgroup>
|
||||||
</map>
|
</map>
|
||||||
|
4
frontend/assets/resources/map/dungeon/tiles2.tsx
Normal file
4
frontend/assets/resources/map/dungeon/tiles2.tsx
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<tileset version="1.2" tiledversion="1.2.3" name="tiles2" tilewidth="16" tileheight="16" tilecount="64" columns="16">
|
||||||
|
<image source="watabou_pixel_dungeon_orig_files/tiles2.png" width="256" height="64"/>
|
||||||
|
</tileset>
|
5
frontend/assets/resources/map/dungeon/tiles2.tsx.meta
Normal file
5
frontend/assets/resources/map/dungeon/tiles2.tsx.meta
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.0.0",
|
||||||
|
"uuid": "2e53500f-f9c8-4b5b-b74c-f2adbc2ec34d",
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
@@ -4,55 +4,27 @@ option go_package = "battle_srv/protos"; // here "./" corresponds to the "--go_o
|
|||||||
package protos;
|
package protos;
|
||||||
import "geometry.proto"; // The import path here is only w.r.t. the proto file, not the Go package.
|
import "geometry.proto"; // The import path here is only w.r.t. the proto file, not the Go package.
|
||||||
|
|
||||||
message BattleColliderInfo {
|
|
||||||
string stageName = 1;
|
|
||||||
map<string, sharedprotos.Vec2DList> strToVec2DListMap = 2;
|
|
||||||
map<string, sharedprotos.Polygon2DList> strToPolygon2DListMap = 3;
|
|
||||||
int32 stageDiscreteW = 4;
|
|
||||||
int32 stageDiscreteH = 5;
|
|
||||||
int32 stageTileW = 6;
|
|
||||||
int32 stageTileH = 7;
|
|
||||||
|
|
||||||
int32 intervalToPing = 8;
|
|
||||||
int32 willKickIfInactiveFor = 9;
|
|
||||||
int32 boundRoomId = 10;
|
|
||||||
int64 battleDurationNanos = 11;
|
|
||||||
int32 serverFps = 12;
|
|
||||||
int32 inputDelayFrames = 13;
|
|
||||||
uint32 inputScaleFrames = 14;
|
|
||||||
int32 nstDelayFrames = 15;
|
|
||||||
int32 inputFrameUpsyncDelayTolerance = 16;
|
|
||||||
int32 maxChasingRenderFramesPerUpdate = 17;
|
|
||||||
int32 playerBattleState = 18;
|
|
||||||
double rollbackEstimatedDtMillis = 19;
|
|
||||||
int64 rollbackEstimatedDtNanos = 20;
|
|
||||||
|
|
||||||
double worldToVirtualGridRatio = 21;
|
|
||||||
double virtualGridToWorldRatio = 22;
|
|
||||||
|
|
||||||
int32 spAtkLookupFrames = 23;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PlayerDownsync {
|
message PlayerDownsync {
|
||||||
int32 id = 1;
|
int32 id = 1;
|
||||||
int32 virtualGridX = 2;
|
int32 virtualGridX = 2;
|
||||||
int32 virtualGridY = 3;
|
int32 virtualGridY = 3;
|
||||||
sharedprotos.Direction dir = 4;
|
int32 dirX = 4;
|
||||||
int32 speed = 5; // in terms of virtual grid units
|
int32 dirY = 5;
|
||||||
int32 battleState = 6;
|
int32 speed = 6; // in terms of virtual grid units
|
||||||
int32 lastMoveGmtMillis = 7;
|
int32 battleState = 7;
|
||||||
int32 score = 10;
|
int32 joinIndex = 8;
|
||||||
bool removed = 11;
|
double colliderRadius = 9;
|
||||||
int32 joinIndex = 12;
|
bool removed = 10;
|
||||||
}
|
int32 score = 11;
|
||||||
|
int32 lastMoveGmtMillis = 12;
|
||||||
|
int32 framesToRecover = 13;
|
||||||
|
int32 hp = 14;
|
||||||
|
int32 maxHp = 15;
|
||||||
|
int32 characterState = 16;
|
||||||
|
|
||||||
message PlayerDownsyncMeta {
|
string name = 17;
|
||||||
int32 id = 1;
|
string displayName = 18;
|
||||||
string name = 2;
|
string avatar = 19;
|
||||||
string displayName = 3;
|
|
||||||
string avatar = 4;
|
|
||||||
int32 joinIndex = 5;
|
|
||||||
double colliderRadius = 6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message InputFrameDecoded {
|
message InputFrameDecoded {
|
||||||
@@ -76,13 +48,6 @@ message HeartbeatUpsync {
|
|||||||
int64 clientTimestamp = 1;
|
int64 clientTimestamp = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message RoomDownsyncFrame {
|
|
||||||
int32 id = 1;
|
|
||||||
map<int32, PlayerDownsync> players = 2;
|
|
||||||
int64 countdownNanos = 3;
|
|
||||||
map<int32, PlayerDownsyncMeta> playerMetas = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WsReq {
|
message WsReq {
|
||||||
int32 msgId = 1;
|
int32 msgId = 1;
|
||||||
int32 playerId = 2;
|
int32 playerId = 2;
|
||||||
@@ -102,3 +67,71 @@ message WsResp {
|
|||||||
repeated InputFrameDownsync inputFrameDownsyncBatch = 5;
|
repeated InputFrameDownsync inputFrameDownsyncBatch = 5;
|
||||||
BattleColliderInfo bciFrame = 6;
|
BattleColliderInfo bciFrame = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message MeleeBullet {
|
||||||
|
// Jargon reference https://www.thegamer.com/fighting-games-frame-data-explained/
|
||||||
|
// ALL lengths are in world coordinate
|
||||||
|
|
||||||
|
// for offender
|
||||||
|
int32 battleLocalId = 1;
|
||||||
|
int32 startupFrames = 2;
|
||||||
|
int32 activeFrames = 3;
|
||||||
|
int32 recoveryFrames = 4;
|
||||||
|
int32 recoveryFramesOnBlock = 5;
|
||||||
|
int32 recoveryFramesOnHit = 6;
|
||||||
|
sharedprotos.Vec2D moveforward = 7;
|
||||||
|
double hitboxOffset = 8;
|
||||||
|
sharedprotos.Vec2D hitboxSize = 9;
|
||||||
|
int32 originatedRenderFrameId = 10;
|
||||||
|
|
||||||
|
// for defender
|
||||||
|
int32 hitStunFrames = 11;
|
||||||
|
int32 blockStunFrames = 12;
|
||||||
|
double pushback = 13;
|
||||||
|
|
||||||
|
int32 releaseTriggerType = 14; // 1: rising-edge, 2: falling-edge
|
||||||
|
int32 damage = 15;
|
||||||
|
|
||||||
|
int32 offenderJoinIndex = 16;
|
||||||
|
int32 offenderPlayerId = 17;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BattleColliderInfo {
|
||||||
|
string stageName = 1;
|
||||||
|
map<string, sharedprotos.Vec2DList> strToVec2DListMap = 2;
|
||||||
|
map<string, sharedprotos.Polygon2DList> strToPolygon2DListMap = 3;
|
||||||
|
int32 stageDiscreteW = 4;
|
||||||
|
int32 stageDiscreteH = 5;
|
||||||
|
int32 stageTileW = 6;
|
||||||
|
int32 stageTileH = 7;
|
||||||
|
|
||||||
|
int32 intervalToPing = 8;
|
||||||
|
int32 willKickIfInactiveFor = 9;
|
||||||
|
int32 boundRoomId = 10;
|
||||||
|
int32 battleDurationFrames = 12;
|
||||||
|
int64 battleDurationNanos = 13;
|
||||||
|
int32 serverFps = 14;
|
||||||
|
int32 inputDelayFrames = 15; // in the count of render frames
|
||||||
|
uint32 inputScaleFrames = 16; // inputDelayedAndScaledFrameId = ((originalFrameId - InputDelayFrames) >> InputScaleFrames)
|
||||||
|
int32 nstDelayFrames = 17; // 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"
|
||||||
|
int32 inputFrameUpsyncDelayTolerance = 18;
|
||||||
|
int32 maxChasingRenderFramesPerUpdate = 19;
|
||||||
|
int32 playerBattleState = 20;
|
||||||
|
double rollbackEstimatedDtMillis = 21;
|
||||||
|
int64 rollbackEstimatedDtNanos = 22;
|
||||||
|
|
||||||
|
double worldToVirtualGridRatio = 23;
|
||||||
|
double virtualGridToWorldRatio = 24;
|
||||||
|
|
||||||
|
int32 spAtkLookupFrames = 25;
|
||||||
|
int32 renderCacheSize = 26;
|
||||||
|
|
||||||
|
map<int32, MeleeBullet> meleeSkillConfig = 27; // skillId -> skill
|
||||||
|
}
|
||||||
|
|
||||||
|
message RoomDownsyncFrame {
|
||||||
|
int32 id = 1;
|
||||||
|
map<int32, PlayerDownsync> players = 2;
|
||||||
|
int64 countdownNanos = 3;
|
||||||
|
repeated MeleeBullet meleeBullets = 4; // I don't know how to mimic inheritance/composition in protobuf by far, thus using an array for each type of bullet as a compromise
|
||||||
|
}
|
||||||
|
@@ -33,14 +33,14 @@
|
|||||||
"_active": true,
|
"_active": true,
|
||||||
"_components": [
|
"_components": [
|
||||||
{
|
{
|
||||||
"__id__": 19
|
"__id__": 22
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"__id__": 20
|
"__id__": 23
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"_prefab": {
|
"_prefab": {
|
||||||
"__id__": 21
|
"__id__": 24
|
||||||
},
|
},
|
||||||
"_opacity": 255,
|
"_opacity": 255,
|
||||||
"_color": {
|
"_color": {
|
||||||
@@ -490,12 +490,15 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"__id__": 15
|
"__id__": 15
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"__id__": 18
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"_active": true,
|
"_active": true,
|
||||||
"_components": [],
|
"_components": [],
|
||||||
"_prefab": {
|
"_prefab": {
|
||||||
"__id__": 18
|
"__id__": 21
|
||||||
},
|
},
|
||||||
"_opacity": 255,
|
"_opacity": 255,
|
||||||
"_color": {
|
"_color": {
|
||||||
@@ -645,7 +648,7 @@
|
|||||||
"_N$_defaultCacheMode": 0,
|
"_N$_defaultCacheMode": 0,
|
||||||
"_N$timeScale": 1,
|
"_N$timeScale": 1,
|
||||||
"_N$debugBones": false,
|
"_N$debugBones": false,
|
||||||
"_N$enableBatch": false,
|
"_N$enableBatch": true,
|
||||||
"_id": ""
|
"_id": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -760,7 +763,7 @@
|
|||||||
"_N$_defaultCacheMode": 0,
|
"_N$_defaultCacheMode": 0,
|
||||||
"_N$timeScale": 1,
|
"_N$timeScale": 1,
|
||||||
"_N$debugBones": false,
|
"_N$debugBones": false,
|
||||||
"_N$enableBatch": false,
|
"_N$enableBatch": true,
|
||||||
"_id": ""
|
"_id": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -774,6 +777,121 @@
|
|||||||
"fileId": "a8ZUEyoP1Ec5azSkL7Z/9h",
|
"fileId": "a8ZUEyoP1Ec5azSkL7Z/9h",
|
||||||
"sync": false
|
"sync": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"__type__": "cc.Node",
|
||||||
|
"_name": "SoldierWaterGhost",
|
||||||
|
"_objFlags": 0,
|
||||||
|
"_parent": {
|
||||||
|
"__id__": 11
|
||||||
|
},
|
||||||
|
"_children": [],
|
||||||
|
"_active": false,
|
||||||
|
"_components": [
|
||||||
|
{
|
||||||
|
"__id__": 19
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"_prefab": {
|
||||||
|
"__id__": 20
|
||||||
|
},
|
||||||
|
"_opacity": 255,
|
||||||
|
"_color": {
|
||||||
|
"__type__": "cc.Color",
|
||||||
|
"r": 255,
|
||||||
|
"g": 255,
|
||||||
|
"b": 255,
|
||||||
|
"a": 255
|
||||||
|
},
|
||||||
|
"_contentSize": {
|
||||||
|
"__type__": "cc.Size",
|
||||||
|
"width": 0,
|
||||||
|
"height": 0
|
||||||
|
},
|
||||||
|
"_anchorPoint": {
|
||||||
|
"__type__": "cc.Vec2",
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.5
|
||||||
|
},
|
||||||
|
"_trs": {
|
||||||
|
"__type__": "TypedArray",
|
||||||
|
"ctor": "Float64Array",
|
||||||
|
"array": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"_eulerAngles": {
|
||||||
|
"__type__": "cc.Vec3",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"_skewX": 0,
|
||||||
|
"_skewY": 0,
|
||||||
|
"_is3DNode": false,
|
||||||
|
"_groupIndex": 0,
|
||||||
|
"groupIndex": 0,
|
||||||
|
"_id": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"__type__": "dragonBones.ArmatureDisplay",
|
||||||
|
"_name": "",
|
||||||
|
"_objFlags": 0,
|
||||||
|
"node": {
|
||||||
|
"__id__": 18
|
||||||
|
},
|
||||||
|
"_enabled": true,
|
||||||
|
"_materials": [
|
||||||
|
{
|
||||||
|
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"_armatureName": "SoldierWaterGhost",
|
||||||
|
"_animationName": "Idle1",
|
||||||
|
"_preCacheMode": 0,
|
||||||
|
"_cacheMode": 0,
|
||||||
|
"playTimes": -1,
|
||||||
|
"premultipliedAlpha": false,
|
||||||
|
"_armatureKey": "a9d7bbc2-134b-4eb4-ba16-6541f3e51e06#e9e703e9-3589-4713-b889-28b23406d220",
|
||||||
|
"_accTime": 0,
|
||||||
|
"_playCount": 0,
|
||||||
|
"_frameCache": null,
|
||||||
|
"_curFrame": null,
|
||||||
|
"_playing": false,
|
||||||
|
"_armatureCache": null,
|
||||||
|
"_N$dragonAsset": {
|
||||||
|
"__uuid__": "a9d7bbc2-134b-4eb4-ba16-6541f3e51e06"
|
||||||
|
},
|
||||||
|
"_N$dragonAtlasAsset": {
|
||||||
|
"__uuid__": "e9e703e9-3589-4713-b889-28b23406d220"
|
||||||
|
},
|
||||||
|
"_N$_defaultArmatureIndex": 0,
|
||||||
|
"_N$_animationIndex": 8,
|
||||||
|
"_N$_defaultCacheMode": 0,
|
||||||
|
"_N$timeScale": 1,
|
||||||
|
"_N$debugBones": false,
|
||||||
|
"_N$enableBatch": true,
|
||||||
|
"_id": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"__type__": "cc.PrefabInfo",
|
||||||
|
"root": {
|
||||||
|
"__id__": 1
|
||||||
|
},
|
||||||
|
"asset": {
|
||||||
|
"__uuid__": "59bff7a2-23e1-4d69-bce7-afb37eae196a"
|
||||||
|
},
|
||||||
|
"fileId": "42Rmp/YOdMOYWzJwr3ET1h",
|
||||||
|
"sync": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"__type__": "cc.PrefabInfo",
|
"__type__": "cc.PrefabInfo",
|
||||||
"root": {
|
"root": {
|
||||||
|
@@ -343,6 +343,7 @@
|
|||||||
"forceBigEndianFloatingNumDecoding": false,
|
"forceBigEndianFloatingNumDecoding": false,
|
||||||
"renderFrameIdLagTolerance": 4,
|
"renderFrameIdLagTolerance": 4,
|
||||||
"jigglingEps1D": 0.001,
|
"jigglingEps1D": 0.001,
|
||||||
|
"bulletTriggerEnabled": true,
|
||||||
"_id": "d12gkAmppNlIzqcRDELa91"
|
"_id": "d12gkAmppNlIzqcRDELa91"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -522,7 +523,7 @@
|
|||||||
"array": [
|
"array": [
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
239.32248305180272,
|
210.23252687912068,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
@@ -1482,7 +1483,6 @@
|
|||||||
"zoomingListenerNode": {
|
"zoomingListenerNode": {
|
||||||
"__id__": 5
|
"__id__": 5
|
||||||
},
|
},
|
||||||
"actionBtnListenerNode": null,
|
|
||||||
"stickhead": {
|
"stickhead": {
|
||||||
"__id__": 25
|
"__id__": 25
|
||||||
},
|
},
|
||||||
|
@@ -440,7 +440,7 @@
|
|||||||
"array": [
|
"array": [
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
344.6705889248102,
|
210.7364624020594,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
@@ -277,6 +277,7 @@
|
|||||||
"forceBigEndianFloatingNumDecoding": false,
|
"forceBigEndianFloatingNumDecoding": false,
|
||||||
"renderFrameIdLagTolerance": 4,
|
"renderFrameIdLagTolerance": 4,
|
||||||
"jigglingEps1D": 0.001,
|
"jigglingEps1D": 0.001,
|
||||||
|
"bulletTriggerEnabled": true,
|
||||||
"_id": "4b+kZ46VhC0LCBixXEK2dk"
|
"_id": "4b+kZ46VhC0LCBixXEK2dk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -453,7 +454,7 @@
|
|||||||
"array": [
|
"array": [
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
216.50635094610968,
|
210.7364624020594,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
@@ -4,6 +4,7 @@ window.ATK_CHARACTER_STATE = {
|
|||||||
Idle1: [0, "Idle1"],
|
Idle1: [0, "Idle1"],
|
||||||
Walking: [1, "Walking"],
|
Walking: [1, "Walking"],
|
||||||
Atk1: [2, "Atk1"],
|
Atk1: [2, "Atk1"],
|
||||||
|
Atked1: [3, "Atked1"],
|
||||||
};
|
};
|
||||||
|
|
||||||
window.ATK_CHARACTER_STATE_ARR = [];
|
window.ATK_CHARACTER_STATE_ARR = [];
|
||||||
@@ -22,6 +23,9 @@ cc.Class({
|
|||||||
|
|
||||||
ctor() {
|
ctor() {
|
||||||
this.speciesName = null;
|
this.speciesName = null;
|
||||||
|
this.hp = 100;
|
||||||
|
this.maxHp = 100;
|
||||||
|
this.framesToRecover = 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
setSpecies(speciesName) {
|
setSpecies(speciesName) {
|
||||||
@@ -34,27 +38,27 @@ cc.Class({
|
|||||||
|
|
||||||
onLoad() {
|
onLoad() {
|
||||||
BaseCharacter.prototype.onLoad.call(this);
|
BaseCharacter.prototype.onLoad.call(this);
|
||||||
this.characterState = ATK_CHARACTER_STATE.Idle1[0];
|
|
||||||
},
|
},
|
||||||
|
|
||||||
scheduleNewDirection(newScheduledDirection, forceAnimSwitch) {
|
updateCharacterAnim(rdfPlayer, prevRdfPlayer, forceAnimSwitch) {
|
||||||
const oldDx = this.activeDirection.dx, oldDy = this.activeDirection.dy;
|
// Update directions
|
||||||
BaseCharacter.prototype.scheduleNewDirection.call(this, newScheduledDirection, forceAnimSwitch);
|
if (this.animComp && this.animComp.node) {
|
||||||
if (ATK_CHARACTER_STATE.Atk1[0] == this.characterState) {
|
if (0 > rdfPlayer.dirX) {
|
||||||
return;
|
this.animComp.node.scaleX = (-1.0);
|
||||||
|
} else if (0 < rdfPlayer.dirX) {
|
||||||
|
this.animComp.node.scaleX = (1.0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let newCharacterState = ATK_CHARACTER_STATE.Idle1[0];
|
// Update per character state
|
||||||
if (0 != newScheduledDirection.dx || 0 != newScheduledDirection.dy) {
|
let newCharacterState = rdfPlayer.characterState;
|
||||||
newCharacterState = ATK_CHARACTER_STATE.Walking[0];
|
let prevCharacterState = (null == prevRdfPlayer ? window.ATK_CHARACTER_STATE.Idle1[0] : prevRdfPlayer.characterState);
|
||||||
}
|
if (newCharacterState != prevCharacterState) {
|
||||||
|
// Anim is edge-triggered
|
||||||
if (newCharacterState != this.characterState) {
|
|
||||||
this.characterState = newCharacterState;
|
|
||||||
const newAnimName = window.ATK_CHARACTER_STATE_ARR[newCharacterState][1];
|
const newAnimName = window.ATK_CHARACTER_STATE_ARR[newCharacterState][1];
|
||||||
if (newAnimName != this.animComp.animationName) {
|
if (newAnimName != this.animComp.animationName) {
|
||||||
this.animComp.playAnimation(newAnimName);
|
this.animComp.playAnimation(newAnimName);
|
||||||
// console.log(`JoinIndex=${this.joinIndex}, Resetting anim to ${newAnimName}, dir changed: (${oldDx}, ${oldDy}) -> (${newScheduledDirection.dx}, ${newScheduledDirection.dy})`);
|
console.log(`JoinIndex=${rdfPlayer.joinIndex}, Resetting anim to ${newAnimName}, state changed: (${prevCharacterState}, prevRdfPlayer is null? ${null == prevRdfPlayer}) -> (${newCharacterState})`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -23,23 +23,6 @@ module.export = cc.Class({
|
|||||||
self.ctrl = joystickInputControllerScriptIns;
|
self.ctrl = joystickInputControllerScriptIns;
|
||||||
},
|
},
|
||||||
|
|
||||||
scheduleNewDirection(newScheduledDirection, forceAnimSwitch) {
|
|
||||||
if (!newScheduledDirection) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (forceAnimSwitch || null == this.activeDirection || (newScheduledDirection.dx != this.activeDirection.dx || newScheduledDirection.dy != this.activeDirection.dy)) {
|
|
||||||
this.activeDirection = newScheduledDirection;
|
|
||||||
if (this.animComp && this.animComp.node) {
|
|
||||||
if (0 > newScheduledDirection.dx) {
|
|
||||||
this.animComp.node.scaleX = (-1.0);
|
|
||||||
} else if (0 < newScheduledDirection.dx) {
|
|
||||||
this.animComp.node.scaleX = (1.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
update(dt) {},
|
update(dt) {},
|
||||||
|
|
||||||
lateUpdate(dt) {},
|
lateUpdate(dt) {},
|
||||||
|
@@ -95,6 +95,9 @@ cc.Class({
|
|||||||
type: cc.Float,
|
type: cc.Float,
|
||||||
default: 1e-3
|
default: 1e-3
|
||||||
},
|
},
|
||||||
|
bulletTriggerEnabled: {
|
||||||
|
default: false
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
_inputFrameIdDebuggable(inputFrameId) {
|
_inputFrameIdDebuggable(inputFrameId) {
|
||||||
@@ -103,7 +106,7 @@ cc.Class({
|
|||||||
|
|
||||||
dumpToRenderCache: function(rdf) {
|
dumpToRenderCache: function(rdf) {
|
||||||
const self = this;
|
const self = this;
|
||||||
const minToKeepRenderFrameId = self.lastAllConfirmedRenderFrameId;
|
const minToKeepRenderFrameId = self.lastAllConfirmedRenderFrameId - 1; // Keep at least 1 prev render frame for anim triggering
|
||||||
while (0 < self.recentRenderCache.cnt && self.recentRenderCache.stFrameId < minToKeepRenderFrameId) {
|
while (0 < self.recentRenderCache.cnt && self.recentRenderCache.stFrameId < minToKeepRenderFrameId) {
|
||||||
self.recentRenderCache.pop();
|
self.recentRenderCache.pop();
|
||||||
}
|
}
|
||||||
@@ -210,7 +213,7 @@ cc.Class({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.info(`inputFrameUpsyncBatch: ${JSON.stringify(inputFrameUpsyncBatch)}`);
|
// console.info(`inputFrameUpsyncBatch: ${JSON.stringify(inputFrameUpsyncBatch)}`);
|
||||||
const reqData = window.pb.protos.WsReq.encode({
|
const reqData = window.pb.protos.WsReq.encode({
|
||||||
msgId: Date.now(),
|
msgId: Date.now(),
|
||||||
playerId: self.selfPlayerInfo.id,
|
playerId: self.selfPlayerInfo.id,
|
||||||
@@ -300,29 +303,31 @@ cc.Class({
|
|||||||
// Clearing previous info of all players. [ENDS]
|
// Clearing previous info of all players. [ENDS]
|
||||||
|
|
||||||
self.renderFrameId = 0; // After battle started
|
self.renderFrameId = 0; // After battle started
|
||||||
|
self.bulletBattleLocalIdCounter = 0;
|
||||||
self.lastAllConfirmedRenderFrameId = -1;
|
self.lastAllConfirmedRenderFrameId = -1;
|
||||||
self.lastAllConfirmedInputFrameId = -1;
|
self.lastAllConfirmedInputFrameId = -1;
|
||||||
self.lastUpsyncInputFrameId = -1;
|
self.lastUpsyncInputFrameId = -1;
|
||||||
self.chaserRenderFrameId = -1; // at any moment, "lastAllConfirmedRenderFrameId <= chaserRenderFrameId <= renderFrameId", but "chaserRenderFrameId" would fluctuate according to "onInputFrameDownsyncBatch"
|
self.chaserRenderFrameId = -1; // at any moment, "lastAllConfirmedRenderFrameId <= chaserRenderFrameId <= renderFrameId", but "chaserRenderFrameId" would fluctuate according to "onInputFrameDownsyncBatch"
|
||||||
|
|
||||||
self.recentRenderCache = new RingBuffer(1024);
|
self.recentRenderCache = new RingBuffer(self.renderCacheSize);
|
||||||
|
|
||||||
self.selfPlayerInfo = null; // This field is kept for distinguishing "self" and "others".
|
self.selfPlayerInfo = null; // This field is kept for distinguishing "self" and "others".
|
||||||
self.recentInputCache = new RingBuffer(1024);
|
self.recentInputCache = new RingBuffer((self.renderCacheSize >> 2) + 1);
|
||||||
|
|
||||||
self.collisionSys = new collisions.Collisions();
|
self.collisionSys = new collisions.Collisions();
|
||||||
|
|
||||||
self.collisionBarrierIndexPrefix = (1 << 16); // For tracking the movements of barriers, though not yet actually used
|
self.collisionBarrierIndexPrefix = (1 << 16); // For tracking the movements of barriers, though not yet actually used
|
||||||
|
self.collisionBulletIndexPrefix = (1 << 15); // For tracking the movements of bullets
|
||||||
self.collisionSysMap = new Map();
|
self.collisionSysMap = new Map();
|
||||||
|
|
||||||
self.transitToState(ALL_MAP_STATES.VISUAL);
|
self.transitToState(ALL_MAP_STATES.VISUAL);
|
||||||
|
|
||||||
self.battleState = ALL_BATTLE_STATES.WAITING;
|
self.battleState = ALL_BATTLE_STATES.WAITING;
|
||||||
|
|
||||||
|
self.countdownNanos = null;
|
||||||
if (self.countdownLabel) {
|
if (self.countdownLabel) {
|
||||||
self.countdownLabel.string = "";
|
self.countdownLabel.string = "";
|
||||||
}
|
}
|
||||||
self.countdownNanos = null;
|
|
||||||
if (self.findingPlayerNode) {
|
if (self.findingPlayerNode) {
|
||||||
const findingPlayerScriptIns = self.findingPlayerNode.getComponent("FindingPlayer");
|
const findingPlayerScriptIns = self.findingPlayerNode.getComponent("FindingPlayer");
|
||||||
findingPlayerScriptIns.init();
|
findingPlayerScriptIns.init();
|
||||||
@@ -376,8 +381,7 @@ cc.Class({
|
|||||||
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
|
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
|
||||||
window.initPersistentSessionClient(self.initAfterWSConnected, null /* Deliberately NOT passing in any `expectedRoomId`. -- YFLu */ );
|
window.initPersistentSessionClient(self.initAfterWSConnected, null /* Deliberately NOT passing in any `expectedRoomId`. -- YFLu */ );
|
||||||
};
|
};
|
||||||
resultPanelScriptIns.onCloseDelegate = () => {
|
resultPanelScriptIns.onCloseDelegate = () => {};
|
||||||
};
|
|
||||||
|
|
||||||
self.gameRuleNode = cc.instantiate(self.gameRulePrefab);
|
self.gameRuleNode = cc.instantiate(self.gameRulePrefab);
|
||||||
self.gameRuleNode.width = self.canvasNode.width;
|
self.gameRuleNode.width = self.canvasNode.width;
|
||||||
@@ -409,19 +413,7 @@ cc.Class({
|
|||||||
/** Init required prefab ended. */
|
/** Init required prefab ended. */
|
||||||
|
|
||||||
window.handleBattleColliderInfo = function(parsedBattleColliderInfo) {
|
window.handleBattleColliderInfo = function(parsedBattleColliderInfo) {
|
||||||
self.inputDelayFrames = parsedBattleColliderInfo.inputDelayFrames;
|
Object.assign(self, parsedBattleColliderInfo);
|
||||||
self.inputScaleFrames = parsedBattleColliderInfo.inputScaleFrames;
|
|
||||||
self.inputFrameUpsyncDelayTolerance = parsedBattleColliderInfo.inputFrameUpsyncDelayTolerance;
|
|
||||||
|
|
||||||
self.battleDurationNanos = parsedBattleColliderInfo.battleDurationNanos;
|
|
||||||
self.rollbackEstimatedDt = parsedBattleColliderInfo.rollbackEstimatedDt;
|
|
||||||
self.rollbackEstimatedDtMillis = parsedBattleColliderInfo.rollbackEstimatedDtMillis;
|
|
||||||
self.rollbackEstimatedDtNanos = parsedBattleColliderInfo.rollbackEstimatedDtNanos;
|
|
||||||
self.maxChasingRenderFramesPerUpdate = parsedBattleColliderInfo.maxChasingRenderFramesPerUpdate;
|
|
||||||
self.spAtkLookupFrames = parsedBattleColliderInfo.spAtkLookupFrames;
|
|
||||||
|
|
||||||
self.worldToVirtualGridRatio = parsedBattleColliderInfo.worldToVirtualGridRatio;
|
|
||||||
self.virtualGridToWorldRatio = parsedBattleColliderInfo.virtualGridToWorldRatio;
|
|
||||||
|
|
||||||
const tiledMapIns = self.node.getComponent(cc.TiledMap);
|
const tiledMapIns = self.node.getComponent(cc.TiledMap);
|
||||||
|
|
||||||
@@ -608,40 +600,42 @@ cc.Class({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const players = rdf.players;
|
const players = rdf.players;
|
||||||
const playerMetas = rdf.playerMetas;
|
self._initPlayerRichInfoDict(players);
|
||||||
self._initPlayerRichInfoDict(players, playerMetas);
|
|
||||||
|
|
||||||
// Show the top status indicators for IN_BATTLE
|
// Show the top status indicators for IN_BATTLE
|
||||||
if (self.playersInfoNode) {
|
if (self.playersInfoNode) {
|
||||||
const playersInfoScriptIns = self.playersInfoNode.getComponent("PlayersInfo");
|
const playersInfoScriptIns = self.playersInfoNode.getComponent("PlayersInfo");
|
||||||
for (let i in playerMetas) {
|
for (let i in players) {
|
||||||
const playerMeta = playerMetas[i];
|
playersInfoScriptIns.updateData(players[i]);
|
||||||
playersInfoScriptIns.updateData(playerMeta);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.renderFrameId = rdf.id;
|
if (null == self.renderFrameId || self.renderFrameId <= rdf.id) {
|
||||||
self.lastRenderFrameIdTriggeredAt = performance.now();
|
// In fact, not having "window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet" should already imply that "self.renderFrameId <= rdf.id", but here we double check and log the anomaly
|
||||||
// In this case it must be true that "rdf.id > chaserRenderFrameId >= lastAllConfirmedRenderFrameId".
|
self.renderFrameId = rdf.id;
|
||||||
self.lastAllConfirmedRenderFrameId = rdf.id;
|
self.lastRenderFrameIdTriggeredAt = performance.now();
|
||||||
self.chaserRenderFrameId = rdf.id;
|
// In this case it must be true that "rdf.id > chaserRenderFrameId >= lastAllConfirmedRenderFrameId".
|
||||||
|
self.lastAllConfirmedRenderFrameId = rdf.id;
|
||||||
|
self.chaserRenderFrameId = rdf.id;
|
||||||
|
|
||||||
if (null != rdf.countdownNanos) {
|
const canvasNode = self.canvasNode;
|
||||||
self.countdownNanos = rdf.countdownNanos;
|
self.ctrl = canvasNode.getComponent("TouchEventsManager");
|
||||||
}
|
self.enableInputControls();
|
||||||
if (null != self.musicEffectManagerScriptIns) {
|
self.transitToState(ALL_MAP_STATES.VISUAL);
|
||||||
self.musicEffectManagerScriptIns.playBGM();
|
self.battleState = ALL_BATTLE_STATES.IN_BATTLE;
|
||||||
}
|
|
||||||
const canvasNode = self.canvasNode;
|
|
||||||
self.ctrl = canvasNode.getComponent("TouchEventsManager");
|
|
||||||
self.enableInputControls();
|
|
||||||
if (self.countdownToBeginGameNode && self.countdownToBeginGameNode.parent) {
|
|
||||||
self.countdownToBeginGameNode.parent.removeChild(self.countdownToBeginGameNode);
|
|
||||||
}
|
|
||||||
self.transitToState(ALL_MAP_STATES.VISUAL);
|
|
||||||
self.battleState = ALL_BATTLE_STATES.IN_BATTLE;
|
|
||||||
self.applyRoomDownsyncFrameDynamics(rdf);
|
|
||||||
|
|
||||||
|
if (self.countdownToBeginGameNode && self.countdownToBeginGameNode.parent) {
|
||||||
|
self.countdownToBeginGameNode.parent.removeChild(self.countdownToBeginGameNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null != self.musicEffectManagerScriptIns) {
|
||||||
|
self.musicEffectManagerScriptIns.playBGM();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn(`Anomaly when onRoomDownsyncFrame is called by rdf=${JSON.stringify(rdf)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// [WARNING] Leave all graphical updates in "update(dt)" by "applyRoomDownsyncFrameDynamics"
|
||||||
return dumpRenderCacheRet;
|
return dumpRenderCacheRet;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -716,7 +710,7 @@ cc.Class({
|
|||||||
self.showPopupInCanvas(self.findingPlayerNode);
|
self.showPopupInCanvas(self.findingPlayerNode);
|
||||||
}
|
}
|
||||||
let findingPlayerScriptIns = self.findingPlayerNode.getComponent("FindingPlayer");
|
let findingPlayerScriptIns = self.findingPlayerNode.getComponent("FindingPlayer");
|
||||||
findingPlayerScriptIns.updatePlayersInfo(rdf.playerMetas);
|
findingPlayerScriptIns.updatePlayersInfo(rdf.players);
|
||||||
},
|
},
|
||||||
|
|
||||||
logBattleStats() {
|
logBattleStats() {
|
||||||
@@ -754,41 +748,33 @@ cc.Class({
|
|||||||
self.playersInfoNode.getComponent("PlayersInfo").clearInfo();
|
self.playersInfoNode.getComponent("PlayersInfo").clearInfo();
|
||||||
},
|
},
|
||||||
|
|
||||||
spawnPlayerNode(joinIndex, vx, vy, playerRichInfo) {
|
spawnPlayerNode(joinIndex, vx, vy, playerDownsyncInfo) {
|
||||||
const self = this;
|
const self = this;
|
||||||
const newPlayerNode = cc.instantiate(self.controlledCharacterPrefab)
|
const newPlayerNode = cc.instantiate(self.controlledCharacterPrefab)
|
||||||
const playerScriptIns = newPlayerNode.getComponent("ControlledCharacter");
|
const playerScriptIns = newPlayerNode.getComponent("ControlledCharacter");
|
||||||
playerScriptIns.joinIndex = joinIndex;
|
|
||||||
|
|
||||||
if (1 == joinIndex) {
|
if (1 == joinIndex) {
|
||||||
playerScriptIns.setSpecies("SoldierElf");
|
playerScriptIns.setSpecies("SoldierWaterGhost");
|
||||||
} else if (2 == joinIndex) {
|
} else if (2 == joinIndex) {
|
||||||
playerScriptIns.setSpecies("SoldierFireGhost");
|
playerScriptIns.setSpecies("SoldierFireGhost");
|
||||||
playerScriptIns.animComp.node.scaleX = (-1.0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const wpos = self.virtualGridToWorldPos(vx, vy);
|
const [wx, wy] = self.virtualGridToWorldPos(vx, vy);
|
||||||
|
newPlayerNode.setPosition(wx, wy);
|
||||||
newPlayerNode.setPosition(cc.v2(wpos[0], wpos[1]));
|
|
||||||
playerScriptIns.mapNode = self.node;
|
playerScriptIns.mapNode = self.node;
|
||||||
const cpos = self.virtualGridToPlayerColliderPos(vx, vy, playerRichInfo);
|
const d = playerDownsyncInfo.colliderRadius * 2,
|
||||||
const d = playerRichInfo.colliderRadius * 2,
|
[x0, y0] = self.virtualGridToPolygonColliderAnchorPos(vx, vy, playerDownsyncInfo.colliderRadius, playerDownsyncInfo.colliderRadius),
|
||||||
x0 = cpos[0],
|
pts = [[0, 0], [d, 0], [d, d], [0, d]];
|
||||||
y0 = cpos[1];
|
|
||||||
let pts = [[0, 0], [d, 0], [d, d], [0, d]];
|
|
||||||
|
|
||||||
const newPlayerCollider = self.collisionSys.createPolygon(x0, y0, pts);
|
const newPlayerCollider = self.collisionSys.createPolygon(x0, y0, pts);
|
||||||
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
||||||
|
newPlayerCollider.data = playerDownsyncInfo;
|
||||||
self.collisionSysMap.set(collisionPlayerIndex, newPlayerCollider);
|
self.collisionSysMap.set(collisionPlayerIndex, newPlayerCollider);
|
||||||
|
|
||||||
safelyAddChild(self.node, newPlayerNode);
|
safelyAddChild(self.node, newPlayerNode);
|
||||||
setLocalZOrder(newPlayerNode, 5);
|
setLocalZOrder(newPlayerNode, 5);
|
||||||
|
|
||||||
newPlayerNode.active = true;
|
newPlayerNode.active = true;
|
||||||
playerScriptIns.scheduleNewDirection({
|
playerScriptIns.updateCharacterAnim(playerDownsyncInfo, null, true);
|
||||||
dx: playerRichInfo.dir.dx,
|
|
||||||
dy: playerRichInfo.dir.dy
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
return [newPlayerNode, playerScriptIns];
|
return [newPlayerNode, playerScriptIns];
|
||||||
},
|
},
|
||||||
@@ -807,9 +793,7 @@ cc.Class({
|
|||||||
currSelfInput = null;
|
currSelfInput = null;
|
||||||
const noDelayInputFrameId = self._convertToInputFrameId(self.renderFrameId, 0); // It's important that "inputDelayFrames == 0" here
|
const noDelayInputFrameId = self._convertToInputFrameId(self.renderFrameId, 0); // It's important that "inputDelayFrames == 0" here
|
||||||
if (self.shouldGenerateInputFrameUpsync(self.renderFrameId)) {
|
if (self.shouldGenerateInputFrameUpsync(self.renderFrameId)) {
|
||||||
const prevAndCurrInputs = self._generateInputFrameUpsync(noDelayInputFrameId);
|
[prevSelfInput, currSelfInput] = self._generateInputFrameUpsync(noDelayInputFrameId);
|
||||||
prevSelfInput = prevAndCurrInputs[0];
|
|
||||||
currSelfInput = prevAndCurrInputs[1];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let t0 = performance.now();
|
let t0 = performance.now();
|
||||||
@@ -829,14 +813,15 @@ cc.Class({
|
|||||||
let t2 = performance.now();
|
let t2 = performance.now();
|
||||||
|
|
||||||
// Inside the following "self.rollbackAndChase" actually ROLLS FORWARD w.r.t. the corresponding delayedInputFrame, REGARDLESS OF whether or not "self.chaserRenderFrameId == self.renderFrameId" now.
|
// Inside the following "self.rollbackAndChase" actually ROLLS FORWARD w.r.t. the corresponding delayedInputFrame, REGARDLESS OF whether or not "self.chaserRenderFrameId == self.renderFrameId" now.
|
||||||
const rdf = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.collisionSys, self.collisionSysMap, false);
|
const [prevRdf, rdf] = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.collisionSys, self.collisionSysMap, false);
|
||||||
/*
|
/*
|
||||||
const nonTrivialChaseEnded = (prevChaserRenderFrameId < nextChaserRenderFrameId && nextChaserRenderFrameId == self.renderFrameId);
|
const nonTrivialChaseEnded = (prevChaserRenderFrameId < nextChaserRenderFrameId && nextChaserRenderFrameId == self.renderFrameId);
|
||||||
if (nonTrivialChaseEnded) {
|
if (nonTrivialChaseEnded) {
|
||||||
console.debug("Non-trivial chase ended, prevChaserRenderFrameId=" + prevChaserRenderFrameId + ", nextChaserRenderFrameId=" + nextChaserRenderFrameId);
|
console.debug("Non-trivial chase ended, prevChaserRenderFrameId=" + prevChaserRenderFrameId + ", nextChaserRenderFrameId=" + nextChaserRenderFrameId);
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
self.applyRoomDownsyncFrameDynamics(rdf);
|
// [WARNING] Don't try to get "prevRdf(i.e. renderFrameId == latest-1)" by "self.recentRenderCache.getByFrameId(...)" here, as the cache might have been updated by asynchronous "onRoomDownsyncFrame(...)" calls!
|
||||||
|
self.applyRoomDownsyncFrameDynamics(rdf, prevRdf);
|
||||||
let t3 = performance.now();
|
let t3 = performance.now();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error during Map.update", err);
|
console.error("Error during Map.update", err);
|
||||||
@@ -937,29 +922,27 @@ cc.Class({
|
|||||||
if (null == self.findingPlayerNode.parent) return;
|
if (null == self.findingPlayerNode.parent) return;
|
||||||
self.findingPlayerNode.parent.removeChild(self.findingPlayerNode);
|
self.findingPlayerNode.parent.removeChild(self.findingPlayerNode);
|
||||||
if (null != rdf) {
|
if (null != rdf) {
|
||||||
self._initPlayerRichInfoDict(rdf.players, rdf.playerMetas);
|
self._initPlayerRichInfoDict(rdf.players);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onBattleReadyToStart(rdf) {
|
onBattleReadyToStart(rdf) {
|
||||||
const self = this;
|
const self = this;
|
||||||
const players = rdf.players;
|
const players = rdf.players;
|
||||||
const playerMetas = rdf.playerMetas;
|
self._initPlayerRichInfoDict(players);
|
||||||
self._initPlayerRichInfoDict(players, playerMetas);
|
|
||||||
|
|
||||||
// Show the top status indicators for IN_BATTLE
|
// Show the top status indicators for IN_BATTLE
|
||||||
if (self.playersInfoNode) {
|
if (self.playersInfoNode) {
|
||||||
const playersInfoScriptIns = self.playersInfoNode.getComponent("PlayersInfo");
|
const playersInfoScriptIns = self.playersInfoNode.getComponent("PlayersInfo");
|
||||||
for (let i in playerMetas) {
|
for (let i in players) {
|
||||||
const playerMeta = playerMetas[i];
|
playersInfoScriptIns.updateData(players[i]);
|
||||||
playersInfoScriptIns.updateData(playerMeta);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log("Calling `onBattleReadyToStart` with:", playerMetas);
|
console.log("Calling `onBattleReadyToStart` with:", players);
|
||||||
if (self.findingPlayerNode) {
|
if (self.findingPlayerNode) {
|
||||||
const findingPlayerScriptIns = self.findingPlayerNode.getComponent("FindingPlayer");
|
const findingPlayerScriptIns = self.findingPlayerNode.getComponent("FindingPlayer");
|
||||||
findingPlayerScriptIns.hideExitButton();
|
findingPlayerScriptIns.hideExitButton();
|
||||||
findingPlayerScriptIns.updatePlayersInfo(playerMetas);
|
findingPlayerScriptIns.updatePlayersInfo(players);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delay to hide the "finding player" GUI, then show a countdown clock
|
// Delay to hide the "finding player" GUI, then show a countdown clock
|
||||||
@@ -973,21 +956,17 @@ cc.Class({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
applyRoomDownsyncFrameDynamics(rdf) {
|
applyRoomDownsyncFrameDynamics(rdf, prevRdf) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
for (let [playerId, playerRichInfo] of self.playerRichInfoDict.entries()) {
|
||||||
self.playerRichInfoDict.forEach((playerRichInfo, playerId) => {
|
|
||||||
const immediatePlayerInfo = rdf.players[playerId];
|
const immediatePlayerInfo = rdf.players[playerId];
|
||||||
const wpos = self.virtualGridToWorldPos(immediatePlayerInfo.virtualGridX, immediatePlayerInfo.virtualGridY);
|
const prevRdfPlayer = (null == prevRdf ? null : prevRdf.players[playerId]);
|
||||||
const dx = (wpos[0] - playerRichInfo.node.x);
|
const [wx, wy] = self.virtualGridToWorldPos(immediatePlayerInfo.virtualGridX, immediatePlayerInfo.virtualGridY);
|
||||||
const dy = (wpos[1] - playerRichInfo.node.y);
|
//const justJiggling = (self.jigglingEps1D >= Math.abs(wx - playerRichInfo.node.x) && self.jigglingEps1D >= Math.abs(wy - playerRichInfo.node.y));
|
||||||
//const justJiggling = (self.jigglingEps1D >= Math.abs(dx) && self.jigglingEps1D >= Math.abs(dy));
|
playerRichInfo.node.setPosition(wx, wy);
|
||||||
playerRichInfo.node.setPosition(wpos[0], wpos[1]);
|
|
||||||
playerRichInfo.virtualGridX = immediatePlayerInfo.virtualGridX;
|
|
||||||
playerRichInfo.virtualGridY = immediatePlayerInfo.virtualGridY;
|
|
||||||
playerRichInfo.scriptIns.scheduleNewDirection(immediatePlayerInfo.dir, false);
|
|
||||||
playerRichInfo.scriptIns.updateSpeed(immediatePlayerInfo.speed);
|
playerRichInfo.scriptIns.updateSpeed(immediatePlayerInfo.speed);
|
||||||
});
|
playerRichInfo.scriptIns.updateCharacterAnim(immediatePlayerInfo, prevRdfPlayer, false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getCachedInputFrameDownsyncWithPrediction(inputFrameId) {
|
getCachedInputFrameDownsyncWithPrediction(inputFrameId) {
|
||||||
@@ -997,7 +976,7 @@ cc.Class({
|
|||||||
const lastAllConfirmedInputFrame = self.recentInputCache.getByFrameId(self.lastAllConfirmedInputFrameId);
|
const lastAllConfirmedInputFrame = self.recentInputCache.getByFrameId(self.lastAllConfirmedInputFrameId);
|
||||||
for (let i = 0; i < inputFrameDownsync.inputList.length; ++i) {
|
for (let i = 0; i < inputFrameDownsync.inputList.length; ++i) {
|
||||||
if (i == self.selfPlayerInfo.joinIndex - 1) continue;
|
if (i == self.selfPlayerInfo.joinIndex - 1) continue;
|
||||||
inputFrameDownsync.inputList[i] = lastAllConfirmedInputFrame.inputList[i];
|
inputFrameDownsync.inputList[i] = (lastAllConfirmedInputFrame.inputList[i] & 15); // Don't predict attack input!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1007,59 +986,191 @@ cc.Class({
|
|||||||
// TODO: Write unit-test for this function to compare with its backend counter part
|
// TODO: Write unit-test for this function to compare with its backend counter part
|
||||||
applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, currRenderFrame, collisionSys, collisionSysMap) {
|
applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, currRenderFrame, collisionSys, collisionSysMap) {
|
||||||
const self = this;
|
const self = this;
|
||||||
const nextRenderFramePlayers = {}
|
const nextRenderFramePlayers = {};
|
||||||
for (let playerId in currRenderFrame.players) {
|
for (let playerId in currRenderFrame.players) {
|
||||||
const currPlayerDownsync = currRenderFrame.players[playerId];
|
const currPlayerDownsync = currRenderFrame.players[playerId];
|
||||||
nextRenderFramePlayers[playerId] = {
|
nextRenderFramePlayers[playerId] = {
|
||||||
id: playerId,
|
id: playerId,
|
||||||
virtualGridX: currPlayerDownsync.virtualGridX,
|
virtualGridX: currPlayerDownsync.virtualGridX,
|
||||||
virtualGridY: currPlayerDownsync.virtualGridY,
|
virtualGridY: currPlayerDownsync.virtualGridY,
|
||||||
dir: {
|
dirX: currPlayerDownsync.dirX,
|
||||||
dx: currPlayerDownsync.dir.dx,
|
dirY: currPlayerDownsync.dirY,
|
||||||
dy: currPlayerDownsync.dir.dy,
|
characterState: currPlayerDownsync.characterState,
|
||||||
},
|
|
||||||
speed: currPlayerDownsync.speed,
|
speed: currPlayerDownsync.speed,
|
||||||
battleState: currPlayerDownsync.battleState,
|
battleState: currPlayerDownsync.battleState,
|
||||||
score: currPlayerDownsync.score,
|
score: currPlayerDownsync.score,
|
||||||
removed: currPlayerDownsync.removed,
|
removed: currPlayerDownsync.removed,
|
||||||
joinIndex: currPlayerDownsync.joinIndex,
|
joinIndex: currPlayerDownsync.joinIndex,
|
||||||
|
framesToRecover: (0 < currPlayerDownsync.framesToRecover ? currPlayerDownsync.framesToRecover - 1 : 0),
|
||||||
|
hp: currPlayerDownsync.hp,
|
||||||
|
maxHp: currPlayerDownsync.maxHp,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const toRet = {
|
const toRet = {
|
||||||
id: currRenderFrame.id + 1,
|
id: currRenderFrame.id + 1,
|
||||||
players: nextRenderFramePlayers,
|
players: nextRenderFramePlayers,
|
||||||
|
meleeBullets: []
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const bulletPushbacks = new Array(self.playerRichInfoArr.length); // Guaranteed determinism regardless of traversal order
|
||||||
|
const effPushbacks = new Array(self.playerRichInfoArr.length); // Guaranteed determinism regardless of traversal order
|
||||||
|
|
||||||
|
// Reset playerCollider position from the "virtual grid position"
|
||||||
|
for (let j in self.playerRichInfoArr) {
|
||||||
|
const joinIndex = parseInt(j) + 1;
|
||||||
|
bulletPushbacks[joinIndex - 1] = [0.0, 0.0];
|
||||||
|
effPushbacks[joinIndex - 1] = [0.0, 0.0];
|
||||||
|
const playerRichInfo = self.playerRichInfoArr[j];
|
||||||
|
const playerId = playerRichInfo.id;
|
||||||
|
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
||||||
|
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
|
||||||
|
const currPlayerDownsync = currRenderFrame.players[playerId];
|
||||||
|
|
||||||
|
const newVx = currPlayerDownsync.virtualGridX;
|
||||||
|
const newVy = currPlayerDownsync.virtualGridY;
|
||||||
|
[playerCollider.x, playerCollider.y] = self.virtualGridToPolygonColliderAnchorPos(newVx, newVy, self.playerRichInfoArr[joinIndex - 1].colliderRadius, self.playerRichInfoArr[joinIndex - 1].colliderRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check bullet-anything collisions first, because the pushbacks caused by bullets might later be reverted by player-barrier collision
|
||||||
|
const bulletColliders = new Map(); // Will all be removed at the end of `applyInputFrameDownsyncDynamicsOnSingleRenderFrame` due to the need for being rollback-compatible
|
||||||
|
const removedBulletsAtCurrFrame = new Set();
|
||||||
|
for (let k in currRenderFrame.meleeBullets) {
|
||||||
|
const meleeBullet = currRenderFrame.meleeBullets[k];
|
||||||
|
if (
|
||||||
|
meleeBullet.originatedRenderFrameId + meleeBullet.startupFrames <= currRenderFrame.id
|
||||||
|
&&
|
||||||
|
meleeBullet.originatedRenderFrameId + meleeBullet.startupFrames + meleeBullet.activeFrames > currRenderFrame.id
|
||||||
|
) {
|
||||||
|
const collisionBulletIndex = self.collisionBulletIndexPrefix + meleeBullet.battleLocalId;
|
||||||
|
const collisionOffenderIndex = self.collisionPlayerIndexPrefix + meleeBullet.offenderJoinIndex;
|
||||||
|
const offenderCollider = collisionSysMap.get(collisionOffenderIndex);
|
||||||
|
const offender = currRenderFrame.players[meleeBullet.offenderPlayerId];
|
||||||
|
|
||||||
|
let xfac = 1; // By now, straight Punch offset doesn't respect "y-axis"
|
||||||
|
if (0 > offender.dirX) {
|
||||||
|
xfac = -1;
|
||||||
|
}
|
||||||
|
const [offenderWx, offenderWy] = self.virtualGridToWorldPos(offender.virtualGridX, offender.virtualGridY);
|
||||||
|
const bulletWx = offenderWx + xfac * meleeBullet.hitboxOffset;
|
||||||
|
const bulletWy = offenderWy;
|
||||||
|
const [bulletCx, bulletCy] = self.worldToPolygonColliderAnchorPos(bulletWx, bulletWy, meleeBullet.hitboxSize.x * 0.5, meleeBullet.hitboxSize.y * 0.5),
|
||||||
|
pts = [[0, 0], [meleeBullet.hitboxSize.x, 0], [meleeBullet.hitboxSize.x, meleeBullet.hitboxSize.y], [0, meleeBullet.hitboxSize.y]];
|
||||||
|
const newBulletCollider = collisionSys.createPolygon(bulletCx, bulletCy, pts);
|
||||||
|
newBulletCollider.data = meleeBullet;
|
||||||
|
collisionSysMap.set(collisionBulletIndex, newBulletCollider);
|
||||||
|
bulletColliders.set(collisionBulletIndex, newBulletCollider);
|
||||||
|
// console.log(`A meleeBullet is added to collisionSys at currRenderFrame.id=${currRenderFrame.id} as start-up frames ended and active frame is not yet ended: ${JSON.stringify(meleeBullet)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
collisionSys.update();
|
||||||
|
const result1 = collisionSys.createResult(); // Can I reuse a "self.collisionSysResult" object throughout the whole battle?
|
||||||
|
|
||||||
|
bulletColliders.forEach((bulletCollider, collisionBulletIndex) => {
|
||||||
|
const potentials = bulletCollider.potentials();
|
||||||
|
const offender = currRenderFrame.players[bulletCollider.data.offenderPlayerId];
|
||||||
|
let shouldRemove = false;
|
||||||
|
for (const potential of potentials) {
|
||||||
|
if (null != potential.data && potential.data.joinIndex == bulletCollider.data.offenderJoinIndex) continue;
|
||||||
|
if (!bulletCollider.collides(potential, result1)) continue;
|
||||||
|
if (null != potential.data && null !== potential.data.joinIndex) {
|
||||||
|
const joinIndex = potential.data.joinIndex;
|
||||||
|
let xfac = 1;
|
||||||
|
if (0 > offender.dirX) {
|
||||||
|
xfac = -1;
|
||||||
|
}
|
||||||
|
bulletPushbacks[joinIndex - 1][0] += xfac * bulletCollider.data.pushback; // Only for straight punch, there's no y-pushback
|
||||||
|
bulletPushbacks[joinIndex - 1][1] += 0;
|
||||||
|
const thatAckedPlayerInNextFrame = nextRenderFramePlayers[potential.data.id];
|
||||||
|
thatAckedPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atked1[0];
|
||||||
|
const oldFramesToRecover = thatAckedPlayerInNextFrame.framesToRecover;
|
||||||
|
thatAckedPlayerInNextFrame.framesToRecover = (oldFramesToRecover > bulletCollider.data.hitStunFrames ? oldFramesToRecover : bulletCollider.data.hitStunFrames); // In case the hit player is already stun, we extend it
|
||||||
|
}
|
||||||
|
shouldRemove = true;
|
||||||
|
}
|
||||||
|
if (shouldRemove) {
|
||||||
|
removedBulletsAtCurrFrame.add(collisionBulletIndex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// [WARNING] Remove bullets from collisionSys ANYWAY for the convenience of rollback
|
||||||
|
for (let k in currRenderFrame.meleeBullets) {
|
||||||
|
const meleeBullet = currRenderFrame.meleeBullets[k];
|
||||||
|
const collisionBulletIndex = self.collisionBulletIndexPrefix + meleeBullet.battleLocalId;
|
||||||
|
if (collisionSysMap.has(collisionBulletIndex)) {
|
||||||
|
const bulletCollider = collisionSysMap.get(collisionBulletIndex);
|
||||||
|
bulletCollider.remove();
|
||||||
|
collisionSysMap.delete(collisionBulletIndex);
|
||||||
|
}
|
||||||
|
if (removedBulletsAtCurrFrame.has(collisionBulletIndex)) continue;
|
||||||
|
toRet.meleeBullets.push(meleeBullet);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process player inputs
|
||||||
if (null != delayedInputFrame) {
|
if (null != delayedInputFrame) {
|
||||||
|
const delayedInputFrameForPrevRenderFrame = self.getCachedInputFrameDownsyncWithPrediction(self._convertToInputFrameId(currRenderFrame.id - 1, self.inputDelayFrames));
|
||||||
const inputList = delayedInputFrame.inputList;
|
const inputList = delayedInputFrame.inputList;
|
||||||
const effPushbacks = new Array(self.playerRichInfoArr.length); // Guaranteed determinism regardless of traversal order
|
|
||||||
for (let j in self.playerRichInfoArr) {
|
for (let j in self.playerRichInfoArr) {
|
||||||
const joinIndex = parseInt(j) + 1;
|
const joinIndex = parseInt(j) + 1;
|
||||||
effPushbacks[joinIndex - 1] = [0.0, 0.0];
|
effPushbacks[joinIndex - 1] = [0.0, 0.0];
|
||||||
const playerId = self.playerRichInfoArr[j].id;
|
const playerRichInfo = self.playerRichInfoArr[j];
|
||||||
|
const playerId = playerRichInfo.id;
|
||||||
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
||||||
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
|
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
|
||||||
const player = currRenderFrame.players[playerId];
|
const currPlayerDownsync = currRenderFrame.players[playerId];
|
||||||
|
const thatPlayerInNextFrame = nextRenderFramePlayers[playerId];
|
||||||
|
if (0 < thatPlayerInNextFrame.framesToRecover) {
|
||||||
|
// No need to process inputs for this player, but there might be bullet pushbacks on this player
|
||||||
|
playerCollider.x += bulletPushbacks[joinIndex - 1][0];
|
||||||
|
playerCollider.y += bulletPushbacks[joinIndex - 1][1];
|
||||||
|
if (0 != bulletPushbacks[joinIndex - 1][0] || 0 != bulletPushbacks[joinIndex - 1][1]) {
|
||||||
|
console.log(`playerId=${playerId}, joinIndex=${joinIndex} is pushbacked back by ${bulletPushbacks[joinIndex - 1]} by bullet impacts, now its framesToRecover is ${thatPlayerInNextFrame.framesToRecover}`);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const decodedInput = self.ctrl.decodeInput(inputList[joinIndex - 1]);
|
const decodedInput = self.ctrl.decodeInput(inputList[joinIndex - 1]);
|
||||||
|
|
||||||
// console.log(`Got non-zero inputs for playerId=${playerId}, decodedInput=${JSON.stringify(decodedInput)} @currRenderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.id}`);
|
const prevDecodedInput = (null == delayedInputFrameForPrevRenderFrame ? null : self.ctrl.decodeInput(delayedInputFrameForPrevRenderFrame.inputList[joinIndex - 1]));
|
||||||
/*
|
const prevBtnALevel = (null == prevDecodedInput ? 0 : prevDecodedInput.btnALevel);
|
||||||
Reset "position" of players in "collisionSys" according to "virtual grid position". The easy part is that we don't have path-dependent-integrals to worry about like that of thermal dynamics.
|
|
||||||
*/
|
if (1 == decodedInput.btnALevel && 0 == prevBtnALevel) {
|
||||||
const newVx = player.virtualGridX + (decodedInput.dx + player.speed * decodedInput.dx);
|
// console.log(`playerId=${playerId} triggered a rising-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
|
||||||
const newVy = player.virtualGridY + (decodedInput.dy + player.speed * decodedInput.dy);
|
if (self.bulletTriggerEnabled) {
|
||||||
const newCpos = self.virtualGridToPlayerColliderPos(newVx, newVy, self.playerRichInfoArr[joinIndex - 1]);
|
const punchSkillId = 1;
|
||||||
playerCollider.x = newCpos[0];
|
const punch = window.pb.protos.MeleeBullet.create(self.meleeSkillConfig[punchSkillId]);
|
||||||
playerCollider.y = newCpos[1];
|
thatPlayerInNextFrame.framesToRecover = punch.recoveryFrames;
|
||||||
// Update directions and thus would eventually update moving animation accordingly
|
punch.battleLocalId = self.bulletBattleLocalIdCounter++;
|
||||||
nextRenderFramePlayers[playerId].dir.dx = decodedInput.dx;
|
punch.offenderJoinIndex = joinIndex;
|
||||||
nextRenderFramePlayers[playerId].dir.dy = decodedInput.dy;
|
punch.offenderPlayerId = playerId;
|
||||||
|
punch.originatedRenderFrameId = currRenderFrame.id;
|
||||||
|
toRet.meleeBullets.push(punch);
|
||||||
|
// console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}: ${self._stringifyRecentInputCache(true)}`);
|
||||||
|
// console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
|
||||||
|
|
||||||
|
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atk1[0];
|
||||||
|
}
|
||||||
|
} else if (0 == decodedInput.btnALevel && 1 == prevBtnALevel) {
|
||||||
|
// console.log(`playerId=${playerId} triggered a falling-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
|
||||||
|
} else {
|
||||||
|
// No bullet trigger, process movement inputs
|
||||||
|
if (0 != decodedInput.dx || 0 != decodedInput.dy) {
|
||||||
|
// Update directions and thus would eventually update moving animation accordingly
|
||||||
|
thatPlayerInNextFrame.dirX = decodedInput.dx;
|
||||||
|
thatPlayerInNextFrame.dirY = decodedInput.dy;
|
||||||
|
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Walking[0];
|
||||||
|
} else {
|
||||||
|
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Idle1[0];
|
||||||
|
}
|
||||||
|
const [movementX, movementY] = self.virtualGridToWorldPos(decodedInput.dx + currPlayerDownsync.speed * decodedInput.dx, decodedInput.dy + currPlayerDownsync.speed * decodedInput.dy);
|
||||||
|
playerCollider.x += movementX;
|
||||||
|
playerCollider.y += movementY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
collisionSys.update();
|
collisionSys.update(); // by now all "bulletCollider"s are removed
|
||||||
const result = collisionSys.createResult(); // Can I reuse a "self.collisionSysResult" object throughout the whole battle?
|
const result2 = collisionSys.createResult(); // Can I reuse a "self.collisionSysResult" object throughout the whole battle?
|
||||||
|
|
||||||
for (let j in self.playerRichInfoArr) {
|
for (let j in self.playerRichInfoArr) {
|
||||||
const joinIndex = parseInt(j) + 1;
|
const joinIndex = parseInt(j) + 1;
|
||||||
@@ -1069,10 +1180,10 @@ cc.Class({
|
|||||||
const potentials = playerCollider.potentials();
|
const potentials = playerCollider.potentials();
|
||||||
for (const potential of potentials) {
|
for (const potential of potentials) {
|
||||||
// Test if the player collides with the wall
|
// Test if the player collides with the wall
|
||||||
if (!playerCollider.collides(potential, result)) continue;
|
if (!playerCollider.collides(potential, result2)) continue;
|
||||||
// Push the player out of the wall
|
// Push the player out of the wall
|
||||||
effPushbacks[joinIndex - 1][0] += result.overlap * result.overlap_x;
|
effPushbacks[joinIndex - 1][0] += result2.overlap * result2.overlap_x;
|
||||||
effPushbacks[joinIndex - 1][1] += result.overlap * result.overlap_y;
|
effPushbacks[joinIndex - 1][1] += result2.overlap * result2.overlap_y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1081,10 +1192,10 @@ cc.Class({
|
|||||||
const playerId = self.playerRichInfoArr[j].id;
|
const playerId = self.playerRichInfoArr[j].id;
|
||||||
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
||||||
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
|
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
|
||||||
const newVpos = self.playerColliderAnchorToVirtualGridPos(playerCollider.x - effPushbacks[joinIndex - 1][0], playerCollider.y - effPushbacks[joinIndex - 1][1], self.playerRichInfoArr[j]);
|
const thatPlayerInNextFrame = nextRenderFramePlayers[playerId];
|
||||||
nextRenderFramePlayers[playerId].virtualGridX = newVpos[0];
|
[thatPlayerInNextFrame.virtualGridX, thatPlayerInNextFrame.virtualGridY] = self.polygonColliderAnchorToVirtualGridPos(playerCollider.x - effPushbacks[joinIndex - 1][0], playerCollider.y - effPushbacks[joinIndex - 1][1], self.playerRichInfoArr[j].colliderRadius, self.playerRichInfoArr[j].colliderRadius);
|
||||||
nextRenderFramePlayers[playerId].virtualGridY = newVpos[1];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return toRet;
|
return toRet;
|
||||||
@@ -1095,29 +1206,30 @@ cc.Class({
|
|||||||
This function eventually calculates a "RoomDownsyncFrame" where "RoomDownsyncFrame.id == renderFrameIdEd" if not interruptted.
|
This function eventually calculates a "RoomDownsyncFrame" where "RoomDownsyncFrame.id == renderFrameIdEd" if not interruptted.
|
||||||
*/
|
*/
|
||||||
const self = this;
|
const self = this;
|
||||||
|
let prevLatestRdf = null;
|
||||||
let latestRdf = self.recentRenderCache.getByFrameId(renderFrameIdSt); // typed "RoomDownsyncFrame"
|
let latestRdf = self.recentRenderCache.getByFrameId(renderFrameIdSt); // typed "RoomDownsyncFrame"
|
||||||
if (null == latestRdf) {
|
if (null == latestRdf) {
|
||||||
console.error(`Couldn't find renderFrameId=${renderFrameIdSt}, to rollback, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`);
|
console.error(`Couldn't find renderFrameId=${renderFrameIdSt}, to rollback, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`);
|
||||||
return latestRdf;
|
return [prevLatestRdf, latestRdf];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (renderFrameIdSt >= renderFrameIdEd) {
|
if (renderFrameIdSt >= renderFrameIdEd) {
|
||||||
return latestRdf;
|
return [prevLatestRdf, latestRdf];
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = renderFrameIdSt; i < renderFrameIdEd; ++i) {
|
for (let i = renderFrameIdSt; i < renderFrameIdEd; ++i) {
|
||||||
const currRenderFrame = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing", this function can be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"!
|
const currRenderFrame = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing", this function can be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"!
|
||||||
if (null == currRenderFrame) {
|
if (null == currRenderFrame) {
|
||||||
console.warn(`Couldn't find renderFrame for i=${i} to rollback, self.renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, might've been interruptted by onRoomDownsyncFrame`);
|
console.warn(`Couldn't find renderFrame for i=${i} to rollback, self.renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, might've been interruptted by onRoomDownsyncFrame`);
|
||||||
return latestRdf;
|
return [prevLatestRdf, latestRdf];
|
||||||
}
|
}
|
||||||
const j = self._convertToInputFrameId(i, self.inputDelayFrames);
|
const j = self._convertToInputFrameId(i, self.inputDelayFrames);
|
||||||
const delayedInputFrame = self.getCachedInputFrameDownsyncWithPrediction(j);
|
const delayedInputFrame = self.getCachedInputFrameDownsyncWithPrediction(j);
|
||||||
if (null == delayedInputFrame) {
|
if (null == delayedInputFrame) {
|
||||||
console.warn(`Failed to get cached delayedInputFrame for i=${i}, j=${j}, self.renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}`);
|
console.warn(`Failed to get cached delayedInputFrame for i=${i}, j=${j}, self.renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}`);
|
||||||
return latestRdf;
|
return [prevLatestRdf, latestRdf];
|
||||||
}
|
}
|
||||||
|
prevLatestRdf = latestRdf;
|
||||||
latestRdf = self.applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, currRenderFrame, collisionSys, collisionSysMap);
|
latestRdf = self.applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, currRenderFrame, collisionSys, collisionSysMap);
|
||||||
if (
|
if (
|
||||||
self._allConfirmed(delayedInputFrame.confirmedList)
|
self._allConfirmed(delayedInputFrame.confirmedList)
|
||||||
@@ -1139,29 +1251,27 @@ cc.Class({
|
|||||||
self.dumpToRenderCache(latestRdf);
|
self.dumpToRenderCache(latestRdf);
|
||||||
}
|
}
|
||||||
|
|
||||||
return latestRdf;
|
return [prevLatestRdf, latestRdf];
|
||||||
},
|
},
|
||||||
|
|
||||||
_initPlayerRichInfoDict(players, playerMetas) {
|
_initPlayerRichInfoDict(players) {
|
||||||
const self = this;
|
const self = this;
|
||||||
for (let k in players) {
|
for (let k in players) {
|
||||||
const playerId = parseInt(k);
|
const playerId = parseInt(k);
|
||||||
if (self.playerRichInfoDict.has(playerId)) continue; // Skip already put keys
|
if (self.playerRichInfoDict.has(playerId)) continue; // Skip already put keys
|
||||||
const immediatePlayerInfo = players[playerId];
|
const immediatePlayerInfo = players[playerId];
|
||||||
const immediatePlayerMeta = playerMetas[playerId];
|
|
||||||
self.playerRichInfoDict.set(playerId, immediatePlayerInfo);
|
self.playerRichInfoDict.set(playerId, immediatePlayerInfo);
|
||||||
Object.assign(self.playerRichInfoDict.get(playerId), immediatePlayerMeta);
|
|
||||||
|
|
||||||
const nodeAndScriptIns = self.spawnPlayerNode(immediatePlayerInfo.joinIndex, immediatePlayerInfo.virtualGridX, immediatePlayerInfo.virtualGridY, self.playerRichInfoDict.get(playerId));
|
const [theNode, theScriptIns] = self.spawnPlayerNode(immediatePlayerInfo.joinIndex, immediatePlayerInfo.virtualGridX, immediatePlayerInfo.virtualGridY, immediatePlayerInfo);
|
||||||
|
|
||||||
Object.assign(self.playerRichInfoDict.get(playerId), {
|
Object.assign(self.playerRichInfoDict.get(playerId), {
|
||||||
node: nodeAndScriptIns[0],
|
node: theNode,
|
||||||
scriptIns: nodeAndScriptIns[1]
|
scriptIns: theScriptIns,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (self.selfPlayerInfo.id == playerId) {
|
if (self.selfPlayerInfo.id == playerId) {
|
||||||
self.selfPlayerInfo = Object.assign(self.selfPlayerInfo, immediatePlayerInfo);
|
self.selfPlayerInfo = Object.assign(self.selfPlayerInfo, immediatePlayerInfo);
|
||||||
nodeAndScriptIns[1].showArrowTipNode();
|
theScriptIns.showArrowTipNode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.playerRichInfoArr = new Array(self.playerRichInfoDict.size);
|
self.playerRichInfoArr = new Array(self.playerRichInfoDict.size);
|
||||||
@@ -1213,23 +1323,23 @@ cc.Class({
|
|||||||
return [wx, wy];
|
return [wx, wy];
|
||||||
},
|
},
|
||||||
|
|
||||||
playerWorldToCollisionPos(wx, wy, playerRichInfo) {
|
worldToPolygonColliderAnchorPos(wx, wy, halfBoundingW, halfBoundingH) {
|
||||||
return [wx - playerRichInfo.colliderRadius, wy - playerRichInfo.colliderRadius];
|
return [wx - halfBoundingW, wy - halfBoundingH];
|
||||||
},
|
},
|
||||||
|
|
||||||
playerColliderAnchorToWorldPos(cx, cy, playerRichInfo) {
|
polygonColliderAnchorToWorldPos(cx, cy, halfBoundingW, halfBoundingH) {
|
||||||
return [cx + playerRichInfo.colliderRadius, cy + playerRichInfo.colliderRadius];
|
return [cx + halfBoundingW, cy + halfBoundingH];
|
||||||
},
|
},
|
||||||
|
|
||||||
playerColliderAnchorToVirtualGridPos(cx, cy, playerRichInfo) {
|
polygonColliderAnchorToVirtualGridPos(cx, cy, halfBoundingW, halfBoundingH) {
|
||||||
const self = this;
|
const self = this;
|
||||||
const wpos = self.playerColliderAnchorToWorldPos(cx, cy, playerRichInfo);
|
const [wx, wy] = self.polygonColliderAnchorToWorldPos(cx, cy, halfBoundingW, halfBoundingH);
|
||||||
return self.worldToVirtualGridPos(wpos[0], wpos[1])
|
return self.worldToVirtualGridPos(wx, wy)
|
||||||
},
|
},
|
||||||
|
|
||||||
virtualGridToPlayerColliderPos(vx, vy, playerRichInfo) {
|
virtualGridToPolygonColliderAnchorPos(vx, vy, halfBoundingW, halfBoundingH) {
|
||||||
const self = this;
|
const self = this;
|
||||||
const wpos = self.virtualGridToWorldPos(vx, vy);
|
const [wx, wy] = self.virtualGridToWorldPos(vx, vy);
|
||||||
return self.playerWorldToCollisionPos(wpos[0], wpos[1], playerRichInfo)
|
return self.worldToPolygonColliderAnchorPos(wx, wy, halfBoundingW, halfBoundingH)
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@@ -10,43 +10,6 @@ cc.Class({
|
|||||||
console.warn("+++++++ Map onDestroy()");
|
console.warn("+++++++ Map onDestroy()");
|
||||||
},
|
},
|
||||||
|
|
||||||
spawnPlayerNode(joinIndex, vx, vy, playerRichInfo) {
|
|
||||||
const self = this;
|
|
||||||
const newPlayerNode = cc.instantiate(self.controlledCharacterPrefab)
|
|
||||||
const playerScriptIns = newPlayerNode.getComponent("ControlledCharacter");
|
|
||||||
if (1 == joinIndex) {
|
|
||||||
playerScriptIns.setSpecies("SoldierElf");
|
|
||||||
} else if (2 == joinIndex) {
|
|
||||||
playerScriptIns.setSpecies("SoldierFireGhost");
|
|
||||||
playerScriptIns.animComp.node.scaleX = (-1.0);
|
|
||||||
}
|
|
||||||
const wpos = self.virtualGridToWorldPos(vx, vy);
|
|
||||||
|
|
||||||
newPlayerNode.setPosition(cc.v2(wpos[0], wpos[1]));
|
|
||||||
|
|
||||||
playerScriptIns.mapNode = self.node;
|
|
||||||
const cpos = self.virtualGridToPlayerColliderPos(vx, vy, playerRichInfo);
|
|
||||||
const d = playerRichInfo.colliderRadius * 2,
|
|
||||||
x0 = cpos[0],
|
|
||||||
y0 = cpos[1];
|
|
||||||
let pts = [[0, 0], [d, 0], [d, d], [0, d]];
|
|
||||||
|
|
||||||
const newPlayerCollider = self.collisionSys.createPolygon(x0, y0, pts);
|
|
||||||
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
|
||||||
self.collisionSysMap.set(collisionPlayerIndex, newPlayerCollider);
|
|
||||||
|
|
||||||
safelyAddChild(self.node, newPlayerNode);
|
|
||||||
setLocalZOrder(newPlayerNode, 5);
|
|
||||||
|
|
||||||
newPlayerNode.active = true;
|
|
||||||
playerScriptIns.scheduleNewDirection({
|
|
||||||
dx: playerRichInfo.dir.dx,
|
|
||||||
dy: playerRichInfo.dir.dy
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
return [newPlayerNode, playerScriptIns];
|
|
||||||
},
|
|
||||||
|
|
||||||
onLoad() {
|
onLoad() {
|
||||||
const self = this;
|
const self = this;
|
||||||
window.mapIns = self;
|
window.mapIns = self;
|
||||||
@@ -73,10 +36,35 @@ cc.Class({
|
|||||||
self.rollbackEstimatedDt = 0.016667;
|
self.rollbackEstimatedDt = 0.016667;
|
||||||
self.rollbackEstimatedDtMillis = 16.667;
|
self.rollbackEstimatedDtMillis = 16.667;
|
||||||
self.rollbackEstimatedDtNanos = 16666666;
|
self.rollbackEstimatedDtNanos = 16666666;
|
||||||
self.maxChasingRenderFramesPerUpdate = 5;
|
|
||||||
|
|
||||||
self.worldToVirtualGridRatio = 1000;
|
self.worldToVirtualGridRatio = 1000;
|
||||||
self.virtualGridToWorldRatio = 1.0 / self.worldToVirtualGridRatio;
|
self.virtualGridToWorldRatio = 1.0 / self.worldToVirtualGridRatio;
|
||||||
|
self.meleeSkillConfig = {
|
||||||
|
1: {
|
||||||
|
// for offender
|
||||||
|
startupFrames: 23,
|
||||||
|
activeFrames: 3,
|
||||||
|
recoveryFrames: 61, // usually but not always "startupFrames+activeFrames", I hereby set it to be 1 frame more than the actual animation to avoid critical transition, i.e. when the animation is 1 frame from ending but "rdfPlayer.framesToRecover" is already counted 0 and the player triggers an other same attack, making an effective bullet trigger but no animation is played due to same animName is still playing
|
||||||
|
recoveryFramesOnBlock: 61,
|
||||||
|
recoveryFramesOnHit: 61,
|
||||||
|
moveforward: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
hitboxOffset: 12.0, // should be about the radius of the PlayerCollider
|
||||||
|
hitboxSize: {
|
||||||
|
x: 45.0,
|
||||||
|
y: 32.0,
|
||||||
|
},
|
||||||
|
|
||||||
|
// for defender
|
||||||
|
hitStunFrames: 18,
|
||||||
|
blockStunFrames: 9,
|
||||||
|
pushback: 11.0,
|
||||||
|
releaseTriggerType: 1, // 1: rising-edge, 2: falling-edge
|
||||||
|
damage: 5
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const tiledMapIns = self.node.getComponent(cc.TiledMap);
|
const tiledMapIns = self.node.getComponent(cc.TiledMap);
|
||||||
|
|
||||||
@@ -144,7 +132,7 @@ cc.Class({
|
|||||||
self.collisionSysMap.set(collisionBarrierIndex, newBarrier);
|
self.collisionSysMap.set(collisionBarrierIndex, newBarrier);
|
||||||
}
|
}
|
||||||
|
|
||||||
const startRdf = {
|
const startRdf = window.pb.protos.RoomDownsyncFrame.create({
|
||||||
id: window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START,
|
id: window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START,
|
||||||
players: {
|
players: {
|
||||||
10: {
|
10: {
|
||||||
@@ -152,23 +140,31 @@ cc.Class({
|
|||||||
joinIndex: 1,
|
joinIndex: 1,
|
||||||
virtualGridX: 0,
|
virtualGridX: 0,
|
||||||
virtualGridY: 0,
|
virtualGridY: 0,
|
||||||
speed: 2 * self.worldToVirtualGridRatio,
|
speed: 1 * self.worldToVirtualGridRatio,
|
||||||
dir: {
|
|
||||||
dx: 0,
|
|
||||||
dy: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
playerMetas: {
|
|
||||||
10: {
|
|
||||||
colliderRadius: 12,
|
colliderRadius: 12,
|
||||||
|
characterState: window.ATK_CHARACTER_STATE.Idle1[0],
|
||||||
|
framesToRecover: 0,
|
||||||
|
dirX: 0,
|
||||||
|
dirY: 0,
|
||||||
|
},
|
||||||
|
11: {
|
||||||
|
id: 11,
|
||||||
|
joinIndex: 2,
|
||||||
|
virtualGridX: 80 * self.worldToVirtualGridRatio,
|
||||||
|
virtualGridY: 40 * self.worldToVirtualGridRatio,
|
||||||
|
speed: 1 * self.worldToVirtualGridRatio,
|
||||||
|
colliderRadius: 12,
|
||||||
|
characterState: window.ATK_CHARACTER_STATE.Idle1[0],
|
||||||
|
framesToRecover: 0,
|
||||||
|
dirX: 0,
|
||||||
|
dirY: 0,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
self.selfPlayerInfo = {
|
self.selfPlayerInfo = {
|
||||||
id: 10
|
id: 10
|
||||||
};
|
};
|
||||||
self._initPlayerRichInfoDict(startRdf.players, startRdf.playerMetas);
|
self._initPlayerRichInfoDict(startRdf.players);
|
||||||
self.onRoomDownsyncFrame(startRdf);
|
self.onRoomDownsyncFrame(startRdf);
|
||||||
|
|
||||||
self.battleState = ALL_BATTLE_STATES.IN_BATTLE;
|
self.battleState = ALL_BATTLE_STATES.IN_BATTLE;
|
||||||
@@ -195,8 +191,8 @@ cc.Class({
|
|||||||
currSelfInput = prevAndCurrInputs[1];
|
currSelfInput = prevAndCurrInputs[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
const rdf = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.collisionSys, self.collisionSysMap, false);
|
const [prevRdf, rdf] = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.collisionSys, self.collisionSysMap, false);
|
||||||
self.applyRoomDownsyncFrameDynamics(rdf);
|
self.applyRoomDownsyncFrameDynamics(rdf, prevRdf);
|
||||||
let t3 = performance.now();
|
let t3 = performance.now();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error during Map.update", err);
|
console.error("Error during Map.update", err);
|
||||||
|
@@ -410,10 +410,10 @@ cc.Class({
|
|||||||
console.error("Unexpected encodedDirection = ", encodedDirection);
|
console.error("Unexpected encodedDirection = ", encodedDirection);
|
||||||
}
|
}
|
||||||
const btnALevel = ((encodedInput >> 4) & 1);
|
const btnALevel = ((encodedInput >> 4) & 1);
|
||||||
return {
|
return window.pb.protos.InputFrameDecoded.create({
|
||||||
dx: mappedDirection[0],
|
dx: mappedDirection[0],
|
||||||
dy: mappedDirection[1],
|
dy: mappedDirection[1],
|
||||||
a: btnALevel,
|
btnALevel: btnALevel,
|
||||||
};
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user