mirror of
https://github.com/genxium/DelayNoMore
synced 2025-10-17 20:46:35 +00:00
Compare commits
41 Commits
v1.0.2
...
v1.0.13-cc
Author | SHA1 | Date | |
---|---|---|---|
|
6713feded1 | ||
|
da1204dc63 | ||
|
ea14ced958 | ||
|
b9beee549f | ||
|
04d8013cbb | ||
|
6b503ec95d | ||
|
71f2a1ecdf | ||
|
de9f3c9090 | ||
|
96e355eab3 | ||
|
16e1d8a913 | ||
|
04b033be7e | ||
|
7fd96b335a | ||
|
8cd5f1d475 | ||
|
21806a3754 | ||
|
e213fdfb04 | ||
|
b9827f8430 | ||
|
91d16b1cc4 | ||
|
b19868920a | ||
|
be5200663c | ||
|
7b878ff947 | ||
|
c78c480f99 | ||
|
b50874f5c4 | ||
|
f1db2972fd | ||
|
16c27b0ce0 | ||
|
a44535cad2 | ||
|
8b5a96e825 | ||
|
618531f5c6 | ||
|
8345d55e76 | ||
|
a48e2f3cc0 | ||
|
83419a6f23 | ||
|
b19549b0a8 | ||
|
60866674b5 | ||
|
a4941c1273 | ||
|
2b304eaa75 | ||
|
c6b98855af | ||
|
4e0928cb1b | ||
|
fb42533f55 | ||
|
9dff989e02 | ||
|
5b7f35b874 | ||
|
2d179d0cdf | ||
|
f4b303eb91 |
@@ -10,6 +10,10 @@ This project is a demo for a websocket-based rollback netcode inspired by [GGPO]
|
||||
|
||||

|
||||
|
||||
**Since v1.0.13, smoothness in worst cases (e.g. turn-around on ground, in air and after dashing) is drastically improved due to update of prediction approach. The gifs and corresponding screenrecordings above are not updated because there's no big difference when network is good -- however, `input delay` is now set to `4 frames` -- while `input delay = 6 frames` was used in the screenrecordings -- and smoothness is even better now (well there's [a new screenrecording for PcWifi vs Android4g here](https://pan.baidu.com/s/1iNrQ2l_wqbWkURMIfyG88w?pwd=fe2f)).** Key changes are listed below.
|
||||
- [change#1](https://github.com/genxium/DelayNoMore/blob/ea14ced958415dbf4ec1bef89088a3c7607fd8b3/jsexport/battle/battle.go#L647)
|
||||
- [change#2](https://github.com/genxium/DelayNoMore/blob/ea14ced958415dbf4ec1bef89088a3c7607fd8b3/frontend/assets/scripts/Map.js#L1446)
|
||||
|
||||
As lots of feedbacks ask for a discussion on using UDP instead, I tried to summarize my personal opinion about it in [ConcerningEdgeCases](./ConcerningEdgeCases.md) -- **since v0.9.25, the project is actually equipped with UDP capabilities as follows**.
|
||||
- When using the so called `native apps` on `Android` and `Windows` (I'm working casually hard to support `iOS` next), the frontends will try to use UDP hole-punching w/ the help of backend as a registry. If UDP hole-punching is working, the rollback is often less than `turn-around frames to recover` and thus not noticeable, being much better than using websocket alone. This video shows how the UDP holepunched p2p performs for [Phone-Wifi v.s. PC-Wifi (viewed by PC side)](https://pan.baidu.com/s/1K6704bJKlrSBTVqGcXhajA?pwd=l7ok).
|
||||
- If UDP hole-punching is not working, e.g. for Symmetric NAT like in 4G/5G cellular network, the frontends will use backend as a UDP tunnel (or relay, whatever you like to call it). This video shows how the UDP tunnel performs for [Phone-4G v.s. PC-Wifi (merged view@v0.9.34, excellent synchronization)](https://pan.baidu.com/s/1yeIrN5TSf6_av_8-N3vdVg?pwd=7tzw).
|
||||
|
@@ -4,9 +4,24 @@ ROOT_DIR=.
|
||||
GOPROXY=https://goproxy.io
|
||||
all: help
|
||||
|
||||
# To install `gojson` executable
|
||||
# ```
|
||||
# go install github.com/ChimeraCoder/gojson/gojson@latest
|
||||
# ```
|
||||
#
|
||||
# OS detection reference https://stackoverflow.com/a/12099167
|
||||
gen-constants:
|
||||
gojson -pkg common -name constants -input common/constants.json -o common/constants_struct.go
|
||||
ifeq ($(OS),Windows_NT)
|
||||
sed -i 's/int64/int/g' common/constants_struct.go
|
||||
else
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
sed -i '' -e 's/int64/int/g' common/constants_struct.go
|
||||
else
|
||||
sed -i 's/int64/int/g' common/constants_struct.go
|
||||
endif
|
||||
endif
|
||||
|
||||
run-test: build
|
||||
ServerEnv=TEST ./$(PROJECTNAME)
|
||||
|
@@ -85,7 +85,6 @@ func (p *playerController) SMSCaptchaGet(c *gin.Context) {
|
||||
c.Set(api.RET, Constants.RetCode.SmsCaptchaRequestedTooFrequently)
|
||||
return
|
||||
}
|
||||
Logger.Info("A new SmsCaptcha record is needed for: ", zap.String("key", redisKey))
|
||||
pass := false
|
||||
var succRet int
|
||||
if Conf.General.ServerEnv == SERVER_ENV_TEST {
|
||||
@@ -93,17 +92,12 @@ func (p *playerController) SMSCaptchaGet(c *gin.Context) {
|
||||
if nil == err && nil != player {
|
||||
pass = true
|
||||
succRet = Constants.RetCode.IsTestAcc
|
||||
Logger.Info("A new SmsCaptcha record is needed for: ", zap.String("key", redisKey), zap.Any("player", player))
|
||||
}
|
||||
}
|
||||
|
||||
if !pass {
|
||||
player, err := models.GetPlayerByName(req.Num)
|
||||
if nil == err && nil != player {
|
||||
pass = true
|
||||
succRet = Constants.RetCode.IsBotAcc
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// Real phonenum is not supported yet!
|
||||
if !pass {
|
||||
if RE_PHONE_NUM.MatchString(req.Num) {
|
||||
succRet = Constants.RetCode.Ok
|
||||
@@ -119,6 +113,7 @@ func (p *playerController) SMSCaptchaGet(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
if !pass {
|
||||
c.Set(api.RET, Constants.RetCode.InvalidRequestParam)
|
||||
return
|
||||
@@ -481,16 +476,6 @@ func (p *playerController) maybeCreateNewPlayer(req smsCaptchaReq) (*models.Play
|
||||
Logger.Info("Got a test env player:", zap.Any("phonenum", req.Num), zap.Any("playerId", player.Id))
|
||||
return player, nil
|
||||
}
|
||||
} else {
|
||||
botPlayer, err := models.GetPlayerByName(req.Num)
|
||||
if err != nil {
|
||||
Logger.Error("Seeking bot player error:", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
if botPlayer != nil {
|
||||
Logger.Info("Got a bot player:", zap.Any("phonenum", req.Num), zap.Any("playerId", botPlayer.Id))
|
||||
return botPlayer, nil
|
||||
}
|
||||
}
|
||||
|
||||
bind, err := models.GetPlayerAuthBinding(Constants.AuthChannel.Sms, extAuthID)
|
||||
|
@@ -74,7 +74,7 @@ func Test_SMSCaptchaGet_illegalPhone(t *testing.T) {
|
||||
|
||||
func Test_SMSCaptchaGet_testAcc(t *testing.T) {
|
||||
player, err := getTestPlayer()
|
||||
if err == nil && player != nil {
|
||||
if nil == err && nil != player {
|
||||
resp := mustDoSmsCaptchaGetReq(fakeSMSCaptchReq(player.Name), t)
|
||||
if resp.Ret != Constants.RetCode.IsTestAcc {
|
||||
t.Fail()
|
||||
|
@@ -44,10 +44,10 @@
|
||||
"PASSWORD_RESET_CODE_GENERATION_PER_EMAIL_TOO_FREQUENTLY": 2020,
|
||||
"TRADE_CREATION_TOO_FREQUENTLY": 2021,
|
||||
"MAP_NOT_UNLOCKED": 2022,
|
||||
|
||||
"GET_SMS_CAPTCHA_RESP_ERROR_CODE": 2023,
|
||||
"SMS_CAPTCHA_REQUESTED_TOO_FREQUENTLY": 2024,
|
||||
"SMS_CAPTCHA_NOT_MATCH": 2025,
|
||||
"SAME_PLAYER_ALREADY_IN_SAME_ROOM": 2026,
|
||||
|
||||
"NOT_IMPLEMENTED_YET": 65535
|
||||
},
|
||||
|
@@ -17,6 +17,7 @@ type constants struct {
|
||||
RetCode struct {
|
||||
ActiveWatchdog int `json:"ACTIVE_WATCHDOG"`
|
||||
BattleStopped int `json:"BATTLE_STOPPED"`
|
||||
ClientMismatchedRenderFrame int `json:"CLIENT_MISMATCHED_RENDER_FRAME"`
|
||||
Duplicated int `json:"DUPLICATED"`
|
||||
FailedToCreate int `json:"FAILED_TO_CREATE"`
|
||||
FailedToDelete int `json:"FAILED_TO_DELETE"`
|
||||
@@ -51,6 +52,7 @@ type constants struct {
|
||||
PlayerNotAddableToRoom int `json:"PLAYER_NOT_ADDABLE_TO_ROOM"`
|
||||
PlayerNotFound int `json:"PLAYER_NOT_FOUND"`
|
||||
PlayerNotReaddableToRoom int `json:"PLAYER_NOT_READDABLE_TO_ROOM"`
|
||||
SamePlayerAlreadyInSameRoom int `json:"SAME_PLAYER_ALREADY_IN_SAME_ROOM"`
|
||||
SendEmailTimeout int `json:"SEND_EMAIL_TIMEOUT"`
|
||||
SmsCaptchaNotMatch int `json:"SMS_CAPTCHA_NOT_MATCH"`
|
||||
SmsCaptchaRequestedTooFrequently int `json:"SMS_CAPTCHA_REQUESTED_TOO_FREQUENTLY"`
|
||||
|
@@ -3,6 +3,7 @@ module battle_srv
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
dnmshared v0.0.0
|
||||
github.com/Masterminds/squirrel v0.0.0-20180815162352-8a7e65843414
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/gin-contrib/cors v0.0.0-20180514151808-6f0a820f94be
|
||||
@@ -19,14 +20,12 @@ require (
|
||||
github.com/thoas/go-funk v0.0.0-20180716193722-1060394a7713
|
||||
go.uber.org/zap v1.9.1
|
||||
google.golang.org/protobuf v1.28.1
|
||||
|
||||
dnmshared v0.0.0
|
||||
jsexport v0.0.0
|
||||
resolv v0.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/ChimeraCoder/gojson v1.0.0 // indirect
|
||||
github.com/ChimeraCoder/gojson v1.1.0 // indirect
|
||||
github.com/fatih/color v1.7.0 // indirect
|
||||
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 // indirect
|
||||
github.com/githubnemo/CompileDaemon v1.0.0 // indirect
|
||||
@@ -44,7 +43,7 @@ require (
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 // indirect
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
|
@@ -2,6 +2,8 @@ github.com/ByteArena/box2d v1.0.2 h1:f7f9KEQWhCs1n516DMLzi5w6u0MeeE78Mes4fWMcj9k
|
||||
github.com/ByteArena/box2d v1.0.2/go.mod h1:LzEuxY9iCz+tskfWCY3o0ywYBRafDDugdSj+/YGI6sE=
|
||||
github.com/ChimeraCoder/gojson v1.0.0 h1:gAYKGTV+xfQ4+l/4C/nazPbiQDUidG0G3ukAJnE7LNE=
|
||||
github.com/ChimeraCoder/gojson v1.0.0/go.mod h1:nYbTQlu6hv8PETM15J927yM0zGj3njIldp72UT1MqSw=
|
||||
github.com/ChimeraCoder/gojson v1.1.0 h1:/6S8djl/jColpJGTYniA3xrqJWuKeyEozzPtpr5L4Pw=
|
||||
github.com/ChimeraCoder/gojson v1.1.0/go.mod h1:nYbTQlu6hv8PETM15J927yM0zGj3njIldp72UT1MqSw=
|
||||
github.com/Masterminds/squirrel v0.0.0-20180815162352-8a7e65843414 h1:VHdYPA0V0YgL97gdjHevN6IVPRX+fOoNMqcYvUAzwNU=
|
||||
github.com/Masterminds/squirrel v0.0.0-20180815162352-8a7e65843414/go.mod h1:xnKTFzjGUiZtiOagBsfnvomW+nJg2usB1ZpordQWqNM=
|
||||
github.com/Pallinder/go-randomdata v0.0.0-20180616180521-15df0648130a h1:0OnS8GR4uI3nau+f/juCZlAq+zCrsHXRJlENrUQ4eU8=
|
||||
@@ -92,3 +94,5 @@ gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2G
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
|
@@ -25,9 +25,16 @@ import (
|
||||
"go.uber.org/zap"
|
||||
|
||||
"net"
|
||||
// _ "net/http/pprof"
|
||||
)
|
||||
|
||||
func main() {
|
||||
/*
|
||||
// Only used for profiling
|
||||
go func() {
|
||||
http.ListenAndServe("0.0.0.0:6060", nil)
|
||||
}()
|
||||
*/
|
||||
MustParseConfig()
|
||||
MustParseConstants()
|
||||
storage.Init()
|
||||
|
@@ -47,8 +47,7 @@ type Player struct {
|
||||
TutorialStage int `db:"tutorial_stage"`
|
||||
|
||||
// other in-battle info fields
|
||||
LastReceivedInputFrameId int32
|
||||
LastUdpReceivedInputFrameId int32
|
||||
LastConsecutiveRecvInputFrameId int32
|
||||
LastSentInputFrameId int32
|
||||
AckingFrameId int32
|
||||
AckingInputFrameId int32
|
||||
@@ -78,13 +77,14 @@ func getPlayer(cond sq.Eq) (*Player, error) {
|
||||
return nil, err
|
||||
}
|
||||
rows, err := storage.MySQLManagerIns.Queryx(query, args...)
|
||||
if err != nil {
|
||||
if nil != err {
|
||||
return nil, err
|
||||
}
|
||||
cols, err := rows.Columns()
|
||||
if nil != err {
|
||||
panic(err)
|
||||
}
|
||||
cnt := 0
|
||||
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)
|
||||
@@ -106,9 +106,14 @@ func getPlayer(cond sq.Eq) (*Player, error) {
|
||||
}
|
||||
}
|
||||
Logger.Debug("Queried player from db", zap.Any("cond", cond), zap.Any("p", p), zap.Any("pd", pd), zap.Any("cols", cols), zap.Any("rowValues", vals))
|
||||
cnt++
|
||||
}
|
||||
if 0 < cnt {
|
||||
p.PlayerDownsync = pd
|
||||
return &p, nil
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Player) Insert(tx *sqlx.Tx) error {
|
||||
|
@@ -135,9 +135,9 @@ type Room struct {
|
||||
CurDynamicsRenderFrameId int32 // [WARNING] The dynamics of backend is ALWAYS MOVING FORWARD BY ALL-CONFIRMED INPUTFRAMES (either by upsync or forced), i.e. no rollback; Moreover when "true == BackendDynamicsEnabled" we always have "Room.CurDynamicsRenderFrameId >= Room.RenderFrameId" because each "all-confirmed inputFrame" is applied on "all applicable renderFrames" in one-go hence often sees a future "renderFrame" earlier
|
||||
EffectivePlayerCount int32
|
||||
DismissalWaitGroup sync.WaitGroup
|
||||
InputsBuffer *battle.RingBuffer // Indices are STRICTLY consecutive
|
||||
InputsBufferLock sync.Mutex // Guards [InputsBuffer, LatestPlayerUpsyncedInputFrameId, LastAllConfirmedInputFrameId, LastAllConfirmedInputList, LastAllConfirmedInputFrameIdWithChange, LastIndividuallyConfirmedInputList, player.LastReceivedInputFrameId, player.LastUdpReceivedInputFrameId]
|
||||
RenderFrameBuffer *battle.RingBuffer // Indices are STRICTLY consecutive
|
||||
InputsBuffer *resolv.RingBuffer // Indices are STRICTLY consecutive
|
||||
InputsBufferLock sync.Mutex // Guards [InputsBuffer, LatestPlayerUpsyncedInputFrameId, LastAllConfirmedInputFrameId, LastAllConfirmedInputList, LastAllConfirmedInputFrameIdWithChange, LastIndividuallyConfirmedInputFrameId, LastIndividuallyConfirmedInputList, player.LastConsecutiveRecvInputFrameId]
|
||||
RenderFrameBuffer *resolv.RingBuffer // Indices are STRICTLY consecutive
|
||||
LatestPlayerUpsyncedInputFrameId int32
|
||||
LastAllConfirmedInputFrameId int32
|
||||
LastAllConfirmedInputFrameIdWithChange int32
|
||||
@@ -157,27 +157,34 @@ type Room struct {
|
||||
TmxPolygonsMap StrToPolygon2DListMap
|
||||
|
||||
rdfIdToActuallyUsedInput map[int32]*pb.InputFrameDownsync
|
||||
LastIndividuallyConfirmedInputFrameId []int32
|
||||
LastIndividuallyConfirmedInputList []uint64
|
||||
|
||||
BattleUdpTunnelLock sync.Mutex
|
||||
BattleUdpTunnelAddr *pb.PeerUdpAddr
|
||||
BattleUdpTunnel *net.UDPConn
|
||||
|
||||
collisionHolder *resolv.Collision
|
||||
effPushbacks []*battle.Vec2D
|
||||
hardPushbackNormsArr [][]*battle.Vec2D
|
||||
jumpedOrNotList []bool
|
||||
dynamicRectangleColliders []*resolv.Object
|
||||
}
|
||||
|
||||
func (pR *Room) updateScore() {
|
||||
pR.Score = calRoomScore(pR.EffectivePlayerCount, pR.Capacity, pR.State)
|
||||
}
|
||||
|
||||
func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, speciesId int, session *websocket.Conn, signalToCloseConnOfThisPlayer SignalToCloseConnCbType) bool {
|
||||
func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, speciesId int, session *websocket.Conn, signalToCloseConnOfThisPlayer SignalToCloseConnCbType) int {
|
||||
playerId := pPlayerFromDbInit.Id
|
||||
// TODO: Any thread-safety concern for accessing "pR" here?
|
||||
if RoomBattleStateIns.IDLE != pR.State && RoomBattleStateIns.WAITING != pR.State {
|
||||
Logger.Warn("AddPlayerIfPossible error, roomState:", zap.Any("playerId", playerId), zap.Any("roomId", pR.Id), zap.Any("roomState", pR.State), zap.Any("roomEffectivePlayerCount", pR.EffectivePlayerCount))
|
||||
return false
|
||||
return Constants.RetCode.PlayerNotAddableToRoom
|
||||
}
|
||||
if _, existent := pR.Players[playerId]; existent {
|
||||
Logger.Warn("AddPlayerIfPossible error, existing in the room.PlayersDict:", zap.Any("playerId", playerId), zap.Any("roomId", pR.Id), zap.Any("roomState", pR.State), zap.Any("roomEffectivePlayerCount", pR.EffectivePlayerCount))
|
||||
return false
|
||||
return Constants.RetCode.SamePlayerAlreadyInSameRoom
|
||||
}
|
||||
|
||||
defer pR.onPlayerAdded(playerId, speciesId)
|
||||
@@ -188,8 +195,7 @@ func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, speciesId int, se
|
||||
pPlayerFromDbInit.AckingFrameId = -1
|
||||
pPlayerFromDbInit.AckingInputFrameId = -1
|
||||
pPlayerFromDbInit.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED
|
||||
pPlayerFromDbInit.LastReceivedInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED
|
||||
pPlayerFromDbInit.LastUdpReceivedInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED
|
||||
pPlayerFromDbInit.LastConsecutiveRecvInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED
|
||||
pPlayerFromDbInit.BattleState = PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK
|
||||
|
||||
pPlayerFromDbInit.ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded
|
||||
@@ -204,19 +210,19 @@ func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, speciesId int, se
|
||||
})
|
||||
newWatchdog.Stop()
|
||||
pR.PlayerActiveWatchdogDict[playerId] = newWatchdog
|
||||
return true
|
||||
return Constants.RetCode.Ok
|
||||
}
|
||||
|
||||
func (pR *Room) ReAddPlayerIfPossible(pTmpPlayerInstance *Player, session *websocket.Conn, signalToCloseConnOfThisPlayer SignalToCloseConnCbType) bool {
|
||||
func (pR *Room) ReAddPlayerIfPossible(pTmpPlayerInstance *Player, session *websocket.Conn, signalToCloseConnOfThisPlayer SignalToCloseConnCbType) int {
|
||||
playerId := pTmpPlayerInstance.Id
|
||||
// TODO: Any thread-safety concern for accessing "pR" and "pEffectiveInRoomPlayerInstance" here?
|
||||
if RoomBattleStateIns.PREPARE != pR.State && RoomBattleStateIns.WAITING != pR.State && RoomBattleStateIns.IN_BATTLE != pR.State && RoomBattleStateIns.IN_SETTLEMENT != pR.State && RoomBattleStateIns.IN_DISMISSAL != pR.State {
|
||||
Logger.Warn("ReAddPlayerIfPossible error due to roomState:", zap.Any("playerId", playerId), zap.Any("roomId", pR.Id), zap.Any("roomState", pR.State), zap.Any("roomEffectivePlayerCount", pR.EffectivePlayerCount))
|
||||
return false
|
||||
return Constants.RetCode.PlayerNotReaddableToRoom
|
||||
}
|
||||
if _, existent := pR.Players[playerId]; !existent {
|
||||
Logger.Warn("ReAddPlayerIfPossible error due to player nonexistent for room:", zap.Any("playerId", playerId), zap.Any("roomId", pR.Id), zap.Any("roomState", pR.State), zap.Any("roomEffectivePlayerCount", pR.EffectivePlayerCount))
|
||||
return false
|
||||
return Constants.RetCode.PlayerNotReaddableToRoom
|
||||
}
|
||||
/*
|
||||
* WARNING: The "pTmpPlayerInstance *Player" used here is a temporarily constructed
|
||||
@@ -231,7 +237,7 @@ func (pR *Room) ReAddPlayerIfPossible(pTmpPlayerInstance *Player, session *webso
|
||||
pEffectiveInRoomPlayerInstance.AckingFrameId = -1
|
||||
pEffectiveInRoomPlayerInstance.AckingInputFrameId = -1
|
||||
pEffectiveInRoomPlayerInstance.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED
|
||||
// [WARNING] DON'T reset "player.LastReceivedInputFrameId" & "player.LastUdpReceivedInputFrameId" upon reconnection!
|
||||
// [WARNING] DON'T reset "player.LastConsecutiveRecvInputFrameId" & "pR.LastIndividuallyConfirmedInputFrameId[...]" upon reconnection!
|
||||
pEffectiveInRoomPlayerInstance.BattleState = PlayerBattleStateIns.READDED_PENDING_BATTLE_COLLIDER_ACK
|
||||
|
||||
pEffectiveInRoomPlayerInstance.ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded
|
||||
@@ -245,7 +251,7 @@ func (pR *Room) ReAddPlayerIfPossible(pTmpPlayerInstance *Player, session *webso
|
||||
}) // For ReAdded player the new watchdog starts immediately
|
||||
|
||||
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 Constants.RetCode.Ok
|
||||
}
|
||||
|
||||
func (pR *Room) ChooseStage() error {
|
||||
@@ -400,6 +406,9 @@ func (pR *Room) rdfIdToActuallyUsedInputString() string {
|
||||
}
|
||||
fireballsStrBldr := make([]string, 0, len(rdf.FireballBullets))
|
||||
for _, fireball := range rdf.FireballBullets {
|
||||
if 0 > fireball.BattleAttr.BulletLocalId {
|
||||
break
|
||||
}
|
||||
fireballsStrBldr = append(fireballsStrBldr, pR.fireballDownsyncStr(fireball))
|
||||
}
|
||||
s = append(s, fmt.Sprintf("rdfId:%d\nplayers:[%v]\nfireballs:[%v]\nactuallyUsedinputList:{%v}", rdfId, strings.Join(playersStrBldr, ","), strings.Join(fireballsStrBldr, ","), pR.inputFrameDownsyncStr(pR.rdfIdToActuallyUsedInput[rdfId])))
|
||||
@@ -420,11 +429,10 @@ func (pR *Room) StartBattle() {
|
||||
|
||||
// Initialize the "collisionSys" as well as "RenderFrameBuffer"
|
||||
pR.CurDynamicsRenderFrameId = 0
|
||||
kickoffFrameJs := &battle.RoomDownsyncFrame{
|
||||
Id: pR.RenderFrameId,
|
||||
PlayersArr: toJsPlayers(pR.Players),
|
||||
CountdownNanos: pR.BattleDurationNanos,
|
||||
}
|
||||
kickoffFrameJs := battle.NewPreallocatedRoomDownsyncFrame(len(pR.Players), 64, 64)
|
||||
battle.CloneRoomDownsyncFrame(pR.RenderFrameId, toJsPlayers(pR.Players), 0, make([]*battle.MeleeBullet, 0), make([]*battle.FireballBullet, 0), kickoffFrameJs)
|
||||
kickoffFrameJs.CountdownNanos = pR.BattleDurationNanos
|
||||
|
||||
pR.RenderFrameBuffer.Put(kickoffFrameJs)
|
||||
|
||||
// Refresh "Colliders"
|
||||
@@ -478,7 +486,7 @@ func (pR *Room) StartBattle() {
|
||||
*/
|
||||
totalElapsedNanos := utils.UnixtimeNano() - battleStartedAt
|
||||
nextRenderFrameId := int32((totalElapsedNanos + pR.dilutedRollbackEstimatedDtNanos - 1) / pR.dilutedRollbackEstimatedDtNanos) // fast ceiling
|
||||
toSleepNanos := int64(0)
|
||||
toSleepNanos := int64(pR.dilutedRollbackEstimatedDtNanos >> 1) // Sleep half-frame time by default
|
||||
if nextRenderFrameId > pR.RenderFrameId {
|
||||
if 0 == pR.RenderFrameId {
|
||||
// It's important to send kickoff frame iff "0 == pR.RenderFrameId && nextRenderFrameId > pR.RenderFrameId", otherwise it might send duplicate kickoff frames
|
||||
@@ -510,7 +518,7 @@ func (pR *Room) StartBattle() {
|
||||
pR.LastRenderFrameIdTriggeredAt = utils.UnixtimeNano()
|
||||
|
||||
elapsedInCalculation := (utils.UnixtimeNano() - stCalculation)
|
||||
toSleepNanos = pR.dilutedRollbackEstimatedDtNanos - elapsedInCalculation // don't sleep if "nextRenderFrame == pR.RenderFrameId"
|
||||
toSleepNanos = pR.dilutedRollbackEstimatedDtNanos - elapsedInCalculation
|
||||
if elapsedInCalculation > pR.RollbackEstimatedDtNanos {
|
||||
Logger.Warn(fmt.Sprintf("SLOW FRAME! Elapsed time statistics: roomId=%v, room.RenderFrameId=%v, elapsedInCalculation=%v ns, dynamicsDuration=%v ns, RollbackEstimatedDtNanos=%v, dilutedRollbackEstimatedDtNanos=%v", pR.Id, pR.RenderFrameId, elapsedInCalculation, dynamicsDuration, pR.RollbackEstimatedDtNanos, pR.dilutedRollbackEstimatedDtNanos))
|
||||
}
|
||||
@@ -539,13 +547,13 @@ func (pR *Room) StartBattle() {
|
||||
}
|
||||
|
||||
select {
|
||||
// [WARNING] DON'T put a "default" block here! Otherwise "for { select {... default: } }" pattern would NEVER block on empty channel and thus consume a lot of CPU time unnecessarily!
|
||||
case inputsBufferSnapshot := <-playerDownsyncChan:
|
||||
pR.downsyncToSinglePlayer(playerId, player, inputsBufferSnapshot.RefRenderFrameId, inputsBufferSnapshot.UnconfirmedMask, inputsBufferSnapshot.ToSendInputFrameDownsyncs, inputsBufferSnapshot.ShouldForceResync)
|
||||
//Logger.Info(fmt.Sprintf("Sent inputsBufferSnapshot(refRenderFrameId:%d, unconfirmedMask:%v) to for (roomId: %d, playerId:%d)#2", inputsBufferSnapshot.RefRenderFrameId, inputsBufferSnapshot.UnconfirmedMask, pR.Id, playerId))
|
||||
case inputsBufferSnapshot2 := <-playerSecondaryDownsyncChan:
|
||||
pR.downsyncPeerInputFrameUpsyncToSinglePlayer(playerId, player, inputsBufferSnapshot2.ToSendInputFrameDownsyncs, inputsBufferSnapshot2.PeerJoinIndex)
|
||||
//Logger.Info(fmt.Sprintf("Sent secondary inputsBufferSnapshot to for (roomId: %d, playerId:%d)#2", pR.Id, playerId))
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -772,7 +780,7 @@ func (pR *Room) OnDismissed() {
|
||||
|
||||
// Always instantiates new HeapRAM blocks and let the old blocks die out due to not being retained by any root reference.
|
||||
pR.BulletBattleLocalIdCounter = 0
|
||||
pR.CollisionMinStep = 8 // the approx minimum distance a player can move per frame in world coordinate
|
||||
pR.CollisionMinStep = 16 // the approx minimum distance a player can move per frame in world coordinate
|
||||
pR.Players = make(map[int32]*Player)
|
||||
pR.PlayersArr = make([]*Player, pR.Capacity)
|
||||
pR.SpeciesIdList = make([]int32, pR.Capacity)
|
||||
@@ -796,9 +804,13 @@ func (pR *Room) OnDismissed() {
|
||||
pR.PlayerSecondarySignalToCloseDict = make(map[int32]SignalToCloseConnCbType)
|
||||
pR.JoinIndexBooleanArr = make([]bool, pR.Capacity)
|
||||
pR.RenderCacheSize = 1024
|
||||
pR.RenderFrameBuffer = battle.NewRingBuffer(pR.RenderCacheSize)
|
||||
pR.InputsBuffer = battle.NewRingBuffer((pR.RenderCacheSize >> 1) + 1)
|
||||
pR.RenderFrameBuffer = resolv.NewRingBuffer(pR.RenderCacheSize)
|
||||
pR.InputsBuffer = resolv.NewRingBuffer((pR.RenderCacheSize >> 1) + 1)
|
||||
pR.rdfIdToActuallyUsedInput = make(map[int32]*pb.InputFrameDownsync)
|
||||
pR.LastIndividuallyConfirmedInputFrameId = make([]int32, pR.Capacity)
|
||||
for i := 0; i < pR.Capacity; i++ {
|
||||
pR.LastIndividuallyConfirmedInputFrameId[i] = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED
|
||||
}
|
||||
pR.LastIndividuallyConfirmedInputList = make([]uint64, pR.Capacity)
|
||||
|
||||
pR.LatestPlayerUpsyncedInputFrameId = -1
|
||||
@@ -810,6 +822,24 @@ func (pR *Room) OnDismissed() {
|
||||
pR.CurDynamicsRenderFrameId = 0
|
||||
pR.NstDelayFrames = 24
|
||||
|
||||
pR.collisionHolder = resolv.NewCollision()
|
||||
pR.effPushbacks = make([]*battle.Vec2D, pR.Capacity)
|
||||
for i := 0; i < pR.Capacity; i++ {
|
||||
pR.effPushbacks[i] = &battle.Vec2D{X: 0, Y: 0}
|
||||
}
|
||||
pR.hardPushbackNormsArr = make([][]*battle.Vec2D, pR.Capacity)
|
||||
for i := 0; i < pR.Capacity; i++ {
|
||||
pR.hardPushbackNormsArr[i] = make([]*battle.Vec2D, 5)
|
||||
for j := 0; j < len(pR.hardPushbackNormsArr[i]); j++ {
|
||||
pR.hardPushbackNormsArr[i][j] = &battle.Vec2D{X: 0, Y: 0}
|
||||
}
|
||||
}
|
||||
pR.jumpedOrNotList = make([]bool, pR.Capacity)
|
||||
pR.dynamicRectangleColliders = make([]*resolv.Object, 64)
|
||||
for i := 0; i < len(pR.dynamicRectangleColliders); i++ {
|
||||
pR.dynamicRectangleColliders[i] = battle.GenerateRectCollider(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, nil, "")
|
||||
}
|
||||
|
||||
serverFps := 60
|
||||
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 logging FAST FRAME
|
||||
@@ -1167,9 +1197,10 @@ func (pR *Room) markConfirmationIfApplicable(inputFrameUpsyncBatch []*pb.InputFr
|
||||
Logger.Debug(fmt.Sprintf("Omitting obsolete inputFrameUpsync#1: roomId=%v, playerId=%v, clientInputFrameId=%v, InputsBuffer=%v", pR.Id, playerId, clientInputFrameId, pR.InputsBufferString(false)))
|
||||
continue
|
||||
}
|
||||
if clientInputFrameId < player.LastReceivedInputFrameId {
|
||||
// [WARNING] It's important for correctness that we use "player.LastReceivedInputFrameId" instead of "player.LastUdpReceivedInputFrameId" here!
|
||||
Logger.Debug(fmt.Sprintf("Omitting obsolete inputFrameUpsync#2: roomId=%v, playerId=%v, clientInputFrameId=%v, playerLastReceivedInputFrameId=%v, InputsBuffer=%v", pR.Id, playerId, clientInputFrameId, player.LastReceivedInputFrameId, pR.InputsBufferString(false)))
|
||||
if clientInputFrameId < player.LastConsecutiveRecvInputFrameId {
|
||||
// [WARNING] It's important for correctness that we use "player.LastConsecutiveRecvInputFrameId" instead of "pR.LastIndividuallyConfirmedInputFrameId[player.JoinIndex-1]" here!
|
||||
//Logger.Debug(fmt.Sprintf("Omitting obsolete inputFrameUpsync#2: roomId=%v, playerId=%v, clientInputFrameId=%v, playerLastConsecutiveRecvInputFrameId=%v, InputsBuffer=%v", pR.Id, playerId, clientInputFrameId, player.LastConsecutiveRecvInputFrameId, pR.InputsBufferString(false)))
|
||||
Logger.Debug(fmt.Sprintf("Omitting obsolete inputFrameUpsync#2: roomId=%v, playerId=%v, clientInputFrameId=%v, playerLastConsecutiveRecvInputFrameId=%v", pR.Id, playerId, clientInputFrameId, player.LastConsecutiveRecvInputFrameId))
|
||||
continue
|
||||
}
|
||||
if clientInputFrameId > pR.InputsBuffer.EdFrameId {
|
||||
@@ -1185,19 +1216,19 @@ func (pR *Room) markConfirmationIfApplicable(inputFrameUpsyncBatch []*pb.InputFr
|
||||
/*
|
||||
[WARNING] We have to distinguish whether or not the incoming batch is from UDP here, otherwise "pR.LatestPlayerUpsyncedInputFrameId - pR.LastAllConfirmedInputFrameId" might become unexpectedly large in case of "UDP packet loss + slow ws session"!
|
||||
|
||||
Moreover, only ws session upsyncs should advance "player.LastReceivedInputFrameId" & "pR.LatestPlayerUpsyncedInputFrameId".
|
||||
Moreover, only ws session upsyncs should advance "player.LastConsecutiveRecvInputFrameId" & "pR.LatestPlayerUpsyncedInputFrameId".
|
||||
|
||||
Kindly note that the updates of "player.LastReceivedInputFrameId" could be discrete before and after reconnection.
|
||||
Kindly note that the updates of "player.LastConsecutiveRecvInputFrameId" could be discrete before and after reconnection.
|
||||
*/
|
||||
player.LastReceivedInputFrameId = clientInputFrameId
|
||||
player.LastConsecutiveRecvInputFrameId = clientInputFrameId
|
||||
if clientInputFrameId > pR.LatestPlayerUpsyncedInputFrameId {
|
||||
pR.LatestPlayerUpsyncedInputFrameId = clientInputFrameId
|
||||
}
|
||||
}
|
||||
|
||||
if clientInputFrameId > player.LastUdpReceivedInputFrameId {
|
||||
// No need to update "player.LastUdpReceivedInputFrameId" only when "true == fromUDP", we should keep "player.LastUdpReceivedInputFrameId >= player.LastReceivedInputFrameId" at any moment.
|
||||
player.LastUdpReceivedInputFrameId = clientInputFrameId
|
||||
if clientInputFrameId > pR.LastIndividuallyConfirmedInputFrameId[player.JoinIndex-1] {
|
||||
// No need to update "pR.LastIndividuallyConfirmedInputFrameId[player.JoinIndex-1]" only when "true == fromUDP", we should keep "pR.LastIndividuallyConfirmedInputFrameId[player.JoinIndex-1] >= player.LastConsecutiveRecvInputFrameId" at any moment.
|
||||
pR.LastIndividuallyConfirmedInputFrameId[player.JoinIndex-1] = clientInputFrameId
|
||||
// It's safe (in terms of getting an eventually correct "RenderFrameBuffer") to put the following update of "pR.LastIndividuallyConfirmedInputList" which is ONLY used for prediction in "InputsBuffer" out of "false == fromUDP" block.
|
||||
pR.LastIndividuallyConfirmedInputList[player.JoinIndex-1] = inputFrameUpsync.Encoded
|
||||
}
|
||||
@@ -1227,7 +1258,7 @@ func (pR *Room) markConfirmationIfApplicable(inputFrameUpsyncBatch []*pb.InputFr
|
||||
shouldBreakConfirmation = true // Could be an `ACTIVE SLOW TICKER` here, but no action needed for now
|
||||
break
|
||||
}
|
||||
Logger.Debug(fmt.Sprintf("markConfirmationIfApplicable for roomId=%v, skipping UNCONFIRMED BUT INACTIVE player(id:%v, joinIndex:%v) while checking inputFrameId=[%v, %v): InputsBuffer=%v", pR.Id, player.Id, player.JoinIndex, inputFrameId1, pR.InputsBuffer.EdFrameId, pR.InputsBufferString(false)))
|
||||
//Logger.Debug(fmt.Sprintf("markConfirmationIfApplicable for roomId=%v, skipping UNCONFIRMED BUT INACTIVE player(id:%v, joinIndex:%v) while checking inputFrameId=[%v, %v): InputsBuffer=%v", pR.Id, player.Id, player.JoinIndex, inputFrameId1, pR.InputsBuffer.EdFrameId, pR.InputsBufferString(false)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1281,7 +1312,7 @@ func (pR *Room) forceConfirmationIfApplicable(prevRenderFrameId int32) uint64 {
|
||||
pR.onInputFrameDownsyncAllConfirmed(inputFrameDownsync, -1)
|
||||
}
|
||||
if 0 < unconfirmedMask {
|
||||
Logger.Info(fmt.Sprintf("[type#1 forceConfirmation] For roomId=%d@renderFrameId=%d, curDynamicsRenderFrameId=%d, LatestPlayerUpsyncedInputFrameId:%d, oldLastAllConfirmedInputFrameId:%d, newLastAllConfirmedInputFrameId:%d, InputFrameUpsyncDelayTolerance:%d, unconfirmedMask=%d; there's a slow ticker suspect, forcing all-confirmation", pR.Id, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, pR.LatestPlayerUpsyncedInputFrameId, oldLastAllConfirmedInputFrameId, pR.LastAllConfirmedInputFrameId, pR.InputFrameUpsyncDelayTolerance, unconfirmedMask))
|
||||
Logger.Info(fmt.Sprintf("[type#1 forceConfirmation] For roomId=%d@renderFrameId=%d, curDynamicsRenderFrameId=%d, LatestPlayerUpsyncedInputFrameId:%d, LastAllConfirmedInputFrameId:%d -> %d, InputFrameUpsyncDelayTolerance:%d, unconfirmedMask=%d; there's a slow ticker suspect, forcing all-confirmation", pR.Id, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, pR.LatestPlayerUpsyncedInputFrameId, oldLastAllConfirmedInputFrameId, pR.LastAllConfirmedInputFrameId, pR.InputFrameUpsyncDelayTolerance, unconfirmedMask))
|
||||
}
|
||||
} else {
|
||||
// Type#2 helps resolve the edge case when all players are disconnected temporarily
|
||||
@@ -1356,8 +1387,7 @@ func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRende
|
||||
}
|
||||
}
|
||||
|
||||
nextRenderFrame := battle.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(pR.InputsBuffer, currRenderFrame, pR.Space, pR.CollisionSysMap, pR.SpaceOffsetX, pR.SpaceOffsetY, pR.CharacterConfigsArr)
|
||||
pR.RenderFrameBuffer.Put(nextRenderFrame)
|
||||
battle.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(pR.InputsBuffer, currRenderFrame.Id, pR.Space, pR.CollisionSysMap, pR.SpaceOffsetX, pR.SpaceOffsetY, pR.CharacterConfigsArr, pR.RenderFrameBuffer, pR.collisionHolder, pR.effPushbacks, pR.hardPushbackNormsArr, pR.jumpedOrNotList, pR.dynamicRectangleColliders, pR.LastIndividuallyConfirmedInputFrameId, pR.LastIndividuallyConfirmedInputList, false, MAGIC_JOIN_INDEX_INVALID) // DON'T mutate inputs upon dynamics on backend to avoid complicating the edge cases
|
||||
pR.CurDynamicsRenderFrameId++
|
||||
}
|
||||
}
|
||||
@@ -1508,6 +1538,7 @@ func (pR *Room) downsyncToAllPlayers(inputsBufferSnapshot *pb.InputsBufferSnapsh
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for _, player := range pR.PlayersArr {
|
||||
@@ -1524,11 +1555,16 @@ func (pR *Room) downsyncToAllPlayers(inputsBufferSnapshot *pb.InputsBufferSnapsh
|
||||
|
||||
if playerDownsyncChan, existent := pR.PlayerDownsyncChanDict[player.Id]; existent {
|
||||
playerDownsyncChan <- (*inputsBufferSnapshot)
|
||||
//Logger.Info(fmt.Sprintf("Sent inputsBufferSnapshot(refRenderFrameId:%d, unconfirmedMask:%v) to for (roomId: %d, playerId:%d, playerDownsyncChan:%p)#1", inputsBufferSnapshot.RefRenderFrameId, inputsBufferSnapshot.UnconfirmedMask, pR.Id, player.Id, playerDownsyncChan))
|
||||
//Logger.Info(fmt.Sprintf("Sent inputsBufferSnapshot{refRenderFrameId:%d, unconfirmedMask:%v} to {roomId: %d, playerId:%d, playerDownsyncChan:%p}#1", inputsBufferSnapshot.RefRenderFrameId, inputsBufferSnapshot.UnconfirmedMask, pR.Id, player.Id, playerDownsyncChan))
|
||||
} else {
|
||||
Logger.Warn(fmt.Sprintf("playerDownsyncChan for (roomId: %d, playerId:%d) is gone", pR.Id, player.Id))
|
||||
}
|
||||
}
|
||||
/*
|
||||
toSendInputFrameDownsyncs := inputsBufferSnapshot.ToSendInputFrameDownsyncs
|
||||
toSendInputFrameIdSt, toSendInputFrameIdEd := toSendInputFrameDownsyncs[0].InputFrameId, toSendInputFrameDownsyncs[len(toSendInputFrameDownsyncs)-1].InputFrameId+1
|
||||
Logger.Info(fmt.Sprintf("Sent inputsBufferSnapshot{refRenderFrameId:%d, unconfirmedMask:%v, inputFrameIdRange:[%d, %d)} to {roomId: %d}", inputsBufferSnapshot.RefRenderFrameId, inputsBufferSnapshot.UnconfirmedMask, toSendInputFrameIdSt, toSendInputFrameIdEd, pR.Id))
|
||||
*/
|
||||
}
|
||||
|
||||
func (pR *Room) downsyncToSinglePlayer(playerId int32, player *Player, refRenderFrameId int32, unconfirmedMask uint64, toSendInputFrameDownsyncsSnapshot []*pb.InputFrameDownsync, shouldForceResync bool) {
|
||||
@@ -1571,9 +1607,10 @@ func (pR *Room) downsyncToSinglePlayer(playerId int32, player *Player, refRender
|
||||
pbRefRenderFrame := toPbRoomDownsyncFrame(refRenderFrame)
|
||||
pbRefRenderFrame.SpeciesIdList = pR.SpeciesIdList
|
||||
pR.sendSafely(pbRefRenderFrame, toSendInputFrameDownsyncsSnapshot, DOWNSYNC_MSG_ACT_FORCED_RESYNC, playerId, false, MAGIC_JOIN_INDEX_DEFAULT)
|
||||
//Logger.Warn(fmt.Sprintf("Sent refRenderFrameId=%v & inputFrameIds [%d, %d), for roomId=%v, playerId=%d, playerJoinIndex=%d, renderFrameId=%d, curDynamicsRenderFrameId=%d, playerLastSentInputFrameId=%d: InputsBuffer=%v", refRenderFrameId, toSendInputFrameIdSt, toSendInputFrameIdEd, pR.Id, playerId, player.JoinIndex, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, player.LastSentInputFrameId, pR.InputsBufferString(false)))
|
||||
if shouldResync1 || shouldResync3 {
|
||||
Logger.Debug(fmt.Sprintf("Sent refRenderFrameId=%v & inputFrameIds [%d, %d), for roomId=%v, playerId=%d, playerJoinIndex=%d, renderFrameId=%d, curDynamicsRenderFrameId=%d, playerLastSentInputFrameId=%d: shouldResync1=%v, shouldResync2=%v, shouldResync3=%v, playerBattleState=%d", refRenderFrameId, toSendInputFrameIdSt, toSendInputFrameIdEd, pR.Id, playerId, player.JoinIndex, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, player.LastSentInputFrameId, shouldResync1, shouldResync2, shouldResync3, playerBattleState))
|
||||
} else {
|
||||
//Logger.Debug(fmt.Sprintf("Sent refRenderFrameId=%v & inputFrameIds [%d, %d), for roomId=%v, playerId=%d, playerJoinIndex=%d, renderFrameId=%d, curDynamicsRenderFrameId=%d, playerLastSentInputFrameId=%d: InputsBuffer=%v", refRenderFrameId, toSendInputFrameIdSt, toSendInputFrameIdEd, pR.Id, playerId, player.JoinIndex, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, player.LastSentInputFrameId, pR.InputsBufferString(false)))
|
||||
}
|
||||
} else {
|
||||
pR.sendSafely(nil, toSendInputFrameDownsyncsSnapshot, DOWNSYNC_MSG_ACT_INPUT_BATCH, playerId, false, MAGIC_JOIN_INDEX_DEFAULT)
|
||||
@@ -1771,7 +1808,7 @@ func (pR *Room) startBattleUdpTunnel() {
|
||||
}
|
||||
_, wrerr := conn.WriteTo(bytes, otherPlayer.BattleUdpTunnelAddr)
|
||||
if nil != wrerr {
|
||||
Logger.Warn(fmt.Sprintf("`BattleUdpTunnel` for roomId=%d failed to forward upsync from (playerId:%d, joinIndex:%d, addr:%s) to (otherPlayerId:%d, otherPlayerJoinIndex:%d, otherPlayerAddr:%s)\n", pR.Id, playerId, peerJoinIndex, remote, otherPlayer.Id, otherPlayer.JoinIndex, otherPlayer.BattleUdpTunnelAddr))
|
||||
//Logger.Debug(fmt.Sprintf("`BattleUdpTunnel` for roomId=%d failed to forward upsync from (playerId:%d, joinIndex:%d, addr:%s) to (otherPlayerId:%d, otherPlayerJoinIndex:%d, otherPlayerAddr:%s)\n", pR.Id, playerId, peerJoinIndex, remote, otherPlayer.Id, otherPlayer.JoinIndex, otherPlayer.BattleUdpTunnelAddr))
|
||||
}
|
||||
}
|
||||
pR.OnBattleCmdReceived(pReq, true) // To help advance "pR.LastAllConfirmedInputFrameId" asap, and even if "pR.LastAllConfirmedInputFrameId" is not advanced due to packet loss, these UDP packets would help prefill the "InputsBuffer" with correct player "future inputs (compared to ws session)" such that when "forceConfirmation" occurs we have as many correct predictions as possible
|
||||
|
@@ -188,34 +188,33 @@ func Serve(c *gin.Context) {
|
||||
}()
|
||||
Logger.Debug("Acquired RoomHeapMux for player:", zap.Any("playerId", playerId))
|
||||
// Logger.Info("The RoomHeapManagerIns has:", zap.Any("addr", fmt.Sprintf("%p", models.RoomHeapManagerIns)), zap.Any("size", len(*(models.RoomHeapManagerIns))))
|
||||
playerSuccessfullyAddedToRoom := false
|
||||
playerRoomRelation := Constants.RetCode.UnknownError
|
||||
if 0 < boundRoomId {
|
||||
if tmpPRoom, existent := (*models.RoomMapManagerIns)[int32(boundRoomId)]; existent {
|
||||
pRoom = tmpPRoom
|
||||
res := pRoom.ReAddPlayerIfPossible(pPlayer, conn, signalToCloseConnOfThisPlayer)
|
||||
if !res {
|
||||
playerRoomRelation = pRoom.ReAddPlayerIfPossible(pPlayer, conn, signalToCloseConnOfThisPlayer)
|
||||
if Constants.RetCode.Ok != playerRoomRelation {
|
||||
Logger.Warn("Failed to get:\n", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Any("forBoundRoomId", boundRoomId))
|
||||
} else {
|
||||
playerSuccessfullyAddedToRoom = true
|
||||
}
|
||||
}
|
||||
} else if 0 < expectedRoomId {
|
||||
if tmpRoom, existent := (*models.RoomMapManagerIns)[int32(expectedRoomId)]; existent {
|
||||
pRoom = tmpRoom
|
||||
|
||||
if pRoom.ReAddPlayerIfPossible(pPlayer, conn, signalToCloseConnOfThisPlayer) {
|
||||
playerSuccessfullyAddedToRoom = true
|
||||
} else if pRoom.AddPlayerIfPossible(pPlayer, speciesId, conn, signalToCloseConnOfThisPlayer) {
|
||||
playerSuccessfullyAddedToRoom = true
|
||||
} else {
|
||||
playerRoomRelation = pRoom.ReAddPlayerIfPossible(pPlayer, conn, signalToCloseConnOfThisPlayer)
|
||||
if Constants.RetCode.Ok != playerRoomRelation {
|
||||
playerRoomRelation = pRoom.AddPlayerIfPossible(pPlayer, speciesId, conn, signalToCloseConnOfThisPlayer)
|
||||
}
|
||||
if Constants.RetCode.Ok != playerRoomRelation {
|
||||
Logger.Warn("Failed to get:\n", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Any("forExpectedRoomId", expectedRoomId))
|
||||
playerSuccessfullyAddedToRoom = false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if false == playerSuccessfullyAddedToRoom {
|
||||
if Constants.RetCode.SamePlayerAlreadyInSameRoom == playerRoomRelation {
|
||||
signalToCloseConnOfThisPlayer(playerRoomRelation, fmt.Sprintf("playerId == %v is already in a room, this account is possibly stolen!", playerId))
|
||||
}
|
||||
|
||||
if Constants.RetCode.Ok != playerRoomRelation {
|
||||
defer func() {
|
||||
if pRoom != nil {
|
||||
heap.Push(models.RoomHeapManagerIns, pRoom)
|
||||
@@ -229,9 +228,9 @@ func Serve(c *gin.Context) {
|
||||
} else {
|
||||
pRoom = tmpRoom
|
||||
Logger.Info("Successfully popped:\n", zap.Any("roomId", pRoom.Id), zap.Any("forPlayerId", playerId))
|
||||
res := pRoom.AddPlayerIfPossible(pPlayer, speciesId, conn, signalToCloseConnOfThisPlayer)
|
||||
if !res {
|
||||
signalToCloseConnOfThisPlayer(Constants.RetCode.PlayerNotAddableToRoom, fmt.Sprintf("AddPlayerIfPossible returns false for roomId == %v, playerId == %v!", pRoom.Id, playerId))
|
||||
playerRoomRelation = pRoom.AddPlayerIfPossible(pPlayer, speciesId, conn, signalToCloseConnOfThisPlayer)
|
||||
if Constants.RetCode.Ok != playerRoomRelation {
|
||||
signalToCloseConnOfThisPlayer(playerRoomRelation, fmt.Sprintf("AddPlayerIfPossible returns false for roomId == %v, playerId == %v!", pRoom.Id, playerId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -12,8 +12,9 @@ func NormVec2D(dx, dy float64) Vec2D {
|
||||
}
|
||||
|
||||
func ConvexPolygonStr(body *resolv.ConvexPolygon) string {
|
||||
var s []string = make([]string, len(body.Points))
|
||||
for i, p := range body.Points {
|
||||
var s []string = make([]string, body.Points.Cnt)
|
||||
for i := int32(0); i < body.Points.Cnt; i++ {
|
||||
p := body.GetPointByOffset(i)
|
||||
s[i] = fmt.Sprintf("[%.2f, %.2f]", p[0]+body.X, p[1]+body.Y)
|
||||
}
|
||||
|
||||
|
@@ -109,6 +109,7 @@ var constants = {
|
||||
"GET_SMS_CAPTCHA_RESP_ERROR_CODE": 2023,
|
||||
"SMS_CAPTCHA_REQUESTED_TOO_FREQUENTLY": 2024,
|
||||
"SMS_CAPTCHA_NOT_MATCH": 2025,
|
||||
"SAME_PLAYER_ALREADY_IN_SAME_ROOM": 2026,
|
||||
|
||||
"NOT_IMPLEMENTED_YET": 65535
|
||||
},
|
||||
|
@@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.2" tiledversion="1.2.3" orientation="orthogonal" renderorder="right-down" width="128" height="64" tilewidth="16" tileheight="16" infinite="0" nextlayerid="7" nextobjectid="137">
|
||||
<map version="1.2" tiledversion="1.2.3" orientation="orthogonal" renderorder="right-down" width="128" height="64" tilewidth="16" tileheight="16" infinite="0" nextlayerid="9" nextobjectid="138">
|
||||
<tileset firstgid="1" source="tiles0.tsx"/>
|
||||
<tileset firstgid="65" source="tiles1.tsx"/>
|
||||
<tileset firstgid="129" source="tiles2.tsx"/>
|
||||
<layer id="6" name="Ground" width="128" height="64">
|
||||
<layer id="8" name="Ground" width="128" height="64">
|
||||
<data encoding="base64" compression="zlib">
|
||||
eJzt201uwjAQhuEIxCY7KugeqTepumHXC/T+xyCqYimy4sQ4E2bE9y6eDf+e1w4bOHZddwQAAAAAAAAAAACAF/jBP+8Onv29P4M35Rkor50ZaK+dGWivnRlor50ZtK39UmnL5/owegz94/X/on8Ykfsv9V27n/72a0/drPqfBucZeeMSjxm8m5b+W1m9Dv3fv//c9eG88f3p397fytr1vNR9j31A/1hq21vtg4gziNz/ZvQY+vubrr0fefdv3QOt+0O9/3cm3ZffntwqlZ4fTZqBdwuv/odROv9/g3vBwUDpjFq8dst7q5//vP/0O2B6m1V/T/Qv979nvedY9/jcwTPXHPov9+lXZldjrb/1fqL/c/2vo6W5bTn/S3tg7/Nfsz/pX+5v+Z1L/3i8f3cdhXcHz/6/4uivjf7a6K+N/tror43+2uivjf7a6K+N/tror43+2uivjf7a6K+N/tror43+2uivjf7a6K+N/trSf6C8W9Cf/vSn/6v7Y7/+Dyz1uAA=
|
||||
eJzt2j1uwkAQgFEESkNHlKRHyk2iNHRcIPc/RlCEJWSxtrHHmRX7itfw651v1zTsNpvNDgAAAAAAAOAffPMnu0Nm/+xryNbyDFpeuxm0vXYzaHvtZtD22s1g3trfJlpyXa9Br9G/vv6f+lej5v5Dfcee1z9+7V23qP4vF4c7+o1LMmbwbOb0Xyrqc/R//v737g+Hhd+v//z+Ucbu56Xua+wD/esytX3UPqhxBjX3Pwa9Rv98t2vfX2X3n7sH5u6P1vt/9XTP9R/vHCcqvb823QyyW2T131515//n4lSwDVA6oxGfPee7Wz///f63vwG3j0X1z6R/uf+p1/ue6B4fK3jknqP/cJ/9yOymGOsfvZ/0f6z/+9XQ3Jac/6E9sPb5n7I/9S/3j/zN1b8+2f+7rkV2h8z+58bp37Y1+x8qlz37WmR30F9//fXXX38AAAAAAAAAAAAg1y8jXMSV
|
||||
</data>
|
||||
</layer>
|
||||
<objectgroup id="1" name="PlayerStartingPos">
|
||||
@@ -84,12 +84,12 @@
|
||||
<property name="boundary_type" value="barrier"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="84" x="640" y="224" width="16" height="800">
|
||||
<object id="84" x="640" y="224" width="16" height="416">
|
||||
<properties>
|
||||
<property name="boundary_type" value="barrier"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="85" x="1680" y="224" width="16" height="800">
|
||||
<object id="85" x="1680" y="224" width="16" height="416">
|
||||
<properties>
|
||||
<property name="boundary_type" value="barrier"/>
|
||||
</properties>
|
||||
@@ -149,17 +149,12 @@
|
||||
<property name="boundary_type" value="barrier"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="113" x="640" y="1008" width="1056" height="16">
|
||||
<properties>
|
||||
<property name="boundary_type" value="barrier"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="114" x="640" y="224" width="1056" height="16">
|
||||
<properties>
|
||||
<property name="boundary_type" value="barrier"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="119" x="656" y="592" width="1024" height="416">
|
||||
<object id="119" x="656" y="592" width="512" height="48">
|
||||
<properties>
|
||||
<property name="boundary_type" value="barrier"/>
|
||||
</properties>
|
||||
@@ -169,5 +164,10 @@
|
||||
<property name="boundary_type" value="barrier"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="137" x="1168" y="592" width="512" height="48">
|
||||
<properties>
|
||||
<property name="boundary_type" value="barrier"/>
|
||||
</properties>
|
||||
</object>
|
||||
</objectgroup>
|
||||
</map>
|
||||
|
@@ -22,6 +22,9 @@
|
||||
},
|
||||
{
|
||||
"__id__": 5
|
||||
},
|
||||
{
|
||||
"__id__": 11
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
@@ -59,7 +62,7 @@
|
||||
"ctor": "Float64Array",
|
||||
"array": [
|
||||
480,
|
||||
320,
|
||||
480,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@@ -192,102 +195,28 @@
|
||||
"fileId": "ab6G+s0otA4rXhUsO3czRN",
|
||||
"sync": false
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "VerticalLayout",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 6
|
||||
},
|
||||
{
|
||||
"__id__": 12
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 53
|
||||
},
|
||||
{
|
||||
"__id__": 54
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 55
|
||||
},
|
||||
"_opacity": 255,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 255,
|
||||
"g": 255,
|
||||
"b": 255,
|
||||
"a": 255
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 960,
|
||||
"height": 265
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_trs": {
|
||||
"__type__": "TypedArray",
|
||||
"ctor": "Float64Array",
|
||||
"array": [
|
||||
0,
|
||||
128,
|
||||
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__": "cc.Node",
|
||||
"_name": "exitButton",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 5
|
||||
"__id__": 1
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 6
|
||||
},
|
||||
{
|
||||
"__id__": 7
|
||||
},
|
||||
{
|
||||
"__id__": 8
|
||||
},
|
||||
{
|
||||
"__id__": 10
|
||||
"__id__": 9
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 11
|
||||
"__id__": 10
|
||||
},
|
||||
"_opacity": 255,
|
||||
"_color": {
|
||||
@@ -312,7 +241,7 @@
|
||||
"ctor": "Float64Array",
|
||||
"array": [
|
||||
-379.577,
|
||||
100.5,
|
||||
140,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@@ -341,7 +270,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 6
|
||||
"__id__": 5
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -375,7 +304,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 6
|
||||
"__id__": 5
|
||||
},
|
||||
"_enabled": true,
|
||||
"_normalMaterial": null,
|
||||
@@ -384,7 +313,7 @@
|
||||
"zoomScale": 1.2,
|
||||
"clickEvents": [
|
||||
{
|
||||
"__id__": 9
|
||||
"__id__": 8
|
||||
}
|
||||
],
|
||||
"_N$interactable": true,
|
||||
@@ -440,7 +369,7 @@
|
||||
"hoverSprite": null,
|
||||
"_N$disabledSprite": null,
|
||||
"_N$target": {
|
||||
"__id__": 6
|
||||
"__id__": 5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
@@ -459,7 +388,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 6
|
||||
"__id__": 5
|
||||
},
|
||||
"_enabled": true,
|
||||
"alignMode": 1,
|
||||
@@ -489,15 +418,86 @@
|
||||
"asset": {
|
||||
"__uuid__": "dc804c5c-ff76-445e-ac69-52269055c3c5"
|
||||
},
|
||||
"fileId": "1cUg34ZtdK9JfdGIG+lpdF",
|
||||
"fileId": "3cdlb7LxhMzLDzxdQv8Z/x",
|
||||
"sync": false
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "VerticalLayout",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 12
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 53
|
||||
},
|
||||
{
|
||||
"__id__": 54
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 55
|
||||
},
|
||||
"_opacity": 255,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 255,
|
||||
"g": 255,
|
||||
"b": 255,
|
||||
"a": 255
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 960,
|
||||
"height": 137
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_trs": {
|
||||
"__type__": "TypedArray",
|
||||
"ctor": "Float64Array",
|
||||
"array": [
|
||||
0,
|
||||
-13.057,
|
||||
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__": "cc.Node",
|
||||
"_name": "HorizontalLayout",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 5
|
||||
"__id__": 11
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
@@ -545,7 +545,7 @@
|
||||
"ctor": "Float64Array",
|
||||
"array": [
|
||||
0,
|
||||
-64,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@@ -1953,7 +1953,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 5
|
||||
"__id__": 11
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -1983,13 +1983,13 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 5
|
||||
"__id__": 11
|
||||
},
|
||||
"_enabled": true,
|
||||
"_layoutSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 960,
|
||||
"height": 265
|
||||
"height": 137
|
||||
},
|
||||
"_resize": 1,
|
||||
"_N$layoutType": 2,
|
||||
@@ -2040,7 +2040,7 @@
|
||||
"__id__": 43
|
||||
},
|
||||
"exitBtnNode": {
|
||||
"__id__": 6
|
||||
"__id__": 5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
|
@@ -400,7 +400,7 @@
|
||||
"ctor": "Float64Array",
|
||||
"array": [
|
||||
0,
|
||||
0,
|
||||
32,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@@ -536,7 +536,7 @@
|
||||
"array": [
|
||||
0,
|
||||
0,
|
||||
216.65450766436658,
|
||||
210.43877906529718,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@@ -2129,7 +2129,7 @@
|
||||
"_left": 0,
|
||||
"_right": 0,
|
||||
"_top": 0,
|
||||
"_bottom": 640,
|
||||
"_bottom": 672,
|
||||
"_verticalCenter": 0,
|
||||
"_horizontalCenter": 0,
|
||||
"_isAbsLeft": true,
|
||||
|
@@ -461,7 +461,7 @@
|
||||
"array": [
|
||||
0,
|
||||
0,
|
||||
210.4441731196186,
|
||||
209.57814771583418,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
|
@@ -72,11 +72,14 @@
|
||||
"__id__": 3
|
||||
},
|
||||
{
|
||||
"__id__": 10
|
||||
"__id__": 9
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 50
|
||||
},
|
||||
{
|
||||
"__id__": 51
|
||||
},
|
||||
@@ -88,9 +91,6 @@
|
||||
},
|
||||
{
|
||||
"__id__": 54
|
||||
},
|
||||
{
|
||||
"__id__": 55
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -156,9 +156,6 @@
|
||||
},
|
||||
{
|
||||
"__id__": 5
|
||||
},
|
||||
{
|
||||
"__id__": 6
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -220,34 +217,6 @@
|
||||
"_tmxFile": null,
|
||||
"_id": "c8MqKDLJdKz7VhPwMjScDw"
|
||||
},
|
||||
{
|
||||
"__type__": "09e1b/tEy5K2qaPIpqHDbae",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 3
|
||||
},
|
||||
"_enabled": true,
|
||||
"BGMEffect": {
|
||||
"__uuid__": "64a79efa-97de-4cb5-b2a9-01500c60573a"
|
||||
},
|
||||
"crashedByTrapBullet": {
|
||||
"__uuid__": "1d604e42-8cee-466f-884d-e74cae21ce3b"
|
||||
},
|
||||
"highScoreTreasurePicked": {
|
||||
"__uuid__": "0164d22c-d965-461f-867e-b30e2d56cc5c"
|
||||
},
|
||||
"treasurePicked": {
|
||||
"__uuid__": "7704b97e-6367-420c-b7af-d0750a2bbb30"
|
||||
},
|
||||
"countDown10SecToEnd": {
|
||||
"__uuid__": "261d1d7d-a5cc-4cb7-a737-194427055fd4"
|
||||
},
|
||||
"mapNode": {
|
||||
"__id__": 3
|
||||
},
|
||||
"_id": "3crA1nz5xPSLAnCSLQIPOq"
|
||||
},
|
||||
{
|
||||
"__type__": "b3810kDSWtD14RhiYzulYVI",
|
||||
"_name": "",
|
||||
@@ -266,7 +235,7 @@
|
||||
"__uuid__": "d92d4831-cd65-4eb5-90bd-b77021aec35b"
|
||||
},
|
||||
"joystickInputControllerNode": {
|
||||
"__id__": 7
|
||||
"__id__": 6
|
||||
},
|
||||
"confirmLogoutPrefab": null,
|
||||
"simplePressToGoDialogPrefab": null,
|
||||
@@ -279,19 +248,19 @@
|
||||
"forceBigEndianFloatingNumDecoding": false,
|
||||
"renderFrameIdLagTolerance": 4,
|
||||
"inputFrameFrontLabel": {
|
||||
"__id__": 14
|
||||
"__id__": 13
|
||||
},
|
||||
"sendingQLabel": {
|
||||
"__id__": 16
|
||||
"__id__": 15
|
||||
},
|
||||
"inputFrameDownsyncQLabel": {
|
||||
"__id__": 18
|
||||
"__id__": 17
|
||||
},
|
||||
"peerInputFrameUpsyncQLabel": {
|
||||
"__id__": 20
|
||||
"__id__": 19
|
||||
},
|
||||
"rollbackFramesLabel": {
|
||||
"__id__": 22
|
||||
"__id__": 21
|
||||
},
|
||||
"skippedRenderFrameCntLabel": null,
|
||||
"_id": "e5xQdv12xLoIRr0b36Pie+"
|
||||
@@ -301,17 +270,17 @@
|
||||
"_name": "JoystickContainer",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 8
|
||||
"__id__": 7
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 45
|
||||
"__id__": 44
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 50
|
||||
"__id__": 49
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -367,26 +336,26 @@
|
||||
"_name": "Interactive",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 9
|
||||
"__id__": 8
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 7
|
||||
"__id__": 6
|
||||
},
|
||||
{
|
||||
"__id__": 34
|
||||
"__id__": 33
|
||||
},
|
||||
{
|
||||
"__id__": 36
|
||||
"__id__": 35
|
||||
},
|
||||
{
|
||||
"__id__": 40
|
||||
"__id__": 39
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 44
|
||||
"__id__": 43
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -413,7 +382,7 @@
|
||||
"ctor": "Float64Array",
|
||||
"array": [
|
||||
0,
|
||||
0,
|
||||
32,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@@ -442,20 +411,20 @@
|
||||
"_name": "WidgetsAboveAll",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 10
|
||||
"__id__": 9
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 12
|
||||
"__id__": 11
|
||||
},
|
||||
{
|
||||
"__id__": 8
|
||||
"__id__": 7
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 33
|
||||
"__id__": 32
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -515,13 +484,13 @@
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 9
|
||||
"__id__": 8
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 11
|
||||
"__id__": 10
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -577,7 +546,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 10
|
||||
"__id__": 9
|
||||
},
|
||||
"_enabled": true,
|
||||
"_cullingMask": 4294967295,
|
||||
@@ -613,35 +582,35 @@
|
||||
"_name": "DebugInfo",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 9
|
||||
"__id__": 8
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 13
|
||||
"__id__": 12
|
||||
},
|
||||
{
|
||||
"__id__": 15
|
||||
"__id__": 14
|
||||
},
|
||||
{
|
||||
"__id__": 17
|
||||
"__id__": 16
|
||||
},
|
||||
{
|
||||
"__id__": 19
|
||||
"__id__": 18
|
||||
},
|
||||
{
|
||||
"__id__": 21
|
||||
"__id__": 20
|
||||
},
|
||||
{
|
||||
"__id__": 23
|
||||
"__id__": 22
|
||||
},
|
||||
{
|
||||
"__id__": 25
|
||||
"__id__": 24
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 32
|
||||
"__id__": 31
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -697,13 +666,13 @@
|
||||
"_name": "inputFrameFront",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 12
|
||||
"__id__": 11
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 14
|
||||
"__id__": 13
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -759,7 +728,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 13
|
||||
"__id__": 12
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -789,13 +758,13 @@
|
||||
"_name": "sendingQ",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 12
|
||||
"__id__": 11
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 16
|
||||
"__id__": 15
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -851,7 +820,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 15
|
||||
"__id__": 14
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -881,13 +850,13 @@
|
||||
"_name": "inputFrameDownsyncQ",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 12
|
||||
"__id__": 11
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 18
|
||||
"__id__": 17
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -943,7 +912,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 17
|
||||
"__id__": 16
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -973,13 +942,13 @@
|
||||
"_name": "peerInputFrameUpsyncQ",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 12
|
||||
"__id__": 11
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 20
|
||||
"__id__": 19
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -1035,7 +1004,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 19
|
||||
"__id__": 18
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -1065,13 +1034,13 @@
|
||||
"_name": "rollbackFrames",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 12
|
||||
"__id__": 11
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 22
|
||||
"__id__": 21
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -1127,7 +1096,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 21
|
||||
"__id__": 20
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -1157,13 +1126,13 @@
|
||||
"_name": "skippedCnt",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 12
|
||||
"__id__": 11
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 24
|
||||
"__id__": 23
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -1219,7 +1188,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 23
|
||||
"__id__": 22
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -1251,23 +1220,23 @@
|
||||
"_name": "RoomIdIndicator",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 12
|
||||
"__id__": 11
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 26
|
||||
"__id__": 25
|
||||
},
|
||||
{
|
||||
"__id__": 28
|
||||
"__id__": 27
|
||||
}
|
||||
],
|
||||
"_active": false,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 30
|
||||
"__id__": 29
|
||||
},
|
||||
{
|
||||
"__id__": 31
|
||||
"__id__": 30
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -1323,13 +1292,13 @@
|
||||
"_name": "label",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 25
|
||||
"__id__": 24
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 27
|
||||
"__id__": 26
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -1385,7 +1354,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 26
|
||||
"__id__": 25
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [],
|
||||
@@ -1413,13 +1382,13 @@
|
||||
"_name": "BoundRoomIdLabel",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 25
|
||||
"__id__": 24
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 29
|
||||
"__id__": 28
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -1475,7 +1444,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 28
|
||||
"__id__": 27
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [],
|
||||
@@ -1503,7 +1472,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 25
|
||||
"__id__": 24
|
||||
},
|
||||
"_enabled": true,
|
||||
"_layoutSize": {
|
||||
@@ -1536,7 +1505,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 25
|
||||
"__id__": 24
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [],
|
||||
@@ -1566,7 +1535,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 12
|
||||
"__id__": 11
|
||||
},
|
||||
"_enabled": true,
|
||||
"_layoutSize": {
|
||||
@@ -1599,7 +1568,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 9
|
||||
"__id__": 8
|
||||
},
|
||||
"_enabled": true,
|
||||
"alignMode": 1,
|
||||
@@ -1626,13 +1595,13 @@
|
||||
"_name": "CountdownSeconds",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 8
|
||||
"__id__": 7
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 35
|
||||
"__id__": 34
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -1688,7 +1657,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 34
|
||||
"__id__": 33
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -1718,11 +1687,11 @@
|
||||
"_name": "BtnA",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 8
|
||||
"__id__": 7
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 37
|
||||
"__id__": 36
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
@@ -1780,16 +1749,16 @@
|
||||
"_name": "Background",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 36
|
||||
"__id__": 35
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 38
|
||||
"__id__": 37
|
||||
},
|
||||
{
|
||||
"__id__": 39
|
||||
"__id__": 38
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -1845,7 +1814,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 37
|
||||
"__id__": 36
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -1879,7 +1848,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 37
|
||||
"__id__": 36
|
||||
},
|
||||
"_enabled": true,
|
||||
"alignMode": 0,
|
||||
@@ -1906,11 +1875,11 @@
|
||||
"_name": "BtnB",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 8
|
||||
"__id__": 7
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 41
|
||||
"__id__": 40
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
@@ -1968,16 +1937,16 @@
|
||||
"_name": "Background",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 40
|
||||
"__id__": 39
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 42
|
||||
"__id__": 41
|
||||
},
|
||||
{
|
||||
"__id__": 43
|
||||
"__id__": 42
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -2033,7 +2002,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 41
|
||||
"__id__": 40
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -2067,7 +2036,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 41
|
||||
"__id__": 40
|
||||
},
|
||||
"_enabled": true,
|
||||
"alignMode": 0,
|
||||
@@ -2094,7 +2063,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 8
|
||||
"__id__": 7
|
||||
},
|
||||
"_enabled": true,
|
||||
"alignMode": 1,
|
||||
@@ -2121,20 +2090,20 @@
|
||||
"_name": "JoystickBG",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 7
|
||||
"__id__": 6
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 46
|
||||
"__id__": 45
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 48
|
||||
"__id__": 47
|
||||
},
|
||||
{
|
||||
"__id__": 49
|
||||
"__id__": 48
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -2190,13 +2159,13 @@
|
||||
"_name": "Joystick",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 45
|
||||
"__id__": 44
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 47
|
||||
"__id__": 46
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -2252,7 +2221,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 46
|
||||
"__id__": 45
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -2286,7 +2255,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 45
|
||||
"__id__": 44
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -2320,7 +2289,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 45
|
||||
"__id__": 44
|
||||
},
|
||||
"_enabled": true,
|
||||
"alignMode": 0,
|
||||
@@ -2347,7 +2316,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 7
|
||||
"__id__": 6
|
||||
},
|
||||
"_enabled": true,
|
||||
"alignMode": 0,
|
||||
@@ -2449,16 +2418,16 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"translationListenerNode": {
|
||||
"__id__": 7
|
||||
"__id__": 6
|
||||
},
|
||||
"zoomingListenerNode": {
|
||||
"__id__": 3
|
||||
},
|
||||
"stickhead": {
|
||||
"__id__": 46
|
||||
"__id__": 45
|
||||
},
|
||||
"base": {
|
||||
"__id__": 45
|
||||
"__id__": 44
|
||||
},
|
||||
"joyStickEps": 0.1,
|
||||
"magicLeanLowerBound": 0.414,
|
||||
@@ -2479,10 +2448,10 @@
|
||||
"linearMovingEps": 0.1,
|
||||
"scaleByEps": 0.0375,
|
||||
"btnA": {
|
||||
"__id__": 36
|
||||
"__id__": 35
|
||||
},
|
||||
"btnB": {
|
||||
"__id__": 40
|
||||
"__id__": 39
|
||||
},
|
||||
"_id": "e9oVYTr7ROlpp/IrNjBUmR"
|
||||
}
|
||||
|
@@ -64,9 +64,6 @@ cc.Class({
|
||||
|
||||
ctor() {
|
||||
this.speciesName = null;
|
||||
this.hp = 100;
|
||||
this.maxHp = 100;
|
||||
this.inAir = true;
|
||||
},
|
||||
|
||||
setSpecies(speciesName) {
|
||||
@@ -86,17 +83,17 @@ cc.Class({
|
||||
updateCharacterAnim(rdfPlayer, prevRdfPlayer, forceAnimSwitch, chConfig) {
|
||||
// As this function might be called after many frames of a rollback, it's possible that the playing animation was predicted, different from "prevRdfPlayer.CharacterState" but same as "newCharacterState". More granular checks are needed to determine whether we should interrupt the playing animation.
|
||||
|
||||
let newCharacterState = rdfPlayer.CharacterState;
|
||||
let newCharacterState = rdfPlayer.GetCharacterState();
|
||||
|
||||
// Update directions
|
||||
if (this.animComp && this.animComp.node) {
|
||||
if (0 > rdfPlayer.DirX) {
|
||||
if (0 > rdfPlayer.GetDirX()) {
|
||||
this.animNode.scaleX = (-1.0);
|
||||
} else if (0 < rdfPlayer.DirX) {
|
||||
} else if (0 < rdfPlayer.GetDirX()) {
|
||||
this.animNode.scaleX = (+1.0);
|
||||
}
|
||||
if (ATK_CHARACTER_STATE.OnWall[0] == newCharacterState || ATK_CHARACTER_STATE.TurnAround1[0] == newCharacterState) {
|
||||
if (0 < rdfPlayer.OnWallNormX) {
|
||||
if (0 < rdfPlayer.GetOnWallNormX()) {
|
||||
this.animNode.scaleX = (-1.0);
|
||||
} else {
|
||||
this.animNode.scaleX = (+1.0);
|
||||
@@ -116,7 +113,6 @@ cc.Class({
|
||||
playingAnimName = (!underlyingAnimationCtrl ? null : underlyingAnimationCtrl.name);
|
||||
//}
|
||||
|
||||
// It turns out that "prevRdfPlayer.CharacterState" is not useful in this function :)
|
||||
if (newAnimName == playingAnimName && window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) {
|
||||
// No need to interrupt
|
||||
// console.warn(`JoinIndex=${rdfPlayer.joinIndex}, not interrupting ${newAnimName} while the playing anim is also ${playingAnimName}, player rdf changed from: ${null == prevRdfPlayer ? null : JSON.stringify(prevRdfPlayer)}, to: ${JSON.stringify(rdfPlayer)}`);
|
||||
@@ -151,7 +147,7 @@ cc.Class({
|
||||
}
|
||||
// The "playTimes" counterpart is managed by each "cc.AnimationClip.wrapMode", already preset in the editor.
|
||||
const targetClip = this.animComp.getClips()[newCharacterState]; // The clips follow the exact order in ATK_CHARACTER_STATE
|
||||
let frameIdxInAnim = rdfPlayer.FramesInChState;
|
||||
let frameIdxInAnim = rdfPlayer.GetFramesInChState();
|
||||
if (window.ATK_CHARACTER_STATE.InAirIdle1ByJump == newCharacterState && null != chConfig) {
|
||||
frameIdxInAnim = chConfig.InAirIdleFrameIdxTurningPoint + (frameIdxInAnim - chConfig.InAirIdleFrameIdxTurningPoint) % chConfig.InAirIdleFrameIdxTurnedCycle; // TODO: Anyway to avoid using division here?
|
||||
}
|
||||
|
@@ -25,7 +25,7 @@ cc.Class({
|
||||
if (!self.mapScriptIns) return;
|
||||
if (!self.mapScriptIns.selfPlayerInfo) return;
|
||||
if (!self.mapScriptIns.playerRichInfoDict) return;
|
||||
const selfPlayerRichInfo = self.mapScriptIns.playerRichInfoDict.get(self.mapScriptIns.selfPlayerInfo.Id);
|
||||
const selfPlayerRichInfo = self.mapScriptIns.playerRichInfoDict.get(self.mapScriptIns.selfPlayerInfo.id);
|
||||
if (!selfPlayerRichInfo) return;
|
||||
const selfPlayerNode = selfPlayerRichInfo.node;
|
||||
if (!selfPlayerNode) return;
|
||||
|
@@ -13,9 +13,9 @@ cc.Class({
|
||||
if (speciesName == this.speciesName) return;
|
||||
if (null != this.speciesName) {
|
||||
for (let k in this.animNode.children) {
|
||||
const child = this.children[k];
|
||||
const child = this.animNode.children[k];
|
||||
if (!child.active) continue;
|
||||
if (child == effAnimNode || child.name == speciesName) continue;
|
||||
if (child == this.effAnimNode || child.name == speciesName) continue;
|
||||
child.active = false;
|
||||
}
|
||||
}
|
||||
|
@@ -337,6 +337,7 @@ cc.Class({
|
||||
const date = Number(res.expiresAt);
|
||||
const selfPlayer = {
|
||||
expiresAt: date,
|
||||
id: res.playerId,
|
||||
playerId: res.playerId,
|
||||
intAuthToken: res.intAuthToken,
|
||||
avatar: res.avatar,
|
||||
|
@@ -44,7 +44,7 @@ window.onUdpMessage = (args) => {
|
||||
if (req.act && window.UPSYNC_MSG_ACT_PLAYER_CMD == req.act) {
|
||||
let effCnt = 0;
|
||||
const peerJoinIndex = req.joinIndex;
|
||||
if (peerJoinIndex == self.selfPlayerInfo.JoinIndex) return;
|
||||
if (peerJoinIndex == self.selfPlayerInfo.joinIndex) return;
|
||||
const batch = req.inputFrameUpsyncBatch;
|
||||
self.onPeerInputFrameUpsync(peerJoinIndex, batch, true);
|
||||
}
|
||||
@@ -156,11 +156,11 @@ cc.Class({
|
||||
|
||||
let previousSelfInput = null,
|
||||
currSelfInput = null;
|
||||
const joinIndex = self.selfPlayerInfo.JoinIndex;
|
||||
const joinIndex = self.selfPlayerInfo.joinIndex;
|
||||
const selfJoinIndexMask = (1 << (joinIndex - 1));
|
||||
const existingInputFrame = self.recentInputCache.GetByFrameId(inputFrameId);
|
||||
const previousInputFrameDownsync = self.recentInputCache.GetByFrameId(inputFrameId - 1);
|
||||
previousSelfInput = (null == previousInputFrameDownsync ? null : previousInputFrameDownsync.InputList[joinIndex - 1]);
|
||||
const existingInputFrame = gopkgs.GetInputFrameDownsync(self.recentInputCache, inputFrameId);
|
||||
const previousInputFrameDownsync = gopkgs.GetInputFrameDownsync(self.recentInputCache, (inputFrameId - 1));
|
||||
previousSelfInput = (null == previousInputFrameDownsync ? null : gopkgs.GetInput(previousInputFrameDownsync, joinIndex - 1));
|
||||
if (
|
||||
null != existingInputFrame
|
||||
&&
|
||||
@@ -168,7 +168,7 @@ cc.Class({
|
||||
) {
|
||||
// This could happen upon either [type#1] or [type#2] forceConfirmation, where "refRenderFrame" is accompanied by some "inputFrameDownsyncs". The check here also guarantees that we don't override history
|
||||
//console.log(`noDelayInputFrameId=${inputFrameId} already exists in recentInputCache: recentInputCache=${self._stringifyRecentInputCache(false)}`);
|
||||
return [previousSelfInput, existingInputFrame.InputList[joinIndex - 1]];
|
||||
return [previousSelfInput, gopkgs.GetInput(existingInputFrame, joinIndex - 1)];
|
||||
}
|
||||
|
||||
const prefabbedInputList = new Array(self.playerRichInfoDict.size).fill(0);
|
||||
@@ -176,31 +176,44 @@ cc.Class({
|
||||
for (let k = 0; k < window.boundRoomCapacity; ++k) {
|
||||
if (null != existingInputFrame) {
|
||||
// When "null != existingInputFrame", it implies that "true == canConfirmSelf" here, we just have to assign "prefabbedInputList[(joinIndex-1)]" specifically and copy all others
|
||||
prefabbedInputList[k] = existingInputFrame.InputList[k];
|
||||
prefabbedInputList[k] = gopkgs.GetInput(existingInputFrame, k);
|
||||
} else if (self.lastIndividuallyConfirmedInputFrameId[k] <= inputFrameId) {
|
||||
prefabbedInputList[k] = self.lastIndividuallyConfirmedInputList[k];
|
||||
// Don't predict "btnA & btnB"!
|
||||
prefabbedInputList[k] = (prefabbedInputList[k] & 15);
|
||||
} else if (null != previousInputFrameDownsync) {
|
||||
// When "self.lastIndividuallyConfirmedInputFrameId[k] > inputFrameId", don't use it to predict a historical input!
|
||||
prefabbedInputList[k] = previousInputFrameDownsync.InputList[k];
|
||||
prefabbedInputList[k] = gopkgs.GetInput(previousInputFrameDownsync, k);
|
||||
// Don't predict "btnA & btnB"!
|
||||
prefabbedInputList[k] = (prefabbedInputList[k] & 15);
|
||||
}
|
||||
}
|
||||
|
||||
// [WARNING] Do not blindly use "selfJoinIndexMask" here, as the "actuallyUsedInput for self" couldn't be confirmed while prefabbing, otherwise we'd have confirmed a wrong self input by "_markConfirmationIfApplicable()"!
|
||||
let initConfirmedList = 0;
|
||||
if (null != existingInputFrame) {
|
||||
// When "null != existingInputFrame", it implies that "true == canConfirmSelf" here
|
||||
initConfirmedList = (existingInputFrame.ConfirmedList | selfJoinIndexMask);
|
||||
initConfirmedList = (existingInputFrame.GetConfirmedList() | selfJoinIndexMask);
|
||||
}
|
||||
currSelfInput = self.ctrl.getEncodedInput(); // When "null == existingInputFrame", it'd be safe to say that the realtime "self.ctrl.getEncodedInput()" is for the requested "inputFrameId"
|
||||
prefabbedInputList[(joinIndex - 1)] = currSelfInput;
|
||||
while (self.recentInputCache.EdFrameId <= inputFrameId) {
|
||||
while (self.recentInputCache.GetEdFrameId() <= inputFrameId) {
|
||||
// Fill the gap
|
||||
// [WARNING] Do not blindly use "selfJoinIndexMask" here, as the "actuallyUsedInput for self" couldn't be confirmed while prefabbing, otherwise we'd have confirmed a wrong self input by "_markConfirmationIfApplicable()"!
|
||||
const prefabbedInputFrameDownsync = gopkgs.NewInputFrameDownsync(self.recentInputCache.EdFrameId, prefabbedInputList.slice(), initConfirmedList);
|
||||
// console.log(`Prefabbed inputFrameId=${prefabbedInputFrameDownsync.InputFrameId}`);
|
||||
self.recentInputCache.Put(prefabbedInputFrameDownsync);
|
||||
const gapInputFrameId = self.recentInputCache.GetEdFrameId();
|
||||
self.recentInputCache.DryPut();
|
||||
let ifdHolder = gopkgs.GetInputFrameDownsync(self.recentInputCache, gapInputFrameId);
|
||||
if (null == ifdHolder) {
|
||||
// Lazy heap alloc, calling "gopkgs.NewInputFrameDownsync" would trigger not only heap alloc but also "gopherjs $externalize", neither is efficient T_T
|
||||
const prefabbedInputFrameDownsync = gopkgs.NewInputFrameDownsync(gapInputFrameId, prefabbedInputList, initConfirmedList);
|
||||
// console.log(`Prefabbed inputFrameId=${prefabbedInputFrameDownsync.GetInputFrameId()}`);
|
||||
self.recentInputCache.SetByFrameId(prefabbedInputFrameDownsync, gapInputFrameId);
|
||||
} else {
|
||||
gopkgs.SetInputFrameId(ifdHolder, gapInputFrameId);
|
||||
for (let k = 0; k < window.boundRoomCapacity; ++k) {
|
||||
gopkgs.SetInput(ifdHolder, k, prefabbedInputList[k]);
|
||||
}
|
||||
gopkgs.SetConfirmedList(ifdHolder, initConfirmedList);
|
||||
}
|
||||
}
|
||||
|
||||
return [previousSelfInput, currSelfInput];
|
||||
@@ -223,9 +236,9 @@ cc.Class({
|
||||
const self = this;
|
||||
let inputFrameUpsyncBatch = [];
|
||||
let batchInputFrameIdSt = self.lastUpsyncInputFrameId + 1;
|
||||
if (batchInputFrameIdSt < self.recentInputCache.StFrameId) {
|
||||
if (batchInputFrameIdSt < self.recentInputCache.GetStFrameId()) {
|
||||
// Upon resync, "self.lastUpsyncInputFrameId" might not have been updated properly.
|
||||
batchInputFrameIdSt = self.recentInputCache.StFrameId;
|
||||
batchInputFrameIdSt = self.recentInputCache.GetStFrameId();
|
||||
}
|
||||
self.networkDoctor.logSending(batchInputFrameIdSt, latestLocalInputFrameId);
|
||||
for (let i = batchInputFrameIdSt; i <= latestLocalInputFrameId; ++i) {
|
||||
@@ -235,7 +248,7 @@ cc.Class({
|
||||
} else {
|
||||
const inputFrameUpsync = {
|
||||
inputFrameId: i,
|
||||
encoded: inputFrameDownsync.InputList[self.selfPlayerInfo.JoinIndex - 1],
|
||||
encoded: inputFrameDownsync.InputList[self.selfPlayerInfo.joinIndex - 1],
|
||||
};
|
||||
inputFrameUpsyncBatch.push(inputFrameUpsync);
|
||||
}
|
||||
@@ -244,19 +257,19 @@ cc.Class({
|
||||
// console.info(`inputFrameUpsyncBatch: ${JSON.stringify(inputFrameUpsyncBatch)}`);
|
||||
const reqData = window.pb.protos.WsReq.encode({
|
||||
msgId: Date.now(),
|
||||
playerId: self.selfPlayerInfo.Id,
|
||||
playerId: self.selfPlayerInfo.id,
|
||||
act: window.UPSYNC_MSG_ACT_PLAYER_CMD,
|
||||
joinIndex: self.selfPlayerInfo.JoinIndex,
|
||||
joinIndex: self.selfPlayerInfo.joinIndex,
|
||||
ackingInputFrameId: self.lastAllConfirmedInputFrameId,
|
||||
inputFrameUpsyncBatch: inputFrameUpsyncBatch,
|
||||
authKey: self.selfPlayerInfo.udpTunnelAuthKey,
|
||||
}).finish();
|
||||
if (cc.sys.isNative) {
|
||||
DelayNoMore.UdpSession.broadcastInputFrameUpsync(reqData, window.boundRoomCapacity, self.selfPlayerInfo.JoinIndex);
|
||||
DelayNoMore.UdpSession.broadcastInputFrameUpsync(reqData, window.boundRoomCapacity, self.selfPlayerInfo.joinIndex);
|
||||
}
|
||||
window.sendSafely(reqData);
|
||||
self.lastUpsyncInputFrameId = latestLocalInputFrameId;
|
||||
if (self.lastUpsyncInputFrameId >= self.recentInputCache.EdFrameId) {
|
||||
if (self.lastUpsyncInputFrameId >= self.recentInputCache.GetEdFrameId()) {
|
||||
throw `noDelayInputFrameId=${self.lastUpsyncInputFrameId} == latestLocalInputFrameId=${latestLocalInputFrameId} seems not properly dumped #2: recentInputCache=${self._stringifyRecentInputCache(false)}`;
|
||||
}
|
||||
},
|
||||
@@ -280,7 +293,7 @@ cc.Class({
|
||||
}
|
||||
},
|
||||
|
||||
popupSimplePressToGo(labelString, hideYesButton) {
|
||||
popupSimplePressToGo(labelString, hideYesButton, additionalOnDismissalCb) {
|
||||
const self = this;
|
||||
self.state = ALL_MAP_STATES.SHOWING_MODAL_POPUP;
|
||||
|
||||
@@ -293,6 +306,9 @@ cc.Class({
|
||||
const postDismissalByYes = () => {
|
||||
self.transitToState(ALL_MAP_STATES.VISUAL);
|
||||
canvasNode.removeChild(simplePressToGoDialogNode);
|
||||
if (additionalOnDismissalCb) {
|
||||
additionalOnDismissalCb();
|
||||
}
|
||||
}
|
||||
simplePressToGoDialogNode.getChildByName("Hint").getComponent(cc.Label).string = labelString;
|
||||
yesButton.once("click", simplePressToGoDialogScriptIns.dismissDialog.bind(simplePressToGoDialogScriptIns, postDismissalByYes));
|
||||
@@ -365,7 +381,24 @@ cc.Class({
|
||||
|
||||
self.lastIndividuallyConfirmedInputFrameId = new Array(window.boundRoomCapacity).fill(-1);
|
||||
self.lastIndividuallyConfirmedInputList = new Array(window.boundRoomCapacity).fill(0);
|
||||
self.recentRenderCache = new RingBuffer(self.renderCacheSize);
|
||||
|
||||
self.collisionHolder = gopkgs.NewCollisionHolder();
|
||||
// [WARNING] For "effPushbacks", "hardPushbackNormsArr" and "jumpedOrNotList", use array literal instead of "new Array" for compliance when passing into "gopkgs.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs"!
|
||||
self.effPushbacks = [];
|
||||
for (let i = 0; i < window.boundRoomCapacity; i++) self.effPushbacks.push(gopkgs.NewVec2DJs(0, 0));
|
||||
self.hardPushbackNormsArr = [];
|
||||
for (let i = 0; i < window.boundRoomCapacity; i++) {
|
||||
const single = [];
|
||||
for (let j = 0; j < 5; j++) {
|
||||
single.push(gopkgs.NewVec2DJs(0, 0));
|
||||
}
|
||||
self.hardPushbackNormsArr.push(single);
|
||||
}
|
||||
self.jumpedOrNotList = [];
|
||||
for (let i = 0; i < window.boundRoomCapacity; i++) self.jumpedOrNotList.push(false);
|
||||
self.dynamicRectangleColliders = gopkgs.NewDynamicRectangleColliders(64);
|
||||
|
||||
self.recentRenderCache = gopkgs.NewRingBufferJs(self.renderCacheSize);
|
||||
|
||||
self.recentInputCache = gopkgs.NewRingBufferJs((self.renderCacheSize >> 1) + 1);
|
||||
|
||||
@@ -408,9 +441,9 @@ cc.Class({
|
||||
for (let k in collisionSpaceObjs) {
|
||||
const body = collisionSpaceObjs[k];
|
||||
let padding = 0;
|
||||
if (null != body.Data && null != body.Data.JoinIndex) {
|
||||
if (null != body.GetData() && null != body.GetData().GetJoinIndex) {
|
||||
// character
|
||||
if (1 == body.Data.JoinIndex) {
|
||||
if (1 == body.GetData().GetJoinIndex()) {
|
||||
g1.strokeColor = cc.Color.BLUE;
|
||||
} else {
|
||||
g1.strokeColor = cc.Color.RED;
|
||||
@@ -420,8 +453,9 @@ cc.Class({
|
||||
// barrier
|
||||
g1.strokeColor = cc.Color.WHITE;
|
||||
}
|
||||
const points = body.Shape.Points;
|
||||
const wpos = [body.X - self.spaceOffsetX, body.Y - self.spaceOffsetY];
|
||||
const points = body.GetShape().Points;
|
||||
const [bodyX, bodyY] = body.Position();
|
||||
const wpos = [bodyX - self.spaceOffsetX, bodyY - self.spaceOffsetY];
|
||||
g1.moveTo(wpos[0], wpos[1]);
|
||||
const cnt = points.length;
|
||||
for (let j = 0; j < cnt; j += 1) {
|
||||
@@ -444,7 +478,7 @@ cc.Class({
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
cc.game.setFrameRate(60);
|
||||
cc.game.setFrameRate(59.9);
|
||||
cc.view.setOrientation(cc.macro.ORIENTATION_LANDSCAPE);
|
||||
cc.view.enableAutoFullScreen(true);
|
||||
|
||||
@@ -478,7 +512,8 @@ cc.Class({
|
||||
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
|
||||
window.initPersistentSessionClient(self.initAfterWSConnected, null /* Deliberately NOT passing in any `expectedRoomId`. -- YFLu */ );
|
||||
};
|
||||
resultPanelScriptIns.onCloseDelegate = () => {};
|
||||
resultPanelScriptIns.onCloseDelegate = () => {
|
||||
};
|
||||
|
||||
self.gameRuleNode = cc.instantiate(self.gameRulePrefab);
|
||||
self.gameRuleNode.width = self.canvasNode.width;
|
||||
@@ -568,7 +603,6 @@ cc.Class({
|
||||
const newBarrierCollider = gopkgs.GenerateConvexPolygonColliderJs(gopkgsBoundary, self.spaceOffsetX, self.spaceOffsetY, gopkgsBarrier, "Barrier");
|
||||
self.gopkgsCollisionSys.Add(newBarrierCollider);
|
||||
|
||||
// console.log("Created barrier: ", newBarrierCollider);
|
||||
++barrierIdCounter;
|
||||
const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter);
|
||||
self.gopkgsCollisionSysMap[collisionBarrierIndex] = newBarrierCollider;
|
||||
@@ -663,7 +697,7 @@ cc.Class({
|
||||
}
|
||||
|
||||
// This function is also applicable to "re-joining".
|
||||
const rdf = gopkgs.NewRoomDownsyncFrameJs(pbRdf.id, jsPlayersArr, pbRdf.bulletLocalIdCounter, jsMeleeBulletsArr, jsFireballBulletsArr);
|
||||
const rdf = gopkgs.NewRoomDownsyncFrameJs(pbRdf.id, jsPlayersArr, pbRdf.bulletLocalIdCounter, jsMeleeBulletsArr, jsFireballBulletsArr); // TODO: Check whether a "proper" preallocated rdf is available and reuse it to avoid redundant heap alloc. By "proper" I mean "pbRdf.id" should yield a "non window.RING_BUFF_FAILED_TO_SET" result, yet currently it's a bit difficult to sort out the following codes for efficient reuse, thus I'm keeping it as-is.
|
||||
const self = window.mapIns;
|
||||
self.onInputFrameDownsyncBatch(accompaniedInputFrameDownsyncBatch); // Important to do this step before setting IN_BATTLE
|
||||
if (!self.recentRenderCache) {
|
||||
@@ -672,14 +706,20 @@ cc.Class({
|
||||
if (ALL_BATTLE_STATES.IN_SETTLEMENT == self.battleState) {
|
||||
return;
|
||||
}
|
||||
const shouldForceDumping1 = (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.Id);
|
||||
let shouldForceDumping2 = (rdf.Id >= self.renderFrameId + self.renderFrameIdLagTolerance);
|
||||
const rdfId = rdf.GetId();
|
||||
const shouldForceDumping1 = (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdfId);
|
||||
let shouldForceDumping2 = (rdfId >= self.renderFrameId + self.renderFrameIdLagTolerance);
|
||||
let shouldForceResync = pbRdf.shouldForceResync;
|
||||
const notSelfUnconfirmed = (0 == (pbRdf.backendUnconfirmedMask & (1 << (self.selfPlayerInfo.JoinIndex - 1))));
|
||||
const notSelfUnconfirmed = (0 == (pbRdf.backendUnconfirmedMask & (1 << (self.selfPlayerInfo.joinIndex - 1))));
|
||||
if (notSelfUnconfirmed) {
|
||||
shouldForceDumping2 = false;
|
||||
shouldForceResync = false;
|
||||
self.othersForcedDownsyncRenderFrameDict.set(rdf.Id, rdf);
|
||||
self.othersForcedDownsyncRenderFrameDict.set(rdfId, [pbRdf, rdf]);
|
||||
if (CC_DEBUG) {
|
||||
console.warn(`Someone else is forced to resync! renderFrameId=${rdfId}
|
||||
backendUnconfirmedMask=${pbRdf.backendUnconfirmedMask}
|
||||
accompaniedInputFrameDownsyncBatchRange=[${null == accompaniedInputFrameDownsyncBatch ? null : accompaniedInputFrameDownsyncBatch[0].inputFrameId}, ${null == accompaniedInputFrameDownsyncBatch ? null : accompaniedInputFrameDownsyncBatch[accompaniedInputFrameDownsyncBatch.length - 1].inputFrameId}]`);
|
||||
}
|
||||
}
|
||||
/*
|
||||
TODO
|
||||
@@ -687,11 +727,11 @@ cc.Class({
|
||||
If "BackendUnconfirmedMask" is non-all-1 and contains the current player, show a label/button to hint manual reconnection. Note that the continuity of "recentInputCache" is not a good indicator, because due to network delay upon a [type#1 forceConfirmation] a player might just lag in upsync networking and have all consecutive inputFrameIds locally.
|
||||
*/
|
||||
|
||||
const [dumpRenderCacheRet, oldStRenderFrameId, oldEdRenderFrameId] = (shouldForceDumping1 || shouldForceDumping2 || shouldForceResync) ? self.recentRenderCache.setByFrameId(rdf, rdf.Id) : [window.RING_BUFF_CONSECUTIVE_SET, null, null];
|
||||
const [dumpRenderCacheRet, oldStRenderFrameId, oldEdRenderFrameId] = (shouldForceDumping1 || shouldForceDumping2 || shouldForceResync) ? self.recentRenderCache.SetByFrameId(rdf, rdfId) : [window.RING_BUFF_CONSECUTIVE_SET, null, null];
|
||||
if (window.RING_BUFF_FAILED_TO_SET == dumpRenderCacheRet) {
|
||||
throw `Failed to dump render cache#1 (maybe recentRenderCache too small)! rdf.id=${rdf.id}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
|
||||
throw `Failed to dump render cache#1 (maybe recentRenderCache too small)! rdf.GetId()=${rdfId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
|
||||
}
|
||||
if (!shouldForceResync && (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START < rdf.id && window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet)) {
|
||||
if (!shouldForceResync && (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START < rdfId && window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet)) {
|
||||
/*
|
||||
Don't change
|
||||
- chaserRenderFrameId, it's updated only in "rollbackAndChase & onInputFrameDownsyncBatch" (except for when RING_BUFF_NON_CONSECUTIVE_SET)
|
||||
@@ -699,27 +739,32 @@ cc.Class({
|
||||
return dumpRenderCacheRet;
|
||||
}
|
||||
|
||||
// The logic below applies to (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id || window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet)
|
||||
// The logic below applies to (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdfId || window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet)
|
||||
if (null == pbRdf.speciesIdList) {
|
||||
console.error(`pbRdf.speciesIdList is required for starting or resyncing battle!`);
|
||||
}
|
||||
self.chConfigsOrderedByJoinIndex = gopkgs.GetCharacterConfigsOrderedByJoinIndex(pbRdf.speciesIdList);
|
||||
self._initPlayerRichInfoDict(rdf.PlayersArr);
|
||||
self._initPlayerRichInfoDict(rdf);
|
||||
|
||||
if (shouldForceDumping1 || shouldForceDumping2 || shouldForceResync) {
|
||||
// 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 fact, not having "window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet" should already imply that "self.renderFrameId <= rdfId", but here we double check and log the anomaly
|
||||
|
||||
if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.Id) {
|
||||
console.log('On battle started! renderFrameId=', rdf.Id);
|
||||
if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdfId) {
|
||||
console.log(`On battle started! renderFrameId=${rdfId}`);
|
||||
} else {
|
||||
self.hideFindingPlayersGUI();
|
||||
console.warn('On battle resynced! renderFrameId=', rdf.Id);
|
||||
if (CC_DEBUG) {
|
||||
console.warn(`On battle resynced! renderFrameId=${rdf.GetId()}
|
||||
accompaniedInputFrameDownsyncBatchRange=[${accompaniedInputFrameDownsyncBatch[0].inputFrameId}, ${accompaniedInputFrameDownsyncBatch[accompaniedInputFrameDownsyncBatch.length - 1].inputFrameId}]`);
|
||||
} else {
|
||||
console.warn(`On battle resynced! renderFrameId=${rdf.GetId()}`);
|
||||
}
|
||||
}
|
||||
|
||||
self.renderFrameId = rdf.Id;
|
||||
self.renderFrameId = rdfId;
|
||||
self.lastRenderFrameIdTriggeredAt = performance.now();
|
||||
// In this case it must be true that "rdf.id > chaserRenderFrameId".
|
||||
self.chaserRenderFrameId = rdf.Id;
|
||||
// In this case it must be true that "rdfId > chaserRenderFrameId".
|
||||
self.chaserRenderFrameId = rdfId;
|
||||
self.networkDoctor.logRollbackFrames(0);
|
||||
|
||||
const canvasNode = self.canvasNode;
|
||||
@@ -727,7 +772,7 @@ cc.Class({
|
||||
self.enableInputControls();
|
||||
self.transitToState(ALL_MAP_STATES.VISUAL);
|
||||
|
||||
const selfPlayerRichInfo = self.playerRichInfoDict.get(self.selfPlayerInfo.Id);
|
||||
const selfPlayerRichInfo = self.playerRichInfoDict.get(self.selfPlayerInfo.id);
|
||||
const newMapPos = cc.v2().sub(selfPlayerRichInfo.node.position);
|
||||
self.node.setPosition(newMapPos);
|
||||
self.battleState = ALL_BATTLE_STATES.IN_BATTLE;
|
||||
@@ -760,19 +805,20 @@ cc.Class({
|
||||
if (null == lhs || null == rhs) return false;
|
||||
if (null == lhs && null != rhs) return false;
|
||||
if (null != lhs && null == rhs) return false;
|
||||
if (lhs.VirtualGridX != rhs.VirtualGridX) return false;
|
||||
if (lhs.VirtualGridY != rhs.VirtualGridY) return false;
|
||||
if (lhs.DirX != rhs.DirX) return false;
|
||||
if (lhs.DirY != rhs.DirY) return false;
|
||||
if (lhs.VelX != rhs.VelX) return false;
|
||||
if (lhs.VelY != rhs.VelY) return false;
|
||||
if (lhs.Speed != rhs.Speed) return false;
|
||||
if (lhs.Hp != rhs.Hp) return false;
|
||||
if (lhs.MaxHp != rhs.MaxHp) return false;
|
||||
if (lhs.CharacterState != rhs.CharacterState) return false;
|
||||
if (lhs.InAir != rhs.InAir) return false;
|
||||
if (lhs.FramesToRecover != rhs.FramesToRecover) return false;
|
||||
if (lhs.FramesInChState != rhs.FramesInChState) return false;
|
||||
if (lhs.GetVirtualGridX() != rhs.GetVirtualGridX()) return false;
|
||||
if (lhs.GetVirtualGridY() != rhs.GetVirtualGridY()) return false;
|
||||
if (lhs.GetDirX() != rhs.GetDirX()) return false;
|
||||
if (lhs.GetDirY() != rhs.GetDirY()) return false;
|
||||
if (lhs.GetVelX() != rhs.GetVelX()) return false;
|
||||
if (lhs.GetVelY() != rhs.GetVelY()) return false;
|
||||
if (lhs.GetSpeed() != rhs.GetSpeed()) return false;
|
||||
if (lhs.GetHp() != rhs.GetHp()) return false;
|
||||
if (lhs.GetMaxHp() != rhs.GetMaxHp()) return false;
|
||||
if (lhs.GetCharacterState() != rhs.GetCharacterState()) return false;
|
||||
if (lhs.GetInAir() != rhs.GetInAir()) return false;
|
||||
if (lhs.GetOnWall() != rhs.GetOnWall()) return false;
|
||||
if (lhs.GetFramesToRecover() != rhs.GetFramesToRecover()) return false;
|
||||
if (lhs.GetFramesInChState() != rhs.GetFramesInChState()) return false;
|
||||
return true;
|
||||
},
|
||||
|
||||
@@ -780,9 +826,9 @@ cc.Class({
|
||||
if (null == lhs || null == rhs) return false;
|
||||
if (null == lhs && null != rhs) return false;
|
||||
if (null != lhs && null == rhs) return false;
|
||||
if (lhs.BattleAttr.BulletLocalId != rhs.BattleAttr.BulletLocalId) return false;
|
||||
if (lhs.BattleAttr.OffenderJoinIndex != rhs.BattleAttr.OffenderJoinIndex) return false;
|
||||
if (lhs.BattleAttr.OriginatedRenderFrameId != rhs.BattleAttr.OriginatedRenderFrameId) return false;
|
||||
if (lhs.GetBulletLocalId() != rhs.GetBulletLocalId()) return false;
|
||||
if (lhs.GetOffenderJoinIndex() != rhs.GetOffenderJoinIndex()) return false;
|
||||
if (lhs.GetOriginatedRenderFrameId() != rhs.GetOriginatedRenderFrameId()) return false;
|
||||
return true;
|
||||
},
|
||||
|
||||
@@ -790,31 +836,39 @@ cc.Class({
|
||||
if (null == lhs || null == rhs) return false;
|
||||
if (null == lhs && null != rhs) return false;
|
||||
if (null != lhs && null == rhs) return false;
|
||||
if (lhs.BattleAttr.BulletLocalId != rhs.BattleAttr.BulletLocalId) return false;
|
||||
if (lhs.BattleAttr.OffenderJoinIndex != rhs.BattleAttr.OffenderJoinIndex) return false;
|
||||
if (lhs.BattleAttr.OriginatedRenderFrameId != rhs.BattleAttr.OriginatedRenderFrameId) return false;
|
||||
if (lhs.GetBulletLocalId() != rhs.GetBulletLocalId()) return false;
|
||||
if (lhs.GetOffenderJoinIndex() != rhs.GetOffenderJoinIndex()) return false;
|
||||
if (lhs.GetOriginatedRenderFrameId() != rhs.GetOriginatedRenderFrameId()) return false;
|
||||
|
||||
if (lhs.VirtualGridX != rhs.Bullet.VirtualGridX) return false;
|
||||
if (lhs.VirtualGridY != rhs.Bullet.VirtualGridY) return false;
|
||||
if (lhs.DirX != rhs.DirX) return false;
|
||||
if (lhs.DirY != rhs.DirY) return false;
|
||||
if (lhs.VelX != rhs.VelX) return false;
|
||||
if (lhs.VelY != rhs.VelY) return false;
|
||||
if (lhs.Speed != rhs.Speed) return false;
|
||||
if (lhs.GetVirtualGridX() != rhs.GetVirtualGridX()) return false;
|
||||
if (lhs.GetVirtualGridY() != rhs.GetVirtualGridY()) return false;
|
||||
if (lhs.GetDirX() != rhs.GetDirX()) return false;
|
||||
if (lhs.GetDirY() != rhs.GetDirY()) return false;
|
||||
if (lhs.GetVelX() != rhs.GetVelX()) return false;
|
||||
if (lhs.GetVelY() != rhs.GetVelY()) return false;
|
||||
if (lhs.GetSpeed() != rhs.GetSpeed()) return false;
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
equalRoomDownsyncFrames(lhs, rhs) {
|
||||
if (null == lhs || null == rhs) return false;
|
||||
for (let k in lhs.PlayersArr) {
|
||||
if (!this.equalPlayers(lhs.PlayersArr[k], rhs.PlayersArr[k])) return false;
|
||||
for (let k = 0; k < window.boundRoomCapacity; k++) {
|
||||
const lp = gopkgs.GetPlayer(lhs, k);
|
||||
const rp = gopkgs.GetPlayer(rhs, k);
|
||||
if (!this.equalPlayers(lp, rp)) return false;
|
||||
}
|
||||
for (let k in lhs.MeleeBullets) {
|
||||
if (!this.equalMeleeBullets(lhs.MeleeBullets[k], rhs.MeleeBullets[k])) return false;
|
||||
for (let k = 0;; k++) {
|
||||
const lblt = gopkgs.GetMeleeBullet(lhs, k);
|
||||
const rblt = gopkgs.GetMeleeBullet(rhs, k);
|
||||
if (null == lblt && null == rblt) break;
|
||||
if (!this.equalMeleeBullets(lblt, rblt)) return false;
|
||||
}
|
||||
for (let k in lhs.fireballBullet) {
|
||||
if (!this.equalFireballBullets(lhs.FireballBullets[k], rhs.FireballBullets[k])) return false;
|
||||
for (let k = 0;; k++) {
|
||||
const lblt = gopkgs.GetFireballBullet(lhs, k);
|
||||
const rblt = gopkgs.GetFireballBullet(rhs, k);
|
||||
if (null == lblt && null == rblt) break;
|
||||
if (!this.equalFireballBullets(lblt, rblt)) return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
@@ -822,13 +876,20 @@ cc.Class({
|
||||
_markConfirmationIfApplicable() {
|
||||
const self = this;
|
||||
let newAllConfirmedCnt = 0;
|
||||
while (self.recentInputCache.StFrameId <= self.lastAllConfirmedInputFrameId && self.lastAllConfirmedInputFrameId < self.recentInputCache.EdFrameId) {
|
||||
const inputFrameDownsync = self.recentInputCache.GetByFrameId(self.lastAllConfirmedInputFrameId);
|
||||
let candidateInputFrameId = (self.lastAllConfirmedInputFrameId + 1);
|
||||
if (candidateInputFrameId < self.recentInputCache.GetStFrameId()) {
|
||||
candidateInputFrameId = self.recentInputCache.GetStFrameId();
|
||||
}
|
||||
while (self.recentInputCache.GetStFrameId() <= candidateInputFrameId && candidateInputFrameId < self.recentInputCache.GetEdFrameId()) {
|
||||
const inputFrameDownsync = gopkgs.GetInputFrameDownsync(self.recentInputCache, candidateInputFrameId);
|
||||
if (null == inputFrameDownsync) break;
|
||||
if (self._allConfirmed(inputFrameDownsync.ConfirmedList)) break;
|
||||
++self.lastAllConfirmedInputFrameId;
|
||||
if (false == self._allConfirmed(inputFrameDownsync.GetConfirmedList())) break;
|
||||
++candidateInputFrameId;
|
||||
++newAllConfirmedCnt;
|
||||
}
|
||||
if (0 < newAllConfirmedCnt) {
|
||||
self.lastAllConfirmedInputFrameId = candidateInputFrameId - 1;
|
||||
}
|
||||
return newAllConfirmedCnt;
|
||||
},
|
||||
|
||||
@@ -855,18 +916,17 @@ cc.Class({
|
||||
}
|
||||
// [WARNING] Now that "inputFrameDownsyncId > self.lastAllConfirmedInputFrameId", we should make an update immediately because unlike its backend counterpart "Room.LastAllConfirmedInputFrameId", the frontend "mapIns.lastAllConfirmedInputFrameId" might inevitably get gaps among discrete values due to "either type#1 or type#2 forceConfirmation" -- and only "onInputFrameDownsyncBatch" can catch this!
|
||||
self.lastAllConfirmedInputFrameId = inputFrameDownsyncId;
|
||||
|
||||
const localInputFrame = self.recentInputCache.GetByFrameId(inputFrameDownsyncId);
|
||||
const localInputFrame = gopkgs.GetInputFrameDownsync(self.recentInputCache, inputFrameDownsyncId);
|
||||
if (null != localInputFrame
|
||||
&&
|
||||
null == firstPredictedYetIncorrectInputFrameId
|
||||
&&
|
||||
!self.equalInputLists(localInputFrame.InputList, inputFrameDownsync.inputList)
|
||||
!self.equalInputLists(localInputFrame.GetInputList(), inputFrameDownsync.inputList)
|
||||
) {
|
||||
firstPredictedYetIncorrectInputFrameId = inputFrameDownsyncId;
|
||||
}
|
||||
// [WARNING] Take all "inputFrameDownsync" from backend as all-confirmed, it'll be later checked by "rollbackAndChase".
|
||||
inputFrameDownsync.confirmedList = (1 << self.playerRichInfoDict.size) - 1;
|
||||
inputFrameDownsync.confirmedList = (1 << window.boundRoomCapacity) - 1;
|
||||
const inputFrameDownsyncLocal = gopkgs.NewInputFrameDownsync(inputFrameDownsync.inputFrameId, inputFrameDownsync.inputList, inputFrameDownsync.confirmedList); // "battle.InputFrameDownsync" in "jsexport"
|
||||
for (let j in self.playerRichInfoArr) {
|
||||
const jj = parseInt(j);
|
||||
@@ -888,7 +948,7 @@ cc.Class({
|
||||
_handleIncorrectlyRenderedPrediction(firstPredictedYetIncorrectInputFrameId, batch, fromUDP) {
|
||||
if (null == firstPredictedYetIncorrectInputFrameId) return;
|
||||
const self = this;
|
||||
const renderFrameId1 = gopkgs.ConvertToFirstUsedRenderFrameId(firstPredictedYetIncorrectInputFrameId) - 1;
|
||||
const renderFrameId1 = gopkgs.ConvertToFirstUsedRenderFrameId(firstPredictedYetIncorrectInputFrameId);
|
||||
if (renderFrameId1 >= self.chaserRenderFrameId) return;
|
||||
|
||||
/*
|
||||
@@ -909,7 +969,7 @@ cc.Class({
|
||||
firstPredictedYetIncorrectInputFrameId: ${firstPredictedYetIncorrectInputFrameId}
|
||||
lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}
|
||||
recentInputCache=${self._stringifyRecentInputCache(false)}
|
||||
batchInputFrameIdRange=[${batch[0].inputFrameId}, ${batch[batch.length - 1].inputFrameId}]
|
||||
batchInputFrameIdRange=[${null == batch ? null : batch[0].inputFrameId}, ${null == batch ? null : batch[batch.length - 1].inputFrameId}]
|
||||
fromUDP=${fromUDP}`);
|
||||
}
|
||||
self.chaserRenderFrameId = renderFrameId1;
|
||||
@@ -952,8 +1012,9 @@ fromUDP=${fromUDP}`);
|
||||
}
|
||||
const peerJoinIndexMask = (1 << (peerJoinIndex - 1));
|
||||
self.getOrPrefabInputFrameUpsync(inputFrameId, false); // Make sure that inputFrame exists locally
|
||||
const existingInputFrame = self.recentInputCache.GetByFrameId(inputFrameId);
|
||||
if (0 < (existingInputFrame.ConfirmedList & peerJoinIndexMask)) {
|
||||
const existingInputFrame = gopkgs.GetInputFrameDownsync(self.recentInputCache, inputFrameId);
|
||||
const existingConfirmedList = existingInputFrame.GetConfirmedList();
|
||||
if (0 < (existingConfirmedList & peerJoinIndexMask)) {
|
||||
continue;
|
||||
}
|
||||
if (inputFrameId > self.lastIndividuallyConfirmedInputFrameId[peerJoinIndex - 1]) {
|
||||
@@ -962,9 +1023,10 @@ fromUDP=${fromUDP}`);
|
||||
}
|
||||
effCnt += 1;
|
||||
// the returned "gopkgs.NewInputFrameDownsync.InputList" is immutable, thus we can only modify the values in "newInputList" and "newConfirmedList"!
|
||||
let newInputList = existingInputFrame.InputList.slice();
|
||||
const existingInputList = existingInputFrame.GetInputList();
|
||||
let newInputList = existingInputFrame.GetInputList().slice();
|
||||
newInputList[peerJoinIndex - 1] = peerEncodedInput;
|
||||
let newConfirmedList = (existingInputFrame.ConfirmedList | peerJoinIndex);
|
||||
let newConfirmedList = (existingConfirmedList | peerJoinIndexMask);
|
||||
const newInputFrameDownsyncLocal = gopkgs.NewInputFrameDownsync(inputFrameId, newInputList, newConfirmedList);
|
||||
//console.log(`Updated encoded input of peerJoinIndex=${peerJoinIndex} to ${peerEncodedInput} for inputFrameId=${inputFrameId}/renderedInputFrameIdUpper=${renderedInputFrameIdUpper} from ${JSON.stringify(inputFrame)}; newInputFrameDownsyncLocal=${self.gopkgsInputFrameDownsyncStr(newInputFrameDownsyncLocal)}; existingInputFrame=${self.gopkgsInputFrameDownsyncStr(existingInputFrame)}`);
|
||||
self.recentInputCache.SetByFrameId(newInputFrameDownsyncLocal, inputFrameId);
|
||||
@@ -975,7 +1037,7 @@ fromUDP=${fromUDP}`);
|
||||
if (
|
||||
null == firstPredictedYetIncorrectInputFrameId
|
||||
&&
|
||||
existingInputFrame.InputList[peerJoinIndex - 1] != peerEncodedInput
|
||||
existingInputList[peerJoinIndex - 1] != peerEncodedInput
|
||||
) {
|
||||
firstPredictedYetIncorrectInputFrameId = inputFrameId;
|
||||
}
|
||||
@@ -1019,7 +1081,7 @@ fromUDP=${fromUDP}`);
|
||||
const newPlayerNode = cc.instantiate(self.controlledCharacterPrefab)
|
||||
const playerScriptIns = newPlayerNode.getComponent("ControlledCharacter");
|
||||
const chConfig = self.chConfigsOrderedByJoinIndex[joinIndex - 1];
|
||||
playerScriptIns.setSpecies(chConfig.SpeciesName);
|
||||
playerScriptIns.setSpecies(chConfig.GetSpeciesName());
|
||||
|
||||
if (1 == joinIndex) {
|
||||
newPlayerNode.color = cc.Color.RED;
|
||||
@@ -1103,24 +1165,26 @@ fromUDP=${fromUDP}`);
|
||||
rollbackFrames = 0;
|
||||
}
|
||||
self.networkDoctor.logRollbackFrames(rollbackFrames);
|
||||
let prevRdf = latestRdfResults[0],
|
||||
rdf = latestRdfResults[1];
|
||||
let prevRdf = latestRdfResults[0], // Having "prevRdf.Id == self.renderFrameId"
|
||||
rdf = latestRdfResults[1]; // Having "rdf.Id == self.renderFrameId+1"
|
||||
/*
|
||||
const nonTrivialChaseEnded = (prevChaserRenderFrameId < nextChaserRenderFrameId && nextChaserRenderFrameId == self.renderFrameId);
|
||||
if (nonTrivialChaseEnded) {
|
||||
console.debug("Non-trivial chase ended, prevChaserRenderFrameId=" + prevChaserRenderFrameId + ", nextChaserRenderFrameId=" + nextChaserRenderFrameId);
|
||||
}
|
||||
*/
|
||||
// [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!
|
||||
if (self.othersForcedDownsyncRenderFrameDict.has(rdf.Id)) {
|
||||
const delayedInputFrameId = gopkgs.ConvertToDelayedInputFrameId(rdf.Id);
|
||||
const othersForcedDownsyncRenderFrame = self.othersForcedDownsyncRenderFrameDict.get(rdf.Id);
|
||||
if (self.othersForcedDownsyncRenderFrameDict.has(rdf.GetId())) {
|
||||
const [pbOthersForcedDownsyncRenderFrame, othersForcedDownsyncRenderFrame] = self.othersForcedDownsyncRenderFrameDict.get(rdf.GetId());
|
||||
if (self.lastAllConfirmedInputFrameId >= delayedInputFrameId && !self.equalRoomDownsyncFrames(othersForcedDownsyncRenderFrame, rdf)) {
|
||||
console.warn(`Mismatched render frame@rdf.id=${rdf.Id} w/ inputFrameId=${delayedInputFrameId}:
|
||||
rdf=${JSON.stringify(rdf)}
|
||||
othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame)}`);
|
||||
rdf = othersForcedDownsyncRenderFrame;
|
||||
self.othersForcedDownsyncRenderFrameDict.delete(rdf.Id);
|
||||
if (CC_DEBUG) {
|
||||
console.warn(`Mismatched render frame@rdf.id=${rdf.GetId()} w/ inputFrameId=${delayedInputFrameId}:
|
||||
rdf=${self._stringifyGopkgRdfForFrameDataLogging(rdf)}
|
||||
othersForcedDownsyncRenderFrame=${self._stringifyGopkgRdfForFrameDataLogging(othersForcedDownsyncRenderFrame)}`);
|
||||
}
|
||||
// [WARNING] When this happens, something is intrinsically wrong -- to avoid having an inconsistent history in the "recentRenderCache", thus a wrong prediction all the way from here on, clear the history!
|
||||
pbOthersForcedDownsyncRenderFrame.backendUnconfirmedMask = ((1 << window.boundRoomCapacity) - 1);
|
||||
self.onRoomDownsyncFrame(pbOthersForcedDownsyncRenderFrame, null);
|
||||
self.othersForcedDownsyncRenderFrameDict.delete(rdf.GetId());
|
||||
}
|
||||
}
|
||||
self.applyRoomDownsyncFrameDynamics(rdf, prevRdf);
|
||||
@@ -1267,17 +1331,16 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
|
||||
|
||||
applyRoomDownsyncFrameDynamics(rdf, prevRdf) {
|
||||
const self = this;
|
||||
const playersArr = rdf.PlayersArr;
|
||||
for (let k in playersArr) {
|
||||
const currPlayerDownsync = playersArr[k];
|
||||
for (let k = 0; k < window.boundRoomCapacity; k++) {
|
||||
const currPlayerDownsync = gopkgs.GetPlayer(rdf, k);
|
||||
const prevRdfPlayer = (null == prevRdf ? null : gopkgs.GetPlayer(prevRdf, k));
|
||||
const chConfig = self.chConfigsOrderedByJoinIndex[k];
|
||||
const prevRdfPlayer = (null == prevRdf ? null : prevRdf.PlayersArr[k]);
|
||||
const [wx, wy] = gopkgs.VirtualGridToWorldPos(currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY);
|
||||
const [wx, wy] = gopkgs.VirtualGridToWorldPos(currPlayerDownsync.GetVirtualGridX(), currPlayerDownsync.GetVirtualGridY());
|
||||
const playerRichInfo = self.playerRichInfoArr[k];
|
||||
playerRichInfo.node.setPosition(wx, wy);
|
||||
playerRichInfo.scriptIns.updateSpeed(currPlayerDownsync.Speed);
|
||||
playerRichInfo.scriptIns.updateSpeed(currPlayerDownsync.GetSpeed());
|
||||
playerRichInfo.scriptIns.updateCharacterAnim(currPlayerDownsync, prevRdfPlayer, false, chConfig);
|
||||
playerRichInfo.scriptIns.hpBar.progress = (currPlayerDownsync.Hp * 1.0) / currPlayerDownsync.MaxHp;
|
||||
playerRichInfo.scriptIns.hpBar.progress = (currPlayerDownsync.GetHp() * 1.0) / currPlayerDownsync.GetMaxHp();
|
||||
}
|
||||
|
||||
// Move all to infinitely far away first
|
||||
@@ -1286,68 +1349,74 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
|
||||
const fireball = pqNode.value;
|
||||
fireball.node.setPosition(cc.v2(Number.MAX_VALUE, Number.MAX_VALUE));
|
||||
}
|
||||
for (let k in rdf.MeleeBullets) {
|
||||
const meleeBullet = rdf.MeleeBullets[k];
|
||||
const isExploding = (window.BULLET_STATE.Exploding == meleeBullet.BlState && meleeBullet.FramesInBlState < meleeBullet.Bullet.ExplosionFrames);
|
||||
for (let k = 0;; k++) {
|
||||
const meleeBullet = gopkgs.GetMeleeBullet(rdf, k);
|
||||
if (null == meleeBullet) {
|
||||
break;
|
||||
}
|
||||
const isExploding = (window.BULLET_STATE.Exploding == meleeBullet.GetBlState() && meleeBullet.GetFramesInBlState() < meleeBullet.GetExplosionFrames());
|
||||
if (isExploding) {
|
||||
let pqNode = self.cachedFireballs.popAny(meleeBullet.BattleAttr.BulletLocalId);
|
||||
let pqNode = self.cachedFireballs.popAny(meleeBullet.GetBulletLocalId());
|
||||
let speciesName = `MeleeExplosion`;
|
||||
let animName = `MeleeExplosion${meleeBullet.Bullet.SpeciesId}`;
|
||||
let animName = `MeleeExplosion${meleeBullet.GetSpeciesId()}`;
|
||||
|
||||
const offender = rdf.PlayersArr[meleeBullet.BattleAttr.OffenderJoinIndex - 1];
|
||||
const offender = gopkgs.GetPlayer(rdf, meleeBullet.GetOffenderJoinIndex() - 1);
|
||||
let xfac = 1; // By now, straight Punch offset doesn't respect "y-axis"
|
||||
if (0 > offender.DirX) {
|
||||
if (0 > offender.GetDirX()) {
|
||||
xfac = -1;
|
||||
}
|
||||
const [wx, wy] = gopkgs.VirtualGridToWorldPos(offender.VirtualGridX + xfac * meleeBullet.Bullet.HitboxOffsetX, offender.VirtualGridY);
|
||||
const [wx, wy] = gopkgs.VirtualGridToWorldPos(offender.GetVirtualGridX() + xfac * meleeBullet.GetHitboxOffsetX(), offender.GetVirtualGridY());
|
||||
|
||||
if (null == pqNode) {
|
||||
pqNode = self.cachedFireballs.pop();
|
||||
//console.log(`@rdf.Id=${rdf.Id}, origRdfId=${meleeBullet.BattleAttr.OriginatedRenderFrameId}, startupFrames=${meleeBullet.Bullet.StartupFrames}, using a new fireball node for rendering for bulletLocalId=${meleeBullet.BattleAttr.BulletLocalId} at wpos=(${wx},${wy})`);
|
||||
//console.log(`@rdf.GetId()=${rdf.GetId()}, origRdfId=${meleeBullet.GetOriginatedRenderFrameId()}, startupFrames=${meleeBullet.GetStartupFrames()}, using a new fireball node for rendering for bulletLocalId=${meleeBullet.GetBulletLocalId()} at wpos=(${wx},${wy})`);
|
||||
} else {
|
||||
//console.log(`@rdf.Id=${rdf.Id}, origRdfId=${meleeBullet.BattleAttr.OriginatedRenderFrameId}, startupFrames=${meleeBullet.Bullet.StartupFrames}, using a cached fireball node for rendering for bulletLocalId=${meleeBullet.BattleAttr.BulletLocalId} at wpos=(${wx},${wy})`);
|
||||
//console.log(`@rdf.GetId()=${rdf.GetId()}, origRdfId=${meleeBullet.GetOriginatedRenderFrameId()}, startupFrames=${meleeBullet.GetStartupFrames()}, using a cached fireball node for rendering for bulletLocalId=${meleeBullet.GetBulletLocalId()} at wpos=(${wx},${wy})`);
|
||||
}
|
||||
const cachedFireball = pqNode.value;
|
||||
cachedFireball.setSpecies(speciesName, meleeBullet, rdf);
|
||||
const newAnimIdx = meleeBullet.Bullet.SpeciesId - 1;
|
||||
cachedFireball.updateAnim(animName, meleeBullet.FramesInBlState, offender.DirX, false, rdf, newAnimIdx);
|
||||
const newAnimIdx = meleeBullet.GetSpeciesId() - 1;
|
||||
cachedFireball.updateAnim(animName, meleeBullet.GetFramesInBlState(), offender.GetDirX(), false, rdf, newAnimIdx);
|
||||
cachedFireball.lastUsed = self.renderFrameId;
|
||||
cachedFireball.bulletLocalId = meleeBullet.BattleAttr.BulletLocalId;
|
||||
cachedFireball.bulletLocalId = meleeBullet.GetBulletLocalId();
|
||||
cachedFireball.node.setPosition(cc.v2(wx, wy));
|
||||
|
||||
self.cachedFireballs.push(cachedFireball.lastUsed, cachedFireball, meleeBullet.BattleAttr.BulletLocalId);
|
||||
self.cachedFireballs.push(cachedFireball.lastUsed, cachedFireball, meleeBullet.GetBulletLocalId());
|
||||
} else {
|
||||
//console.log(`@rdf.Id=${rdf.Id}, origRdfId=${meleeBullet.BattleAttr.OriginatedRenderFrameId}, startupFrames=${meleeBullet.Bullet.StartupFrames}, activeFrames=${meleeBullet.Bullet.ActiveFrames}, not rendering melee node for bulletLocalId=${meleeBullet.BattleAttr.BulletLocalId}`);
|
||||
//console.log(`@rdf.GetId()=${rdf.GetId()}, origRdfId=${meleeBullet.GetOriginatedRenderFrameId()}, startupFrames=${meleeBullet.GetStartupFrames()}, activeFrames=${meleeBullet.GetActiveFrames()}, not rendering melee node for bulletLocalId=${meleeBullet.GetBulletLocalId()}`);
|
||||
}
|
||||
}
|
||||
for (let k in rdf.FireballBullets) {
|
||||
const fireballBullet = rdf.FireballBullets[k];
|
||||
const isExploding = (window.BULLET_STATE.Exploding == fireballBullet.BlState);
|
||||
if (gopkgs.IsFireballBulletActive(fireballBullet, rdf) || isExploding) {
|
||||
let pqNode = self.cachedFireballs.popAny(fireballBullet.BattleAttr.BulletLocalId);
|
||||
let speciesName = `Fireball${fireballBullet.Bullet.SpeciesId}`;
|
||||
let animName = (BULLET_STATE.Exploding == fireballBullet.BlState ? `Fireball${fireballBullet.Bullet.SpeciesId}Explosion` : speciesName);
|
||||
for (let k = 0;; k++) {
|
||||
const fireballBullet = gopkgs.GetFireballBullet(rdf, k);
|
||||
if (null == fireballBullet) {
|
||||
break;
|
||||
}
|
||||
const isExploding = (window.BULLET_STATE.Exploding == fireballBullet.GetBlState());
|
||||
if (gopkgs.IsGeneralBulletActive(fireballBullet.GetBlState(), fireballBullet.GetOriginatedRenderFrameId(), fireballBullet.GetStartupFrames(), fireballBullet.GetActiveFrames(), rdf.GetId()) || isExploding) {
|
||||
let pqNode = self.cachedFireballs.popAny(fireballBullet.GetBulletLocalId());
|
||||
let speciesName = `Fireball${fireballBullet.GetSpeciesId()}`;
|
||||
let animName = (isExploding ? `Fireball${fireballBullet.GetSpeciesId()}Explosion` : speciesName);
|
||||
|
||||
const [wx, wy] = gopkgs.VirtualGridToWorldPos(fireballBullet.VirtualGridX, fireballBullet.VirtualGridY);
|
||||
const [wx, wy] = gopkgs.VirtualGridToWorldPos(fireballBullet.GetVirtualGridX(), fireballBullet.GetVirtualGridY());
|
||||
|
||||
if (null == pqNode) {
|
||||
pqNode = self.cachedFireballs.pop();
|
||||
//console.log(`@rdf.Id=${rdf.Id}, origRdfId=${fireballBullet.BattleAttr.OriginatedRenderFrameId}, startupFrames=${fireballBullet.Bullet.StartupFrames}, using a new fireball node for rendering for bulletLocalId=${fireballBullet.BattleAttr.BulletLocalId} at wpos=(${wx},${wy})`);
|
||||
//console.log(`@rdf.GetId()=${rdf.GetId()}, origRdfId=${fireballBullet.GetOriginatedRenderFrameId()}, startupFrames=${fireballBullet.GetStartupFrames()}, using a new fireball node for rendering for bulletLocalId=${fireballBullet.GetBulletLocalId()} at wpos=(${wx},${wy})`);
|
||||
} else {
|
||||
//console.log(`@rdf.Id=${rdf.Id}, origRdfId=${fireballBullet.BattleAttr.OriginatedRenderFrameId}, startupFrames=${fireballBullet.Bullet.StartupFrames}, using a cached fireball node for rendering for bulletLocalId=${fireballBullet.BattleAttr.BulletLocalId} at wpos=(${wx},${wy})`);
|
||||
//console.log(`@rdf.GetId()=${rdf.GetId()}, origRdfId=${fireballBullet.GetOriginatedRenderFrameId()}, startupFrames=${fireballBullet.GetStartupFrames()}, using a cached fireball node for rendering for bulletLocalId=${fireballBullet.GetBulletLocalId()} at wpos=(${wx},${wy})`);
|
||||
}
|
||||
const cachedFireball = pqNode.value;
|
||||
cachedFireball.setSpecies(speciesName, fireballBullet, rdf);
|
||||
const spontaneousLooping = !isExploding;
|
||||
const newAnimIdx = (spontaneousLooping ? 0 : 1);
|
||||
cachedFireball.updateAnim(animName, fireballBullet.FramesInBlState, fireballBullet.DirX, spontaneousLooping, rdf, newAnimIdx);
|
||||
cachedFireball.updateAnim(animName, fireballBullet.GetFramesInBlState(), fireballBullet.GetDirX(), spontaneousLooping, rdf, newAnimIdx);
|
||||
cachedFireball.lastUsed = self.renderFrameId;
|
||||
cachedFireball.bulletLocalId = fireballBullet.BattleAttr.BulletLocalId;
|
||||
cachedFireball.bulletLocalId = fireballBullet.GetBulletLocalId();
|
||||
cachedFireball.node.setPosition(cc.v2(wx, wy));
|
||||
|
||||
self.cachedFireballs.push(cachedFireball.lastUsed, cachedFireball, fireballBullet.BattleAttr.BulletLocalId);
|
||||
self.cachedFireballs.push(cachedFireball.lastUsed, cachedFireball, fireballBullet.GetBulletLocalId());
|
||||
} else {
|
||||
//console.log(`@rdf.Id=${rdf.Id}, origRdfId=${fireballBullet.BattleAttr.OriginatedRenderFrameId}, startupFrames=${fireballBullet.Bullet.StartupFrames}, activeFrames=${fireballBullet.Bullet.ActiveFrames}, not rendering fireball node for bulletLocalId=${fireballBullet.BattleAttr.BulletLocalId}`);
|
||||
//console.log(`@rdf.GetId()=${rdf.GetId()}, origRdfId=${fireballBullet.GetOriginatedRenderFrameId()}, startupFrames=${fireballBullet.GetStartupFrames()}, activeFrames=${fireballBullet.GetActiveFrames()}, not rendering fireball node for bulletLocalId=${fireballBullet.GetBulletLocalId()}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1363,31 +1432,43 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
|
||||
let prevLatestRdf = null,
|
||||
latestRdf = null;
|
||||
for (let i = renderFrameIdSt; i < renderFrameIdEd; i++) {
|
||||
const currRdf = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing" and using Firefox, this function could be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"!
|
||||
const currRdf = gopkgs.GetRoomDownsyncFrame(self.recentRenderCache, i); // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing" and using Firefox, this function could be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"!
|
||||
if (null == currRdf) {
|
||||
throw `Couldn't find renderFrame for i=${i} to rollback (are you using Firefox?), self.renderFrameId=${self.renderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, might've been interruptted by onRoomDownsyncFrame`;
|
||||
}
|
||||
const j = gopkgs.ConvertToDelayedInputFrameId(i);
|
||||
const delayedInputFrame = self.recentInputCache.GetByFrameId(j);
|
||||
const delayedInputFrame = gopkgs.GetInputFrameDownsync(self.recentInputCache, j);
|
||||
|
||||
const allowUpdateInputFrameInPlaceUponDynamics = (!isChasing); // [WARNING] Input mutation could trigger chasing on frontend, thus don't trigger mutation when chasing to avoid confusing recursion.
|
||||
const hasInputFrameUpdatedOnDynamics = gopkgs.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(self.recentInputCache, i, collisionSys, collisionSysMap, self.spaceOffsetX, self.spaceOffsetY, self.chConfigsOrderedByJoinIndex, self.recentRenderCache, self.collisionHolder, self.effPushbacks, self.hardPushbackNormsArr, self.jumpedOrNotList, self.dynamicRectangleColliders, self.lastIndividuallyConfirmedInputFrameId, self.lastIndividuallyConfirmedInputList, allowUpdateInputFrameInPlaceUponDynamics, self.selfPlayerInfo.joinIndex);
|
||||
if (hasInputFrameUpdatedOnDynamics) {
|
||||
const ii = gopkgs.ConvertToFirstUsedRenderFrameId(j);
|
||||
if (ii < i) {
|
||||
/*
|
||||
[WARNING]
|
||||
If we don't rollback at this spot, when the mutated "delayedInputFrame.inputList" a.k.a. "inputFrame#j" matches the later downsynced version, rollback WOULDN'T be triggered for the incorrectly rendered "renderFrame#(ii+1)", and it would STAY IN HISTORY FOREVER -- as the history becomes incorrect, EVERY LATEST renderFrame since "inputFrame#j" was mutated would be ALWAYS incorrectly rendering too!
|
||||
*/
|
||||
self._handleIncorrectlyRenderedPrediction(j, null, false);
|
||||
}
|
||||
}
|
||||
if (self.frameDataLoggingEnabled) {
|
||||
const actuallyUsedInputClone = delayedInputFrame.InputList.slice();
|
||||
// [WARNING] The "inputList" of "delayedInputFrame" could be mutated in "ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs", thus should clone after dynamics is applied!
|
||||
const actuallyUsedInputClone = delayedInputFrame.GetInputList().slice();
|
||||
const inputFrameDownsyncClone = {
|
||||
inputFrameId: delayedInputFrame.InputFrameId,
|
||||
inputFrameId: j,
|
||||
inputList: actuallyUsedInputClone,
|
||||
confirmedList: delayedInputFrame.ConfirmedList,
|
||||
};
|
||||
self.rdfIdToActuallyUsedInput.set(currRdf.Id, inputFrameDownsyncClone);
|
||||
self.rdfIdToActuallyUsedInput.set(i, inputFrameDownsyncClone);
|
||||
}
|
||||
const nextRdf = gopkgs.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(self.recentInputCache, currRdf, collisionSys, collisionSysMap, self.spaceOffsetX, self.spaceOffsetY, self.chConfigsOrderedByJoinIndex);
|
||||
const nextRdf = gopkgs.GetRoomDownsyncFrame(self.recentRenderCache, i + 1);
|
||||
|
||||
if (true == isChasing) {
|
||||
// [WARNING] Move the cursor "self.chaserRenderFrameId" when "true == isChasing", keep in mind that "self.chaserRenderFrameId" is not monotonic!
|
||||
self.chaserRenderFrameId = nextRdf.Id;
|
||||
} else if (nextRdf.Id == self.chaserRenderFrameId + 1) {
|
||||
self.chaserRenderFrameId = nextRdf.Id; // To avoid redundant calculation
|
||||
self.chaserRenderFrameId = nextRdf.GetId();
|
||||
} else if (nextRdf.GetId() == self.chaserRenderFrameId + 1) {
|
||||
self.chaserRenderFrameId = nextRdf.GetId(); // To avoid redundant calculation
|
||||
}
|
||||
self.recentRenderCache.setByFrameId(nextRdf, nextRdf.Id);
|
||||
prevLatestRdf = currRdf;
|
||||
latestRdf = nextRdf;
|
||||
}
|
||||
@@ -1395,16 +1476,16 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
|
||||
return [prevLatestRdf, latestRdf];
|
||||
},
|
||||
|
||||
_initPlayerRichInfoDict(playersArr) {
|
||||
_initPlayerRichInfoDict(rdf) {
|
||||
const self = this;
|
||||
for (let k in playersArr) {
|
||||
const immediatePlayerInfo = playersArr[k];
|
||||
const playerId = immediatePlayerInfo.Id;
|
||||
for (let k = 0; k < window.boundRoomCapacity; k++) {
|
||||
const immediatePlayerInfo = gopkgs.GetPlayer(rdf, k);
|
||||
const playerId = immediatePlayerInfo.GetId();
|
||||
if (self.playerRichInfoDict.has(playerId)) continue; // Skip already put keys
|
||||
self.playerRichInfoDict.set(playerId, immediatePlayerInfo);
|
||||
const joinIndex = immediatePlayerInfo.joinIndex || immediatePlayerInfo.JoinIndex;
|
||||
const vx = immediatePlayerInfo.virtualGridX || immediatePlayerInfo.VirtualGridX;
|
||||
const vy = immediatePlayerInfo.virtualGridY || immediatePlayerInfo.VirtualGridY;
|
||||
const joinIndex = immediatePlayerInfo.GetJoinIndex();
|
||||
const vx = immediatePlayerInfo.GetVirtualGridX();
|
||||
const vy = immediatePlayerInfo.GetVirtualGridY();
|
||||
const nodeAndScriptIns = self.spawnPlayerNode(joinIndex, vx, vy, immediatePlayerInfo);
|
||||
|
||||
Object.assign(self.playerRichInfoDict.get(playerId), {
|
||||
@@ -1412,15 +1493,15 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
|
||||
scriptIns: nodeAndScriptIns[1],
|
||||
});
|
||||
|
||||
const selfPlayerId = self.selfPlayerInfo.Id;
|
||||
const selfPlayerId = self.selfPlayerInfo.id;
|
||||
if (selfPlayerId == playerId) {
|
||||
self.selfPlayerInfo.JoinIndex = immediatePlayerInfo.JoinIndex; // Update here in case of any change during WAITING phase
|
||||
self.selfPlayerInfo.joinIndex = joinIndex; // Update here in case of any change during WAITING phase
|
||||
nodeAndScriptIns[1].showArrowTipNode();
|
||||
}
|
||||
}
|
||||
self.playerRichInfoArr = new Array(self.playerRichInfoDict.size);
|
||||
self.playerRichInfoDict.forEach((playerRichInfo, playerId) => {
|
||||
self.playerRichInfoArr[playerRichInfo.JoinIndex - 1] = playerRichInfo;
|
||||
self.playerRichInfoArr[playerRichInfo.GetJoinIndex() - 1] = playerRichInfo;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1428,23 +1509,23 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
|
||||
const self = this;
|
||||
if (true == usefullOutput) {
|
||||
let s = [];
|
||||
for (let i = self.recentInputCache.StFrameId; i < self.recentInputCache.EdFrameId; ++i) {
|
||||
for (let i = self.recentInputCache.GetStFrameId(); i < self.recentInputCache.GetEdFrameId(); ++i) {
|
||||
s.push(JSON.stringify(self.recentInputCache.GetByFrameId(i)));
|
||||
}
|
||||
|
||||
return s.join('\n');
|
||||
}
|
||||
return `[stInputFrameId=${self.recentInputCache.StFrameId}, edInputFrameId=${self.recentInputCache.EdFrameId})`;
|
||||
return `[stInputFrameId=${self.recentInputCache.GetStFrameId()}, edInputFrameId=${self.recentInputCache.GetEdFrameId()})`;
|
||||
},
|
||||
|
||||
_stringifyGopkgRoomDownsyncFrame(rdf) {
|
||||
let s = [];
|
||||
s.push(`{`);
|
||||
s.push(` id: ${rdf.Id}`);
|
||||
s.push(` id: ${rdf.GetId()}`);
|
||||
s.push(` players: [`);
|
||||
for (let k in rdf.PlayersArr) {
|
||||
const player = rdf.PlayersArr[k];
|
||||
s.push(` {joinIndex: ${player.JoinIndex}, id: ${player.Id}, vx: ${player.VirtualGridX}, vy: ${player.VirtualGridY}, velX: ${player.VelX}, velY: ${player.VelY}}`);
|
||||
for (let k in rdf.GetPlayersArr()) {
|
||||
const player = rdf.GetPlayersArr()[k];
|
||||
s.push(` {joinIndex: ${player.GetJoinIndex()}, id: ${player.Id}, vx: ${player.GetVirtualGridX()}, vy: ${player.GetVirtualGridY()}, velX: ${player.GetVelX()}, velY: ${player.GetVelY()}}`);
|
||||
}
|
||||
s.push(` ]`);
|
||||
s.push(`}`);
|
||||
@@ -1455,24 +1536,24 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
|
||||
const self = this;
|
||||
if (true == usefullOutput) {
|
||||
let s = [];
|
||||
for (let i = self.recentRenderCache.stFrameId; i < self.recentRenderCache.edFrameId; ++i) {
|
||||
const rdf = self.recentRenderCache.getByFrameId(i);
|
||||
for (let i = self.recentRenderCache.GetStFrameId(); i < self.recentRenderCache.GetEdFrameId(); ++i) {
|
||||
const rdf = self.recentRenderCache.GetByFrameId(i);
|
||||
s.push(self._stringifyGopkgRoomDownsyncFrame(rdf));
|
||||
}
|
||||
|
||||
return s.join("\n");
|
||||
}
|
||||
return `[stRenderFrameId=${self.recentRenderCache.stFrameId}, edRenderFrameId=${self.recentRenderCache.edFrameId})`;
|
||||
return `[stRenderFrameId=${self.recentRenderCache.GetStFrameId()}, edRenderFrameId=${self.recentRenderCache.GetEdFrameId()})`;
|
||||
},
|
||||
|
||||
playerDownsyncStr(playerDownsync) {
|
||||
if (null == playerDownsync) return "";
|
||||
return `{${playerDownsync.JoinIndex},${playerDownsync.VirtualGridX},${playerDownsync.VirtualGridY},${playerDownsync.VelX},${playerDownsync.VelY},${playerDownsync.FramesToRecover},${playerDownsync.InAir ? 1 : 0},${playerDownsync.OnWall ? 1 : 0}}`;
|
||||
return `{${playerDownsync.GetJoinIndex()},${playerDownsync.GetVirtualGridX()},${playerDownsync.GetVirtualGridY()},${playerDownsync.GetVelX()},${playerDownsync.GetVelY()},${playerDownsync.GetFramesToRecover()},${playerDownsync.GetInAir() ? 1 : 0},${playerDownsync.GetOnWall() ? 1 : 0}}`;
|
||||
},
|
||||
|
||||
fireballDownsyncStr(fireball) {
|
||||
if (null == fireball) return "";
|
||||
return `{${fireball.BattleAttr.BulletLocalId},${fireball.BattleAttr.OriginatedRenderFrameId},${fireball.BattleAttr.OffenderJoinIndex},${fireball.VirtualGridX},${fireball.VirtualGridY},${fireball.VelX},${fireball.VelY},${fireball.DirX},${fireball.DirY},${fireball.Bullet.HitboxSizeX},${fireball.Bullet.HitboxSizeY}}`;
|
||||
return `{${fireball.GetBulletLocalId()},${fireball.GetOriginatedRenderFrameId()},${fireball.GetOffenderJoinIndex()},${fireball.GetVirtualGridX()},${fireball.GetVirtualGridY()},${fireball.GetVelX()},${fireball.GetVelY()},${fireball.GetDirX()},${fireball.GetDirY()},${fireball.GetHitboxSizeX()},${fireball.GetHitboxSizeY()}}`;
|
||||
},
|
||||
|
||||
inputFrameDownsyncStr(inputFrameDownsync) {
|
||||
@@ -1495,34 +1576,41 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
|
||||
if (null == inputFrameDownsync) return "{}";
|
||||
const self = this;
|
||||
let s = [];
|
||||
s.push(`InputFrameId:${inputFrameDownsync.InputFrameId}`);
|
||||
s.push(`InputFrameId:${inputFrameDownsync.GetInputFrameId()}`);
|
||||
let ss = [];
|
||||
for (let k = 0; k < window.boundRoomCapacity; ++k) {
|
||||
ss.push(`"${inputFrameDownsync.InputList[k]}"`);
|
||||
ss.push(`"${inputFrameDownsync.GetInputList()[k]}"`);
|
||||
}
|
||||
s.push(`InputList:[${ss.join(',')}]`);
|
||||
s.push(`ConfirmedList:${inputFrameDownsync.ConfirmedList}`);
|
||||
s.push(`ConfirmedList:${inputFrameDownsync.GetConfirmedList()}`);
|
||||
|
||||
return `{${s.join(',')}}`;
|
||||
},
|
||||
|
||||
_stringifyGopkgRdfForFrameDataLogging(rdf) {
|
||||
const playersStrBldr = [];
|
||||
for (let k = 0; k < window.boundRoomCapacity; k++) {
|
||||
playersStrBldr.push(this.playerDownsyncStr(gopkgs.GetPlayer(rdf, k)));
|
||||
}
|
||||
const fireballsStrBldr = [];
|
||||
for (let k = 0;; k++) {
|
||||
const fireball = gopkgs.GetFireballBullet(rdf, k);
|
||||
if (null == fireball) break;
|
||||
fireballsStrBldr.push(this.fireballDownsyncStr(fireball));
|
||||
}
|
||||
return `rdfId:${rdf.GetId()}
|
||||
players:[${playersStrBldr.join(',')}]
|
||||
fireballs:[${fireballsStrBldr.join(',')}]`;
|
||||
},
|
||||
|
||||
_stringifyRdfIdToActuallyUsedInput() {
|
||||
const self = this;
|
||||
let s = [];
|
||||
for (let i = self.recentRenderCache.stFrameId; i < self.recentRenderCache.edFrameId; i++) {
|
||||
for (let i = self.recentRenderCache.GetStFrameId(); i < self.recentRenderCache.GetEdFrameId(); i++) {
|
||||
const actuallyUsedInputClone = self.rdfIdToActuallyUsedInput.get(i);
|
||||
const rdf = self.recentRenderCache.getByFrameId(i);
|
||||
const playersStrBldr = [];
|
||||
for (let k in rdf.PlayersArr) {
|
||||
playersStrBldr.push(self.playerDownsyncStr(rdf.PlayersArr[k]));
|
||||
}
|
||||
const fireballsStrBldr = [];
|
||||
for (let k in rdf.FireballBullets) {
|
||||
fireballsStrBldr.push(self.fireballDownsyncStr(rdf.FireballBullets[k]));
|
||||
}
|
||||
s.push(`rdfId:${i}
|
||||
players:[${playersStrBldr.join(',')}]
|
||||
fireballs:[${fireballsStrBldr.join(',')}]
|
||||
const rdf = gopkgs.GetRoomDownsyncFrame(self.recentRenderCache, i);
|
||||
const rdfStr = self._stringifyGopkgRdfForFrameDataLogging(rdf);
|
||||
s.push(`${rdfStr}
|
||||
actuallyUsedinputList:{${self.inputFrameDownsyncStr(actuallyUsedInputClone)}}`);
|
||||
}
|
||||
|
||||
@@ -1544,30 +1632,30 @@ actuallyUsedinputList:{${self.inputFrameDownsyncStr(actuallyUsedInputClone)}}`);
|
||||
let g2 = self.g2;
|
||||
g2.clear();
|
||||
|
||||
for (let k in rdf.PlayersArr) {
|
||||
const player = rdf.PlayersArr[k];
|
||||
if (1 == player.JoinIndex) {
|
||||
for (let k = 0; k < window.boundRoomCapacity; k++) {
|
||||
const player = gopkgs.GetPlayer(rdf, k);
|
||||
if (1 == player.GetJoinIndex()) {
|
||||
g2.strokeColor = cc.Color.BLUE;
|
||||
} else {
|
||||
g2.strokeColor = cc.Color.RED;
|
||||
}
|
||||
|
||||
let [colliderWidth, colliderHeight] = [player.ColliderRadius * 2, player.ColliderRadius * 4];
|
||||
switch (player.CharacterState) {
|
||||
let [colliderWidth, colliderHeight] = [player.GetColliderRadius() * 2, player.GetColliderRadius() * 4];
|
||||
switch (player.GetCharacterState()) {
|
||||
case ATK_CHARACTER_STATE.LayDown1[0]:
|
||||
[colliderWidth, colliderHeight] = [player.ColliderRadius * 4, player.ColliderRadius * 2];
|
||||
[colliderWidth, colliderHeight] = [player.GetColliderRadius() * 4, player.GetColliderRadius() * 2];
|
||||
break;
|
||||
case ATK_CHARACTER_STATE.BlownUp1[0]:
|
||||
case ATK_CHARACTER_STATE.InAirIdle1NoJump[0]:
|
||||
case ATK_CHARACTER_STATE.InAirIdle1ByJump[0]:
|
||||
case ATK_CHARACTER_STATE.OnWall[0]:
|
||||
[colliderWidth, colliderHeight] = [player.ColliderRadius * 2, player.ColliderRadius * 2];
|
||||
[colliderWidth, colliderHeight] = [player.GetColliderRadius() * 2, player.GetColliderRadius() * 2];
|
||||
break;
|
||||
}
|
||||
|
||||
const [halfColliderWidth, halfColliderHeight] = gopkgs.VirtualGridToWorldPos((colliderWidth >> 1), (colliderHeight >> 1));
|
||||
|
||||
const [wx, wy] = gopkgs.VirtualGridToWorldPos(player.VirtualGridX, player.VirtualGridY);
|
||||
const [wx, wy] = gopkgs.VirtualGridToWorldPos(player.GetVirtualGridX(), player.GetVirtualGridY());
|
||||
const [cx, cy] = gopkgs.WorldToPolygonColliderBLPos(wx, wy, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, 0, 0);
|
||||
const pts = [[0, 0], [leftPadding + halfColliderWidth * 2 + rightPadding, 0], [leftPadding + halfColliderWidth * 2 + rightPadding, bottomPadding + halfColliderHeight * 2 + topPadding], [0, bottomPadding + halfColliderHeight * 2 + topPadding]];
|
||||
|
||||
@@ -1579,22 +1667,25 @@ actuallyUsedinputList:{${self.inputFrameDownsyncStr(actuallyUsedInputClone)}}`);
|
||||
g2.stroke();
|
||||
}
|
||||
|
||||
for (let k in rdf.MeleeBullets) {
|
||||
const meleeBullet = rdf.MeleeBullets[k];
|
||||
if (gopkgs.IsMeleeBulletActive(meleeBullet, rdf)) {
|
||||
const offender = rdf.PlayersArr[meleeBullet.BattleAttr.OffenderJoinIndex - 1];
|
||||
if (1 == offender.JoinIndex) {
|
||||
for (let k = 0;; k++) {
|
||||
const meleeBullet = gopkgs.GetMeleeBullet(rdf, k);
|
||||
if (null == meleeBullet) {
|
||||
break;
|
||||
}
|
||||
if (gopkgs.IsGeneralBulletActive(meleeBullet.GetBlState(), meleeBullet.GetOriginatedRenderFrameId(), meleeBullet.GetStartupFrames(), meleeBullet.GetActiveFrames(), rdf.GetId())) {
|
||||
const offender = gopkgs.GetPlayer(rdf, meleeBullet.GetOffenderJoinIndex() - 1);
|
||||
if (1 == offender.GetJoinIndex()) {
|
||||
g2.strokeColor = cc.Color.BLUE;
|
||||
} else {
|
||||
g2.strokeColor = cc.Color.RED;
|
||||
}
|
||||
|
||||
let xfac = 1; // By now, straight Punch offset doesn't respect "y-axis"
|
||||
if (0 > offender.DirX) {
|
||||
if (0 > offender.GetDirX()) {
|
||||
xfac = -1;
|
||||
}
|
||||
const [bulletWx, bulletWy] = gopkgs.VirtualGridToWorldPos(offender.VirtualGridX + xfac * meleeBullet.Bullet.HitboxOffsetX, offender.VirtualGridY);
|
||||
const [halfColliderWidth, halfColliderHeight] = gopkgs.VirtualGridToWorldPos((meleeBullet.Bullet.HitboxSizeX >> 1), (meleeBullet.Bullet.HitboxSizeY >> 1));
|
||||
const [bulletWx, bulletWy] = gopkgs.VirtualGridToWorldPos(offender.GetVirtualGridX() + xfac * meleeBullet.GetHitboxOffsetX(), offender.GetVirtualGridY());
|
||||
const [halfColliderWidth, halfColliderHeight] = gopkgs.VirtualGridToWorldPos((meleeBullet.GetHitboxSizeX() >> 1), (meleeBullet.GetHitboxSizeY() >> 1));
|
||||
const [bulletCx, bulletCy] = gopkgs.WorldToPolygonColliderBLPos(bulletWx, bulletWy, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, 0, 0);
|
||||
const pts = [[0, 0], [leftPadding + halfColliderWidth * 2 + rightPadding, 0], [leftPadding + halfColliderWidth * 2 + rightPadding, bottomPadding + halfColliderHeight * 2 + topPadding], [0, bottomPadding + halfColliderHeight * 2 + topPadding]];
|
||||
|
||||
@@ -1607,18 +1698,21 @@ actuallyUsedinputList:{${self.inputFrameDownsyncStr(actuallyUsedInputClone)}}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (let k in rdf.FireballBullets) {
|
||||
const fireballBullet = rdf.FireballBullets[k];
|
||||
if (gopkgs.IsFireballBulletActive(fireballBullet, rdf)) {
|
||||
const offender = rdf.PlayersArr[fireballBullet.BattleAttr.OffenderJoinIndex - 1];
|
||||
if (1 == offender.JoinIndex) {
|
||||
for (let k = 0;; k++) {
|
||||
const fireballBullet = gopkgs.GetFireballBullet(rdf, k);
|
||||
if (null == fireballBullet) {
|
||||
break;
|
||||
}
|
||||
if (gopkgs.IsGeneralBulletActive(fireballBullet.GetBlState(), fireballBullet.GetOriginatedRenderFrameId(), fireballBullet.GetStartupFrames(), fireballBullet.GetActiveFrames(), rdf.GetId())) {
|
||||
const offender = gopkgs.GetPlayer(rdf, fireballBullet.GetOffenderJoinIndex() - 1);
|
||||
if (1 == offender.GetJoinIndex()) {
|
||||
g2.strokeColor = cc.Color.BLUE;
|
||||
} else {
|
||||
g2.strokeColor = cc.Color.RED;
|
||||
}
|
||||
|
||||
const [bulletWx, bulletWy] = gopkgs.VirtualGridToWorldPos(fireballBullet.VirtualGridX, fireballBullet.VirtualGridY);
|
||||
const [halfColliderWidth, halfColliderHeight] = gopkgs.VirtualGridToWorldPos((fireballBullet.Bullet.HitboxSizeX >> 1), (fireballBullet.Bullet.HitboxSizeY >> 1));
|
||||
const [bulletWx, bulletWy] = gopkgs.VirtualGridToWorldPos(fireballBullet.GetVirtualGridX(), fireballBullet.GetVirtualGridY());
|
||||
const [halfColliderWidth, halfColliderHeight] = gopkgs.VirtualGridToWorldPos((fireballBullet.GetHitboxSizeX() >> 1), (fireballBullet.GetHitboxSizeY() >> 1));
|
||||
const [bulletCx, bulletCy] = gopkgs.WorldToPolygonColliderBLPos(bulletWx, bulletWy, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, 0, 0);
|
||||
const pts = [[0, 0], [leftPadding + halfColliderWidth * 2 + rightPadding, 0], [leftPadding + halfColliderWidth * 2 + rightPadding, bottomPadding + halfColliderHeight * 2 + topPadding], [0, bottomPadding + halfColliderHeight * 2 + topPadding]];
|
||||
|
||||
|
@@ -1,66 +0,0 @@
|
||||
cc.Class({
|
||||
extends: cc.Component,
|
||||
|
||||
properties: {
|
||||
BGMEffect: {
|
||||
type: cc.AudioClip,
|
||||
default: null
|
||||
},
|
||||
crashedByTrapBullet: {
|
||||
type: cc.AudioClip,
|
||||
default: null
|
||||
},
|
||||
highScoreTreasurePicked: {
|
||||
type: cc.AudioClip,
|
||||
default: null
|
||||
},
|
||||
treasurePicked: {
|
||||
type: cc.AudioClip,
|
||||
default: null
|
||||
},
|
||||
countDown10SecToEnd: {
|
||||
type: cc.AudioClip,
|
||||
default: null
|
||||
},
|
||||
mapNode: {
|
||||
type: cc.Node,
|
||||
default: null
|
||||
},
|
||||
},
|
||||
|
||||
// LIFE-CYCLE CALLBACKS:
|
||||
|
||||
onLoad() {
|
||||
cc.audioEngine.setEffectsVolume(1);
|
||||
cc.audioEngine.setMusicVolume(0.5);
|
||||
},
|
||||
stopAllMusic() {
|
||||
cc.audioEngine.stopAll();
|
||||
},
|
||||
playBGM() {
|
||||
if(this.BGMEffect) {
|
||||
cc.audioEngine.playMusic(this.BGMEffect, true);
|
||||
}
|
||||
},
|
||||
playCrashedByTrapBullet() {
|
||||
if(this.crashedByTrapBullet) {
|
||||
cc.audioEngine.playEffect(this.crashedByTrapBullet, false);
|
||||
}
|
||||
},
|
||||
playHighScoreTreasurePicked() {
|
||||
if(this.highScoreTreasurePicked) {
|
||||
cc.audioEngine.playEffect(this.highScoreTreasurePicked, false);
|
||||
}
|
||||
},
|
||||
playTreasurePicked() {
|
||||
if(this.treasurePicked) {
|
||||
cc.audioEngine.playEffect(this.treasurePicked, false);
|
||||
}
|
||||
},
|
||||
playCountDown10SecToEnd() {
|
||||
if(this.countDown10SecToEnd) {
|
||||
cc.audioEngine.playEffect(this.countDown10SecToEnd, false);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"ver": "1.0.5",
|
||||
"uuid": "09e1bfed-132e-4ada-a68f-229a870db69e",
|
||||
"isPlugin": false,
|
||||
"loadPluginInWeb": true,
|
||||
"loadPluginInNative": true,
|
||||
"loadPluginInEditor": false,
|
||||
"subMetas": {}
|
||||
}
|
@@ -91,7 +91,7 @@ NetworkDoctor.prototype.isTooFast = function(mapIns) {
|
||||
// Don't send too fast
|
||||
if (CC_DEBUG) {
|
||||
// Printing of this message might induce a performance impact.
|
||||
console.log(`Sending too fast, sendingFps=${sendingFps}`);
|
||||
// console.log(`Sending too fast, sendingFps=${sendingFps}`);
|
||||
}
|
||||
return [true, inputFrameIdFront, sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, skippedRenderFrameCnt];
|
||||
} else {
|
||||
@@ -109,7 +109,7 @@ NetworkDoctor.prototype.isTooFast = function(mapIns) {
|
||||
// first comparison condition is to avoid numeric overflow
|
||||
if (CC_DEBUG) {
|
||||
// Printing of this message might induce a performance impact.
|
||||
console.log(`Game logic ticking too fast, selfInputFrameIdFront=${inputFrameIdFront}, minInputFrameIdFront=${minInputFrameIdFront}, inputFrameUpsyncDelayTolerance=${mapIns.inputFrameUpsyncDelayTolerance}`);
|
||||
// console.log(`Game logic ticking too fast, selfInputFrameIdFront=${inputFrameIdFront}, minInputFrameIdFront=${minInputFrameIdFront}, inputFrameUpsyncDelayTolerance=${mapIns.inputFrameUpsyncDelayTolerance}`);
|
||||
}
|
||||
return [true, inputFrameIdFront, sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, skippedRenderFrameCnt];
|
||||
}
|
||||
|
@@ -11,13 +11,13 @@ cc.Class({
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
cc.game.setFrameRate(60);
|
||||
cc.game.setFrameRate(59.9);
|
||||
cc.view.setOrientation(cc.macro.ORIENTATION_LANDSCAPE);
|
||||
cc.view.enableAutoFullScreen(true);
|
||||
const self = this;
|
||||
window.mapIns = self;
|
||||
self.showCriticalCoordinateLabels = false;
|
||||
self.showNetworkDoctorInfo = true;
|
||||
self.showNetworkDoctorInfo = false;
|
||||
|
||||
const mapNode = self.node;
|
||||
const canvasNode = mapNode.parent;
|
||||
@@ -33,9 +33,9 @@ cc.Class({
|
||||
/** Init required prefab ended. */
|
||||
|
||||
self.inputFrameUpsyncDelayTolerance = 2;
|
||||
self.collisionMinStep = 2;
|
||||
self.collisionMinStep = 16;
|
||||
|
||||
self.renderCacheSize = 1024;
|
||||
self.renderCacheSize = 128;
|
||||
self.serverFps = 60;
|
||||
self.rollbackEstimatedDt = 0.016667;
|
||||
self.rollbackEstimatedDtMillis = 16.667;
|
||||
@@ -73,6 +73,7 @@ cc.Class({
|
||||
self.spaceOffsetX = ((newMapSize.width * newTileSize.width) >> 1);
|
||||
self.spaceOffsetY = ((newMapSize.height * newTileSize.height) >> 1);
|
||||
|
||||
window.boundRoomCapacity = 2;
|
||||
self._resetCurrentMatch();
|
||||
let barrierIdCounter = 0;
|
||||
const boundaryObjs = tileCollisionManager.extractBoundaryObjects(self.node);
|
||||
@@ -98,7 +99,7 @@ cc.Class({
|
||||
const p2Vpos = gopkgs.WorldToVirtualGridPos(boundaryObjs.playerStartingPositions[1].x, boundaryObjs.playerStartingPositions[1].y);
|
||||
const colliderRadiusV = gopkgs.WorldToVirtualGridPos(12.0, 0);
|
||||
|
||||
const speciesIdList = [4096, 0];
|
||||
const speciesIdList = [1, 0];
|
||||
const chConfigsOrderedByJoinIndex = gopkgs.GetCharacterConfigsOrderedByJoinIndex(speciesIdList);
|
||||
|
||||
const startRdf = window.pb.protos.RoomDownsyncFrame.create({
|
||||
@@ -111,7 +112,7 @@ cc.Class({
|
||||
virtualGridY: p1Vpos[1],
|
||||
revivalVirtualGridX: p1Vpos[0],
|
||||
revivalVirtualGridY: p1Vpos[1],
|
||||
speed: chConfigsOrderedByJoinIndex[0].Speed,
|
||||
speed: chConfigsOrderedByJoinIndex[0].GetSpeed(),
|
||||
colliderRadius: colliderRadiusV[0],
|
||||
characterState: window.ATK_CHARACTER_STATE.InAirIdle1NoJump[0],
|
||||
framesToRecover: 0,
|
||||
@@ -131,7 +132,7 @@ cc.Class({
|
||||
virtualGridY: p2Vpos[1],
|
||||
revivalVirtualGridX: p2Vpos[0],
|
||||
revivalVirtualGridY: p2Vpos[1],
|
||||
speed: chConfigsOrderedByJoinIndex[1].Speed,
|
||||
speed: chConfigsOrderedByJoinIndex[1].GetSpeed(),
|
||||
colliderRadius: colliderRadiusV[0],
|
||||
characterState: window.ATK_CHARACTER_STATE.InAirIdle1NoJump[0],
|
||||
framesToRecover: 0,
|
||||
@@ -149,8 +150,8 @@ cc.Class({
|
||||
});
|
||||
|
||||
self.selfPlayerInfo = {
|
||||
Id: 10,
|
||||
JoinIndex: 1,
|
||||
id: 10,
|
||||
joinIndex: 1,
|
||||
};
|
||||
if (cc.sys.isNative) {
|
||||
window.onUdpMessage = (args) => {
|
||||
@@ -163,7 +164,7 @@ cc.Class({
|
||||
const echoed = window.pb.protos.HolePunchUpsync.decode(ui8Arr);
|
||||
cc.log(`#2 Js called back by CPP: onUdpMessage: ${JSON.stringify(echoed)}`);
|
||||
};
|
||||
const res1 = DelayNoMore.UdpSession.openUdpSession(8888 + self.selfPlayerInfo.JoinIndex);
|
||||
const res1 = DelayNoMore.UdpSession.openUdpSession(8888 + self.selfPlayerInfo.joinIndex);
|
||||
const holePunchData = window.pb.protos.HolePunchUpsync.encode({
|
||||
boundRoomId: 22,
|
||||
intAuthToken: "foobar",
|
||||
|
@@ -87,8 +87,8 @@ window.handleHbRequirements = function(resp) {
|
||||
if (constants.RET_CODE.OK != resp.ret) return;
|
||||
// The assignment of "window.mapIns" is inside "Map.onLoad", which precedes "initPersistentSessionClient".
|
||||
window.mapIns.selfPlayerInfo = JSON.parse(cc.sys.localStorage.getItem('selfPlayer')); // This field is kept for distinguishing "self" and "others".
|
||||
window.mapIns.selfPlayerInfo.Id = window.mapIns.selfPlayerInfo.playerId;
|
||||
window.mapIns.selfPlayerInfo.JoinIndex = resp.peerJoinIndex;
|
||||
window.mapIns.selfPlayerInfo.id = window.mapIns.selfPlayerInfo.playerId;
|
||||
window.mapIns.selfPlayerInfo.joinIndex = resp.peerJoinIndex;
|
||||
console.log(`Handle hb requirements #2`);
|
||||
if (null == window.boundRoomId || null == window.boundRoomCapacity) {
|
||||
window.boundRoomId = resp.bciFrame.boundRoomId;
|
||||
@@ -109,7 +109,7 @@ window.handleHbRequirements = function(resp) {
|
||||
window.initSecondarySession(null, window.boundRoomId);
|
||||
} else {
|
||||
console.log(`Handle hb requirements #5, native, bciFrame.battleUdpTunnel=${resp.bciFrame.battleUdpTunnel}, selfPlayerInfo=${JSON.stringify(window.mapIns.selfPlayerInfo)}`);
|
||||
const res1 = DelayNoMore.UdpSession.openUdpSession(8888 + window.mapIns.selfPlayerInfo.JoinIndex);
|
||||
const res1 = DelayNoMore.UdpSession.openUdpSession(8888 + window.mapIns.selfPlayerInfo.joinIndex);
|
||||
window.mapIns.selfPlayerInfo.udpTunnelAuthKey = resp.bciFrame.battleUdpTunnel.authKey;
|
||||
const intAuthToken = window.mapIns.selfPlayerInfo.intAuthToken;
|
||||
const authKey = Math.floor(Math.random() * 65535);
|
||||
@@ -121,7 +121,7 @@ window.handleHbRequirements = function(resp) {
|
||||
}).finish();
|
||||
const udpTunnelHolePunchData = window.pb.protos.WsReq.encode({
|
||||
msgId: Date.now(),
|
||||
playerId: window.mapIns.selfPlayerInfo.Id,
|
||||
playerId: window.mapIns.selfPlayerInfo.id,
|
||||
act: window.UPSYNC_MSG_ACT_PLAYER_CMD,
|
||||
authKey: resp.bciFrame.battleUdpTunnel.authKey,
|
||||
}).finish();
|
||||
@@ -250,7 +250,7 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
|
||||
console.log(`Got DOWNSYNC_MSG_ACT_PEER_UDP_ADDR peerAddrList=${JSON.stringify(peerAddrList)}; boundRoomCapacity=${window.boundRoomCapacity}`);
|
||||
for (let j = 0; j < 3; ++j) {
|
||||
setTimeout(() => {
|
||||
DelayNoMore.UdpSession.upsertPeerUdpAddr(peerAddrList, window.boundRoomCapacity, window.mapIns.selfPlayerInfo.JoinIndex); // In C++ impl it actually broadcasts the peer-punching message to all known peers within "window.boundRoomCapacity"
|
||||
DelayNoMore.UdpSession.upsertPeerUdpAddr(peerAddrList, window.boundRoomCapacity, window.mapIns.selfPlayerInfo.joinIndex); // In C++ impl it actually broadcasts the peer-punching message to all known peers within "window.boundRoomCapacity"
|
||||
}, j * 500);
|
||||
}
|
||||
}
|
||||
@@ -288,13 +288,21 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
|
||||
`);
|
||||
}
|
||||
break;
|
||||
case constants.RET_CODE.SAME_PLAYER_ALREADY_IN_SAME_ROOM:
|
||||
mapIns.popupSimplePressToGo("You just logged into a conflicting account, please use a different account to retry", false, () => {
|
||||
window.clearLocalStorageAndBackToLoginScene(true);
|
||||
});
|
||||
break;
|
||||
case constants.RET_CODE.PLAYER_NOT_ADDABLE_TO_ROOM:
|
||||
case constants.RET_CODE.PLAYER_NOT_READDABLE_TO_ROOM:
|
||||
window.clearBoundRoomIdInBothVolatileAndPersistentStorage(); // To favor the player to join other rooms
|
||||
mapIns.onManualRejoinRequired("Couldn't join any room at the moment, please retry");
|
||||
mapIns.popupSimplePressToGo("Couldn't join any room at the moment, please retry", false, () => {
|
||||
window.clearLocalStorageAndBackToLoginScene(true);
|
||||
});
|
||||
break;
|
||||
case constants.RET_CODE.ACTIVE_WATCHDOG:
|
||||
mapIns.onManualRejoinRequired("Disconnected due to long-time inactivity, please rejoin");
|
||||
mapIns.popupSimplePressToGo("Couldn't join any room at the moment, please retry", false, () => {
|
||||
window.clearLocalStorageAndBackToLoginScene(true);
|
||||
});
|
||||
break;
|
||||
case constants.RET_CODE.UNKNOWN_ERROR:
|
||||
case constants.RET_CODE.MYSQL_ERROR:
|
||||
@@ -305,9 +313,19 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
|
||||
console.warn(`${mapIns._stringifyRdfIdToActuallyUsedInput()}
|
||||
`);
|
||||
}
|
||||
mapIns.popupSimplePressToGo("Disconnected unexpectedly, please retry", false, () => {
|
||||
window.clearLocalStorageAndBackToLoginScene(true);
|
||||
break;
|
||||
});
|
||||
default:
|
||||
if (cc.sys.isNative) {
|
||||
// [WARNING] This could be a BUG in CocosCreator JSB implementation of WebSocket client, the "evt.code" is always "undefined" in the "onclose" callback!
|
||||
if (window.ALL_BATTLE_STATES.IN_SETTLEMENT != mapIns.battleState && window.ALL_BATTLE_STATES.IN_DISMISSAL != mapIns.battleState) {
|
||||
|
||||
mapIns.popupSimplePressToGo("Disconnected unexpectedly, please retry", false, () => {
|
||||
window.clearLocalStorageAndBackToLoginScene(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"ver": "1.0.5",
|
||||
"uuid": "eeaa56f4-bd6c-4208-bec4-6ab1aa39ca93",
|
||||
"uuid": "02c5cdc1-9797-49ab-bc11-963215909926",
|
||||
"isPlugin": true,
|
||||
"loadPluginInWeb": true,
|
||||
"loadPluginInNative": true,
|
||||
|
@@ -32,20 +32,43 @@ SendWork* SendRingBuff::pop() {
|
||||
}
|
||||
|
||||
// Recving
|
||||
bool isFullWithLoadedVals(int n, int oldCnt, int oldSt, int oldEd) {
|
||||
return (n <= oldCnt && oldEd == oldSt) || (n > oldCnt && 0 < oldCnt && oldEd == oldSt);
|
||||
}
|
||||
|
||||
void RecvRingBuff::put(char* newBytes, size_t newBytesLen) {
|
||||
RecvWork* slotEle = (&eles[ed.load()]); // Save for later update
|
||||
|
||||
// "RecvRingBuff.ed" is only accessed in "UvRecvThread", thus the order of it relative to the other two is not important.
|
||||
int oldEd = ed.load();
|
||||
|
||||
// We want to increase the success rate of "pop()" if it's being executed by "GameThread/pollUdpRecvRingBuff", thus the below order of loading is IMPORTANT, i.e. load "cnt" first because it's decremented earlier than "st" being incremented.
|
||||
int oldCnt = cnt.load();
|
||||
/*
|
||||
[WARNING]
|
||||
|
||||
Note that "RecvRingBuff.st" might have decremented in "GameThread" by a successful "pop()" between "cnt.load()" and "st.load()" here in "UvRecvThread"! Therefore "n <= oldCnt" doesn't necessarily imply "oldEd == oldSt"!
|
||||
*/
|
||||
int oldSt = st.load(); // Used to guard against "cnt decremented in pop(...), but st not yet incremented and thus return value not yet copied to avoid contamination"
|
||||
int tried = 0;
|
||||
while (n <= oldCnt && !ed.compare_exchange_weak(oldSt, oldSt) && 3 > tried) {
|
||||
/*
|
||||
1. When "n <= oldCnt", it might still be true "oldEd != oldSt" (see the note above);
|
||||
2. When "n > oldCnt", it might still be true that "oldEd == oldSt" if "pop()" hasn't successfully incremented "st" due to any reason;
|
||||
3. When "oldEd == oldSt", it doesn't imply anything useful, because any of the following could be true
|
||||
- a. "n <= oldCnt", i.e. the ringbuff is full
|
||||
- b. "n > oldCnt && 0 < oldCnt" during the execution of "pop()", i.e. the ringbuff is still effectively full
|
||||
- c. "n > oldCnt && 0 == oldCnt", i.e. the ringbuff is empty
|
||||
*/
|
||||
bool isFull = isFullWithLoadedVals(n, oldCnt, oldSt, oldEd);
|
||||
while (isFull && 3 > tried) {
|
||||
// Make room for the new element
|
||||
this->pop(NULL);
|
||||
oldCnt = cnt.load(); // If "pop()" above failed, it'd only be due to concurrent calls to "pop()", either way the updated "cnt" should be good to go
|
||||
oldSt = st.load();
|
||||
isFull = isFullWithLoadedVals(n, oldCnt, oldSt, oldEd);
|
||||
++tried;
|
||||
}
|
||||
if (n <= oldCnt && !ed.compare_exchange_weak(oldSt, oldSt) && 3 == tried) {
|
||||
if (isFull && 3 == tried) {
|
||||
// Failed silently, UDP packet can be dropped.
|
||||
return;
|
||||
}
|
||||
@@ -56,11 +79,13 @@ void RecvRingBuff::put(char* newBytes, size_t newBytesLen) {
|
||||
}
|
||||
|
||||
// No need to compare-and-swap, only "UvRecvThread" will access "RecvRingBuff.ed".
|
||||
ed++;
|
||||
if (ed >= n) {
|
||||
ed -= n; // Deliberately not using "%" operator for performance concern
|
||||
int newEd = oldEd+1;
|
||||
if (newEd >= n) {
|
||||
newEd -= n; // Deliberately not using "%" operator for performance concern
|
||||
}
|
||||
|
||||
ed.compare_exchange_weak(oldEd, newEd); // Definitely succeeds because "RecvRingBuff.ed" is only accessed in "UvRecvThread"
|
||||
|
||||
// Only increment cnt when the putting of new element is fully done.
|
||||
cnt++;
|
||||
}
|
||||
|
@@ -160,7 +160,6 @@ void startRecvLoop(void* arg) {
|
||||
|
||||
int uvCloseRet = uv_loop_close(l);
|
||||
CCLOG("UDP recv loop is closed in UvRecvThread, uvCloseRet=%d", uvCloseRet);
|
||||
uv_mutex_destroy(&recvRingBuffLock);
|
||||
}
|
||||
|
||||
void startSendLoop(void* arg) {
|
||||
@@ -174,7 +173,6 @@ void startSendLoop(void* arg) {
|
||||
|
||||
int uvCloseRet = uv_loop_close(l);
|
||||
CCLOG("UDP send loop is closed in UvSendThread, uvCloseRet=%d", uvCloseRet);
|
||||
uv_mutex_destroy(&sendRingBuffLock);
|
||||
}
|
||||
|
||||
int initSendLoop(struct sockaddr const* pUdpAddr) {
|
||||
@@ -189,9 +187,6 @@ int initSendLoop(struct sockaddr const* pUdpAddr) {
|
||||
uv_mutex_init(&sendRingBuffLock);
|
||||
sendRingBuff = new SendRingBuff(maxBuffedMsgs);
|
||||
|
||||
uv_mutex_init(&recvRingBuffLock);
|
||||
recvRingBuff = new RecvRingBuff(maxBuffedMsgs);
|
||||
|
||||
uv_async_init(sendLoop, &uvSendLoopStopSig, _onUvStopSig);
|
||||
uv_async_init(sendLoop, &uvSendLoopTriggerSig, _onUvSthNewToSend);
|
||||
|
||||
@@ -208,6 +203,9 @@ bool initRecvLoop(struct sockaddr const* pUdpAddr) {
|
||||
CCLOGERROR("Failed to bind recv; recvSockInitRes=%d, recvbindRes=%d, reason=%s", recvSockInitRes, recvbindRes, uv_strerror(recvbindRes));
|
||||
exit(-1);
|
||||
}
|
||||
uv_mutex_init(&recvRingBuffLock);
|
||||
recvRingBuff = new RecvRingBuff(maxBuffedMsgs);
|
||||
|
||||
uv_udp_recv_start(udpRecvSocket, _allocBuffer, _onRead);
|
||||
uv_async_init(recvLoop, &uvRecvLoopStopSig, _onUvStopSig);
|
||||
|
||||
@@ -249,6 +247,12 @@ bool DelayNoMore::UdpSession::openUdpSession(int port) {
|
||||
bool DelayNoMore::UdpSession::closeUdpSession() {
|
||||
CCLOG("About to close udp session and dealloc all resources...");
|
||||
|
||||
/*
|
||||
[WARNING] It's possible that "closeUdpSession" is called when "openUdpSession" was NEVER CALLED, thus we have to avoid program crash in this case.
|
||||
|
||||
In general one shouldn't just check the state of "sendTid" by whether or not "NULL == sendLoop", but in this particular game, both "openUdpSession" and "closeUdpSession" are only called from "GameThread", no thread-safety concern here, i.e. if "openUdpSession" was ever called earlier, then "sendLoop" wouldn't be NULL when "closeUdpSession" is later called.
|
||||
*/
|
||||
if (NULL != sendLoop) {
|
||||
uv_async_send(&uvSendLoopStopSig);
|
||||
CCLOG("Signaling UvSendThread to end in GameThread...");
|
||||
uv_thread_join(&sendTid);
|
||||
@@ -256,6 +260,14 @@ bool DelayNoMore::UdpSession::closeUdpSession() {
|
||||
free(sendLoop);
|
||||
delete sendRingBuff;
|
||||
|
||||
udpSendSocket = NULL;
|
||||
sendLoop = NULL;
|
||||
sendRingBuff = NULL;
|
||||
|
||||
uv_mutex_destroy(&sendRingBuffLock);
|
||||
}
|
||||
|
||||
if (NULL != recvLoop) {
|
||||
uv_async_send(&uvRecvLoopStopSig); // The few if not only guaranteed thread safe utility of libuv :) See http://docs.libuv.org/en/v1.x/async.html#c.uv_async_send
|
||||
CCLOG("Signaling UvRecvThread to end in GameThread...");
|
||||
uv_thread_join(&recvTid);
|
||||
@@ -263,6 +275,13 @@ bool DelayNoMore::UdpSession::closeUdpSession() {
|
||||
free(recvLoop);
|
||||
delete recvRingBuff;
|
||||
|
||||
udpRecvSocket = NULL;
|
||||
recvLoop = NULL;
|
||||
recvRingBuff = NULL;
|
||||
|
||||
uv_mutex_destroy(&recvRingBuffLock);
|
||||
}
|
||||
|
||||
CCLOG("Closed udp session and dealloc all resources in GameThread...");
|
||||
|
||||
return true;
|
||||
@@ -353,11 +372,13 @@ bool DelayNoMore::UdpSession::pollUdpRecvRingBuff() {
|
||||
// This function is called by GameThread 60 fps.
|
||||
|
||||
//uv_mutex_lock(&recvRingBuffLock);
|
||||
while (true) {
|
||||
while (true && NULL != recvLoop) {
|
||||
RecvWork f;
|
||||
bool res = recvRingBuff->pop(&f);
|
||||
if (!res) return false;
|
||||
|
||||
if (!res) {
|
||||
// Deliberately returning "true" here to prevent "jswrapper" from printing "Failed to invoke Xxx..." too frequently
|
||||
return true;
|
||||
}
|
||||
// [WARNING] Declaring "AutoHandleScope" is critical here, otherwise "onUdpMessageCb.toObject()" wouldn't be recognized as a function of the ScriptEngine!
|
||||
se::AutoHandleScope hs;
|
||||
// [WARNING] Use of the "ScriptEngine" is only allowed in "GameThread a.k.a. CocosThread"!
|
||||
|
@@ -27,8 +27,7 @@
|
||||
android:label="@string/app_name"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity="" >
|
||||
android:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
|
@@ -76,7 +76,7 @@
|
||||
"shelter_z_reducer",
|
||||
"shelter"
|
||||
],
|
||||
"last-module-event-record-time": 1675852779064,
|
||||
"last-module-event-record-time": 1678432182471,
|
||||
"simulator-orientation": false,
|
||||
"simulator-resolution": {
|
||||
"height": 640,
|
||||
|
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
MAX_FLOAT64 = 1.7e+308
|
||||
MAX_FLOAT64 = resolv.MaxFloat64
|
||||
MAX_INT32 = int32(999999999)
|
||||
COLLISION_PLAYER_INDEX_PREFIX = (1 << 17)
|
||||
COLLISION_BARRIER_INDEX_PREFIX = (1 << 16)
|
||||
@@ -16,13 +16,13 @@ const (
|
||||
PATTERN_ID_UNABLE_TO_OP = -2
|
||||
PATTERN_ID_NO_OP = -1
|
||||
|
||||
WORLD_TO_VIRTUAL_GRID_RATIO = float64(100.0)
|
||||
WORLD_TO_VIRTUAL_GRID_RATIO = float64(10.0)
|
||||
VIRTUAL_GRID_TO_WORLD_RATIO = float64(1.0) / WORLD_TO_VIRTUAL_GRID_RATIO
|
||||
|
||||
GRAVITY_X = int32(0)
|
||||
GRAVITY_Y = -int32(float64(0.5) * WORLD_TO_VIRTUAL_GRID_RATIO) // makes all "playerCollider.Y" a multiple of 0.5 in all cases
|
||||
|
||||
INPUT_DELAY_FRAMES = int32(6) // in the count of render frames
|
||||
INPUT_DELAY_FRAMES = int32(4) // in the count of render frames
|
||||
|
||||
/*
|
||||
[WARNING]
|
||||
@@ -43,6 +43,11 @@ const (
|
||||
NO_SKILL_HIT = -1
|
||||
|
||||
NO_LOCK_VEL = int32(-1)
|
||||
|
||||
// Used in preallocated RoomDownsyncFrame to check termination
|
||||
TERMINATING_BULLET_LOCAL_ID = int32(-1)
|
||||
TERMINATING_PLAYER_ID = int32(-1)
|
||||
TERMINATING_RENDER_FRAME_ID = int32(-1)
|
||||
)
|
||||
|
||||
// These directions are chosen such that when speed is changed to "(speedX+delta, speedY+delta)" for any of them, the direction is unchanged.
|
||||
@@ -96,6 +101,7 @@ var inAirSet = map[int32]bool{
|
||||
ATK_CHARACTER_STATE_INAIR_ATKED1: true,
|
||||
ATK_CHARACTER_STATE_BLOWN_UP1: true,
|
||||
ATK_CHARACTER_STATE_ONWALL: true,
|
||||
ATK_CHARACTER_STATE_DASHING: true, // Yes dashing is an inair state even if you dashed on the ground :)
|
||||
}
|
||||
|
||||
var noOpSet = map[int32]bool{
|
||||
@@ -211,26 +217,43 @@ func calcPushbacks(oldDx, oldDy float64, playerShape, barrierShape *resolv.Conve
|
||||
}
|
||||
|
||||
func isPolygonPairOverlapped(a, b *resolv.ConvexPolygon, result *SatResult) bool {
|
||||
aCnt, bCnt := len(a.Points), len(b.Points)
|
||||
aCnt, bCnt := a.Points.Cnt, b.Points.Cnt
|
||||
// Single point case
|
||||
if 1 == aCnt && 1 == bCnt {
|
||||
if nil != result {
|
||||
result.Overlap = 0
|
||||
}
|
||||
return a.Points[0][0] == b.Points[0][0] && a.Points[0][1] == b.Points[0][1]
|
||||
aPoint := a.GetPointByOffset(0)
|
||||
bPoint := b.GetPointByOffset(0)
|
||||
return aPoint[0] == bPoint[0] && aPoint[1] == bPoint[1]
|
||||
}
|
||||
|
||||
if 1 < aCnt {
|
||||
for _, axis := range a.SATAxes() {
|
||||
if isPolygonPairSeparatedByDir(a, b, axis.Unit(), result) {
|
||||
// Deliberately using "Points" instead of "SATAxes" to avoid unnecessary heap memory alloc
|
||||
for i := int32(0); i < a.Points.Cnt; i++ {
|
||||
u, v := a.GetPointByOffset(i), a.GetPointByOffset(0)
|
||||
if i != a.Points.Cnt-1 {
|
||||
v = a.GetPointByOffset(i + 1)
|
||||
}
|
||||
dy := v[1] - u[1]
|
||||
dx := v[0] - u[0]
|
||||
axis := resolv.Vector{dy, -dx}.Unit()
|
||||
if isPolygonPairSeparatedByDir(a, b, axis, result) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if 1 < bCnt {
|
||||
for _, axis := range b.SATAxes() {
|
||||
if isPolygonPairSeparatedByDir(a, b, axis.Unit(), result) {
|
||||
for i := int32(0); i < b.Points.Cnt; i++ {
|
||||
u, v := b.GetPointByOffset(i), b.GetPointByOffset(0)
|
||||
if i != b.Points.Cnt-1 {
|
||||
v = b.GetPointByOffset(i + 1)
|
||||
}
|
||||
dy := v[1] - u[1]
|
||||
dx := v[0] - u[0]
|
||||
axis := resolv.Vector{dy, -dx}.Unit()
|
||||
if isPolygonPairSeparatedByDir(a, b, axis, result) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -239,11 +262,15 @@ func isPolygonPairOverlapped(a, b *resolv.ConvexPolygon, result *SatResult) bool
|
||||
return true
|
||||
}
|
||||
|
||||
func IsMeleeBulletActive(meleeBullet *MeleeBullet, currRenderFrame *RoomDownsyncFrame) bool {
|
||||
if BULLET_EXPLODING == meleeBullet.BlState {
|
||||
func IsGeneralBulletActive(blState, originatedRenderFrameId, startupFrames, activeFrames, renderFrameId int32) bool {
|
||||
if BULLET_EXPLODING == blState {
|
||||
return false
|
||||
}
|
||||
return (meleeBullet.BattleAttr.OriginatedRenderFrameId+meleeBullet.Bullet.StartupFrames <= currRenderFrame.Id) && (meleeBullet.BattleAttr.OriginatedRenderFrameId+meleeBullet.Bullet.StartupFrames+meleeBullet.Bullet.ActiveFrames > currRenderFrame.Id)
|
||||
return (originatedRenderFrameId+startupFrames < renderFrameId) && (originatedRenderFrameId+startupFrames+activeFrames > renderFrameId)
|
||||
}
|
||||
|
||||
func IsMeleeBulletActive(meleeBullet *MeleeBullet, currRenderFrame *RoomDownsyncFrame) bool {
|
||||
return IsGeneralBulletActive(meleeBullet.BlState, meleeBullet.BattleAttr.OriginatedRenderFrameId, meleeBullet.Bullet.StartupFrames, meleeBullet.Bullet.ActiveFrames, currRenderFrame.Id)
|
||||
}
|
||||
|
||||
func IsMeleeBulletAlive(meleeBullet *MeleeBullet, currRenderFrame *RoomDownsyncFrame) bool {
|
||||
@@ -254,10 +281,7 @@ func IsMeleeBulletAlive(meleeBullet *MeleeBullet, currRenderFrame *RoomDownsyncF
|
||||
}
|
||||
|
||||
func IsFireballBulletActive(fireballBullet *FireballBullet, currRenderFrame *RoomDownsyncFrame) bool {
|
||||
if BULLET_EXPLODING == fireballBullet.BlState {
|
||||
return false
|
||||
}
|
||||
return (fireballBullet.BattleAttr.OriginatedRenderFrameId+fireballBullet.Bullet.StartupFrames < currRenderFrame.Id) && (fireballBullet.BattleAttr.OriginatedRenderFrameId+fireballBullet.Bullet.StartupFrames+fireballBullet.Bullet.ActiveFrames > currRenderFrame.Id)
|
||||
return IsGeneralBulletActive(fireballBullet.BlState, fireballBullet.BattleAttr.OriginatedRenderFrameId, fireballBullet.Bullet.StartupFrames, fireballBullet.Bullet.ActiveFrames, currRenderFrame.Id)
|
||||
}
|
||||
|
||||
func IsFireballBulletAlive(fireballBullet *FireballBullet, currRenderFrame *RoomDownsyncFrame) bool {
|
||||
@@ -285,7 +309,8 @@ func isPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e resolv.Vector, re
|
||||
*/
|
||||
|
||||
var aStart, aEnd, bStart, bEnd float64 = MAX_FLOAT64, -MAX_FLOAT64, MAX_FLOAT64, -MAX_FLOAT64
|
||||
for _, p := range a.Points {
|
||||
for i := int32(0); i < a.Points.Cnt; i++ {
|
||||
p := a.GetPointByOffset(i)
|
||||
dot := (p[0]+a.X)*e[0] + (p[1]+a.Y)*e[1]
|
||||
|
||||
if aStart > dot {
|
||||
@@ -297,7 +322,8 @@ func isPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e resolv.Vector, re
|
||||
}
|
||||
}
|
||||
|
||||
for _, p := range b.Points {
|
||||
for i := int32(0); i < b.Points.Cnt; i++ {
|
||||
p := b.GetPointByOffset(i)
|
||||
dot := (p[0]+b.X)*e[0] + (p[1]+b.Y)*e[1]
|
||||
|
||||
if bStart > dot {
|
||||
@@ -406,8 +432,7 @@ func VirtualGridToPolygonColliderBLPos(vx, vy int32, halfBoundingW, halfBounding
|
||||
return WorldToPolygonColliderBLPos(wx, wy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY)
|
||||
}
|
||||
|
||||
func calcHardPushbacksNorms(joinIndex int32, currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync, playerCollider *resolv.Object, playerShape *resolv.ConvexPolygon, snapIntoPlatformOverlap float64, pEffPushback *Vec2D) *[]Vec2D {
|
||||
ret := make([]Vec2D, 0, 10) // no one would simultaneously have more than 5 hardPushbacks
|
||||
func calcHardPushbacksNorms(joinIndex int32, currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync, playerCollider *resolv.Object, playerShape *resolv.ConvexPolygon, snapIntoPlatformOverlap float64, effPushback *Vec2D, hardPushbackNorms []*Vec2D, collision *resolv.Collision) int {
|
||||
virtualGripToWall := float64(0)
|
||||
if ATK_CHARACTER_STATE_ONWALL == currPlayerDownsync.CharacterState && 0 == thatPlayerInNextFrame.VelX && currPlayerDownsync.DirX == thatPlayerInNextFrame.DirX {
|
||||
/*
|
||||
@@ -424,14 +449,19 @@ func calcHardPushbacksNorms(joinIndex int32, currPlayerDownsync, thatPlayerInNex
|
||||
}
|
||||
virtualGripToWall = xfac * float64(currPlayerDownsync.Speed) * VIRTUAL_GRID_TO_WORLD_RATIO
|
||||
}
|
||||
collision := playerCollider.Check(virtualGripToWall, 0)
|
||||
if nil == collision {
|
||||
return &ret
|
||||
retCnt := 0
|
||||
collided := playerCollider.CheckAllWithHolder(virtualGripToWall, 0, collision)
|
||||
if !collided {
|
||||
return retCnt
|
||||
}
|
||||
|
||||
//playerColliderCenterX, playerColliderCenterY := playerCollider.Center()
|
||||
//fmt.Printf("joinIndex=%d calcHardPushbacksNorms has non-empty collision;playerColliderPos=(%.2f,%.2f)\n", joinIndex, playerColliderCenterX, playerColliderCenterY)
|
||||
for _, obj := range collision.Objects {
|
||||
for true {
|
||||
obj := collision.PopFirstCollidedObject()
|
||||
if nil == obj {
|
||||
break
|
||||
}
|
||||
isBarrier := false
|
||||
switch obj.Data.(type) {
|
||||
case *PlayerDownsync, *MeleeBullet, *FireballBullet:
|
||||
@@ -451,15 +481,39 @@ func calcHardPushbacksNorms(joinIndex int32, currPlayerDownsync, thatPlayerInNex
|
||||
// ALWAY snap into hardPushbacks!
|
||||
// [OverlapX, OverlapY] is the unit vector that points into the platform
|
||||
pushbackX, pushbackY = (overlapResult.Overlap-snapIntoPlatformOverlap)*overlapResult.OverlapX, (overlapResult.Overlap-snapIntoPlatformOverlap)*overlapResult.OverlapY
|
||||
ret = append(ret, Vec2D{X: overlapResult.OverlapX, Y: overlapResult.OverlapY})
|
||||
pEffPushback.X += pushbackX
|
||||
pEffPushback.Y += pushbackY
|
||||
hardPushbackNorms[retCnt].X, hardPushbackNorms[retCnt].Y = overlapResult.OverlapX, overlapResult.OverlapY
|
||||
effPushback.X += pushbackX
|
||||
effPushback.Y += pushbackY
|
||||
retCnt++
|
||||
//fmt.Printf("joinIndex=%d calcHardPushbacksNorms found one hardpushback; immediatePushback=(%.2f,%.2f)\n", joinIndex, pushbackX, pushbackY)
|
||||
}
|
||||
return &ret
|
||||
return retCnt
|
||||
}
|
||||
|
||||
func deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync, currRenderFrame *RoomDownsyncFrame, chConfig *CharacterConfig, inputsBuffer *RingBuffer) (int, bool, int32, int32) {
|
||||
func UpdateInputFrameInPlaceUponDynamics(inputFrameId int32, roomCapacity int, confirmedList uint64, inputList []uint64, lastIndividuallyConfirmedInputFrameId []int32, lastIndividuallyConfirmedInputList []uint64, toExcludeJoinIndexUpdateInputFrameInPlaceUponDynamics int32) bool {
|
||||
hasInputFrameUpdatedOnDynamics := false
|
||||
for i := 0; i < roomCapacity; i++ {
|
||||
if int32(i+1) == toExcludeJoinIndexUpdateInputFrameInPlaceUponDynamics {
|
||||
// On frontend, a "self input" is only confirmed by websocket downsync, which is quite late and might get the "self input" incorrectly overwritten if not excluded here
|
||||
continue
|
||||
}
|
||||
if 0 < (confirmedList & (1 << uint32(i))) {
|
||||
// This in-place update on the "inputsBuffer" is only correct when "delayed input for this player is not yet confirmed"
|
||||
continue
|
||||
}
|
||||
if lastIndividuallyConfirmedInputFrameId[i] >= inputFrameId {
|
||||
continue
|
||||
}
|
||||
newVal := (lastIndividuallyConfirmedInputList[i] & uint64(15))
|
||||
if newVal != inputList[i] {
|
||||
inputList[i] = newVal
|
||||
hasInputFrameUpdatedOnDynamics = true
|
||||
}
|
||||
}
|
||||
return hasInputFrameUpdatedOnDynamics
|
||||
}
|
||||
|
||||
func deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync, currRenderFrame *RoomDownsyncFrame, chConfig *CharacterConfig, inputsBuffer *resolv.RingBuffer) (int, bool, int32, int32) {
|
||||
// returns (patternId, jumpedOrNot, effectiveDx, effectiveDy)
|
||||
delayedInputFrameId := ConvertToDelayedInputFrameId(currRenderFrame.Id)
|
||||
delayedInputFrameIdForPrevRdf := ConvertToDelayedInputFrameId(currRenderFrame.Id - 1)
|
||||
@@ -472,10 +526,13 @@ func deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync,
|
||||
return PATTERN_ID_UNABLE_TO_OP, false, 0, 0
|
||||
}
|
||||
|
||||
delayedInputList := inputsBuffer.GetByFrameId(delayedInputFrameId).(*InputFrameDownsync).InputList
|
||||
delayedInputFrameDownsync := inputsBuffer.GetByFrameId(delayedInputFrameId).(*InputFrameDownsync)
|
||||
delayedInputList := delayedInputFrameDownsync.InputList
|
||||
|
||||
var delayedInputListForPrevRdf []uint64 = nil
|
||||
if 0 < delayedInputFrameIdForPrevRdf {
|
||||
delayedInputListForPrevRdf = inputsBuffer.GetByFrameId(delayedInputFrameIdForPrevRdf).(*InputFrameDownsync).InputList
|
||||
delayedInputFrameDownsyncForPrevRdf := inputsBuffer.GetByFrameId(delayedInputFrameIdForPrevRdf).(*InputFrameDownsync)
|
||||
delayedInputListForPrevRdf = delayedInputFrameDownsyncForPrevRdf.InputList
|
||||
}
|
||||
|
||||
jumpedOrNot := false
|
||||
@@ -531,82 +588,66 @@ func deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync,
|
||||
/*
|
||||
[LONG TERM PERFORMANCE ENHANCEMENT PLAN]
|
||||
|
||||
The function "ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame" is creating new heap-memory blocks at 60fps, e.g. nextRenderFramePlayers & nextRenderFrameMeleeBullets & nextRenderFrameFireballBullets & effPushbacks & hardPushbackNorms & jumpedOrNotList & playerColliders & bulletColliders, which would induce "possibly performance impacting garbage collections" when many rooms are running simultaneously.
|
||||
|
||||
It's not easy to remove all of the dynamic heap-memory blocks allocation/deallocation, but we can reduce them to some extent. For example, the creation of new "RoomDownsyncFrame" in heap-memory can be avoided by adding
|
||||
|
||||
```
|
||||
func overwriteRoomDownsyncFrame(src *RoomDownsyncFrame, dst *RoomDownsyncFrame) {
|
||||
// Copy "src" into "dst" down to every primitive field; as for a same room, the "RenderFrameBuffer" is always accessed (R & W) by a same kernel thread (both frontend & backend), no thread-safety concern here
|
||||
}
|
||||
|
||||
type Room struct {
|
||||
newRoomDownsyncFrameHolder *RoomDownsyncFrame
|
||||
}
|
||||
|
||||
func (pR *Room) provisionNewRoomDownsyncFrameHolder(src *RoomDownsyncFrame) {
|
||||
overwriteRoomDownsyncFrame(src, pR.newRoomDownsyncFrameHolder)
|
||||
}
|
||||
```
|
||||
|
||||
then pass in the whole "renderFrameBuffer *SpecificRingBuffer" to this function and overwrite the target slot IN-PLACE, i.e. need write new "SpecificRingBuffer.Put/SetByFrameId" to use the new function "overwriteRoomDownsyncFrame(src, dst)" to keep "%p of every SpecificRingBuffer.Eles[i]" constant.
|
||||
|
||||
However, the enhancement for "playerColliders & bulletColliders" of each room is even more difficult, because the feasibility of doing in-place overwrites depends on the collision library in use.
|
||||
The function "ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame" is creating new heap-memory blocks at 60fps, e.g. nextRenderFramePlayers & nextRenderFrameMeleeBullets & nextRenderFrameFireballBullets & effPushbacks & hardPushbackNorms & jumpedOrNotList & dynamicRectangleColliders("player" & "bullet"), which would induce "possibly performance impacting garbage collections" when many rooms are running simultaneously.
|
||||
*/
|
||||
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer, currRenderFrame *RoomDownsyncFrame, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, collisionSpaceOffsetX, collisionSpaceOffsetY float64, chConfigsOrderedByJoinIndex []*CharacterConfig) *RoomDownsyncFrame {
|
||||
// [WARNING] On backend this function MUST BE called while "InputsBufferLock" is locked!
|
||||
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.RingBuffer, currRenderFrameId int32, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, collisionSpaceOffsetX, collisionSpaceOffsetY float64, chConfigsOrderedByJoinIndex []*CharacterConfig, renderFrameBuffer *resolv.RingBuffer, collision *resolv.Collision, effPushbacks []*Vec2D, hardPushbackNormsArr [][]*Vec2D, jumpedOrNotList []bool, dynamicRectangleColliders []*resolv.Object, lastIndividuallyConfirmedInputFrameId []int32, lastIndividuallyConfirmedInputList []uint64, allowUpdateInputFrameInPlaceUponDynamics bool, toExcludeJoinIndexUpdateInputFrameInPlaceUponDynamics int32) bool {
|
||||
hasInputFrameUpdatedOnDynamics := false
|
||||
currRenderFrame := renderFrameBuffer.GetByFrameId(currRenderFrameId).(*RoomDownsyncFrame)
|
||||
nextRenderFrameId := currRenderFrameId + 1
|
||||
roomCapacity := len(currRenderFrame.PlayersArr)
|
||||
nextRenderFramePlayers := make([]*PlayerDownsync, roomCapacity)
|
||||
var ret *RoomDownsyncFrame = nil
|
||||
candidate := renderFrameBuffer.GetByFrameId(nextRenderFrameId)
|
||||
if nil == candidate {
|
||||
if nextRenderFrameId == renderFrameBuffer.EdFrameId {
|
||||
renderFrameBuffer.DryPut()
|
||||
candidate = renderFrameBuffer.GetByFrameId(nextRenderFrameId)
|
||||
if nil == candidate {
|
||||
// Lazy alloc heap-mem for holder
|
||||
ret = NewPreallocatedRoomDownsyncFrame(roomCapacity, 64, 64)
|
||||
renderFrameBuffer.SetByFrameId(ret, nextRenderFrameId)
|
||||
} else {
|
||||
ret = candidate.(*RoomDownsyncFrame)
|
||||
}
|
||||
} else {
|
||||
panic("Invalid nextRenderFrameId=" + string(nextRenderFrameId) + "!")
|
||||
}
|
||||
} else {
|
||||
ret = candidate.(*RoomDownsyncFrame)
|
||||
}
|
||||
// [WARNING] On backend this function MUST BE called while "InputsBufferLock" is locked!
|
||||
nextRenderFramePlayers := ret.PlayersArr
|
||||
// Make a copy first
|
||||
for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
|
||||
nextRenderFramePlayers[i] = &PlayerDownsync{
|
||||
Id: currPlayerDownsync.Id,
|
||||
VirtualGridX: currPlayerDownsync.VirtualGridX,
|
||||
VirtualGridY: currPlayerDownsync.VirtualGridY,
|
||||
DirX: currPlayerDownsync.DirX,
|
||||
DirY: currPlayerDownsync.DirY,
|
||||
VelX: currPlayerDownsync.VelX,
|
||||
VelY: currPlayerDownsync.VelY,
|
||||
CharacterState: currPlayerDownsync.CharacterState,
|
||||
InAir: true,
|
||||
OnWall: false,
|
||||
Speed: currPlayerDownsync.Speed,
|
||||
BattleState: currPlayerDownsync.BattleState,
|
||||
Score: currPlayerDownsync.Score,
|
||||
Removed: currPlayerDownsync.Removed,
|
||||
JoinIndex: currPlayerDownsync.JoinIndex,
|
||||
Hp: currPlayerDownsync.Hp,
|
||||
MaxHp: currPlayerDownsync.MaxHp,
|
||||
FramesToRecover: currPlayerDownsync.FramesToRecover - 1,
|
||||
FramesInChState: currPlayerDownsync.FramesInChState + 1,
|
||||
ActiveSkillId: currPlayerDownsync.ActiveSkillId,
|
||||
ActiveSkillHit: currPlayerDownsync.ActiveSkillHit,
|
||||
FramesInvinsible: currPlayerDownsync.FramesInvinsible - 1,
|
||||
BulletTeamId: currPlayerDownsync.BulletTeamId,
|
||||
ChCollisionTeamId: currPlayerDownsync.ChCollisionTeamId,
|
||||
RevivalVirtualGridX: currPlayerDownsync.RevivalVirtualGridX,
|
||||
RevivalVirtualGridY: currPlayerDownsync.RevivalVirtualGridY,
|
||||
ColliderRadius: currPlayerDownsync.ColliderRadius,
|
||||
OnWallNormX: currPlayerDownsync.OnWallNormX,
|
||||
OnWallNormY: currPlayerDownsync.OnWallNormY,
|
||||
CapturedByInertia: currPlayerDownsync.CapturedByInertia,
|
||||
for i, src := range currRenderFrame.PlayersArr {
|
||||
framesToRecover := src.FramesToRecover - 1
|
||||
framesInChState := src.FramesInChState + 1
|
||||
framesInvinsible := src.FramesInvinsible - 1
|
||||
if framesToRecover < 0 {
|
||||
framesToRecover = 0
|
||||
}
|
||||
if nextRenderFramePlayers[i].FramesToRecover < 0 {
|
||||
nextRenderFramePlayers[i].FramesToRecover = 0
|
||||
}
|
||||
if nextRenderFramePlayers[i].FramesInvinsible < 0 {
|
||||
nextRenderFramePlayers[i].FramesInvinsible = 0
|
||||
if framesInvinsible < 0 {
|
||||
framesInvinsible = 0
|
||||
}
|
||||
ClonePlayerDownsync(src.Id, src.VirtualGridX, src.VirtualGridY, src.DirX, src.DirY, src.VelX, src.VelY, framesToRecover, framesInChState, src.ActiveSkillId, src.ActiveSkillHit, framesInvinsible, src.Speed, src.BattleState, src.CharacterState, src.JoinIndex, src.Hp, src.MaxHp, src.ColliderRadius, true, false, src.OnWallNormX, src.OnWallNormY, src.CapturedByInertia, src.BulletTeamId, src.ChCollisionTeamId, src.RevivalVirtualGridX, src.RevivalVirtualGridY, nextRenderFramePlayers[i])
|
||||
}
|
||||
|
||||
nextRenderFrameMeleeBullets := make([]*MeleeBullet, 0, len(currRenderFrame.MeleeBullets)) // Is there any better way to reduce malloc/free impact, e.g. smart prediction for fixed memory allocation?
|
||||
nextRenderFrameFireballBullets := make([]*FireballBullet, 0, len(currRenderFrame.FireballBullets))
|
||||
effPushbacks := make([]Vec2D, roomCapacity)
|
||||
hardPushbackNorms := make([]*[]Vec2D, roomCapacity)
|
||||
jumpedOrNotList := make([]bool, roomCapacity)
|
||||
meleeBulletCnt := 0
|
||||
nextRenderFrameMeleeBullets := ret.MeleeBullets
|
||||
fireballBulletCnt := 0
|
||||
nextRenderFrameFireballBullets := ret.FireballBullets
|
||||
|
||||
bulletLocalId := currRenderFrame.BulletLocalIdCounter
|
||||
// 1. Process player inputs
|
||||
delayedInputFrameId := ConvertToDelayedInputFrameId(currRenderFrame.Id)
|
||||
|
||||
if 0 < delayedInputFrameId {
|
||||
delayedInputFrameDownsync := inputsBuffer.GetByFrameId(delayedInputFrameId).(*InputFrameDownsync)
|
||||
delayedInputList := delayedInputFrameDownsync.InputList
|
||||
roomCapacity := len(delayedInputList)
|
||||
if allowUpdateInputFrameInPlaceUponDynamics {
|
||||
hasInputFrameUpdatedOnDynamics = UpdateInputFrameInPlaceUponDynamics(delayedInputFrameId, roomCapacity, delayedInputFrameDownsync.ConfirmedList, delayedInputList, lastIndividuallyConfirmedInputFrameId, lastIndividuallyConfirmedInputList, toExcludeJoinIndexUpdateInputFrameInPlaceUponDynamics)
|
||||
}
|
||||
}
|
||||
|
||||
for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
|
||||
chConfig := chConfigsOrderedByJoinIndex[i]
|
||||
thatPlayerInNextFrame := nextRenderFramePlayers[i]
|
||||
@@ -614,7 +655,7 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
|
||||
|
||||
jumpedOrNotList[i] = jumpedOrNot
|
||||
joinIndex := currPlayerDownsync.JoinIndex
|
||||
skillId := chConfig.SkillMapper(patternId, currPlayerDownsync)
|
||||
skillId := chConfig.SkillMapper(patternId, currPlayerDownsync, chConfig.SpeciesId)
|
||||
if skillConfig, existent := skills[skillId]; existent {
|
||||
thatPlayerInNextFrame.ActiveSkillId = int32(skillId)
|
||||
thatPlayerInNextFrame.ActiveSkillHit = 0
|
||||
@@ -628,16 +669,9 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
|
||||
// Hardcoded to use only the first hit for now
|
||||
switch v := skillConfig.Hits[thatPlayerInNextFrame.ActiveSkillHit].(type) {
|
||||
case *MeleeBullet:
|
||||
var newBullet MeleeBullet = *v // Copied primitive fields into an onstack variable
|
||||
newBullet.BattleAttr = &BulletBattleAttr{
|
||||
BulletLocalId: bulletLocalId,
|
||||
OriginatedRenderFrameId: currRenderFrame.Id,
|
||||
OffenderJoinIndex: joinIndex,
|
||||
TeamId: currPlayerDownsync.BulletTeamId,
|
||||
}
|
||||
CloneMeleeBullet(BULLET_STARTUP, 0, bulletLocalId, currRenderFrameId, joinIndex, currPlayerDownsync.BulletTeamId, v.Bullet, nextRenderFrameMeleeBullets[meleeBulletCnt])
|
||||
bulletLocalId++
|
||||
newBullet.BlState = BULLET_STARTUP
|
||||
nextRenderFrameMeleeBullets = append(nextRenderFrameMeleeBullets, &newBullet)
|
||||
meleeBulletCnt++
|
||||
if NO_LOCK_VEL != v.Bullet.SelfLockVelX {
|
||||
hasLockVel = true
|
||||
thatPlayerInNextFrame.VelX = xfac * v.Bullet.SelfLockVelX
|
||||
@@ -647,23 +681,9 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
|
||||
thatPlayerInNextFrame.VelY = v.Bullet.SelfLockVelY
|
||||
}
|
||||
case *FireballBullet:
|
||||
var newBullet FireballBullet = *v // Copied primitive fields into an onstack variable
|
||||
newBullet.BattleAttr = &BulletBattleAttr{
|
||||
BulletLocalId: bulletLocalId,
|
||||
OriginatedRenderFrameId: currRenderFrame.Id,
|
||||
OffenderJoinIndex: joinIndex,
|
||||
TeamId: currPlayerDownsync.BulletTeamId,
|
||||
}
|
||||
CloneFireballBullet(BULLET_STARTUP, 0, currPlayerDownsync.VirtualGridX+xfac*v.Bullet.HitboxOffsetX, currPlayerDownsync.VirtualGridY+v.Bullet.HitboxOffsetY, xfac, 0, v.Speed*xfac, 0, v.Speed, bulletLocalId, currRenderFrameId, joinIndex, currPlayerDownsync.BulletTeamId, v.Bullet, nextRenderFrameFireballBullets[fireballBulletCnt])
|
||||
bulletLocalId++
|
||||
newBullet.VirtualGridX, newBullet.VirtualGridY = currPlayerDownsync.VirtualGridX+xfac*newBullet.Bullet.HitboxOffsetX, currPlayerDownsync.VirtualGridY+newBullet.Bullet.HitboxOffsetY
|
||||
newBullet.DirX = xfac
|
||||
newBullet.DirY = 0
|
||||
newBullet.VelX = newBullet.Speed * xfac
|
||||
newBullet.VelY = 0
|
||||
|
||||
newBullet.BlState = BULLET_STARTUP
|
||||
nextRenderFrameFireballBullets = append(nextRenderFrameFireballBullets, &newBullet)
|
||||
//fmt.Printf("Created new fireball @currRenderFrame.Id=%d, %p, bulletLocalId=%d, virtualGridX=%d, virtualGridY=%d, offenderVpos=(%d,%d)\n", currRenderFrame.Id, &newBullet, bulletLocalId, newBullet.VirtualGridX, newBullet.VirtualGridY, currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY)
|
||||
fireballBulletCnt++
|
||||
if NO_LOCK_VEL != v.Bullet.SelfLockVelX {
|
||||
hasLockVel = true
|
||||
thatPlayerInNextFrame.VelX = xfac * v.Bullet.SelfLockVelX
|
||||
@@ -716,7 +736,9 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
|
||||
} else if stoppingFromWalking {
|
||||
thatPlayerInNextFrame.FramesToRecover = chConfig.InertiaFramesToRecover
|
||||
} else {
|
||||
thatPlayerInNextFrame.FramesToRecover = ((chConfig.InertiaFramesToRecover >> 1) + (chConfig.InertiaFramesToRecover >> 2))
|
||||
// Updates CharacterState and thus the animation to make user see graphical feedback asap.
|
||||
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_WALKING
|
||||
thatPlayerInNextFrame.FramesToRecover = (chConfig.InertiaFramesToRecover >> 1)
|
||||
}
|
||||
} else {
|
||||
thatPlayerInNextFrame.CapturedByInertia = false
|
||||
@@ -744,8 +766,14 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
[WARNING]
|
||||
1. The dynamic colliders will all be removed from "Space" at the end of this function due to the need for being rollback-compatible.
|
||||
2. To achieve "zero gc" in "ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame", I deliberately chose a collision system that doesn't use dynamic tree node alloc.
|
||||
*/
|
||||
colliderCnt := 0
|
||||
|
||||
// 2. Process player movement
|
||||
playerColliders := make([]*resolv.Object, len(currRenderFrame.PlayersArr), len(currRenderFrame.PlayersArr)) // Will all be removed at the end of this function due to the need for being rollback-compatible
|
||||
for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
|
||||
joinIndex := currPlayerDownsync.JoinIndex
|
||||
effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y = float64(0), float64(0)
|
||||
@@ -804,11 +832,12 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
|
||||
|
||||
colliderWorldWidth, colliderWorldHeight := VirtualGridToWorldPos(colliderWidth, colliderHeight)
|
||||
|
||||
playerCollider := GenerateRectCollider(wx, wy, colliderWorldWidth, colliderWorldHeight, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, collisionSpaceOffsetX, collisionSpaceOffsetY, currPlayerDownsync, "Player") // the coords of all barrier boundaries are multiples of tileWidth(i.e. 16), by adding snapping y-padding when "landedOnGravityPushback" all "playerCollider.Y" would be a multiple of 1.0
|
||||
playerColliders[i] = playerCollider
|
||||
playerCollider := dynamicRectangleColliders[colliderCnt]
|
||||
UpdateRectCollider(playerCollider, wx, wy, colliderWorldWidth, colliderWorldHeight, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, collisionSpaceOffsetX, collisionSpaceOffsetY, currPlayerDownsync, "Player") // the coords of all barrier boundaries are multiples of tileWidth(i.e. 16), by adding snapping y-padding when "landedOnGravityPushback" all "playerCollider.Y" would be a multiple of 1.0
|
||||
colliderCnt++
|
||||
|
||||
// Add to collision system
|
||||
collisionSys.Add(playerCollider)
|
||||
collisionSys.AddSingle(playerCollider)
|
||||
|
||||
if currPlayerDownsync.InAir {
|
||||
if ATK_CHARACTER_STATE_ONWALL == currPlayerDownsync.CharacterState && !jumpedOrNotList[i] {
|
||||
@@ -823,30 +852,25 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Add bullet colliders into collision system
|
||||
// 3. Add bullet colliders into collision system; [DIRTY TRICK] Players always precede bullets in "dynamicRectangleColliders".
|
||||
// [WARNING] For rollback compatibility, static data of "BulletConfig" & "BattleAttr(static since instantiated)" can just be copies of the pointers in "RenderFrameBuffer", however, FireballBullets movement data as well as bullet animation data must be copies of instances for each RenderFrame!
|
||||
bulletColliders := make([]*resolv.Object, 0, ((len(currRenderFrame.MeleeBullets) + len(currRenderFrame.FireballBullets)) << 1)) // Will all be removed at the end of this function due to the need for being rollback-compatible
|
||||
for _, prevFireball := range currRenderFrame.FireballBullets {
|
||||
fireballBullet := &FireballBullet{
|
||||
VirtualGridX: prevFireball.VirtualGridX,
|
||||
VirtualGridY: prevFireball.VirtualGridY,
|
||||
DirX: prevFireball.DirX,
|
||||
DirY: prevFireball.DirY,
|
||||
VelX: prevFireball.VelX,
|
||||
VelY: prevFireball.VelY,
|
||||
Speed: prevFireball.Speed,
|
||||
Bullet: prevFireball.Bullet,
|
||||
BattleAttr: prevFireball.BattleAttr,
|
||||
FramesInBlState: prevFireball.FramesInBlState + 1,
|
||||
BlState: prevFireball.BlState,
|
||||
if TERMINATING_BULLET_LOCAL_ID == prevFireball.BattleAttr.BulletLocalId {
|
||||
break
|
||||
}
|
||||
fireballBullet := nextRenderFrameFireballBullets[fireballBulletCnt]
|
||||
CloneFireballBullet(prevFireball.BlState, prevFireball.FramesInBlState+1, prevFireball.VirtualGridX, prevFireball.VirtualGridY, prevFireball.DirX, prevFireball.DirY, prevFireball.VelX, prevFireball.VelY, prevFireball.Speed, prevFireball.BattleAttr.BulletLocalId, prevFireball.BattleAttr.OriginatedRenderFrameId, prevFireball.BattleAttr.OffenderJoinIndex, prevFireball.BattleAttr.TeamId, prevFireball.Bullet, fireballBullet)
|
||||
|
||||
if IsFireballBulletAlive(fireballBullet, currRenderFrame) {
|
||||
if IsFireballBulletActive(fireballBullet, currRenderFrame) {
|
||||
bulletWx, bulletWy := VirtualGridToWorldPos(fireballBullet.VirtualGridX, fireballBullet.VirtualGridY)
|
||||
hitboxSizeWx, hitboxSizeWy := VirtualGridToWorldPos(fireballBullet.Bullet.HitboxSizeX, fireballBullet.Bullet.HitboxSizeY)
|
||||
newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, hitboxSizeWx, hitboxSizeWy, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, collisionSpaceOffsetX, collisionSpaceOffsetY, fireballBullet, "FireballBullet")
|
||||
collisionSys.Add(newBulletCollider)
|
||||
bulletColliders = append(bulletColliders, newBulletCollider)
|
||||
|
||||
newBulletCollider := dynamicRectangleColliders[colliderCnt]
|
||||
UpdateRectCollider(newBulletCollider, bulletWx, bulletWy, hitboxSizeWx, hitboxSizeWy, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, collisionSpaceOffsetX, collisionSpaceOffsetY, fireballBullet, "FireballBullet")
|
||||
colliderCnt++
|
||||
|
||||
collisionSys.AddSingle(newBulletCollider)
|
||||
fireballBullet.BlState = BULLET_ACTIVE
|
||||
if fireballBullet.BlState != prevFireball.BlState {
|
||||
fireballBullet.FramesInBlState = 0
|
||||
@@ -861,17 +885,19 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
|
||||
}
|
||||
//fmt.Printf("Pushing non-active fireball to next frame @currRenderFrame.Id=%d, bulletLocalId=%d, virtualGridX=%d, virtualGridY=%d, blState=%d\n", currRenderFrame.Id, fireballBullet.BattleAttr.BulletLocalId, fireballBullet.VirtualGridX, fireballBullet.VirtualGridY, fireballBullet.BlState)
|
||||
}
|
||||
nextRenderFrameFireballBullets = append(nextRenderFrameFireballBullets, fireballBullet)
|
||||
fireballBulletCnt++
|
||||
}
|
||||
}
|
||||
// Explicitly specify termination of fireball bullets
|
||||
nextRenderFrameFireballBullets[fireballBulletCnt].BattleAttr.BulletLocalId = TERMINATING_BULLET_LOCAL_ID
|
||||
|
||||
for _, prevMelee := range currRenderFrame.MeleeBullets {
|
||||
meleeBullet := &MeleeBullet{
|
||||
Bullet: prevMelee.Bullet,
|
||||
BattleAttr: prevMelee.BattleAttr,
|
||||
FramesInBlState: prevMelee.FramesInBlState + 1,
|
||||
BlState: prevMelee.BlState,
|
||||
if TERMINATING_BULLET_LOCAL_ID == prevMelee.BattleAttr.BulletLocalId {
|
||||
break
|
||||
}
|
||||
meleeBullet := nextRenderFrameMeleeBullets[meleeBulletCnt]
|
||||
CloneMeleeBullet(prevMelee.BlState, prevMelee.FramesInBlState+1, prevMelee.BattleAttr.BulletLocalId, prevMelee.BattleAttr.OriginatedRenderFrameId, prevMelee.BattleAttr.OffenderJoinIndex, prevMelee.BattleAttr.TeamId, prevMelee.Bullet, meleeBullet)
|
||||
|
||||
if IsMeleeBulletAlive(meleeBullet, currRenderFrame) {
|
||||
offender := currRenderFrame.PlayersArr[meleeBullet.BattleAttr.OffenderJoinIndex-1]
|
||||
if _, existent := noOpSet[offender.CharacterState]; existent {
|
||||
@@ -885,30 +911,39 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
|
||||
}
|
||||
bulletWx, bulletWy := VirtualGridToWorldPos(offender.VirtualGridX+xfac*meleeBullet.Bullet.HitboxOffsetX, offender.VirtualGridY)
|
||||
hitboxSizeWx, hitboxSizeWy := VirtualGridToWorldPos(meleeBullet.Bullet.HitboxSizeX, meleeBullet.Bullet.HitboxSizeY)
|
||||
newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, hitboxSizeWx, hitboxSizeWy, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, collisionSpaceOffsetX, collisionSpaceOffsetY, meleeBullet, "MeleeBullet")
|
||||
collisionSys.Add(newBulletCollider)
|
||||
bulletColliders = append(bulletColliders, newBulletCollider)
|
||||
|
||||
newBulletCollider := dynamicRectangleColliders[colliderCnt]
|
||||
UpdateRectCollider(newBulletCollider, bulletWx, bulletWy, hitboxSizeWx, hitboxSizeWy, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, collisionSpaceOffsetX, collisionSpaceOffsetY, meleeBullet, "MeleeBullet")
|
||||
colliderCnt++
|
||||
|
||||
collisionSys.AddSingle(newBulletCollider)
|
||||
meleeBullet.BlState = BULLET_ACTIVE
|
||||
if meleeBullet.BlState != prevMelee.BlState {
|
||||
meleeBullet.FramesInBlState = 0
|
||||
}
|
||||
}
|
||||
nextRenderFrameMeleeBullets = append(nextRenderFrameMeleeBullets, meleeBullet)
|
||||
meleeBulletCnt++
|
||||
}
|
||||
}
|
||||
// Explicitly specify termination of melee bullets
|
||||
nextRenderFrameMeleeBullets[meleeBulletCnt].BattleAttr.BulletLocalId = TERMINATING_BULLET_LOCAL_ID
|
||||
|
||||
// 4. Calc pushbacks for each player (after its movement) w/o bullets
|
||||
for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
|
||||
joinIndex := currPlayerDownsync.JoinIndex
|
||||
playerCollider := playerColliders[i]
|
||||
playerCollider := dynamicRectangleColliders[i]
|
||||
playerShape := playerCollider.Shape.(*resolv.ConvexPolygon)
|
||||
thatPlayerInNextFrame := nextRenderFramePlayers[i]
|
||||
hardPushbackNorms[joinIndex-1] = calcHardPushbacksNorms(joinIndex, currPlayerDownsync, thatPlayerInNextFrame, playerCollider, playerShape, SNAP_INTO_PLATFORM_OVERLAP, &(effPushbacks[joinIndex-1]))
|
||||
hardPushbackCnt := calcHardPushbacksNorms(joinIndex, currPlayerDownsync, thatPlayerInNextFrame, playerCollider, playerShape, SNAP_INTO_PLATFORM_OVERLAP, effPushbacks[joinIndex-1], hardPushbackNormsArr[joinIndex-1], collision)
|
||||
chConfig := chConfigsOrderedByJoinIndex[i]
|
||||
landedOnGravityPushback := false
|
||||
|
||||
if collision := playerCollider.Check(0, 0); nil != collision {
|
||||
for _, obj := range collision.Objects {
|
||||
if collided := playerCollider.CheckAllWithHolder(0, 0, collision); collided {
|
||||
for true {
|
||||
obj := collision.PopFirstCollidedObject()
|
||||
if nil == obj {
|
||||
break
|
||||
}
|
||||
isBarrier, isAnotherPlayer, isBullet := false, false, false
|
||||
switch v := obj.Data.(type) {
|
||||
case *PlayerDownsync:
|
||||
@@ -938,7 +973,8 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
|
||||
// [WARNING] The "zero overlap collision" might be randomly detected/missed on either frontend or backend, to have deterministic result we added paddings to all sides of a playerCollider. As each velocity component of (velX, velY) being a multiple of 0.5 at any renderFrame, each position component of (x, y) can only be a multiple of 0.5 too, thus whenever a 1-dimensional collision happens between players from [player#1: i*0.5, player#2: j*0.5, not collided yet] to [player#1: (i+k)*0.5, player#2: j*0.5, collided], the overlap becomes (i+k-j)*0.5+2*s, and after snapping subtraction the effPushback magnitude for each player is (i+k-j)*0.5, resulting in 0.5-multiples-position for the next renderFrame.
|
||||
pushbackX, pushbackY = (overlapResult.Overlap-SNAP_INTO_PLATFORM_OVERLAP*2)*overlapResult.OverlapX, (overlapResult.Overlap-SNAP_INTO_PLATFORM_OVERLAP*2)*overlapResult.OverlapY
|
||||
}
|
||||
for _, hardPushbackNorm := range *hardPushbackNorms[joinIndex-1] {
|
||||
for i := 0; i < hardPushbackCnt; i++ {
|
||||
hardPushbackNorm := hardPushbackNormsArr[joinIndex-1][i]
|
||||
projectedMagnitude := pushbackX*hardPushbackNorm.X + pushbackY*hardPushbackNorm.Y
|
||||
if isBarrier || (isAnotherPlayer && 0 > projectedMagnitude) {
|
||||
pushbackX -= projectedMagnitude * hardPushbackNorm.X
|
||||
@@ -951,10 +987,11 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
|
||||
if SNAP_INTO_PLATFORM_THRESHOLD < normAlignmentWithGravity {
|
||||
landedOnGravityPushback = true
|
||||
//playerColliderCenterX, playerColliderCenterY := playerCollider.Center()
|
||||
//fmt.Printf("joinIndex=%d landedOnGravityPushback\n{renderFrame.id: %d, isBarrier: %v, isAnotherPlayer: %v}\nhardPushbackNormsOfThisPlayer=%v, playerColliderPos=(%.2f,%.2f), immediatePushback={%.3f, %.3f}, effPushback={%.3f, %.3f}, overlapMag=%.4f\n", joinIndex, currRenderFrame.Id, isBarrier, isAnotherPlayer, *hardPushbackNorms[joinIndex-1], playerColliderCenterX, playerColliderCenterY, pushbackX, pushbackY, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y, overlapResult.Overlap)
|
||||
//fmt.Printf("joinIndex=%d landedOnGravityPushback\n{renderFrame.id: %d, isBarrier: %v, isAnotherPlayer: %v}\nhardPushbackNormsOfThisPlayer=%v, playerColliderPos=(%.2f,%.2f), immediatePushback={%.3f, %.3f}, effPushback={%.3f, %.3f}, overlapMag=%.4f\n", joinIndex, currRenderFrame.Id, isBarrier, isAnotherPlayer, hardPushbackNorms[joinIndex-1], playerColliderCenterX, playerColliderCenterY, pushbackX, pushbackY, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y, overlapResult.Overlap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if landedOnGravityPushback {
|
||||
thatPlayerInNextFrame.InAir = false
|
||||
fallStopping := (currPlayerDownsync.InAir && 0 >= currPlayerDownsync.VelY)
|
||||
@@ -962,6 +999,8 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
|
||||
thatPlayerInNextFrame.VelY = 0
|
||||
thatPlayerInNextFrame.VelX = 0
|
||||
if ATK_CHARACTER_STATE_DYING == thatPlayerInNextFrame.CharacterState {
|
||||
|
||||
// No update needed for Dying
|
||||
} else if ATK_CHARACTER_STATE_BLOWN_UP1 == thatPlayerInNextFrame.CharacterState {
|
||||
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_LAY_DOWN1
|
||||
thatPlayerInNextFrame.FramesToRecover = chConfig.LayDownFramesToRecover
|
||||
@@ -980,7 +1019,8 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
|
||||
// landedOnGravityPushback not fallStopping, could be in LayDown or GetUp or Dying
|
||||
if _, existent := nonAttackingSet[thatPlayerInNextFrame.CharacterState]; existent {
|
||||
if ATK_CHARACTER_STATE_DYING == thatPlayerInNextFrame.CharacterState {
|
||||
// No update needed for Dying
|
||||
thatPlayerInNextFrame.VelY = 0
|
||||
thatPlayerInNextFrame.VelX = 0
|
||||
} else if ATK_CHARACTER_STATE_LAY_DOWN1 == thatPlayerInNextFrame.CharacterState {
|
||||
if 0 == thatPlayerInNextFrame.FramesToRecover {
|
||||
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_GET_UP1
|
||||
@@ -1001,7 +1041,8 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
|
||||
// [WARNING] Sticking to wall MUST BE based on "InAir", otherwise we would get gravity reduction from ground up incorrectly!
|
||||
if _, existent := noOpSet[currPlayerDownsync.CharacterState]; !existent {
|
||||
// [WARNING] Sticking to wall could only be triggered by proactive player input
|
||||
for _, hardPushbackNorm := range *hardPushbackNorms[joinIndex-1] {
|
||||
for i := 0; i < hardPushbackCnt; i++ {
|
||||
hardPushbackNorm := hardPushbackNormsArr[joinIndex-1][i]
|
||||
normAlignmentWithHorizon1 := (hardPushbackNorm.X*float64(1.0) + hardPushbackNorm.Y*float64(0.0))
|
||||
normAlignmentWithHorizon2 := (hardPushbackNorm.X*float64(-1.0) + hardPushbackNorm.Y*float64(0.0))
|
||||
if VERTICAL_PLATFORM_THRESHOLD < normAlignmentWithHorizon1 {
|
||||
@@ -1021,19 +1062,19 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
|
||||
thatPlayerInNextFrame.OnWallNormX, thatPlayerInNextFrame.OnWallNormY = 0, 0
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 5. Check bullet-anything collisions
|
||||
for _, bulletCollider := range bulletColliders {
|
||||
collision := bulletCollider.Check(0, 0)
|
||||
bulletCollider.Space.Remove(bulletCollider) // Make sure that the bulletCollider is always removed for each renderFrame
|
||||
exploded := false
|
||||
explodedOnAnotherPlayer := false
|
||||
if nil == collision {
|
||||
for i := len(nextRenderFramePlayers); i < colliderCnt; i++ {
|
||||
bulletCollider := dynamicRectangleColliders[i]
|
||||
collided := bulletCollider.CheckAllWithHolder(0, 0, collision)
|
||||
if !collided {
|
||||
continue
|
||||
}
|
||||
|
||||
exploded := false
|
||||
explodedOnAnotherPlayer := false
|
||||
|
||||
var bulletStaticAttr *BulletConfig = nil
|
||||
var bulletBattleAttr *BulletBattleAttr = nil
|
||||
switch v := bulletCollider.Data.(type) {
|
||||
@@ -1047,7 +1088,11 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
|
||||
|
||||
bulletShape := bulletCollider.Shape.(*resolv.ConvexPolygon)
|
||||
offender := currRenderFrame.PlayersArr[bulletBattleAttr.OffenderJoinIndex-1]
|
||||
for _, obj := range collision.Objects {
|
||||
for true {
|
||||
obj := collision.PopFirstCollidedObject()
|
||||
if nil == obj {
|
||||
break
|
||||
}
|
||||
defenderShape := obj.Shape.(*resolv.ConvexPolygon)
|
||||
switch t := obj.Data.(type) {
|
||||
case *PlayerDownsync:
|
||||
@@ -1072,15 +1117,15 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
|
||||
}
|
||||
atkedPlayerInNextFrame := nextRenderFramePlayers[t.JoinIndex-1]
|
||||
atkedPlayerInNextFrame.Hp -= bulletStaticAttr.Damage
|
||||
pushbackVelX, pushbackVelY := xfac*bulletStaticAttr.PushbackVelX, bulletStaticAttr.PushbackVelY
|
||||
atkedPlayerInNextFrame.VelX = pushbackVelX
|
||||
atkedPlayerInNextFrame.VelY = pushbackVelY
|
||||
if 0 >= atkedPlayerInNextFrame.Hp {
|
||||
// [WARNING] We don't have "dying in air" animation for now, and for better graphical recognition, play the same dying animation even in air
|
||||
atkedPlayerInNextFrame.Hp = 0
|
||||
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_DYING
|
||||
atkedPlayerInNextFrame.FramesToRecover = DYING_FRAMES_TO_RECOVER
|
||||
} else {
|
||||
pushbackVelX, pushbackVelY := xfac*bulletStaticAttr.PushbackVelX, bulletStaticAttr.PushbackVelY
|
||||
atkedPlayerInNextFrame.VelX = pushbackVelX
|
||||
atkedPlayerInNextFrame.VelY = pushbackVelY
|
||||
if bulletStaticAttr.BlowUp {
|
||||
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_BLOWN_UP1
|
||||
} else {
|
||||
@@ -1118,7 +1163,7 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
|
||||
// 6. Get players out of stuck barriers if there's any
|
||||
for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
|
||||
joinIndex := currPlayerDownsync.JoinIndex
|
||||
playerCollider := playerColliders[i]
|
||||
playerCollider := dynamicRectangleColliders[i]
|
||||
// Update "virtual grid position"
|
||||
thatPlayerInNextFrame := nextRenderFramePlayers[i]
|
||||
thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY = PolygonColliderBLToVirtualGridPos(playerCollider.X-effPushbacks[joinIndex-1].X, playerCollider.Y-effPushbacks[joinIndex-1].Y, playerCollider.W*0.5, playerCollider.H*0.5, 0, 0, 0, 0, collisionSpaceOffsetX, collisionSpaceOffsetY)
|
||||
@@ -1164,17 +1209,15 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
|
||||
}
|
||||
}
|
||||
|
||||
for _, playerCollider := range playerColliders {
|
||||
playerCollider.Space.Remove(playerCollider)
|
||||
for i := 0; i < colliderCnt; i++ {
|
||||
dynamicCollider := dynamicRectangleColliders[i]
|
||||
dynamicCollider.Space.RemoveSingle(dynamicCollider)
|
||||
}
|
||||
|
||||
return &RoomDownsyncFrame{
|
||||
Id: currRenderFrame.Id + 1,
|
||||
PlayersArr: nextRenderFramePlayers,
|
||||
BulletLocalIdCounter: bulletLocalId,
|
||||
MeleeBullets: nextRenderFrameMeleeBullets,
|
||||
FireballBullets: nextRenderFrameFireballBullets,
|
||||
}
|
||||
ret.Id = nextRenderFrameId
|
||||
ret.BulletLocalIdCounter = bulletLocalId
|
||||
|
||||
return hasInputFrameUpdatedOnDynamics
|
||||
}
|
||||
|
||||
func GenerateRectCollider(wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) *resolv.Object {
|
||||
@@ -1183,13 +1226,23 @@ func GenerateRectCollider(wx, wy, w, h, topPadding, bottomPadding, leftPadding,
|
||||
}
|
||||
|
||||
func generateRectColliderInCollisionSpace(blX, blY, w, h float64, data interface{}, tag string) *resolv.Object {
|
||||
collider := resolv.NewObject(blX, blY, w, h, tag) // Unlike its frontend counter part, the position of a "resolv.Object" must be specified by "bottom-left point" because "w" and "h" must be positive, see "resolv.Object.BoundsToSpace" for details
|
||||
collider := resolv.NewObjectSingleTag(blX, blY, w, h, tag) // Unlike its frontend counter part, the position of a "resolv.Object" must be specified by "bottom-left point" because "w" and "h" must be positive, see "resolv.Object.BoundsToSpace" for details
|
||||
shape := resolv.NewRectangle(0, 0, w, h)
|
||||
collider.SetShape(shape)
|
||||
collider.Data = data
|
||||
return collider
|
||||
}
|
||||
|
||||
func UpdateRectCollider(collider *resolv.Object, wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) {
|
||||
blX, blY := WorldToPolygonColliderBLPos(wx, wy, w*0.5, h*0.5, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY)
|
||||
effW, effH := leftPadding+w+rightPadding, bottomPadding+h+topPadding
|
||||
collider.X, collider.Y, collider.W, collider.H = blX, blY, effW, effH
|
||||
rectShape := collider.Shape.(*resolv.ConvexPolygon)
|
||||
rectShape.UpdateAsRectangle(0, 0, effW, effH)
|
||||
collider.Data = data
|
||||
// Ignore "tag" for now
|
||||
}
|
||||
|
||||
func GenerateConvexPolygonCollider(unalignedSrc *Polygon2D, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) *resolv.Object {
|
||||
aligned := AlignPolygon2DToBoundingBox(unalignedSrc)
|
||||
var w, h float64 = 0, 0
|
||||
@@ -1294,6 +1347,8 @@ func NewMeleeBullet(bulletLocalId, originatedRenderFrameId, offenderJoinIndex, s
|
||||
|
||||
func NewFireballBullet(bulletLocalId, originatedRenderFrameId, offenderJoinIndex, startupFrames, cancellableStFrame, cancellableEdFrame, activeFrames, hitStunFrames, blockStunFrames, pushbackVelX, pushbackVelY, damage, selfLockVelX, selfLockVelY, hitboxOffsetX, hitboxOffsetY, hitboxSizeX, hitboxSizeY int32, blowUp bool, teamId int32, virtualGridX, virtualGridY, dirX, dirY, velX, velY, speed, blState, framesInBlState, explosionFrames, speciesId int32) *FireballBullet {
|
||||
return &FireballBullet{
|
||||
BlState: blState,
|
||||
FramesInBlState: framesInBlState,
|
||||
VirtualGridX: virtualGridX,
|
||||
VirtualGridY: virtualGridY,
|
||||
DirX: dirX,
|
||||
@@ -1333,3 +1388,150 @@ func NewFireballBullet(bulletLocalId, originatedRenderFrameId, offenderJoinIndex
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewPlayerDownsync(id, virtualGridX, virtualGridY, dirX, dirY, velX, velY, framesToRecover, framesInChState, activeSkillId, activeSkillHit, framesInvinsible, speed, battleState, characterState, joinIndex, hp, maxHp, colliderRadius int32, inAir, onWall bool, onWallNormX, onWallNormY int32, capturedByInertia bool, bulletTeamId, chCollisionTeamId int32, revivalVirtualGridX, revivalVirtualGridY int32) *PlayerDownsync {
|
||||
return &PlayerDownsync{
|
||||
Id: id,
|
||||
VirtualGridX: virtualGridX,
|
||||
VirtualGridY: virtualGridY,
|
||||
DirX: dirX,
|
||||
DirY: dirY,
|
||||
VelX: velX,
|
||||
VelY: velY,
|
||||
FramesToRecover: framesToRecover,
|
||||
FramesInChState: framesInChState,
|
||||
ActiveSkillId: activeSkillId,
|
||||
ActiveSkillHit: activeSkillHit,
|
||||
FramesInvinsible: framesInvinsible,
|
||||
Speed: speed,
|
||||
BattleState: battleState,
|
||||
CharacterState: characterState,
|
||||
JoinIndex: joinIndex,
|
||||
Hp: hp,
|
||||
MaxHp: maxHp,
|
||||
ColliderRadius: colliderRadius,
|
||||
InAir: inAir,
|
||||
OnWall: onWall,
|
||||
OnWallNormX: onWallNormX,
|
||||
OnWallNormY: onWallNormY,
|
||||
CapturedByInertia: capturedByInertia,
|
||||
BulletTeamId: bulletTeamId,
|
||||
ChCollisionTeamId: chCollisionTeamId,
|
||||
RevivalVirtualGridX: revivalVirtualGridX,
|
||||
RevivalVirtualGridY: revivalVirtualGridY,
|
||||
}
|
||||
}
|
||||
|
||||
func CloneMeleeBullet(blState, framesInBlState, bulletLocalId, originatedRenderFrameId, offenderJoinIndex, teamId int32, staticBulletConfig *BulletConfig, dst *MeleeBullet /* preallocated */) {
|
||||
dst.BlState = blState
|
||||
dst.FramesInBlState = framesInBlState
|
||||
dst.BattleAttr.BulletLocalId = bulletLocalId
|
||||
dst.BattleAttr.OriginatedRenderFrameId = originatedRenderFrameId
|
||||
dst.BattleAttr.OffenderJoinIndex = offenderJoinIndex
|
||||
dst.BattleAttr.TeamId = teamId
|
||||
dst.Bullet = staticBulletConfig // It's OK to just assign the pointer here, static bullet config is meant to be passed this way
|
||||
}
|
||||
|
||||
func CloneFireballBullet(blState, framesInBlState, virtualGridX, virtualGridY, dirX, dirY, velX, velY, speed, bulletLocalId, originatedRenderFrameId, offenderJoinIndex, teamId int32, staticBulletConfig *BulletConfig, dst *FireballBullet /* preallocated */) {
|
||||
dst.BlState = blState
|
||||
dst.FramesInBlState = framesInBlState
|
||||
dst.VirtualGridX = virtualGridX
|
||||
dst.VirtualGridY = virtualGridY
|
||||
dst.DirX = dirX
|
||||
dst.DirY = dirY
|
||||
dst.VelX = velX
|
||||
dst.VelY = velY
|
||||
dst.Speed = speed
|
||||
dst.BattleAttr.BulletLocalId = bulletLocalId
|
||||
dst.BattleAttr.OriginatedRenderFrameId = originatedRenderFrameId
|
||||
dst.BattleAttr.OffenderJoinIndex = offenderJoinIndex
|
||||
dst.BattleAttr.TeamId = teamId
|
||||
dst.Bullet = staticBulletConfig // It's OK to just assign the pointer here, static bullet config is meant to be passed this way
|
||||
}
|
||||
|
||||
func ClonePlayerDownsync(id, virtualGridX, virtualGridY, dirX, dirY, velX, velY, framesToRecover, framesInChState, activeSkillId, activeSkillHit, framesInvinsible, speed, battleState, characterState, joinIndex, hp, maxHp, colliderRadius int32, inAir, onWall bool, onWallNormX, onWallNormY int32, capturedByInertia bool, bulletTeamId, chCollisionTeamId, revivalVirtualGridX, revivalVirtualGridY int32, dst *PlayerDownsync) {
|
||||
dst.Id = id
|
||||
dst.VirtualGridX = virtualGridX
|
||||
dst.VirtualGridY = virtualGridY
|
||||
dst.DirX = dirX
|
||||
dst.DirY = dirY
|
||||
dst.VelX = velX
|
||||
dst.VelY = velY
|
||||
dst.FramesToRecover = framesToRecover
|
||||
dst.FramesInChState = framesInChState
|
||||
dst.ActiveSkillId = activeSkillId
|
||||
dst.ActiveSkillHit = activeSkillHit
|
||||
dst.FramesInvinsible = framesInvinsible
|
||||
dst.Speed = speed
|
||||
dst.BattleState = battleState
|
||||
dst.CharacterState = characterState
|
||||
dst.JoinIndex = joinIndex
|
||||
dst.Hp = hp
|
||||
dst.MaxHp = maxHp
|
||||
dst.ColliderRadius = colliderRadius
|
||||
dst.InAir = inAir
|
||||
dst.OnWall = onWall
|
||||
dst.OnWallNormX = onWallNormX
|
||||
dst.OnWallNormY = onWallNormY
|
||||
dst.CapturedByInertia = capturedByInertia
|
||||
dst.BulletTeamId = bulletTeamId
|
||||
dst.ChCollisionTeamId = chCollisionTeamId
|
||||
dst.RevivalVirtualGridX = revivalVirtualGridX
|
||||
dst.RevivalVirtualGridY = revivalVirtualGridY
|
||||
}
|
||||
|
||||
func CloneRoomDownsyncFrame(id int32, playersArr []*PlayerDownsync, bulletLocalIdCounter int32, meleeBullets []*MeleeBullet, fireballBullets []*FireballBullet, dst *RoomDownsyncFrame) {
|
||||
dst.Id = id
|
||||
dst.BulletLocalIdCounter = bulletLocalIdCounter
|
||||
for i := 0; i < len(playersArr); i++ {
|
||||
src := playersArr[i]
|
||||
if nil == src || TERMINATING_PLAYER_ID == src.Id {
|
||||
break
|
||||
}
|
||||
ClonePlayerDownsync(src.Id, src.VirtualGridX, src.VirtualGridY, src.DirX, src.DirY, src.VelX, src.VelY, src.FramesToRecover, src.FramesInChState, src.ActiveSkillId, src.ActiveSkillHit, src.FramesInvinsible, src.Speed, src.BattleState, src.CharacterState, src.JoinIndex, src.Hp, src.MaxHp, src.ColliderRadius, src.InAir, src.OnWall, src.OnWallNormX, src.OnWallNormY, src.CapturedByInertia, src.BulletTeamId, src.ChCollisionTeamId, src.RevivalVirtualGridX, src.RevivalVirtualGridY, dst.PlayersArr[i])
|
||||
}
|
||||
|
||||
for i := 0; i < len(meleeBullets); i++ {
|
||||
src := meleeBullets[i]
|
||||
if nil == src || TERMINATING_BULLET_LOCAL_ID == src.BattleAttr.BulletLocalId {
|
||||
break
|
||||
}
|
||||
CloneMeleeBullet(src.BlState, src.FramesInBlState, src.BattleAttr.BulletLocalId, src.BattleAttr.OriginatedRenderFrameId, src.BattleAttr.OffenderJoinIndex, src.BattleAttr.TeamId, src.Bullet, dst.MeleeBullets[i])
|
||||
}
|
||||
|
||||
for i := 0; i < len(fireballBullets); i++ {
|
||||
src := fireballBullets[i]
|
||||
if nil == src || TERMINATING_BULLET_LOCAL_ID == src.BattleAttr.BulletLocalId {
|
||||
break
|
||||
}
|
||||
CloneFireballBullet(src.BlState, src.FramesInBlState, src.VirtualGridX, src.VirtualGridY, src.DirX, src.DirY, src.VelX, src.VelY, src.Speed, src.BattleAttr.BulletLocalId, src.BattleAttr.OriginatedRenderFrameId, src.BattleAttr.OffenderJoinIndex, src.BattleAttr.TeamId, src.Bullet, dst.FireballBullets[i])
|
||||
}
|
||||
}
|
||||
|
||||
func NewPreallocatedRoomDownsyncFrame(roomCapacity, preallocMeleeBulletCount int, preallocFireballBulletCount int) *RoomDownsyncFrame {
|
||||
preallocatedPlayers := make([]*PlayerDownsync, roomCapacity)
|
||||
for i := 0; i < roomCapacity; i++ {
|
||||
preallocatedPlayer := NewPlayerDownsync(TERMINATING_PLAYER_ID, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false, false, 0, 0, false, 0, 0, 0, 0)
|
||||
preallocatedPlayers[i] = preallocatedPlayer
|
||||
}
|
||||
|
||||
preallocatedMeleeBullets := make([]*MeleeBullet, preallocMeleeBulletCount)
|
||||
for i := 0; i < preallocMeleeBulletCount; i++ {
|
||||
preallocatedMelee := NewMeleeBullet(TERMINATING_BULLET_LOCAL_ID, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false, 0, 0, 0, 0, 0)
|
||||
preallocatedMeleeBullets[i] = preallocatedMelee
|
||||
}
|
||||
|
||||
preallocatedFireballBullets := make([]*FireballBullet, preallocFireballBulletCount)
|
||||
for i := 0; i < preallocFireballBulletCount; i++ {
|
||||
preallocatedFireball := NewFireballBullet(TERMINATING_BULLET_LOCAL_ID, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
|
||||
preallocatedFireballBullets[i] = preallocatedFireball
|
||||
}
|
||||
|
||||
return &RoomDownsyncFrame{
|
||||
Id: TERMINATING_RENDER_FRAME_ID,
|
||||
BulletLocalIdCounter: TERMINATING_BULLET_LOCAL_ID,
|
||||
PlayersArr: preallocatedPlayers,
|
||||
MeleeBullets: preallocatedMeleeBullets,
|
||||
FireballBullets: preallocatedFireballBullets,
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package battle
|
||||
|
||||
type SkillMapperType func(patternId int, currPlayerDownsync *PlayerDownsync) int
|
||||
type SkillMapperType = func(patternId int, currPlayerDownsync *PlayerDownsync, speciesId int) int
|
||||
|
||||
type CharacterConfig struct {
|
||||
SpeciesId int
|
||||
@@ -31,34 +31,9 @@ type CharacterConfig struct {
|
||||
SkillMapper SkillMapperType
|
||||
}
|
||||
|
||||
var Characters = map[int]*CharacterConfig{
|
||||
0: &CharacterConfig{
|
||||
SpeciesId: 0,
|
||||
SpeciesName: "MonkGirl",
|
||||
|
||||
InAirIdleFrameIdxTurningPoint: 11,
|
||||
InAirIdleFrameIdxTurnedCycle: 1,
|
||||
|
||||
LayDownFrames: int32(16),
|
||||
LayDownFramesToRecover: int32(16),
|
||||
|
||||
GetUpInvinsibleFrames: int32(10),
|
||||
GetUpFramesToRecover: int32(27),
|
||||
|
||||
Speed: int32(float64(2.1) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
JumpingInitVelY: int32(float64(8) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
JumpingFramesToRecover: int32(2),
|
||||
|
||||
InertiaFramesToRecover: int32(9),
|
||||
|
||||
DashingEnabled: true,
|
||||
OnWallEnabled: true,
|
||||
WallJumpingFramesToRecover: int32(8), // 8 would be the minimum for an avg human
|
||||
WallJumpingInitVelX: int32(float64(2.8) * WORLD_TO_VIRTUAL_GRID_RATIO), // Default is "appeared facing right", but actually holding ctrl against left
|
||||
WallJumpingInitVelY: int32(float64(7) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
WallSlidingVelY: int32(float64(-1) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
|
||||
SkillMapper: func(patternId int, currPlayerDownsync *PlayerDownsync) int {
|
||||
func defaultSkillMapper(patternId int, currPlayerDownsync *PlayerDownsync, speciesId int) int {
|
||||
switch speciesId {
|
||||
case 0:
|
||||
if 1 == patternId {
|
||||
if 0 == currPlayerDownsync.FramesToRecover {
|
||||
if currPlayerDownsync.InAir {
|
||||
@@ -92,35 +67,7 @@ var Characters = map[int]*CharacterConfig{
|
||||
|
||||
// By default no skill can be fired
|
||||
return NO_SKILL
|
||||
},
|
||||
},
|
||||
1: &CharacterConfig{
|
||||
SpeciesId: 1,
|
||||
SpeciesName: "KnifeGirl",
|
||||
|
||||
InAirIdleFrameIdxTurningPoint: 9,
|
||||
InAirIdleFrameIdxTurnedCycle: 1,
|
||||
|
||||
LayDownFrames: int32(16),
|
||||
LayDownFramesToRecover: int32(16),
|
||||
|
||||
GetUpInvinsibleFrames: int32(10),
|
||||
GetUpFramesToRecover: int32(27),
|
||||
|
||||
Speed: int32(float64(2.19) * WORLD_TO_VIRTUAL_GRID_RATIO), // I don't know why "2.2" is so special that it throws a compile error
|
||||
JumpingInitVelY: int32(float64(7.5) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
JumpingFramesToRecover: int32(2),
|
||||
|
||||
InertiaFramesToRecover: int32(9),
|
||||
|
||||
DashingEnabled: true,
|
||||
OnWallEnabled: true,
|
||||
WallJumpingFramesToRecover: int32(8), // 8 would be the minimum for an avg human
|
||||
WallJumpingInitVelX: int32(float64(2.8) * WORLD_TO_VIRTUAL_GRID_RATIO), // Default is "appeared facing right", but actually holding ctrl against left
|
||||
WallJumpingInitVelY: int32(float64(7) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
WallSlidingVelY: int32(float64(-1) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
|
||||
SkillMapper: func(patternId int, currPlayerDownsync *PlayerDownsync) int {
|
||||
case 1:
|
||||
if 1 == patternId {
|
||||
if 0 == currPlayerDownsync.FramesToRecover {
|
||||
if currPlayerDownsync.InAir {
|
||||
@@ -153,31 +100,7 @@ var Characters = map[int]*CharacterConfig{
|
||||
|
||||
// By default no skill can be fired
|
||||
return NO_SKILL
|
||||
},
|
||||
},
|
||||
4096: &CharacterConfig{
|
||||
SpeciesId: 4096,
|
||||
SpeciesName: "Monk",
|
||||
|
||||
InAirIdleFrameIdxTurningPoint: 42,
|
||||
InAirIdleFrameIdxTurnedCycle: 2,
|
||||
|
||||
LayDownFrames: int32(14),
|
||||
LayDownFramesToRecover: int32(14),
|
||||
|
||||
GetUpInvinsibleFrames: int32(8),
|
||||
GetUpFramesToRecover: int32(30),
|
||||
|
||||
Speed: int32(float64(1.8) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
JumpingInitVelY: int32(float64(7.8) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
JumpingFramesToRecover: int32(2),
|
||||
|
||||
InertiaFramesToRecover: int32(9),
|
||||
|
||||
DashingEnabled: true,
|
||||
OnWallEnabled: false,
|
||||
|
||||
SkillMapper: func(patternId int, currPlayerDownsync *PlayerDownsync) int {
|
||||
case 4096:
|
||||
if 1 == patternId {
|
||||
if 0 == currPlayerDownsync.FramesToRecover {
|
||||
if currPlayerDownsync.InAir {
|
||||
@@ -215,7 +138,91 @@ var Characters = map[int]*CharacterConfig{
|
||||
|
||||
// By default no skill can be fired
|
||||
return NO_SKILL
|
||||
}
|
||||
|
||||
return NO_SKILL
|
||||
}
|
||||
|
||||
var Characters = map[int]*CharacterConfig{
|
||||
0: &CharacterConfig{
|
||||
SpeciesId: 0,
|
||||
SpeciesName: "MonkGirl",
|
||||
|
||||
InAirIdleFrameIdxTurningPoint: 11,
|
||||
InAirIdleFrameIdxTurnedCycle: 1,
|
||||
|
||||
LayDownFrames: int32(16),
|
||||
LayDownFramesToRecover: int32(16),
|
||||
|
||||
GetUpInvinsibleFrames: int32(10),
|
||||
GetUpFramesToRecover: int32(27),
|
||||
|
||||
Speed: int32(float64(2.1) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
JumpingInitVelY: int32(float64(8) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
JumpingFramesToRecover: int32(2),
|
||||
|
||||
InertiaFramesToRecover: int32(9),
|
||||
|
||||
DashingEnabled: true,
|
||||
OnWallEnabled: true,
|
||||
WallJumpingFramesToRecover: int32(8), // 8 would be the minimum for an avg human
|
||||
WallJumpingInitVelX: int32(float64(2.8) * WORLD_TO_VIRTUAL_GRID_RATIO), // Default is "appeared facing right", but actually holding ctrl against left
|
||||
WallJumpingInitVelY: int32(float64(7) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
WallSlidingVelY: int32(float64(-1) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
|
||||
SkillMapper: defaultSkillMapper,
|
||||
},
|
||||
1: &CharacterConfig{
|
||||
SpeciesId: 1,
|
||||
SpeciesName: "KnifeGirl",
|
||||
|
||||
InAirIdleFrameIdxTurningPoint: 9,
|
||||
InAirIdleFrameIdxTurnedCycle: 1,
|
||||
|
||||
LayDownFrames: int32(16),
|
||||
LayDownFramesToRecover: int32(16),
|
||||
|
||||
GetUpInvinsibleFrames: int32(10),
|
||||
GetUpFramesToRecover: int32(27),
|
||||
|
||||
Speed: int32(float64(2.2) * WORLD_TO_VIRTUAL_GRID_RATIO), // I don't know why "2.2" is so special that it throws a compile error
|
||||
JumpingInitVelY: int32(float64(7.5) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
JumpingFramesToRecover: int32(2),
|
||||
|
||||
InertiaFramesToRecover: int32(9),
|
||||
|
||||
DashingEnabled: true,
|
||||
OnWallEnabled: true,
|
||||
WallJumpingFramesToRecover: int32(8), // 8 would be the minimum for an avg human
|
||||
WallJumpingInitVelX: int32(float64(2.8) * WORLD_TO_VIRTUAL_GRID_RATIO), // Default is "appeared facing right", but actually holding ctrl against left
|
||||
WallJumpingInitVelY: int32(float64(7) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
WallSlidingVelY: int32(float64(-1) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
|
||||
SkillMapper: defaultSkillMapper,
|
||||
},
|
||||
4096: &CharacterConfig{
|
||||
SpeciesId: 4096,
|
||||
SpeciesName: "Monk",
|
||||
|
||||
InAirIdleFrameIdxTurningPoint: 42,
|
||||
InAirIdleFrameIdxTurnedCycle: 2,
|
||||
|
||||
LayDownFrames: int32(14),
|
||||
LayDownFramesToRecover: int32(14),
|
||||
|
||||
GetUpInvinsibleFrames: int32(8),
|
||||
GetUpFramesToRecover: int32(30),
|
||||
|
||||
Speed: int32(float64(1.8) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
JumpingInitVelY: int32(float64(7.8) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
JumpingFramesToRecover: int32(2),
|
||||
|
||||
InertiaFramesToRecover: int32(9),
|
||||
|
||||
DashingEnabled: true,
|
||||
OnWallEnabled: false,
|
||||
|
||||
SkillMapper: defaultSkillMapper,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -226,7 +233,7 @@ var skills = map[int]*Skill{
|
||||
RecoveryFramesOnHit: int32(30),
|
||||
ReleaseTriggerType: int32(1),
|
||||
BoundChState: ATK_CHARACTER_STATE_ATK1,
|
||||
Hits: []interface{}{
|
||||
Hits: []AnyBullet{
|
||||
&MeleeBullet{
|
||||
Bullet: &BulletConfig{
|
||||
StartupFrames: int32(7),
|
||||
@@ -234,7 +241,7 @@ var skills = map[int]*Skill{
|
||||
HitStunFrames: int32(13),
|
||||
BlockStunFrames: int32(9),
|
||||
Damage: int32(5),
|
||||
SelfLockVelX: int32(float64(0.05) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
SelfLockVelX: int32(float64(0.1) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
SelfLockVelY: NO_LOCK_VEL,
|
||||
PushbackVelX: int32(float64(0.5) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
PushbackVelY: int32(0),
|
||||
@@ -261,7 +268,7 @@ var skills = map[int]*Skill{
|
||||
RecoveryFramesOnHit: int32(36),
|
||||
ReleaseTriggerType: int32(1),
|
||||
BoundChState: ATK_CHARACTER_STATE_ATK2,
|
||||
Hits: []interface{}{
|
||||
Hits: []AnyBullet{
|
||||
&MeleeBullet{
|
||||
Bullet: &BulletConfig{
|
||||
StartupFrames: int32(18),
|
||||
@@ -295,7 +302,7 @@ var skills = map[int]*Skill{
|
||||
RecoveryFramesOnHit: int32(50),
|
||||
ReleaseTriggerType: int32(1),
|
||||
BoundChState: ATK_CHARACTER_STATE_ATK3,
|
||||
Hits: []interface{}{
|
||||
Hits: []AnyBullet{
|
||||
&MeleeBullet{
|
||||
Bullet: &BulletConfig{
|
||||
StartupFrames: int32(8),
|
||||
@@ -324,7 +331,7 @@ var skills = map[int]*Skill{
|
||||
RecoveryFramesOnHit: int32(30),
|
||||
ReleaseTriggerType: int32(1),
|
||||
BoundChState: ATK_CHARACTER_STATE_ATK1,
|
||||
Hits: []interface{}{
|
||||
Hits: []AnyBullet{
|
||||
&MeleeBullet{
|
||||
Bullet: &BulletConfig{
|
||||
StartupFrames: int32(7),
|
||||
@@ -332,7 +339,7 @@ var skills = map[int]*Skill{
|
||||
HitStunFrames: int32(13),
|
||||
BlockStunFrames: int32(9),
|
||||
Damage: int32(5),
|
||||
SelfLockVelX: int32(float64(0.05) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
SelfLockVelX: int32(float64(0.1) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
SelfLockVelY: NO_LOCK_VEL,
|
||||
PushbackVelX: int32(float64(0.5) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
PushbackVelY: int32(0),
|
||||
@@ -359,7 +366,7 @@ var skills = map[int]*Skill{
|
||||
RecoveryFramesOnHit: int32(36),
|
||||
ReleaseTriggerType: int32(1),
|
||||
BoundChState: ATK_CHARACTER_STATE_ATK2,
|
||||
Hits: []interface{}{
|
||||
Hits: []AnyBullet{
|
||||
&MeleeBullet{
|
||||
Bullet: &BulletConfig{
|
||||
StartupFrames: int32(18),
|
||||
@@ -393,7 +400,7 @@ var skills = map[int]*Skill{
|
||||
RecoveryFramesOnHit: int32(45),
|
||||
ReleaseTriggerType: int32(1),
|
||||
BoundChState: ATK_CHARACTER_STATE_ATK3,
|
||||
Hits: []interface{}{
|
||||
Hits: []AnyBullet{
|
||||
&MeleeBullet{
|
||||
Bullet: &BulletConfig{
|
||||
StartupFrames: int32(8),
|
||||
@@ -422,7 +429,7 @@ var skills = map[int]*Skill{
|
||||
RecoveryFramesOnHit: int32(30),
|
||||
ReleaseTriggerType: int32(1),
|
||||
BoundChState: ATK_CHARACTER_STATE_ATK1,
|
||||
Hits: []interface{}{
|
||||
Hits: []AnyBullet{
|
||||
&MeleeBullet{
|
||||
Bullet: &BulletConfig{
|
||||
StartupFrames: int32(7),
|
||||
@@ -457,7 +464,7 @@ var skills = map[int]*Skill{
|
||||
RecoveryFramesOnHit: int32(36),
|
||||
ReleaseTriggerType: int32(1),
|
||||
BoundChState: ATK_CHARACTER_STATE_ATK2,
|
||||
Hits: []interface{}{
|
||||
Hits: []AnyBullet{
|
||||
&MeleeBullet{
|
||||
Bullet: &BulletConfig{
|
||||
StartupFrames: int32(18),
|
||||
@@ -491,7 +498,7 @@ var skills = map[int]*Skill{
|
||||
RecoveryFramesOnHit: int32(40),
|
||||
ReleaseTriggerType: int32(1),
|
||||
BoundChState: ATK_CHARACTER_STATE_ATK3,
|
||||
Hits: []interface{}{
|
||||
Hits: []AnyBullet{
|
||||
&MeleeBullet{
|
||||
Bullet: &BulletConfig{
|
||||
StartupFrames: int32(7),
|
||||
@@ -520,7 +527,7 @@ var skills = map[int]*Skill{
|
||||
RecoveryFramesOnHit: int32(38),
|
||||
ReleaseTriggerType: int32(1),
|
||||
BoundChState: ATK_CHARACTER_STATE_ATK4,
|
||||
Hits: []interface{}{
|
||||
Hits: []AnyBullet{
|
||||
&FireballBullet{
|
||||
Speed: int32(float64(6) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
Bullet: &BulletConfig{
|
||||
@@ -550,7 +557,7 @@ var skills = map[int]*Skill{
|
||||
RecoveryFramesOnHit: int32(60),
|
||||
ReleaseTriggerType: int32(1),
|
||||
BoundChState: ATK_CHARACTER_STATE_ATK5,
|
||||
Hits: []interface{}{
|
||||
Hits: []AnyBullet{
|
||||
&MeleeBullet{
|
||||
Bullet: &BulletConfig{
|
||||
StartupFrames: int32(3),
|
||||
@@ -579,7 +586,7 @@ var skills = map[int]*Skill{
|
||||
RecoveryFramesOnHit: int32(10),
|
||||
ReleaseTriggerType: int32(1),
|
||||
BoundChState: ATK_CHARACTER_STATE_DASHING,
|
||||
Hits: []interface{}{
|
||||
Hits: []AnyBullet{
|
||||
&MeleeBullet{
|
||||
Bullet: &BulletConfig{
|
||||
StartupFrames: int32(3),
|
||||
@@ -606,7 +613,7 @@ var skills = map[int]*Skill{
|
||||
RecoveryFramesOnHit: int32(12),
|
||||
ReleaseTriggerType: int32(1),
|
||||
BoundChState: ATK_CHARACTER_STATE_DASHING,
|
||||
Hits: []interface{}{
|
||||
Hits: []AnyBullet{
|
||||
&MeleeBullet{
|
||||
Bullet: &BulletConfig{
|
||||
StartupFrames: int32(3),
|
||||
@@ -633,7 +640,7 @@ var skills = map[int]*Skill{
|
||||
RecoveryFramesOnHit: int32(8),
|
||||
ReleaseTriggerType: int32(1),
|
||||
BoundChState: ATK_CHARACTER_STATE_DASHING,
|
||||
Hits: []interface{}{
|
||||
Hits: []AnyBullet{
|
||||
&MeleeBullet{
|
||||
Bullet: &BulletConfig{
|
||||
StartupFrames: int32(4),
|
||||
@@ -660,7 +667,7 @@ var skills = map[int]*Skill{
|
||||
RecoveryFramesOnHit: int32(48),
|
||||
ReleaseTriggerType: int32(1),
|
||||
BoundChState: ATK_CHARACTER_STATE_ATK4,
|
||||
Hits: []interface{}{
|
||||
Hits: []AnyBullet{
|
||||
&FireballBullet{
|
||||
Speed: int32(float64(4) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
Bullet: &BulletConfig{
|
||||
@@ -690,7 +697,7 @@ var skills = map[int]*Skill{
|
||||
RecoveryFramesOnHit: int32(60),
|
||||
ReleaseTriggerType: int32(1),
|
||||
BoundChState: ATK_CHARACTER_STATE_ATK4,
|
||||
Hits: []interface{}{
|
||||
Hits: []AnyBullet{
|
||||
&FireballBullet{
|
||||
Speed: int32(float64(4) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
Bullet: &BulletConfig{
|
||||
@@ -720,7 +727,7 @@ var skills = map[int]*Skill{
|
||||
RecoveryFramesOnHit: int32(30),
|
||||
ReleaseTriggerType: int32(1),
|
||||
BoundChState: ATK_CHARACTER_STATE_INAIR_ATK1,
|
||||
Hits: []interface{}{
|
||||
Hits: []AnyBullet{
|
||||
&MeleeBullet{
|
||||
Bullet: &BulletConfig{
|
||||
StartupFrames: int32(3),
|
||||
@@ -749,7 +756,7 @@ var skills = map[int]*Skill{
|
||||
RecoveryFramesOnHit: int32(20),
|
||||
ReleaseTriggerType: int32(1),
|
||||
BoundChState: ATK_CHARACTER_STATE_INAIR_ATK1,
|
||||
Hits: []interface{}{
|
||||
Hits: []AnyBullet{
|
||||
&MeleeBullet{
|
||||
Bullet: &BulletConfig{
|
||||
StartupFrames: int32(3),
|
||||
@@ -778,7 +785,7 @@ var skills = map[int]*Skill{
|
||||
RecoveryFramesOnHit: int32(30),
|
||||
ReleaseTriggerType: int32(1),
|
||||
BoundChState: ATK_CHARACTER_STATE_INAIR_ATK1,
|
||||
Hits: []interface{}{
|
||||
Hits: []AnyBullet{
|
||||
&MeleeBullet{
|
||||
Bullet: &BulletConfig{
|
||||
StartupFrames: int32(4),
|
||||
|
264
jsexport/battle/getter.go
Normal file
264
jsexport/battle/getter.go
Normal file
@@ -0,0 +1,264 @@
|
||||
package battle
|
||||
|
||||
// CharacterConfig
|
||||
func (c *CharacterConfig) GetSpeed() int32 {
|
||||
return c.Speed
|
||||
}
|
||||
func (c *CharacterConfig) GetSpeciesId() int {
|
||||
return c.SpeciesId
|
||||
}
|
||||
func (c *CharacterConfig) GetSpeciesName() string {
|
||||
return c.SpeciesName
|
||||
}
|
||||
|
||||
// InputFrameDownsync
|
||||
func (ifd *InputFrameDownsync) GetInputFrameId() int32 {
|
||||
return ifd.InputFrameId
|
||||
}
|
||||
|
||||
func (ifd *InputFrameDownsync) GetInputList() []uint64 {
|
||||
return ifd.InputList
|
||||
}
|
||||
|
||||
func (ifd *InputFrameDownsync) GetConfirmedList() uint64 {
|
||||
return ifd.ConfirmedList
|
||||
}
|
||||
|
||||
// PlayerDownsync
|
||||
func (p *PlayerDownsync) GetId() int32 {
|
||||
return p.Id
|
||||
}
|
||||
|
||||
func (p *PlayerDownsync) GetJoinIndex() int32 {
|
||||
return p.JoinIndex
|
||||
}
|
||||
|
||||
func (p *PlayerDownsync) GetVirtualGridX() int32 {
|
||||
return p.VirtualGridX
|
||||
}
|
||||
|
||||
func (p *PlayerDownsync) GetVirtualGridY() int32 {
|
||||
return p.VirtualGridY
|
||||
}
|
||||
|
||||
func (p *PlayerDownsync) GetDirX() int32 {
|
||||
return p.DirX
|
||||
}
|
||||
|
||||
func (p *PlayerDownsync) GetDirY() int32 {
|
||||
return p.DirY
|
||||
}
|
||||
|
||||
func (p *PlayerDownsync) GetVelX() int32 {
|
||||
return p.VelX
|
||||
}
|
||||
|
||||
func (p *PlayerDownsync) GetVelY() int32 {
|
||||
return p.VelY
|
||||
}
|
||||
|
||||
func (p *PlayerDownsync) GetSpeed() int32 {
|
||||
return p.Speed
|
||||
}
|
||||
|
||||
func (p *PlayerDownsync) GetHp() int32 {
|
||||
return p.Hp
|
||||
}
|
||||
|
||||
func (p *PlayerDownsync) GetMaxHp() int32 {
|
||||
return p.MaxHp
|
||||
}
|
||||
|
||||
func (p *PlayerDownsync) GetCharacterState() int32 {
|
||||
return p.CharacterState
|
||||
}
|
||||
|
||||
func (p *PlayerDownsync) GetFramesToRecover() int32 {
|
||||
return p.FramesToRecover
|
||||
}
|
||||
|
||||
func (p *PlayerDownsync) GetFramesInChState() int32 {
|
||||
return p.FramesInChState
|
||||
}
|
||||
|
||||
func (p *PlayerDownsync) GetInAir() bool {
|
||||
return p.InAir
|
||||
}
|
||||
|
||||
func (p *PlayerDownsync) GetOnWall() bool {
|
||||
return p.OnWall
|
||||
}
|
||||
|
||||
func (p *PlayerDownsync) GetOnWallNormX() int32 {
|
||||
return p.OnWallNormX
|
||||
}
|
||||
|
||||
func (p *PlayerDownsync) GetColliderRadius() int32 {
|
||||
return p.ColliderRadius
|
||||
}
|
||||
|
||||
// MeleeBullet
|
||||
func (b *MeleeBullet) GetBlState() int32 {
|
||||
return b.BlState
|
||||
}
|
||||
|
||||
func (b *MeleeBullet) GetFramesInBlState() int32 {
|
||||
return b.FramesInBlState
|
||||
}
|
||||
|
||||
func (b *MeleeBullet) GetBulletLocalId() int32 {
|
||||
return b.BattleAttr.BulletLocalId
|
||||
}
|
||||
|
||||
func (b *MeleeBullet) GetOffenderJoinIndex() int32 {
|
||||
return b.BattleAttr.OffenderJoinIndex
|
||||
}
|
||||
|
||||
func (b *MeleeBullet) GetOriginatedRenderFrameId() int32 {
|
||||
return b.BattleAttr.OriginatedRenderFrameId
|
||||
}
|
||||
|
||||
func (b *MeleeBullet) GetStartupFrames() int32 {
|
||||
return b.Bullet.StartupFrames
|
||||
}
|
||||
|
||||
func (b *MeleeBullet) GetActiveFrames() int32 {
|
||||
return b.Bullet.ActiveFrames
|
||||
}
|
||||
|
||||
func (b *MeleeBullet) GetHitboxSizeX() int32 {
|
||||
return b.Bullet.HitboxSizeX
|
||||
}
|
||||
|
||||
func (b *MeleeBullet) GetHitboxSizeY() int32 {
|
||||
return b.Bullet.HitboxSizeY
|
||||
}
|
||||
|
||||
func (b *MeleeBullet) GetHitboxOffsetX() int32 {
|
||||
return b.Bullet.HitboxOffsetX
|
||||
}
|
||||
|
||||
func (b *MeleeBullet) GetHitboxOffsetY() int32 {
|
||||
return b.Bullet.HitboxOffsetY
|
||||
}
|
||||
|
||||
func (b *MeleeBullet) GetExplosionFrames() int32 {
|
||||
return b.Bullet.ExplosionFrames
|
||||
}
|
||||
|
||||
func (b *MeleeBullet) GetSpeciesId() int32 {
|
||||
return b.Bullet.SpeciesId
|
||||
}
|
||||
|
||||
// FireballBullet
|
||||
func (p *FireballBullet) GetVirtualGridX() int32 {
|
||||
return p.VirtualGridX
|
||||
}
|
||||
|
||||
func (p *FireballBullet) GetVirtualGridY() int32 {
|
||||
return p.VirtualGridY
|
||||
}
|
||||
|
||||
func (p *FireballBullet) GetDirX() int32 {
|
||||
return p.DirX
|
||||
}
|
||||
|
||||
func (p *FireballBullet) GetDirY() int32 {
|
||||
return p.DirY
|
||||
}
|
||||
|
||||
func (p *FireballBullet) GetVelX() int32 {
|
||||
return p.VelX
|
||||
}
|
||||
|
||||
func (p *FireballBullet) GetVelY() int32 {
|
||||
return p.VelY
|
||||
}
|
||||
|
||||
func (p *FireballBullet) GetSpeed() int32 {
|
||||
return p.Speed
|
||||
}
|
||||
func (b *FireballBullet) GetBlState() int32 {
|
||||
return b.BlState
|
||||
}
|
||||
|
||||
func (b *FireballBullet) GetFramesInBlState() int32 {
|
||||
return b.FramesInBlState
|
||||
}
|
||||
|
||||
func (b *FireballBullet) GetBulletLocalId() int32 {
|
||||
return b.BattleAttr.BulletLocalId
|
||||
}
|
||||
|
||||
func (b *FireballBullet) GetOffenderJoinIndex() int32 {
|
||||
return b.BattleAttr.OffenderJoinIndex
|
||||
}
|
||||
|
||||
func (b *FireballBullet) GetOriginatedRenderFrameId() int32 {
|
||||
return b.BattleAttr.OriginatedRenderFrameId
|
||||
}
|
||||
|
||||
func (b *FireballBullet) GetStartupFrames() int32 {
|
||||
return b.Bullet.StartupFrames
|
||||
}
|
||||
|
||||
func (b *FireballBullet) GetActiveFrames() int32 {
|
||||
return b.Bullet.ActiveFrames
|
||||
}
|
||||
|
||||
func (b *FireballBullet) GetHitboxSizeX() int32 {
|
||||
return b.Bullet.HitboxSizeX
|
||||
}
|
||||
|
||||
func (b *FireballBullet) GetHitboxSizeY() int32 {
|
||||
return b.Bullet.HitboxSizeY
|
||||
}
|
||||
|
||||
func (b *FireballBullet) GetHitboxOffsetX() int32 {
|
||||
return b.Bullet.HitboxOffsetX
|
||||
}
|
||||
|
||||
func (b *FireballBullet) GetHitboxOffsetY() int32 {
|
||||
return b.Bullet.HitboxOffsetY
|
||||
}
|
||||
|
||||
func (b *FireballBullet) GetExplosionFrames() int32 {
|
||||
return b.Bullet.ExplosionFrames
|
||||
}
|
||||
|
||||
func (b *FireballBullet) GetSpeciesId() int32 {
|
||||
return b.Bullet.SpeciesId
|
||||
}
|
||||
|
||||
// RoomDownsyncFrame
|
||||
func (r *RoomDownsyncFrame) GetId() int32 {
|
||||
return r.Id
|
||||
}
|
||||
|
||||
func (r *RoomDownsyncFrame) GetCountdownNanos() int64 {
|
||||
return r.CountdownNanos
|
||||
}
|
||||
|
||||
func (r *RoomDownsyncFrame) GetBackendUnconfirmedMask() uint64 {
|
||||
return r.BackendUnconfirmedMask
|
||||
}
|
||||
|
||||
func (r *RoomDownsyncFrame) GetBulletLocalIdCounter() int32 {
|
||||
return r.BulletLocalIdCounter
|
||||
}
|
||||
|
||||
func (r *RoomDownsyncFrame) GetShouldForceResync() bool {
|
||||
return r.ShouldForceResync
|
||||
}
|
||||
|
||||
func (r *RoomDownsyncFrame) GetPlayersArr() []*PlayerDownsync {
|
||||
return r.PlayersArr
|
||||
}
|
||||
|
||||
func (r *RoomDownsyncFrame) GetMeleeBullets() []*MeleeBullet {
|
||||
return r.MeleeBullets
|
||||
}
|
||||
|
||||
func (r *RoomDownsyncFrame) GetFireballBullets() []*FireballBullet {
|
||||
return r.FireballBullets
|
||||
}
|
@@ -2,6 +2,8 @@ package battle
|
||||
|
||||
// TODO: Replace all "int32", "int64", "uint32" and "uint64" with just "int" for better performance in JavaScript! Reference https://github.com/gopherjs/gopherjs#performance-tips
|
||||
|
||||
type AnyBullet interface{}
|
||||
|
||||
type Vec2D struct {
|
||||
X float64
|
||||
Y float64
|
||||
@@ -132,7 +134,7 @@ type Skill struct {
|
||||
RecoveryFramesOnHit int32
|
||||
ReleaseTriggerType int32 // 1: rising-edge, 2: falling-edge
|
||||
BoundChState int32
|
||||
Hits []interface{} // Hits within a "Skill" are automatically triggered
|
||||
Hits []AnyBullet // Hits within a "Skill" are automatically triggered
|
||||
// [WARN] Multihit of a fireball is more difficult to handle than that of melee, because we have to count from the fireball's first hit; the situation becomes even more complicated when a multihit fireball is in a crowd -- remains to be designed
|
||||
}
|
||||
|
||||
|
176
jsexport/main.go
176
jsexport/main.go
@@ -6,8 +6,23 @@ import (
|
||||
"resolv"
|
||||
)
|
||||
|
||||
/*
|
||||
[WARNING] Should avoid using "MakeFullWrapper" as much as possible, and completely remove its usage in 60fps calls like "update(dt)" on frontend!
|
||||
*/
|
||||
func NewDynamicRectangleColliders(cnt int) []*js.Object {
|
||||
ret := make([]*js.Object, cnt)
|
||||
for i := 0; i < cnt; i++ {
|
||||
ret[i] = js.MakeWrapper(GenerateRectCollider(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, nil, ""))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func NewCollisionHolder() *js.Object {
|
||||
return js.MakeWrapper(resolv.NewCollision())
|
||||
}
|
||||
|
||||
func NewInputFrameDownsync(inputFrameId int32, inputList []uint64, confirmedList uint64) *js.Object {
|
||||
return js.MakeFullWrapper(&InputFrameDownsync{
|
||||
return js.MakeWrapper(&InputFrameDownsync{
|
||||
InputFrameId: inputFrameId,
|
||||
InputList: inputList,
|
||||
ConfirmedList: confirmedList,
|
||||
@@ -15,7 +30,7 @@ func NewInputFrameDownsync(inputFrameId int32, inputList []uint64, confirmedList
|
||||
}
|
||||
|
||||
func NewRingBufferJs(n int32) *js.Object {
|
||||
return js.MakeFullWrapper(NewRingBuffer(n))
|
||||
return js.MakeWrapper(resolv.NewRingBuffer(n))
|
||||
}
|
||||
|
||||
func NewCollisionSpaceJs(spaceW, spaceH, minStepW, minStepH int) *js.Object {
|
||||
@@ -23,14 +38,14 @@ func NewCollisionSpaceJs(spaceW, spaceH, minStepW, minStepH int) *js.Object {
|
||||
}
|
||||
|
||||
func NewVec2DJs(x, y float64) *js.Object {
|
||||
return js.MakeFullWrapper(&Vec2D{
|
||||
return js.MakeWrapper(&Vec2D{
|
||||
X: x,
|
||||
Y: y,
|
||||
})
|
||||
}
|
||||
|
||||
func NewPolygon2DJs(anchor *Vec2D, points []*Vec2D) *js.Object {
|
||||
return js.MakeFullWrapper(&Polygon2D{
|
||||
return js.MakeWrapper(&Polygon2D{
|
||||
Anchor: anchor,
|
||||
Points: points,
|
||||
})
|
||||
@@ -43,36 +58,7 @@ func NewBarrierJs(boundary *Polygon2D) *js.Object {
|
||||
}
|
||||
|
||||
func NewPlayerDownsyncJs(id, virtualGridX, virtualGridY, dirX, dirY, velX, velY, framesToRecover, framesInChState, activeSkillId, activeSkillHit, framesInvinsible, speed, battleState, characterState, joinIndex, hp, maxHp, colliderRadius int32, inAir, onWall bool, onWallNormX, onWallNormY int32, capturedByInertia bool, bulletTeamId, chCollisionTeamId int32, revivalVirtualGridX, revivalVirtualGridY int32) *js.Object {
|
||||
return js.MakeWrapper(&PlayerDownsync{
|
||||
Id: id,
|
||||
VirtualGridX: virtualGridX,
|
||||
VirtualGridY: virtualGridY,
|
||||
DirX: dirX,
|
||||
DirY: dirY,
|
||||
VelX: velX,
|
||||
VelY: velY,
|
||||
FramesToRecover: framesToRecover,
|
||||
FramesInChState: framesInChState,
|
||||
ActiveSkillId: activeSkillId,
|
||||
ActiveSkillHit: activeSkillHit,
|
||||
FramesInvinsible: framesInvinsible,
|
||||
Speed: speed,
|
||||
BattleState: battleState,
|
||||
CharacterState: characterState,
|
||||
JoinIndex: joinIndex,
|
||||
Hp: hp,
|
||||
MaxHp: maxHp,
|
||||
ColliderRadius: colliderRadius,
|
||||
InAir: inAir,
|
||||
OnWall: onWall,
|
||||
OnWallNormX: onWallNormX,
|
||||
OnWallNormY: onWallNormY,
|
||||
CapturedByInertia: capturedByInertia,
|
||||
BulletTeamId: bulletTeamId,
|
||||
ChCollisionTeamId: chCollisionTeamId,
|
||||
RevivalVirtualGridX: revivalVirtualGridX,
|
||||
RevivalVirtualGridY: revivalVirtualGridY,
|
||||
})
|
||||
return js.MakeWrapper(NewPlayerDownsync(id, virtualGridX, virtualGridY, dirX, dirY, velX, velY, framesToRecover, framesInChState, activeSkillId, activeSkillHit, framesInvinsible, speed, battleState, characterState, joinIndex, hp, maxHp, colliderRadius, inAir, onWall, onWallNormX, onWallNormY, capturedByInertia, bulletTeamId, chCollisionTeamId, revivalVirtualGridX, revivalVirtualGridY))
|
||||
}
|
||||
|
||||
func NewMeleeBulletJs(bulletLocalId, originatedRenderFrameId, offenderJoinIndex, startupFrames, cancellableStFrame, cancellableEdFrame, activeFrames, hitStunFrames, blockStunFrames, pushbackVelX, pushbackVelY, damage, selfLockVelX, selfLockVelY, hitboxOffsetX, hitboxOffsetY, hitboxSizeX, hitboxSizeY int32, blowUp bool, teamId, blState, framesInBlState, explosionFrames, speciesId int32) *js.Object {
|
||||
@@ -84,7 +70,7 @@ func NewFireballBulletJs(bulletLocalId, originatedRenderFrameId, offenderJoinInd
|
||||
}
|
||||
|
||||
func NewNpcPatrolCue(flAct, frAct uint64, x, y float64) *js.Object {
|
||||
return js.MakeFullWrapper(&NpcPatrolCue{
|
||||
return js.MakeWrapper(&NpcPatrolCue{
|
||||
FlAct: flAct,
|
||||
FrAct: frAct,
|
||||
X: x,
|
||||
@@ -93,45 +79,23 @@ func NewNpcPatrolCue(flAct, frAct uint64, x, y float64) *js.Object {
|
||||
}
|
||||
|
||||
func NewRoomDownsyncFrameJs(id int32, playersArr []*PlayerDownsync, bulletLocalIdCounter int32, meleeBullets []*MeleeBullet, fireballBullets []*FireballBullet) *js.Object {
|
||||
// [WARNING] Avoid using "pb.RoomDownsyncFrame" here, in practive "MakeFullWrapper" doesn't expose the public fields for a "protobuf struct" as expected and requires helper functions like "GetCollisionSpaceObjsJs".
|
||||
return js.MakeFullWrapper(&RoomDownsyncFrame{
|
||||
Id: id,
|
||||
PlayersArr: playersArr,
|
||||
BulletLocalIdCounter: bulletLocalIdCounter,
|
||||
MeleeBullets: meleeBullets,
|
||||
FireballBullets: fireballBullets,
|
||||
})
|
||||
preallocatedRdf := NewPreallocatedRoomDownsyncFrame(len(playersArr), 64, 64)
|
||||
CloneRoomDownsyncFrame(id, playersArr, bulletLocalIdCounter, meleeBullets, fireballBullets, preallocatedRdf)
|
||||
return js.MakeWrapper(preallocatedRdf)
|
||||
}
|
||||
|
||||
func GetCollisionSpaceObjsJs(space *resolv.Space) []*js.Object {
|
||||
// [WARNING] We couldn't just use the existing method "space.Objects()" to access them in JavaScript, there'd a stackoverflow error
|
||||
objs := space.Objects()
|
||||
ret := make([]*js.Object, 0, len(objs))
|
||||
for _, obj := range objs {
|
||||
ret = append(ret, js.MakeFullWrapper(obj))
|
||||
ret := make([]*js.Object, len(objs))
|
||||
for i, obj := range objs {
|
||||
ret[i] = js.MakeWrapper(obj)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func GenerateRectColliderJs(wx, wy, w, h, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) *js.Object {
|
||||
/*
|
||||
[WARNING] It's important to note that we don't need "js.MakeFullWrapper" for a call sequence as follows.
|
||||
```
|
||||
var space = gopkgs.NewCollisionSpaceJs(2048, 2048, 8, 8);
|
||||
var a = gopkgs.GenerateRectColliderJs(189, 497, 48, 48, spaceOffsetX, spaceOffsetY, "Player");
|
||||
space.Add(a);
|
||||
```
|
||||
The "space" variable doesn't need access to the field of "a" in JavaScript level to run "space.Add(...)" method, which is good.
|
||||
|
||||
However, the full wrapper access here is used for updating "collider.X/collider.Y" at JavaScript runtime.
|
||||
*/
|
||||
topPadding, bottomPadding, leftPadding, rightPadding := SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP
|
||||
return js.MakeFullWrapper(GenerateRectCollider(wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, data, tag))
|
||||
|
||||
}
|
||||
|
||||
func GenerateConvexPolygonColliderJs(unalignedSrc *Polygon2D, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) *js.Object {
|
||||
return js.MakeFullWrapper(GenerateConvexPolygonCollider(unalignedSrc, spaceOffsetX, spaceOffsetY, data, tag))
|
||||
return js.MakeWrapper(GenerateConvexPolygonCollider(unalignedSrc, spaceOffsetX, spaceOffsetY, data, tag))
|
||||
}
|
||||
|
||||
func GetCharacterConfigsOrderedByJoinIndex(speciesIdList []int) []*js.Object {
|
||||
@@ -142,9 +106,72 @@ func GetCharacterConfigsOrderedByJoinIndex(speciesIdList []int) []*js.Object {
|
||||
return ret
|
||||
}
|
||||
|
||||
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(inputsBuffer *RingBuffer, currRenderFrame *RoomDownsyncFrame, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, collisionSpaceOffsetX, collisionSpaceOffsetY float64, chConfigsOrderedByJoinIndex []*CharacterConfig) *js.Object {
|
||||
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(inputsBuffer *resolv.RingBuffer, currRenderFrameId int32, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, collisionSpaceOffsetX, collisionSpaceOffsetY float64, chConfigsOrderedByJoinIndex []*CharacterConfig, renderFrameBuffer *resolv.RingBuffer, collision *resolv.Collision, effPushbacks []*Vec2D, hardPushbackNormsArr [][]*Vec2D, jumpedOrNotList []bool, dynamicRectangleColliders []*resolv.Object, lastIndividuallyConfirmedInputFrameId []int32, lastIndividuallyConfirmedInputList []uint64, allowUpdateInputFrameInPlaceUponDynamics bool, toExcludeJoinIndexUpdateInputFrameInPlaceUponDynamics int32) bool {
|
||||
// We need access to all fields of RoomDownsyncFrame for displaying in frontend
|
||||
return js.MakeFullWrapper(ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer, currRenderFrame, collisionSys, collisionSysMap, collisionSpaceOffsetX, collisionSpaceOffsetY, chConfigsOrderedByJoinIndex))
|
||||
return ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer, currRenderFrameId, collisionSys, collisionSysMap, collisionSpaceOffsetX, collisionSpaceOffsetY, chConfigsOrderedByJoinIndex, renderFrameBuffer, collision, effPushbacks, hardPushbackNormsArr, jumpedOrNotList, dynamicRectangleColliders, lastIndividuallyConfirmedInputFrameId, lastIndividuallyConfirmedInputList, allowUpdateInputFrameInPlaceUponDynamics, toExcludeJoinIndexUpdateInputFrameInPlaceUponDynamics)
|
||||
}
|
||||
|
||||
func GetRoomDownsyncFrame(renderFrameBuffer *resolv.RingBuffer, frameId int32) *js.Object {
|
||||
// [WARNING] Calling "renderFrameBuffer.GetByFrameId(frameId)" directly from transpiled frontend code would automatically invoke the expensive "$externalize" and "$mapArray"! See profiling result for more details.
|
||||
candidate := renderFrameBuffer.GetByFrameId(frameId)
|
||||
if nil == candidate {
|
||||
return nil
|
||||
}
|
||||
return js.MakeWrapper(candidate.(*RoomDownsyncFrame))
|
||||
}
|
||||
|
||||
func GetInputFrameDownsync(inputsBuffer *resolv.RingBuffer, inputFrameId int32) *js.Object {
|
||||
candidate := inputsBuffer.GetByFrameId(inputFrameId)
|
||||
if nil == candidate {
|
||||
return nil
|
||||
}
|
||||
return js.MakeWrapper(candidate.(*InputFrameDownsync))
|
||||
}
|
||||
|
||||
func GetInput(ifd *InputFrameDownsync, i int) uint64 {
|
||||
// [WARNING] Calling "ifd.GetInputList()" directly from transpiled frontend code would make a copy of the array.
|
||||
return ifd.InputList[i]
|
||||
}
|
||||
|
||||
func SetInputFrameId(ifd *InputFrameDownsync, newVal int32) bool {
|
||||
// [WARNING] This function should be only used by frontend which is single-threaded; on the backend more rigorous thread-safety concerns are taken care of by proper locking.
|
||||
ifd.InputFrameId = newVal
|
||||
return true
|
||||
}
|
||||
|
||||
func SetInput(ifd *InputFrameDownsync, i int, newVal uint64) bool {
|
||||
// [WARNING] This function should be only used by frontend which is single-threaded; on the backend more rigorous thread-safety concerns are taken care of by proper locking.
|
||||
if i >= len(ifd.InputList) {
|
||||
return false
|
||||
}
|
||||
|
||||
ifd.InputList[i] = newVal
|
||||
return true
|
||||
}
|
||||
|
||||
func SetConfirmedList(ifd *InputFrameDownsync, newVal uint64) bool {
|
||||
// [WARNING] This function should be only used by frontend which is single-threaded; on the backend more rigorous thread-safety concerns are taken care of by proper locking.
|
||||
ifd.ConfirmedList = newVal
|
||||
return true
|
||||
}
|
||||
|
||||
func GetPlayer(rdf *RoomDownsyncFrame, i int) *js.Object {
|
||||
// [WARNING] Calling "rdf.GetPlayersArr()" directly from transpiled frontend code would automatically invoke the expensive "$externalize" and "$mapArray"! See profiling result for more details.
|
||||
return js.MakeWrapper(rdf.PlayersArr[i])
|
||||
}
|
||||
|
||||
func GetMeleeBullet(rdf *RoomDownsyncFrame, i int) *js.Object {
|
||||
if TERMINATING_BULLET_LOCAL_ID == rdf.MeleeBullets[i].GetBulletLocalId() {
|
||||
return nil
|
||||
}
|
||||
return js.MakeWrapper(rdf.MeleeBullets[i])
|
||||
}
|
||||
|
||||
func GetFireballBullet(rdf *RoomDownsyncFrame, i int) *js.Object {
|
||||
if TERMINATING_BULLET_LOCAL_ID == rdf.FireballBullets[i].GetBulletLocalId() {
|
||||
return nil
|
||||
}
|
||||
return js.MakeWrapper(rdf.FireballBullets[i])
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -158,9 +185,9 @@ func main() {
|
||||
"NewNpcPatrolCue": NewNpcPatrolCue,
|
||||
"NewRoomDownsyncFrameJs": NewRoomDownsyncFrameJs,
|
||||
"NewCollisionSpaceJs": NewCollisionSpaceJs,
|
||||
"NewCollisionHolder": NewCollisionHolder,
|
||||
"NewInputFrameDownsync": NewInputFrameDownsync,
|
||||
"NewRingBufferJs": NewRingBufferJs,
|
||||
"GenerateRectColliderJs": GenerateRectColliderJs,
|
||||
"GenerateConvexPolygonColliderJs": GenerateConvexPolygonColliderJs,
|
||||
"GetCollisionSpaceObjsJs": GetCollisionSpaceObjsJs,
|
||||
"WorldToPolygonColliderBLPos": WorldToPolygonColliderBLPos, // No need to wrap primitive return types
|
||||
@@ -174,9 +201,16 @@ func main() {
|
||||
"ConvertToFirstUsedRenderFrameId": ConvertToFirstUsedRenderFrameId,
|
||||
"ConvertToLastUsedRenderFrameId": ConvertToLastUsedRenderFrameId,
|
||||
"ShouldGenerateInputFrameUpsync": ShouldGenerateInputFrameUpsync,
|
||||
"IsMeleeBulletActive": IsMeleeBulletActive,
|
||||
"IsMeleeBulletAlive": IsMeleeBulletAlive,
|
||||
"IsFireballBulletActive": IsFireballBulletActive,
|
||||
"IsFireballBulletAlive": IsFireballBulletAlive,
|
||||
"IsGeneralBulletActive": IsGeneralBulletActive,
|
||||
"GetRoomDownsyncFrame": GetRoomDownsyncFrame,
|
||||
"GetInputFrameDownsync": GetInputFrameDownsync,
|
||||
"GetPlayer": GetPlayer,
|
||||
"GetMeleeBullet": GetMeleeBullet,
|
||||
"GetFireballBullet": GetFireballBullet,
|
||||
"GetInput": GetInput,
|
||||
"NewDynamicRectangleColliders": NewDynamicRectangleColliders,
|
||||
"SetInputFrameId": SetInputFrameId,
|
||||
"SetInput": SetInput,
|
||||
"SetConfirmedList": SetConfirmedList,
|
||||
})
|
||||
}
|
||||
|
@@ -1,164 +0,0 @@
|
||||
// This file contains code from the gonum repository:
|
||||
// https://github.com/gonum/gonum/blob/master/internal/asm/f64/scalunitaryto_amd64.s
|
||||
// it is distributed under the 3-Clause BSD license:
|
||||
//
|
||||
// Copyright ©2013 The Gonum Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// * Neither the name of the Gonum project nor the names of its authors and
|
||||
// contributors may be used to endorse or promote products derived from this
|
||||
// software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Some of the loop unrolling code is copied from:
|
||||
// http://golang.org/src/math/big/arith_amd64.s
|
||||
// which is distributed under these terms:
|
||||
//
|
||||
// Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// +build !noasm
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
#define X_PTR SI
|
||||
#define Y_PTR DX
|
||||
#define DST_PTR DI
|
||||
#define IDX AX
|
||||
#define LEN CX
|
||||
#define TAIL BX
|
||||
#define ALPHA X0
|
||||
#define ALPHA_2 X1
|
||||
|
||||
// func axpyUnitaryTo(dst []float64, alpha float64, x, y []float64)
|
||||
TEXT ·axpyUnitaryTo(SB), NOSPLIT, $0
|
||||
MOVQ dst_base+0(FP), DST_PTR // DST_PTR := &dst
|
||||
MOVQ x_base+32(FP), X_PTR // X_PTR := &x
|
||||
MOVQ y_base+56(FP), Y_PTR // Y_PTR := &y
|
||||
MOVQ x_len+40(FP), LEN // LEN = min( len(x), len(y), len(dst) )
|
||||
CMPQ y_len+64(FP), LEN
|
||||
CMOVQLE y_len+64(FP), LEN
|
||||
CMPQ dst_len+8(FP), LEN
|
||||
CMOVQLE dst_len+8(FP), LEN
|
||||
|
||||
CMPQ LEN, $0
|
||||
JE end // if LEN == 0 { return }
|
||||
|
||||
XORQ IDX, IDX // IDX = 0
|
||||
MOVSD alpha+24(FP), ALPHA
|
||||
SHUFPD $0, ALPHA, ALPHA // ALPHA := { alpha, alpha }
|
||||
MOVQ Y_PTR, TAIL // Check memory alignment
|
||||
ANDQ $15, TAIL // TAIL = &y % 16
|
||||
JZ no_trim // if TAIL == 0 { goto no_trim }
|
||||
|
||||
// Align on 16-byte boundary
|
||||
MOVSD (X_PTR), X2 // X2 := x[0]
|
||||
MULSD ALPHA, X2 // X2 *= a
|
||||
ADDSD (Y_PTR), X2 // X2 += y[0]
|
||||
MOVSD X2, (DST_PTR) // y[0] = X2
|
||||
INCQ IDX // i++
|
||||
DECQ LEN // LEN--
|
||||
JZ end // if LEN == 0 { return }
|
||||
|
||||
no_trim:
|
||||
MOVQ LEN, TAIL
|
||||
ANDQ $7, TAIL // TAIL := n % 8
|
||||
SHRQ $3, LEN // LEN = floor( n / 8 )
|
||||
JZ tail_start // if LEN == 0 { goto tail_start }
|
||||
|
||||
MOVUPS ALPHA, ALPHA_2 // ALPHA_2 := ALPHA for pipelining
|
||||
|
||||
loop: // do {
|
||||
// y[i] += alpha * x[i] unrolled 8x.
|
||||
MOVUPS (X_PTR)(IDX*8), X2 // X_i = x[i]
|
||||
MOVUPS 16(X_PTR)(IDX*8), X3
|
||||
MOVUPS 32(X_PTR)(IDX*8), X4
|
||||
MOVUPS 48(X_PTR)(IDX*8), X5
|
||||
|
||||
MULPD ALPHA, X2 // X_i *= alpha
|
||||
MULPD ALPHA_2, X3
|
||||
MULPD ALPHA, X4
|
||||
MULPD ALPHA_2, X5
|
||||
|
||||
ADDPD (Y_PTR)(IDX*8), X2 // X_i += y[i]
|
||||
ADDPD 16(Y_PTR)(IDX*8), X3
|
||||
ADDPD 32(Y_PTR)(IDX*8), X4
|
||||
ADDPD 48(Y_PTR)(IDX*8), X5
|
||||
|
||||
MOVUPS X2, (DST_PTR)(IDX*8) // y[i] = X_i
|
||||
MOVUPS X3, 16(DST_PTR)(IDX*8)
|
||||
MOVUPS X4, 32(DST_PTR)(IDX*8)
|
||||
MOVUPS X5, 48(DST_PTR)(IDX*8)
|
||||
|
||||
ADDQ $8, IDX // i += 8
|
||||
DECQ LEN
|
||||
JNZ loop // } while --LEN > 0
|
||||
CMPQ TAIL, $0 // if TAIL == 0 { return }
|
||||
JE end
|
||||
|
||||
tail_start: // Reset loop registers
|
||||
MOVQ TAIL, LEN // Loop counter: LEN = TAIL
|
||||
SHRQ $1, LEN // LEN = floor( TAIL / 2 )
|
||||
JZ tail_one // if LEN == 0 { goto tail }
|
||||
|
||||
tail_two: // do {
|
||||
MOVUPS (X_PTR)(IDX*8), X2 // X2 = x[i]
|
||||
MULPD ALPHA, X2 // X2 *= alpha
|
||||
ADDPD (Y_PTR)(IDX*8), X2 // X2 += y[i]
|
||||
MOVUPS X2, (DST_PTR)(IDX*8) // y[i] = X2
|
||||
ADDQ $2, IDX // i += 2
|
||||
DECQ LEN
|
||||
JNZ tail_two // } while --LEN > 0
|
||||
|
||||
ANDQ $1, TAIL
|
||||
JZ end // if TAIL == 0 { goto end }
|
||||
|
||||
tail_one:
|
||||
MOVSD (X_PTR)(IDX*8), X2 // X2 = x[i]
|
||||
MULSD ALPHA, X2 // X2 *= a
|
||||
ADDSD (Y_PTR)(IDX*8), X2 // X2 += y[i]
|
||||
MOVSD X2, (DST_PTR)(IDX*8) // y[i] = X2
|
||||
|
||||
end:
|
||||
RET
|
@@ -1,137 +0,0 @@
|
||||
// This file contains code from the gonum repository:
|
||||
// https://github.com/gonum/gonum/blob/master/internal/asm/f64/axpyunitaryto_amd64.s
|
||||
// it is distributed under the 3-Clause BSD license:
|
||||
//
|
||||
// Copyright ©2013 The Gonum Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// * Neither the name of the Gonum project nor the names of its authors and
|
||||
// contributors may be used to endorse or promote products derived from this
|
||||
// software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Some of the loop unrolling code is copied from:
|
||||
// http://golang.org/src/math/big/arith_amd64.s
|
||||
// which is distributed under these terms:
|
||||
//
|
||||
// Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// +build !noasm
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
#define MOVDDUP_ALPHA LONG $0x44120FF2; WORD $0x2024 // @ MOVDDUP 32(SP), X0 /*XMM0, 32[RSP]*/
|
||||
|
||||
#define X_PTR SI
|
||||
#define DST_PTR DI
|
||||
#define IDX AX
|
||||
#define LEN CX
|
||||
#define TAIL BX
|
||||
#define ALPHA X0
|
||||
#define ALPHA_2 X1
|
||||
|
||||
// func scalUnitaryTo(dst []float64, alpha float64, x []float64)
|
||||
// This function assumes len(dst) >= len(x).
|
||||
TEXT ·scalUnitaryTo(SB), NOSPLIT, $0
|
||||
MOVQ x_base+32(FP), X_PTR // X_PTR = &x
|
||||
MOVQ dst_base+0(FP), DST_PTR // DST_PTR = &dst
|
||||
MOVDDUP_ALPHA // ALPHA = { alpha, alpha }
|
||||
MOVQ x_len+40(FP), LEN // LEN = len(x)
|
||||
CMPQ LEN, $0
|
||||
JE end // if LEN == 0 { return }
|
||||
|
||||
XORQ IDX, IDX // IDX = 0
|
||||
MOVQ LEN, TAIL
|
||||
ANDQ $7, TAIL // TAIL = LEN % 8
|
||||
SHRQ $3, LEN // LEN = floor( LEN / 8 )
|
||||
JZ tail_start // if LEN == 0 { goto tail_start }
|
||||
|
||||
MOVUPS ALPHA, ALPHA_2 // ALPHA_2 = ALPHA for pipelining
|
||||
|
||||
loop: // do { // dst[i] = alpha * x[i] unrolled 8x.
|
||||
MOVUPS (X_PTR)(IDX*8), X2 // X_i = x[i]
|
||||
MOVUPS 16(X_PTR)(IDX*8), X3
|
||||
MOVUPS 32(X_PTR)(IDX*8), X4
|
||||
MOVUPS 48(X_PTR)(IDX*8), X5
|
||||
|
||||
MULPD ALPHA, X2 // X_i *= ALPHA
|
||||
MULPD ALPHA_2, X3
|
||||
MULPD ALPHA, X4
|
||||
MULPD ALPHA_2, X5
|
||||
|
||||
MOVUPS X2, (DST_PTR)(IDX*8) // dst[i] = X_i
|
||||
MOVUPS X3, 16(DST_PTR)(IDX*8)
|
||||
MOVUPS X4, 32(DST_PTR)(IDX*8)
|
||||
MOVUPS X5, 48(DST_PTR)(IDX*8)
|
||||
|
||||
ADDQ $8, IDX // i += 8
|
||||
DECQ LEN
|
||||
JNZ loop // while --LEN > 0
|
||||
CMPQ TAIL, $0
|
||||
JE end // if TAIL == 0 { return }
|
||||
|
||||
tail_start: // Reset loop counters
|
||||
MOVQ TAIL, LEN // Loop counter: LEN = TAIL
|
||||
SHRQ $1, LEN // LEN = floor( TAIL / 2 )
|
||||
JZ tail_one // if LEN == 0 { goto tail_one }
|
||||
|
||||
tail_two: // do {
|
||||
MOVUPS (X_PTR)(IDX*8), X2 // X_i = x[i]
|
||||
MULPD ALPHA, X2 // X_i *= ALPHA
|
||||
MOVUPS X2, (DST_PTR)(IDX*8) // dst[i] = X_i
|
||||
ADDQ $2, IDX // i += 2
|
||||
DECQ LEN
|
||||
JNZ tail_two // while --LEN > 0
|
||||
|
||||
ANDQ $1, TAIL
|
||||
JZ end // if TAIL == 0 { return }
|
||||
|
||||
tail_one:
|
||||
MOVSD (X_PTR)(IDX*8), X2 // X_i = x[i]
|
||||
MULSD ALPHA, X2 // X_i *= ALPHA
|
||||
MOVSD X2, (DST_PTR)(IDX*8) // dst[i] = X_i
|
||||
|
||||
end:
|
||||
RET
|
@@ -1,9 +0,0 @@
|
||||
//go:build !noasm
|
||||
// +build !noasm
|
||||
|
||||
package resolv
|
||||
|
||||
// functions from the gonum package that optimizes arithmetic
|
||||
// operations on lists of float64 values
|
||||
func axpyUnitaryTo(dst []float64, alpha float64, x, y []float64)
|
||||
func scalUnitaryTo(dst []float64, alpha float64, x []float64)
|
@@ -3,33 +3,35 @@ package resolv
|
||||
// Cell is used to contain and organize Object information.
|
||||
type Cell struct {
|
||||
X, Y int // The X and Y position of the cell in the Space - note that this is in Grid position, not World position.
|
||||
Objects []*Object // The Objects that a Cell contains.
|
||||
Objects *RingBuffer // The Objects that a Cell contains.
|
||||
}
|
||||
|
||||
// newCell creates a new cell at the specified X and Y position. Should not be used directly.
|
||||
func newCell(x, y int) *Cell {
|
||||
return &Cell{
|
||||
X: x,
|
||||
Y: y,
|
||||
Objects: []*Object{},
|
||||
}
|
||||
c := &Cell{}
|
||||
c.X = x
|
||||
c.Y = y
|
||||
c.Objects = NewRingBuffer(16) // A single cell is so small thus wouldn't have many touching objects simultaneously
|
||||
return c
|
||||
}
|
||||
|
||||
// register registers an object with a Cell. Should not be used directly.
|
||||
func (cell *Cell) register(obj *Object) {
|
||||
if !cell.Contains(obj) {
|
||||
cell.Objects = append(cell.Objects, obj)
|
||||
cell.Objects.Put(obj)
|
||||
}
|
||||
}
|
||||
|
||||
// unregister unregisters an object from a Cell. Should not be used directly.
|
||||
func (cell *Cell) unregister(obj *Object) {
|
||||
|
||||
for i, o := range cell.Objects {
|
||||
|
||||
rb := cell.Objects
|
||||
for i := rb.StFrameId; i < rb.EdFrameId; i++ {
|
||||
o := rb.GetByFrameId(i).(*Object)
|
||||
if o == obj {
|
||||
cell.Objects[i] = cell.Objects[len(cell.Objects)-1]
|
||||
cell.Objects = cell.Objects[:len(cell.Objects)-1]
|
||||
// swap with the st element
|
||||
rb.SetByFrameId(rb.GetByFrameId(rb.StFrameId), i)
|
||||
// pop the current st element
|
||||
rb.Pop()
|
||||
break
|
||||
}
|
||||
|
||||
@@ -39,7 +41,9 @@ func (cell *Cell) unregister(obj *Object) {
|
||||
|
||||
// Contains returns whether a Cell contains the specified Object at its position.
|
||||
func (cell *Cell) Contains(obj *Object) bool {
|
||||
for _, o := range cell.Objects {
|
||||
rb := cell.Objects
|
||||
for i := rb.StFrameId; i < rb.EdFrameId; i++ {
|
||||
o := rb.GetByFrameId(i).(*Object)
|
||||
if o == obj {
|
||||
return true
|
||||
}
|
||||
@@ -49,7 +53,9 @@ func (cell *Cell) Contains(obj *Object) bool {
|
||||
|
||||
// ContainsTags returns whether a Cell contains an Object that has the specified tag at its position.
|
||||
func (cell *Cell) ContainsTags(tags ...string) bool {
|
||||
for _, o := range cell.Objects {
|
||||
rb := cell.Objects
|
||||
for i := rb.StFrameId; i < rb.EdFrameId; i++ {
|
||||
o := rb.GetByFrameId(i).(*Object)
|
||||
if o.HasTags(tags...) {
|
||||
return true
|
||||
}
|
||||
@@ -59,5 +65,5 @@ func (cell *Cell) ContainsTags(tags ...string) bool {
|
||||
|
||||
// Occupied returns whether a Cell contains any Objects at all.
|
||||
func (cell *Cell) Occupied() bool {
|
||||
return len(cell.Objects) > 0
|
||||
return 0 < cell.Objects.Cnt
|
||||
}
|
||||
|
@@ -5,21 +5,37 @@ package resolv
|
||||
type Collision struct {
|
||||
checkingObject *Object // The checking object
|
||||
dx, dy float64 // The delta the checking object was moving on that caused this collision
|
||||
Objects []*Object // Slice of objects that were collided with; sorted according to distance to calling Object.
|
||||
Cells []*Cell // Slice of cells that were collided with; sorted according to distance to calling Object.
|
||||
Objects *RingBuffer // Slice of objects that were collided with; sorted according to distance to calling Object.
|
||||
Cells *RingBuffer // Slice of cells that were collided with; sorted according to distance to calling Object.
|
||||
}
|
||||
|
||||
func NewCollision() *Collision {
|
||||
return &Collision{
|
||||
Objects: []*Object{},
|
||||
c := &Collision{}
|
||||
c.Objects = NewRingBuffer(16) // I don't expect it to exceed 10 actually
|
||||
c.Cells = NewRingBuffer(16)
|
||||
return c
|
||||
}
|
||||
|
||||
func (cc *Collision) Clear() {
|
||||
cc.checkingObject = nil
|
||||
cc.dx = 0
|
||||
cc.dy = 0
|
||||
cc.Objects.Clear()
|
||||
cc.Cells.Clear()
|
||||
}
|
||||
|
||||
func (cc *Collision) PopFirstCollidedObject() *Object {
|
||||
if 0 >= cc.Objects.Cnt {
|
||||
return nil
|
||||
}
|
||||
return cc.Objects.Pop().(*Object)
|
||||
}
|
||||
|
||||
// HasTags returns whether any objects within the Collision have all of the specified tags. This slice does not contain the Object that called Check().
|
||||
func (cc *Collision) HasTags(tags ...string) bool {
|
||||
|
||||
for _, o := range cc.Objects {
|
||||
|
||||
rb := cc.Objects
|
||||
for i := rb.StFrameId; i < rb.EdFrameId; i++ {
|
||||
o := rb.GetByFrameId(i).(*Object)
|
||||
if o == cc.checkingObject {
|
||||
continue
|
||||
}
|
||||
@@ -36,38 +52,39 @@ func (cc *Collision) HasTags(tags ...string) bool {
|
||||
// This slice does not contain the Object that called Check().
|
||||
func (cc *Collision) ObjectsByTags(tags ...string) []*Object {
|
||||
|
||||
objects := []*Object{}
|
||||
|
||||
for _, o := range cc.Objects {
|
||||
objs := []*Object{}
|
||||
|
||||
rb := cc.Objects
|
||||
for i := rb.StFrameId; i < rb.EdFrameId; i++ {
|
||||
o := rb.GetByFrameId(i).(*Object)
|
||||
if o == cc.checkingObject {
|
||||
continue
|
||||
}
|
||||
if o.HasTags(tags...) {
|
||||
objects = append(objects, o)
|
||||
objs = append(objs, o)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return objects
|
||||
return objs
|
||||
|
||||
}
|
||||
|
||||
// ContactWithObject returns the delta to move to come into contact with the specified Object.
|
||||
func (cc *Collision) ContactWithObject(object *Object) Vector {
|
||||
func (cc *Collision) ContactWithObject(obj *Object) Vector {
|
||||
|
||||
delta := Vector{0, 0}
|
||||
|
||||
if cc.dx < 0 {
|
||||
delta[0] = object.X + object.W - cc.checkingObject.X
|
||||
delta[0] = obj.X + obj.W - cc.checkingObject.X
|
||||
} else if cc.dx > 0 {
|
||||
delta[0] = object.X - cc.checkingObject.W - cc.checkingObject.X
|
||||
delta[0] = obj.X - cc.checkingObject.W - cc.checkingObject.X
|
||||
}
|
||||
|
||||
if cc.dy < 0 {
|
||||
delta[1] = object.Y + object.H - cc.checkingObject.Y
|
||||
delta[1] = obj.Y + obj.H - cc.checkingObject.Y
|
||||
} else if cc.dy > 0 {
|
||||
delta[1] = object.Y - cc.checkingObject.H - cc.checkingObject.Y
|
||||
delta[1] = obj.Y - cc.checkingObject.H - cc.checkingObject.Y
|
||||
}
|
||||
|
||||
return delta
|
||||
@@ -105,7 +122,7 @@ func (cc *Collision) SlideAgainstCell(cell *Cell, avoidTags ...string) Vector {
|
||||
|
||||
sp := cc.checkingObject.Space
|
||||
|
||||
collidingCell := cc.Cells[0]
|
||||
collidingCell := cc.Cells.GetByFrameId(cc.Cells.StFrameId).(*Cell)
|
||||
ccX, ccY := sp.SpaceToWorld(collidingCell.X, collidingCell.Y)
|
||||
hX := float64(sp.CellWidth) / 2.0
|
||||
hY := float64(sp.CellHeight) / 2.0
|
||||
@@ -118,10 +135,10 @@ func (cc *Collision) SlideAgainstCell(cell *Cell, avoidTags ...string) Vector {
|
||||
diffX := oX - ccX
|
||||
diffY := oY - ccY
|
||||
|
||||
left := sp.Cell(collidingCell.X-1, collidingCell.Y)
|
||||
right := sp.Cell(collidingCell.X+1, collidingCell.Y)
|
||||
up := sp.Cell(collidingCell.X, collidingCell.Y-1)
|
||||
down := sp.Cell(collidingCell.X, collidingCell.Y+1)
|
||||
left := sp.GetCell(collidingCell.X-1, collidingCell.Y)
|
||||
right := sp.GetCell(collidingCell.X+1, collidingCell.Y)
|
||||
up := sp.GetCell(collidingCell.X, collidingCell.Y-1)
|
||||
down := sp.GetCell(collidingCell.X, collidingCell.Y+1)
|
||||
|
||||
slide := Vector{0, 0}
|
||||
|
||||
|
@@ -1,24 +0,0 @@
|
||||
//go:build !amd64 || noasm
|
||||
// +build !amd64 noasm
|
||||
|
||||
package resolv
|
||||
|
||||
// This function is from the gonum repository:
|
||||
// https://github.com/gonum/gonum/blob/c3867503e73e5c3fee7ab93e3c2c562eb2be8178/internal/asm/f64/axpy.go#L23
|
||||
func axpyUnitaryTo(dst []float64, alpha float64, x, y []float64) {
|
||||
dim := len(y)
|
||||
for i, v := range x {
|
||||
if i == dim {
|
||||
return
|
||||
}
|
||||
dst[i] = alpha*v + y[i]
|
||||
}
|
||||
}
|
||||
|
||||
// This function is from the gonum repository:
|
||||
// https://github.com/gonum/gonum/blob/c3867503e73e5c3fee7ab93e3c2c562eb2be8178/internal/asm/f64/scal.go#L23
|
||||
func scalUnitaryTo(dst []float64, alpha float64, x []float64) {
|
||||
for i := range x {
|
||||
dst[i] *= alpha
|
||||
}
|
||||
}
|
@@ -1,32 +1,40 @@
|
||||
package resolv
|
||||
|
||||
import (
|
||||
"math"
|
||||
//"sort"
|
||||
)
|
||||
|
||||
// Object represents an object that can be spread across one or more Cells in a Space. An Object is essentially an AABB (Axis-Aligned Bounding Box) Rectangle.
|
||||
type Object struct {
|
||||
Shape Shape // A shape for more specific collision-checking.
|
||||
Space *Space // Reference to the Space the Object exists within
|
||||
X, Y, W, H float64 // Position and size of the Object in the Space
|
||||
TouchingCells []*Cell // An array of Cells the Object is touching
|
||||
TouchingCells *RingBuffer // An array of Cells the Object is touching
|
||||
Data interface{} // A pointer to a user-definable object
|
||||
ignoreList map[*Object]bool // Set of Objects to ignore when checking for collisions
|
||||
tags []string // A list of tags the Object has
|
||||
}
|
||||
|
||||
// NewObject returns a new Object of the specified position and size.
|
||||
func NewObject(x, y, w, h float64, tags ...string) *Object {
|
||||
o := &Object{
|
||||
X: x,
|
||||
Y: y,
|
||||
W: w,
|
||||
H: h,
|
||||
tags: []string{},
|
||||
ignoreList: map[*Object]bool{},
|
||||
func NewObjectSingleTag(x, y, w, h float64, tag string) *Object {
|
||||
o := &Object{}
|
||||
o.X = x
|
||||
o.Y = y
|
||||
o.W = w
|
||||
o.H = h
|
||||
o.TouchingCells = NewRingBuffer(512) // [WARNING] Should make N large enough to cover all "TouchingCells", otherwise some cells would fail to unregister an object, resulting in memory corruption and incorrect detection result!
|
||||
o.tags = []string{tag}
|
||||
o.ignoreList = make(map[*Object]bool)
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
func NewObject(x, y, w, h float64, tags ...string) *Object {
|
||||
o := &Object{}
|
||||
o.X = x
|
||||
o.Y = y
|
||||
o.W = w
|
||||
o.H = h
|
||||
o.TouchingCells = NewRingBuffer(512)
|
||||
o.tags = []string{}
|
||||
o.ignoreList = make(map[*Object]bool)
|
||||
|
||||
if len(tags) > 0 {
|
||||
o.AddTags(tags...)
|
||||
}
|
||||
@@ -34,6 +42,18 @@ func NewObject(x, y, w, h float64, tags ...string) *Object {
|
||||
return o
|
||||
}
|
||||
|
||||
func (obj *Object) GetData() interface{} {
|
||||
return obj.Data
|
||||
}
|
||||
|
||||
func (obj *Object) GetShape() *Shape {
|
||||
return &(obj.Shape)
|
||||
}
|
||||
|
||||
func (obj *Object) Position() (float64, float64) {
|
||||
return obj.X, obj.Y
|
||||
}
|
||||
|
||||
// Clone clones the Object with its properties into another Object. It also clones the Object's Shape (if it has one).
|
||||
func (obj *Object) Clone() *Object {
|
||||
newObj := NewObject(obj.X, obj.Y, obj.W, obj.H, obj.Tags()...)
|
||||
@@ -41,7 +61,7 @@ func (obj *Object) Clone() *Object {
|
||||
if obj.Shape != nil {
|
||||
newObj.SetShape(obj.Shape.Clone())
|
||||
}
|
||||
for k := range obj.ignoreList {
|
||||
for k, _ := range obj.ignoreList {
|
||||
newObj.AddToIgnoreList(k)
|
||||
}
|
||||
return newObj
|
||||
@@ -59,7 +79,7 @@ func (obj *Object) Update() {
|
||||
|
||||
space := obj.Space
|
||||
|
||||
obj.Space.Remove(obj)
|
||||
obj.Space.RemoveSingle(obj)
|
||||
|
||||
obj.Space = space
|
||||
|
||||
@@ -69,11 +89,11 @@ func (obj *Object) Update() {
|
||||
|
||||
for x := cx; x <= ex; x++ {
|
||||
|
||||
c := obj.Space.Cell(x, y)
|
||||
c := obj.Space.GetCell(x, y)
|
||||
|
||||
if c != nil {
|
||||
c.register(obj)
|
||||
obj.TouchingCells = append(obj.TouchingCells, c)
|
||||
obj.TouchingCells.Put(c)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -154,17 +174,22 @@ func (obj *Object) BoundsToSpace(dx, dy float64) (int, int, int, int) {
|
||||
|
||||
// SharesCells returns whether the Object occupies a cell shared by the specified other Object.
|
||||
func (obj *Object) SharesCells(other *Object) bool {
|
||||
for _, cell := range obj.TouchingCells {
|
||||
rb := obj.TouchingCells
|
||||
for i := rb.StFrameId; i < rb.EdFrameId; i++ {
|
||||
cell := rb.GetByFrameId(i).(*Cell)
|
||||
if cell.Contains(other) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// SharesCellsTags returns if the Cells the Object occupies have an object with the specified tags.
|
||||
func (obj *Object) SharesCellsTags(tags ...string) bool {
|
||||
for _, cell := range obj.TouchingCells {
|
||||
rb := obj.TouchingCells
|
||||
for i := rb.StFrameId; i < rb.EdFrameId; i++ {
|
||||
cell := rb.GetByFrameId(i).(*Cell)
|
||||
if cell.ContainsTags(tags...) {
|
||||
return true
|
||||
}
|
||||
@@ -218,25 +243,24 @@ func (obj *Object) SetBounds(topLeft, bottomRight Vector) {
|
||||
// Check checks the space around the object using the designated delta movement (dx and dy). This is done by querying the containing Space's Cells
|
||||
// so that it can see if moving it would coincide with a cell that houses another Object (filtered using the given selection of tag strings). If so,
|
||||
// Check returns a Collision. If no objects are found or the Object does not exist within a Space, this function returns nil.
|
||||
func (obj *Object) Check(dx, dy float64, tags ...string) *Collision {
|
||||
func (obj *Object) CheckAllWithHolder(dx, dy float64, cc *Collision) bool {
|
||||
|
||||
if obj.Space == nil {
|
||||
return nil
|
||||
return false
|
||||
}
|
||||
|
||||
cc := NewCollision()
|
||||
cc.Clear()
|
||||
cc.checkingObject = obj
|
||||
|
||||
if dx < 0 {
|
||||
dx = math.Min(dx, -1)
|
||||
dx = Min(dx, -1)
|
||||
} else if dx > 0 {
|
||||
dx = math.Max(dx, 1)
|
||||
dx = Max(dx, 1)
|
||||
}
|
||||
|
||||
if dy < 0 {
|
||||
dy = math.Min(dy, -1)
|
||||
dy = Min(dy, -1)
|
||||
} else if dy > 0 {
|
||||
dy = math.Max(dy, 1)
|
||||
dy = Max(dy, 1)
|
||||
}
|
||||
|
||||
cc.dx = dx
|
||||
@@ -251,65 +275,38 @@ func (obj *Object) Check(dx, dy float64, tags ...string) *Collision {
|
||||
|
||||
for x := cx; x <= ex; x++ {
|
||||
|
||||
if c := obj.Space.Cell(x, y); c != nil {
|
||||
|
||||
for _, o := range c.Objects {
|
||||
if c := obj.Space.GetCell(x, y); c != nil {
|
||||
|
||||
rb := c.Objects
|
||||
for i := rb.StFrameId; i < rb.EdFrameId; i++ {
|
||||
o := rb.GetByFrameId(i).(*Object)
|
||||
// We only want cells that have objects other than the checking object, or that aren't on the ignore list.
|
||||
if ignored := obj.ignoreList[o]; o == obj || ignored {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, added := objectsAdded[o]; (len(tags) == 0 || o.HasTags(tags...)) && !added {
|
||||
|
||||
cc.Objects = append(cc.Objects, o)
|
||||
if _, added := objectsAdded[o]; !added {
|
||||
cc.Objects.Put(o)
|
||||
objectsAdded[o] = true
|
||||
if _, added := cellsAdded[c]; !added {
|
||||
cc.Cells = append(cc.Cells, c)
|
||||
cc.Cells.Put(c)
|
||||
cellsAdded[c] = true
|
||||
}
|
||||
continue
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if 0 >= cc.Objects.Cnt {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(cc.Objects) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
// In my use case, order of objects within a collision instance is not needed, and this also favors both runtime performance & size reduction of `jsexport.js`.
|
||||
|
||||
ox, oy := cc.checkingObject.Center()
|
||||
oc := Vector{ox, oy}
|
||||
sort.Slice(cc.Objects, func(i, j int) bool {
|
||||
|
||||
ix, iy := cc.Objects[i].Center()
|
||||
jx, jy := cc.Objects[j].Center()
|
||||
return Vector{ix, iy}.Sub(oc).Magnitude2() < Vector{jx, jy}.Sub(oc).Magnitude2()
|
||||
|
||||
})
|
||||
|
||||
cw := cc.checkingObject.Space.CellWidth
|
||||
ch := cc.checkingObject.Space.CellHeight
|
||||
|
||||
sort.Slice(cc.Cells, func(i, j int) bool {
|
||||
|
||||
return Vector{float64(cc.Cells[i].X*cw + (cw / 2)), float64(cc.Cells[i].Y*ch + (ch / 2))}.Sub(oc).Magnitude2() <
|
||||
Vector{float64(cc.Cells[j].X*cw + (cw / 2)), float64(cc.Cells[j].Y*ch + (ch / 2))}.Sub(oc).Magnitude2()
|
||||
|
||||
})
|
||||
*/
|
||||
|
||||
return cc
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Overlaps returns if an Object overlaps another Object.
|
||||
|
@@ -1,11 +1,14 @@
|
||||
package battle
|
||||
package resolv
|
||||
|
||||
const (
|
||||
RING_BUFF_CONSECUTIVE_SET = int32(0)
|
||||
RING_BUFF_NON_CONSECUTIVE_SET = int32(1)
|
||||
RING_BUFF_FAILED_TO_SET = int32(2)
|
||||
// Declare type "int32" explicitly to prevent go2cs from transpiling them to "var"
|
||||
RING_BUFF_CONSECUTIVE_SET int32 = 0
|
||||
RING_BUFF_NON_CONSECUTIVE_SET int32 = 1
|
||||
RING_BUFF_FAILED_TO_SET int32 = 2
|
||||
)
|
||||
|
||||
type AnyObj interface{}
|
||||
|
||||
type RingBuffer struct {
|
||||
Ed int32 // write index, open index
|
||||
St int32 // read index, closed index
|
||||
@@ -13,20 +16,35 @@ type RingBuffer struct {
|
||||
StFrameId int32
|
||||
N int32
|
||||
Cnt int32 // the count of valid elements in the buffer, used mainly to distinguish what "st == ed" means for "Pop" and "Get" methods
|
||||
Eles []interface{}
|
||||
Eles []AnyObj
|
||||
}
|
||||
|
||||
func NewRingBuffer(n int32) *RingBuffer {
|
||||
return &RingBuffer{
|
||||
Ed: 0,
|
||||
St: 0,
|
||||
N: n,
|
||||
Cnt: 0,
|
||||
Eles: make([]interface{}, n),
|
||||
ret := &RingBuffer{}
|
||||
ret.Ed = 0
|
||||
ret.St = 0
|
||||
ret.EdFrameId = 0
|
||||
ret.StFrameId = 0
|
||||
ret.N = n
|
||||
ret.Cnt = 0
|
||||
ret.Eles = make([]AnyObj, n)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (rb *RingBuffer) DryPut() {
|
||||
for 0 < rb.Cnt && rb.Cnt >= rb.N {
|
||||
// Make room for the new element
|
||||
rb.Pop()
|
||||
}
|
||||
rb.EdFrameId++
|
||||
rb.Cnt++
|
||||
rb.Ed++
|
||||
if rb.Ed >= rb.N {
|
||||
rb.Ed -= rb.N // Deliberately not using "%" operator for performance concern
|
||||
}
|
||||
}
|
||||
|
||||
func (rb *RingBuffer) Put(pItem interface{}) {
|
||||
func (rb *RingBuffer) Put(pItem AnyObj) {
|
||||
for 0 < rb.Cnt && rb.Cnt >= rb.N {
|
||||
// Make room for the new element
|
||||
rb.Pop()
|
||||
@@ -40,7 +58,7 @@ func (rb *RingBuffer) Put(pItem interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func (rb *RingBuffer) Pop() interface{} {
|
||||
func (rb *RingBuffer) Pop() AnyObj {
|
||||
if 0 == rb.Cnt {
|
||||
return nil
|
||||
}
|
||||
@@ -78,7 +96,7 @@ func (rb *RingBuffer) GetArrIdxByOffset(offsetFromSt int32) int32 {
|
||||
return -1
|
||||
}
|
||||
|
||||
func (rb *RingBuffer) GetByOffset(offsetFromSt int32) interface{} {
|
||||
func (rb *RingBuffer) GetByOffset(offsetFromSt int32) AnyObj {
|
||||
arrIdx := rb.GetArrIdxByOffset(offsetFromSt)
|
||||
if -1 == arrIdx {
|
||||
return nil
|
||||
@@ -86,7 +104,7 @@ func (rb *RingBuffer) GetByOffset(offsetFromSt int32) interface{} {
|
||||
return rb.Eles[arrIdx]
|
||||
}
|
||||
|
||||
func (rb *RingBuffer) GetByFrameId(frameId int32) interface{} {
|
||||
func (rb *RingBuffer) GetByFrameId(frameId int32) AnyObj {
|
||||
if frameId >= rb.EdFrameId || frameId < rb.StFrameId {
|
||||
return nil
|
||||
}
|
||||
@@ -94,7 +112,7 @@ func (rb *RingBuffer) GetByFrameId(frameId int32) interface{} {
|
||||
}
|
||||
|
||||
// [WARNING] During a battle, frontend could receive non-consecutive frames (either renderFrame or inputFrame) due to resync, the buffer should handle these frames properly.
|
||||
func (rb *RingBuffer) SetByFrameId(pItem interface{}, frameId int32) (int32, int32, int32) {
|
||||
func (rb *RingBuffer) SetByFrameId(pItem AnyObj, frameId int32) (int32, int32, int32) {
|
||||
oldStFrameId, oldEdFrameId := rb.StFrameId, rb.EdFrameId
|
||||
if frameId < oldStFrameId {
|
||||
return RING_BUFF_FAILED_TO_SET, oldStFrameId, oldEdFrameId
|
||||
@@ -122,3 +140,25 @@ func (rb *RingBuffer) SetByFrameId(pItem interface{}, frameId int32) (int32, int
|
||||
|
||||
return ret, oldStFrameId, oldEdFrameId
|
||||
}
|
||||
|
||||
func (rb *RingBuffer) Clear() {
|
||||
for 0 < rb.Cnt {
|
||||
rb.Pop()
|
||||
}
|
||||
rb.St = 0
|
||||
rb.Ed = 0
|
||||
rb.StFrameId = 0
|
||||
rb.EdFrameId = 0
|
||||
}
|
||||
|
||||
func (rb *RingBuffer) GetStFrameId() int32 {
|
||||
return rb.StFrameId
|
||||
}
|
||||
|
||||
func (rb *RingBuffer) GetEdFrameId() int32 {
|
||||
return rb.EdFrameId
|
||||
}
|
||||
|
||||
func (rb *RingBuffer) GetCnt() int32 {
|
||||
return rb.Cnt
|
||||
}
|
@@ -1,9 +1,5 @@
|
||||
package resolv
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
type Shape interface {
|
||||
// Intersection tests to see if a Shape intersects with the other given Shape. dx and dy are delta movement variables indicating
|
||||
// movement to be applied before the intersection check (thereby allowing you to see if a Shape would collide with another if it
|
||||
@@ -27,23 +23,16 @@ type Line struct {
|
||||
}
|
||||
|
||||
func NewLine(x, y, x2, y2 float64) *Line {
|
||||
return &Line{
|
||||
Start: Vector{x, y},
|
||||
End: Vector{x2, y2},
|
||||
}
|
||||
}
|
||||
|
||||
func (line *Line) Project(axis Vector) Vector {
|
||||
return line.Vector().Scale(axis.Dot(line.Start.Sub(line.End)))
|
||||
l := &Line{}
|
||||
l.Start = Vector{x, y}
|
||||
l.End = Vector{x2, y2}
|
||||
return l
|
||||
}
|
||||
|
||||
func (line *Line) Normal() Vector {
|
||||
v := line.Vector()
|
||||
return Vector{v[1], -v[0]}.Unit()
|
||||
}
|
||||
|
||||
func (line *Line) Vector() Vector {
|
||||
return line.End.Clone().Sub(line.Start).Unit()
|
||||
dy := line.End[1] - line.Start[1]
|
||||
dx := line.End[0] - line.Start[0]
|
||||
return Vector{dy, -dx}.Unit()
|
||||
}
|
||||
|
||||
// IntersectionPointsLine returns the intersection point of a Line with another Line as a Vector. If no intersection is found, it will return nil.
|
||||
@@ -78,53 +67,8 @@ func (line *Line) IntersectionPointsLine(other *Line) Vector {
|
||||
|
||||
}
|
||||
|
||||
// IntersectionPointsCircle returns a slice of Vectors, each indicating the intersection point. If no intersection is found, it will return an empty slice.
|
||||
func (line *Line) IntersectionPointsCircle(circle *Circle) []Vector {
|
||||
|
||||
points := []Vector{}
|
||||
|
||||
cp := Vector{circle.X, circle.Y}
|
||||
lStart := line.Start.Sub(cp)
|
||||
lEnd := line.End.Sub(cp)
|
||||
diff := lEnd.Sub(lStart)
|
||||
|
||||
a := diff[0]*diff[0] + diff[1]*diff[1]
|
||||
b := 2 * ((diff[0] * lStart[0]) + (diff[1] * lStart[1]))
|
||||
c := (lStart[0] * lStart[0]) + (lStart[1] * lStart[1]) - (circle.Radius * circle.Radius)
|
||||
|
||||
det := b*b - (4 * a * c)
|
||||
|
||||
if det < 0 {
|
||||
// Do nothing, no intersections
|
||||
} else if det == 0 {
|
||||
|
||||
t := -b / (2 * a)
|
||||
|
||||
if t >= 0 && t <= 1 {
|
||||
points = append(points, Vector{line.Start[0] + t*diff[0], line.Start[1] + t*diff[1]})
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
t := (-b + math.Sqrt(det)) / (2 * a)
|
||||
|
||||
// We have to ensure t is between 0 and 1; otherwise, the collision points are on the circle as though the lines were infinite in length.
|
||||
if t >= 0 && t <= 1 {
|
||||
points = append(points, Vector{line.Start[0] + t*diff[0], line.Start[1] + t*diff[1]})
|
||||
}
|
||||
t = (-b - math.Sqrt(det)) / (2 * a)
|
||||
if t >= 0 && t <= 1 {
|
||||
points = append(points, Vector{line.Start[0] + t*diff[0], line.Start[1] + t*diff[1]})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return points
|
||||
|
||||
}
|
||||
|
||||
type ConvexPolygon struct {
|
||||
Points []Vector
|
||||
Points *RingBuffer
|
||||
X, Y float64
|
||||
Closed bool
|
||||
}
|
||||
@@ -134,67 +78,88 @@ type ConvexPolygon struct {
|
||||
// polygon square, with the vertices at {0,0}, {10,0}, {10, 10}, and {0, 10}.
|
||||
func NewConvexPolygon(points ...float64) *ConvexPolygon {
|
||||
|
||||
// if len(points)/2 < 2 {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
cp := &ConvexPolygon{Points: []Vector{}, Closed: true}
|
||||
cp := &ConvexPolygon{}
|
||||
cp.Points = NewRingBuffer(6) // I don't expected more points to be coped with in this particular game
|
||||
cp.Closed = true
|
||||
|
||||
cp.AddPoints(points...)
|
||||
|
||||
return cp
|
||||
}
|
||||
|
||||
func (cp *ConvexPolygon) Clone() Shape {
|
||||
|
||||
points := []Vector{}
|
||||
|
||||
for _, point := range cp.Points {
|
||||
points = append(points, point.Clone())
|
||||
func (cp *ConvexPolygon) GetPointByOffset(offset int32) Vector {
|
||||
if cp.Points.Cnt <= offset {
|
||||
return nil
|
||||
}
|
||||
return cp.Points.GetByFrameId(cp.Points.StFrameId + offset).(Vector)
|
||||
}
|
||||
|
||||
func (cp *ConvexPolygon) Clone() Shape {
|
||||
|
||||
newPoly := NewConvexPolygon()
|
||||
newPoly.X = cp.X
|
||||
newPoly.Y = cp.Y
|
||||
newPoly.AddPointsVec(points...)
|
||||
for i := int32(0); i < cp.Points.Cnt; i++ {
|
||||
newPoly.Points.Put(cp.GetPointByOffset(i))
|
||||
}
|
||||
newPoly.Closed = cp.Closed
|
||||
return newPoly
|
||||
}
|
||||
|
||||
// AddPointsVec allows you to add points to the ConvexPolygon with a slice of Vectors, each indicating a point / vertex.
|
||||
func (cp *ConvexPolygon) AddPointsVec(points ...Vector) {
|
||||
cp.Points = append(cp.Points, points...)
|
||||
}
|
||||
|
||||
// AddPoints allows you to add points to the ConvexPolygon with a slice or selection of float64s, with each pair indicating an X or Y value for
|
||||
// a point / vertex (i.e. AddPoints(0, 1, 2, 3) would add two points - one at {0, 1}, and another at {2, 3}).
|
||||
func (cp *ConvexPolygon) AddPoints(vertexPositions ...float64) {
|
||||
for v := 0; v < len(vertexPositions); v += 2 {
|
||||
cp.Points = append(cp.Points, Vector{vertexPositions[v], vertexPositions[v+1]})
|
||||
// "resolv.Vector" is an alias of "[]float64", thus already a pointer type
|
||||
cp.Points.Put(Vector{vertexPositions[v], vertexPositions[v+1]})
|
||||
}
|
||||
}
|
||||
|
||||
func (cp *ConvexPolygon) UpdateAsRectangle(x, y, w, h float64) bool {
|
||||
// This function might look ugly but it's a fast in-place update!
|
||||
if 4 != cp.Points.Cnt {
|
||||
panic("ConvexPolygon not having exactly 4 vertices to form a rectangle#1!")
|
||||
}
|
||||
for i := int32(0); i < cp.Points.Cnt; i++ {
|
||||
thatVec := cp.GetPointByOffset(i)
|
||||
if nil == thatVec {
|
||||
panic("ConvexPolygon not having exactly 4 vertices to form a rectangle#2!")
|
||||
}
|
||||
switch i {
|
||||
case 0:
|
||||
thatVec[0] = x
|
||||
thatVec[1] = y
|
||||
case 1:
|
||||
thatVec[0] = x + w
|
||||
thatVec[1] = y
|
||||
case 2:
|
||||
thatVec[0] = x + w
|
||||
thatVec[1] = y + h
|
||||
case 3:
|
||||
thatVec[0] = x
|
||||
thatVec[1] = y + h
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Lines returns a slice of transformed Lines composing the ConvexPolygon.
|
||||
func (cp *ConvexPolygon) Lines() []*Line {
|
||||
|
||||
lines := []*Line{}
|
||||
|
||||
vertices := cp.Transformed()
|
||||
linesCnt := len(vertices)
|
||||
if !cp.Closed {
|
||||
linesCnt -= 1
|
||||
}
|
||||
lines := make([]*Line, linesCnt)
|
||||
|
||||
for i := 0; i < len(vertices); i++ {
|
||||
|
||||
for i := 0; i < linesCnt; i++ {
|
||||
start, end := vertices[i], vertices[0]
|
||||
|
||||
if i < len(vertices)-1 {
|
||||
end = vertices[i+1]
|
||||
} else if !cp.Closed {
|
||||
break
|
||||
}
|
||||
|
||||
line := NewLine(start[0], start[1], end[0], end[1])
|
||||
|
||||
lines = append(lines, line)
|
||||
|
||||
lines[i] = line
|
||||
}
|
||||
|
||||
return lines
|
||||
@@ -203,9 +168,10 @@ func (cp *ConvexPolygon) Lines() []*Line {
|
||||
|
||||
// Transformed returns the ConvexPolygon's points / vertices, transformed according to the ConvexPolygon's position.
|
||||
func (cp *ConvexPolygon) Transformed() []Vector {
|
||||
transformed := []Vector{}
|
||||
for _, point := range cp.Points {
|
||||
transformed = append(transformed, Vector{point[0] + cp.X, point[1] + cp.Y})
|
||||
transformed := make([]Vector, cp.Points.Cnt)
|
||||
for i := int32(0); i < cp.Points.Cnt; i++ {
|
||||
point := cp.GetPointByOffset(i)
|
||||
transformed[i] = Vector{point[0] + cp.X, point[1] + cp.Y}
|
||||
}
|
||||
return transformed
|
||||
}
|
||||
@@ -217,7 +183,7 @@ func (cp *ConvexPolygon) Bounds() (Vector, Vector) {
|
||||
transformed := cp.Transformed()
|
||||
|
||||
topLeft := Vector{transformed[0][0], transformed[0][1]}
|
||||
bottomRight := topLeft.Clone()
|
||||
bottomRight := Vector{transformed[0][0], transformed[0][1]}
|
||||
|
||||
for i := 0; i < len(transformed); i++ {
|
||||
|
||||
@@ -254,8 +220,8 @@ func (cp *ConvexPolygon) SetPosition(x, y float64) {
|
||||
// SetPositionVec allows you to set the position of the ConvexPolygon using a Vector. The offset of the vertices compared to the X and Y
|
||||
// position is relative to however you initially defined the polygon and added the vertices.
|
||||
func (cp *ConvexPolygon) SetPositionVec(vec Vector) {
|
||||
cp.X = vec.X()
|
||||
cp.Y = vec.Y()
|
||||
cp.X = vec.GetX()
|
||||
cp.Y = vec.GetY()
|
||||
}
|
||||
|
||||
// Move translates the ConvexPolygon by the designated X and Y values.
|
||||
@@ -266,49 +232,16 @@ func (cp *ConvexPolygon) Move(x, y float64) {
|
||||
|
||||
// MoveVec translates the ConvexPolygon by the designated Vector.
|
||||
func (cp *ConvexPolygon) MoveVec(vec Vector) {
|
||||
cp.X += vec.X()
|
||||
cp.Y += vec.Y()
|
||||
}
|
||||
|
||||
// Center returns the transformed Center of the ConvexPolygon.
|
||||
func (cp *ConvexPolygon) Center() Vector {
|
||||
|
||||
pos := Vector{0, 0}
|
||||
|
||||
for _, v := range cp.Transformed() {
|
||||
pos.Add(v)
|
||||
}
|
||||
|
||||
pos[0] /= float64(len(cp.Transformed()))
|
||||
pos[1] /= float64(len(cp.Transformed()))
|
||||
|
||||
return pos
|
||||
|
||||
}
|
||||
|
||||
// Project projects (i.e. flattens) the ConvexPolygon onto the provided axis.
|
||||
func (cp *ConvexPolygon) Project(axis Vector) Projection {
|
||||
axis = axis.Unit()
|
||||
vertices := cp.Transformed()
|
||||
min := axis.Dot(Vector{vertices[0][0], vertices[0][1]})
|
||||
max := min
|
||||
for i := 1; i < len(vertices); i++ {
|
||||
p := axis.Dot(Vector{vertices[i][0], vertices[i][1]})
|
||||
if p < min {
|
||||
min = p
|
||||
} else if p > max {
|
||||
max = p
|
||||
}
|
||||
}
|
||||
return Projection{min, max}
|
||||
cp.X += vec.GetX()
|
||||
cp.Y += vec.GetY()
|
||||
}
|
||||
|
||||
// SATAxes returns the axes of the ConvexPolygon for SAT intersection testing.
|
||||
func (cp *ConvexPolygon) SATAxes() []Vector {
|
||||
|
||||
axes := []Vector{}
|
||||
for _, line := range cp.Lines() {
|
||||
axes = append(axes, line.Normal())
|
||||
lines := cp.Lines()
|
||||
axes := make([]Vector, len(lines))
|
||||
for i, line := range lines {
|
||||
axes[i] = line.Normal()
|
||||
}
|
||||
return axes
|
||||
|
||||
@@ -339,11 +272,11 @@ type ContactSet struct {
|
||||
}
|
||||
|
||||
func NewContactSet() *ContactSet {
|
||||
return &ContactSet{
|
||||
Points: []Vector{},
|
||||
MTV: Vector{0, 0},
|
||||
Center: Vector{0, 0},
|
||||
}
|
||||
cs := &ContactSet{}
|
||||
cs.Points = []Vector{}
|
||||
cs.MTV = Vector{0, 0}
|
||||
cs.Center = Vector{}
|
||||
return cs
|
||||
}
|
||||
|
||||
// LeftmostPoint returns the left-most point out of the ContactSet's Points slice. If the Points slice is empty somehow, this returns nil.
|
||||
@@ -427,13 +360,7 @@ func (cp *ConvexPolygon) Intersection(dx, dy float64, other Shape) *ContactSet {
|
||||
cp.X += dx
|
||||
cp.Y += dy
|
||||
|
||||
if circle, isCircle := other.(*Circle); isCircle {
|
||||
|
||||
for _, line := range cp.Lines() {
|
||||
contactSet.Points = append(contactSet.Points, line.IntersectionPointsCircle(circle)...)
|
||||
}
|
||||
|
||||
} else if poly, isPoly := other.(*ConvexPolygon); isPoly {
|
||||
if poly, isPoly := other.(*ConvexPolygon); isPoly {
|
||||
|
||||
for _, line := range cp.Lines() {
|
||||
|
||||
@@ -450,29 +377,11 @@ func (cp *ConvexPolygon) Intersection(dx, dy float64, other Shape) *ContactSet {
|
||||
}
|
||||
|
||||
if len(contactSet.Points) > 0 {
|
||||
|
||||
for _, point := range contactSet.Points {
|
||||
contactSet.Center = contactSet.Center.Add(point)
|
||||
}
|
||||
|
||||
contactSet.Center[0] /= float64(len(contactSet.Points))
|
||||
contactSet.Center[1] /= float64(len(contactSet.Points))
|
||||
|
||||
if mtv := cp.calculateMTV(contactSet, other); mtv != nil {
|
||||
contactSet.MTV = mtv
|
||||
}
|
||||
|
||||
// Do nothing
|
||||
} else {
|
||||
contactSet = nil
|
||||
}
|
||||
|
||||
// If dx or dy aren't 0, then the MTV will be greater to compensate; this adjusts the vector back.
|
||||
if contactSet != nil && (dx != 0 || dy != 0) {
|
||||
deltaMagnitude := Vector{dx, dy}.Magnitude()
|
||||
ogMagnitude := contactSet.MTV.Magnitude()
|
||||
contactSet.MTV = contactSet.MTV.Unit().Scale(ogMagnitude - deltaMagnitude)
|
||||
}
|
||||
|
||||
cp.X = ogX
|
||||
cp.Y = ogY
|
||||
|
||||
@@ -480,112 +389,6 @@ func (cp *ConvexPolygon) Intersection(dx, dy float64, other Shape) *ContactSet {
|
||||
|
||||
}
|
||||
|
||||
// calculateMTV returns the MTV, if possible, and a bool indicating whether it was possible or not.
|
||||
func (cp *ConvexPolygon) calculateMTV(contactSet *ContactSet, otherShape Shape) Vector {
|
||||
|
||||
delta := Vector{0, 0}
|
||||
|
||||
smallest := Vector{math.MaxFloat64, 0}
|
||||
|
||||
switch other := otherShape.(type) {
|
||||
|
||||
case *ConvexPolygon:
|
||||
|
||||
for _, axis := range cp.SATAxes() {
|
||||
if !cp.Project(axis).Overlapping(other.Project(axis)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
overlap := cp.Project(axis).Overlap(other.Project(axis))
|
||||
|
||||
if smallest.Magnitude() > overlap {
|
||||
smallest = axis.Scale(overlap)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for _, axis := range other.SATAxes() {
|
||||
|
||||
if !cp.Project(axis).Overlapping(other.Project(axis)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
overlap := cp.Project(axis).Overlap(other.Project(axis))
|
||||
|
||||
if smallest.Magnitude() > overlap {
|
||||
smallest = axis.Scale(overlap)
|
||||
}
|
||||
|
||||
}
|
||||
// Removed support of "Circle" to remove dependency of "sort" module
|
||||
}
|
||||
|
||||
delta[0] = smallest[0]
|
||||
delta[1] = smallest[1]
|
||||
|
||||
return delta
|
||||
}
|
||||
|
||||
// ContainedBy returns if the ConvexPolygon is wholly contained by the other shape provided.
|
||||
func (cp *ConvexPolygon) ContainedBy(otherShape Shape) bool {
|
||||
|
||||
switch other := otherShape.(type) {
|
||||
|
||||
case *ConvexPolygon:
|
||||
|
||||
for _, axis := range cp.SATAxes() {
|
||||
if !cp.Project(axis).IsInside(other.Project(axis)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, axis := range other.SATAxes() {
|
||||
if !cp.Project(axis).IsInside(other.Project(axis)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// FlipH flips the ConvexPolygon's vertices horizontally according to their initial offset when adding the points.
|
||||
func (cp *ConvexPolygon) FlipH() {
|
||||
|
||||
for _, v := range cp.Points {
|
||||
v[0] = -v[0]
|
||||
}
|
||||
// We have to reverse vertex order after flipping the vertices to ensure the winding order is consistent between Objects (so that the normals are consistently outside or inside, which is important
|
||||
// when doing Intersection tests). If we assume that the normal of a line, going from vertex A to vertex B, is one direction, then the normal would be inverted if the vertices were flipped in position,
|
||||
// but not in order. This would make Intersection tests drive objects into each other, instead of giving the delta to move away.
|
||||
cp.ReverseVertexOrder()
|
||||
|
||||
}
|
||||
|
||||
// FlipV flips the ConvexPolygon's vertices vertically according to their initial offset when adding the points.
|
||||
func (cp *ConvexPolygon) FlipV() {
|
||||
|
||||
for _, v := range cp.Points {
|
||||
v[1] = -v[1]
|
||||
}
|
||||
cp.ReverseVertexOrder()
|
||||
|
||||
}
|
||||
|
||||
// ReverseVertexOrder reverses the vertex ordering of the ConvexPolygon.
|
||||
func (cp *ConvexPolygon) ReverseVertexOrder() {
|
||||
|
||||
verts := []Vector{cp.Points[0]}
|
||||
|
||||
for i := len(cp.Points) - 1; i >= 1; i-- {
|
||||
verts = append(verts, cp.Points[i])
|
||||
}
|
||||
|
||||
cp.Points = verts
|
||||
|
||||
}
|
||||
|
||||
// NewRectangle returns a rectangular ConvexPolygon with the vertices in clockwise order. In actuality, an AABBRectangle should be its own
|
||||
// "thing" with its own optimized Intersection code check.
|
||||
func NewRectangle(x, y, w, h float64) *ConvexPolygon {
|
||||
@@ -596,159 +399,3 @@ func NewRectangle(x, y, w, h float64) *ConvexPolygon {
|
||||
x, y+h,
|
||||
)
|
||||
}
|
||||
|
||||
type Circle struct {
|
||||
X, Y, Radius float64
|
||||
}
|
||||
|
||||
// NewCircle returns a new Circle, with its center at the X and Y position given, and with the defined radius.
|
||||
func NewCircle(x, y, radius float64) *Circle {
|
||||
circle := &Circle{
|
||||
X: x,
|
||||
Y: y,
|
||||
Radius: radius,
|
||||
}
|
||||
return circle
|
||||
}
|
||||
|
||||
func (circle *Circle) Clone() Shape {
|
||||
return NewCircle(circle.X, circle.Y, circle.Radius)
|
||||
}
|
||||
|
||||
// Bounds returns the top-left and bottom-right corners of the Circle.
|
||||
func (circle *Circle) Bounds() (Vector, Vector) {
|
||||
return Vector{circle.X - circle.Radius, circle.Y - circle.Radius}, Vector{circle.X + circle.Radius, circle.Y + circle.Radius}
|
||||
}
|
||||
|
||||
// Intersection tests to see if a Circle intersects with the other given Shape. dx and dy are delta movement variables indicating
|
||||
// movement to be applied before the intersection check (thereby allowing you to see if a Shape would collide with another if it
|
||||
// were in a different relative location). If an Intersection is found, a ContactSet will be returned, giving information regarding
|
||||
// the intersection.
|
||||
func (circle *Circle) Intersection(dx, dy float64, other Shape) *ContactSet {
|
||||
|
||||
var contactSet *ContactSet
|
||||
|
||||
ox := circle.X
|
||||
oy := circle.Y
|
||||
|
||||
circle.X += dx
|
||||
circle.Y += dy
|
||||
|
||||
// here
|
||||
|
||||
switch shape := other.(type) {
|
||||
case *ConvexPolygon:
|
||||
// Maybe this would work?
|
||||
contactSet = shape.Intersection(-dx, -dy, circle)
|
||||
if contactSet != nil {
|
||||
contactSet.MTV = contactSet.MTV.Scale(-1)
|
||||
}
|
||||
case *Circle:
|
||||
|
||||
contactSet = NewContactSet()
|
||||
|
||||
contactSet.Points = circle.IntersectionPointsCircle(shape)
|
||||
|
||||
if len(contactSet.Points) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
contactSet.MTV = Vector{circle.X - shape.X, circle.Y - shape.Y}
|
||||
dist := contactSet.MTV.Magnitude()
|
||||
contactSet.MTV = contactSet.MTV.Unit().Scale(circle.Radius + shape.Radius - dist)
|
||||
|
||||
for _, point := range contactSet.Points {
|
||||
contactSet.Center = contactSet.Center.Add(point)
|
||||
}
|
||||
|
||||
contactSet.Center[0] /= float64(len(contactSet.Points))
|
||||
contactSet.Center[1] /= float64(len(contactSet.Points))
|
||||
|
||||
// if contactSet != nil {
|
||||
// contactSet.MTV[0] -= dx
|
||||
// contactSet.MTV[1] -= dy
|
||||
// }
|
||||
|
||||
// contactSet.MTV = Vector{circle.X - shape.X, circle.Y - shape.Y}
|
||||
}
|
||||
|
||||
circle.X = ox
|
||||
circle.Y = oy
|
||||
|
||||
return contactSet
|
||||
}
|
||||
|
||||
// Move translates the Circle by the designated X and Y values.
|
||||
func (circle *Circle) Move(x, y float64) {
|
||||
circle.X += x
|
||||
circle.Y += y
|
||||
}
|
||||
|
||||
// MoveVec translates the Circle by the designated Vector.
|
||||
func (circle *Circle) MoveVec(vec Vector) {
|
||||
circle.X += vec.X()
|
||||
circle.Y += vec.Y()
|
||||
}
|
||||
|
||||
// SetPosition sets the center position of the Circle using the X and Y values given.
|
||||
func (circle *Circle) SetPosition(x, y float64) {
|
||||
circle.X = x
|
||||
circle.Y = y
|
||||
}
|
||||
|
||||
// SetPosition sets the center position of the Circle using the Vector given.
|
||||
func (circle *Circle) SetPositionVec(vec Vector) {
|
||||
circle.X = vec.X()
|
||||
circle.Y = vec.Y()
|
||||
}
|
||||
|
||||
// Position() returns the X and Y position of the Circle.
|
||||
func (circle *Circle) Position() (float64, float64) {
|
||||
return circle.X, circle.Y
|
||||
}
|
||||
|
||||
// PointInside returns if the given Vector is inside of the circle.
|
||||
func (circle *Circle) PointInside(point Vector) bool {
|
||||
return point.Sub(Vector{circle.X, circle.Y}).Magnitude() <= circle.Radius
|
||||
}
|
||||
|
||||
// IntersectionPointsCircle returns the intersection points of the two circles provided.
|
||||
func (circle *Circle) IntersectionPointsCircle(other *Circle) []Vector {
|
||||
|
||||
d := math.Sqrt(math.Pow(other.X-circle.X, 2) + math.Pow(other.Y-circle.Y, 2))
|
||||
|
||||
if d > circle.Radius+other.Radius || d < math.Abs(circle.Radius-other.Radius) || d == 0 && circle.Radius == other.Radius {
|
||||
return nil
|
||||
}
|
||||
|
||||
a := (math.Pow(circle.Radius, 2) - math.Pow(other.Radius, 2) + math.Pow(d, 2)) / (2 * d)
|
||||
h := math.Sqrt(math.Pow(circle.Radius, 2) - math.Pow(a, 2))
|
||||
|
||||
x2 := circle.X + a*(other.X-circle.X)/d
|
||||
y2 := circle.Y + a*(other.Y-circle.Y)/d
|
||||
|
||||
return []Vector{
|
||||
{x2 + h*(other.Y-circle.Y)/d, y2 - h*(other.X-circle.X)/d},
|
||||
{x2 - h*(other.Y-circle.Y)/d, y2 + h*(other.X-circle.X)/d},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type Projection struct {
|
||||
Min, Max float64
|
||||
}
|
||||
|
||||
// Overlapping returns whether a Projection is overlapping with the other, provided Projection. Credit to https://www.sevenson.com.au/programming/sat/
|
||||
func (projection Projection) Overlapping(other Projection) bool {
|
||||
return projection.Overlap(other) > 0
|
||||
}
|
||||
|
||||
// Overlap returns the amount that a Projection is overlapping with the other, provided Projection. Credit to https://dyn4j.org/2010/01/sat/#sat-nointer
|
||||
func (projection Projection) Overlap(other Projection) float64 {
|
||||
return math.Min(projection.Max, other.Max) - math.Max(projection.Min, other.Min)
|
||||
}
|
||||
|
||||
// IsInside returns whether the Projection is wholly inside of the other, provided Projection.
|
||||
func (projection Projection) IsInside(other Projection) bool {
|
||||
return projection.Min >= other.Min && projection.Max <= other.Max
|
||||
}
|
||||
|
112
resolv_tailored/simple_math.go
Normal file
112
resolv_tailored/simple_math.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package resolv
|
||||
|
||||
import "unsafe"
|
||||
|
||||
const (
|
||||
uvnan = 0x7FF8000000000001
|
||||
uvinf = 0x7FF0000000000000
|
||||
uvneginf = 0xFFF0000000000000
|
||||
uvone = 0x3FF0000000000000
|
||||
mask = 0x7FF
|
||||
shift = 64 - 11 - 1
|
||||
bias = 1023
|
||||
signMask = 1 << 63
|
||||
fracMask = 1<<shift - 1
|
||||
MaxFloat64 = 1.79e+308
|
||||
magic32 = 0x5f3759df
|
||||
magic64 = 0x5fe6eb50c7b537a9
|
||||
)
|
||||
|
||||
func Max(a, b float64) float64 {
|
||||
if a > b {
|
||||
return a
|
||||
} else {
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
func Min(a, b float64) float64 {
|
||||
if a < b {
|
||||
return a
|
||||
} else {
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
func Floor(x float64) float64 {
|
||||
if x == 0 || IsInf(x, 0) || IsNaN(x) {
|
||||
return x
|
||||
}
|
||||
if x < 0 {
|
||||
d, fract := Modf(-x)
|
||||
if fract != 0.0 {
|
||||
d = d + 1
|
||||
}
|
||||
return -d
|
||||
}
|
||||
d, _ := Modf(x)
|
||||
return d
|
||||
}
|
||||
|
||||
func Modf(f float64) (outval float64, frac float64) {
|
||||
if f < 1 {
|
||||
if f < 0 {
|
||||
outval1, frac1 := Modf(-f)
|
||||
return -outval1, -frac1
|
||||
} else if f == 0 {
|
||||
return f, f // Return -0, -0 when f == -0
|
||||
}
|
||||
return 0, f
|
||||
}
|
||||
|
||||
x := Float64bits(f)
|
||||
e := ((uint)(x>>shift))&mask - bias
|
||||
|
||||
// Keep the top 12+e bits, the integer part; clear the rest.
|
||||
if e < 64-12 {
|
||||
x &^= 1<<(64-12-e) - 1
|
||||
}
|
||||
outval = Float64frombits(x)
|
||||
frac = f - outval
|
||||
return
|
||||
}
|
||||
|
||||
func Float32bits(f float32) uint32 { return *(*uint32)(unsafe.Pointer(&f)) }
|
||||
func Float32frombits(b uint32) float32 { return *(*float32)(unsafe.Pointer(&b)) }
|
||||
func Float64bits(f float64) uint64 { return *(*uint64)(unsafe.Pointer(&f)) }
|
||||
func Float64frombits(b uint64) float64 { return *(*float64)(unsafe.Pointer(&b)) }
|
||||
|
||||
func NaN() float64 { return Float64frombits(uvnan) }
|
||||
|
||||
func IsNaN(f float64) (is bool) {
|
||||
return f != f
|
||||
}
|
||||
|
||||
func IsInf(f float64, sign int) bool {
|
||||
return sign >= 0 && f > MaxFloat64 || sign <= 0 && f < -MaxFloat64
|
||||
}
|
||||
|
||||
// FastInvSqrt reference https://medium.com/@adrien.za/fast-inverse-square-root-in-go-and-javascript-for-fun-6b891e74e5a8
|
||||
func FastInvSqrt32(n float32) float32 {
|
||||
if n < 0 {
|
||||
return float32(NaN())
|
||||
}
|
||||
n2, th := n*0.5, float32(1.5)
|
||||
b := Float32bits(n)
|
||||
b = magic32 - (b >> 1)
|
||||
f := Float32frombits(b)
|
||||
f *= th - (n2 * f * f)
|
||||
return f
|
||||
}
|
||||
|
||||
func FastInvSqrt64(n float64) float64 {
|
||||
if n < 0 {
|
||||
return NaN()
|
||||
}
|
||||
n2, th := n*0.5, float64(1.5)
|
||||
b := Float64bits(n)
|
||||
b = magic64 - (b >> 1)
|
||||
f := Float64frombits(b)
|
||||
f *= th - (n2 * f * f)
|
||||
return f
|
||||
}
|
@@ -1,9 +1,5 @@
|
||||
package resolv
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// Space represents a collision space. Internally, each Space contains a 2D array of Cells, with each Cell being the same size. Cells contain information on which
|
||||
// Objects occupy those spaces.
|
||||
type Space struct {
|
||||
@@ -16,21 +12,30 @@ type Space struct {
|
||||
// speed of one cell size per collision check to avoid missing any possible collisions.
|
||||
func NewSpace(spaceWidth, spaceHeight, cellWidth, cellHeight int) *Space {
|
||||
|
||||
sp := &Space{
|
||||
CellWidth: cellWidth,
|
||||
CellHeight: cellHeight,
|
||||
}
|
||||
sp := &Space{}
|
||||
sp.CellWidth = cellWidth
|
||||
sp.CellHeight = cellHeight
|
||||
|
||||
sp.Resize(spaceWidth/cellWidth, spaceHeight/cellHeight)
|
||||
|
||||
// sp.Resize(int(math.Ceil(float64(spaceWidth)/float64(cellWidth))),
|
||||
// int(math.Ceil(float64(spaceHeight)/float64(cellHeight))))
|
||||
|
||||
return sp
|
||||
|
||||
}
|
||||
|
||||
// [WARNING] The slice type boxing/unboxing is proved by profiling to be heavy after transpiled to JavaScript, thus adding some "XxxSingle" shortcuts here.
|
||||
// Add adds the specified Objects to the Space, updating the Space's cells to refer to the Object.
|
||||
func (sp *Space) AddSingle(obj *Object) {
|
||||
|
||||
if sp == nil {
|
||||
panic("ERROR: space is nil")
|
||||
}
|
||||
|
||||
obj.Space = sp
|
||||
|
||||
// We call Update() once to make sure the object gets its cells added.
|
||||
obj.Update()
|
||||
}
|
||||
|
||||
func (sp *Space) Add(objects ...*Object) {
|
||||
|
||||
if sp == nil {
|
||||
@@ -50,6 +55,20 @@ func (sp *Space) Add(objects ...*Object) {
|
||||
|
||||
// Remove removes the specified Objects from being associated with the Space. This should be done whenever an Object is removed from the
|
||||
// game.
|
||||
func (sp *Space) RemoveSingle(obj *Object) {
|
||||
|
||||
if sp == nil {
|
||||
panic("ERROR: space is nil")
|
||||
}
|
||||
|
||||
for 0 < obj.TouchingCells.Cnt {
|
||||
cell := obj.TouchingCells.Pop().(*Cell)
|
||||
cell.unregister(obj)
|
||||
}
|
||||
|
||||
obj.Space = nil
|
||||
}
|
||||
|
||||
func (sp *Space) Remove(objects ...*Object) {
|
||||
|
||||
if sp == nil {
|
||||
@@ -57,13 +76,11 @@ func (sp *Space) Remove(objects ...*Object) {
|
||||
}
|
||||
|
||||
for _, obj := range objects {
|
||||
|
||||
for _, cell := range obj.TouchingCells {
|
||||
for 0 < obj.TouchingCells.Cnt {
|
||||
cell := obj.TouchingCells.Pop().(*Cell)
|
||||
cell.unregister(obj)
|
||||
}
|
||||
|
||||
obj.TouchingCells = []*Cell{}
|
||||
|
||||
obj.Space = nil
|
||||
|
||||
}
|
||||
@@ -76,20 +93,18 @@ func (sp *Space) Objects() []*Object {
|
||||
|
||||
objectsAdded := map[*Object]bool{}
|
||||
objects := []*Object{}
|
||||
|
||||
for cy := range sp.Cells {
|
||||
|
||||
for cx := range sp.Cells[cy] {
|
||||
|
||||
for _, o := range sp.Cells[cy][cx].Objects {
|
||||
|
||||
cyUpper := len(sp.Cells)
|
||||
for cy := 0; cy < cyUpper; cy++ {
|
||||
cxUpper := len(sp.Cells[cy])
|
||||
for cx := 0; cx < cxUpper; cx++ {
|
||||
rb := sp.Cells[cy][cx].Objects
|
||||
for i := rb.StFrameId; i < rb.EdFrameId; i++ {
|
||||
o := rb.GetByFrameId(i).(*Object)
|
||||
if _, added := objectsAdded[o]; !added {
|
||||
objects = append(objects, o)
|
||||
objectsAdded[o] = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -100,24 +115,16 @@ func (sp *Space) Objects() []*Object {
|
||||
|
||||
// Resize resizes the internal Cells array.
|
||||
func (sp *Space) Resize(width, height int) {
|
||||
|
||||
sp.Cells = [][]*Cell{}
|
||||
|
||||
sp.Cells = make([][]*Cell, height)
|
||||
for y := 0; y < height; y++ {
|
||||
|
||||
sp.Cells = append(sp.Cells, []*Cell{})
|
||||
|
||||
sp.Cells[y] = make([]*Cell, width)
|
||||
for x := 0; x < width; x++ {
|
||||
sp.Cells[y] = append(sp.Cells[y], newCell(x, y))
|
||||
sp.Cells[y][x] = newCell(x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Cell returns the Cell at the given cellular / spatial (not world) X and Y position in the Space. If the X and Y position are
|
||||
// out of bounds, Cell() will return nil.
|
||||
func (sp *Space) Cell(x, y int) *Cell {
|
||||
func (sp *Space) GetCell(x, y int) *Cell {
|
||||
|
||||
if y >= 0 && y < len(sp.Cells) && x >= 0 && x < len(sp.Cells[y]) {
|
||||
return sp.Cells[y][x]
|
||||
@@ -134,28 +141,26 @@ func (sp *Space) CheckCells(x, y, w, h int, tags ...string) *Object {
|
||||
|
||||
for iy := y; iy < y+h; iy++ {
|
||||
|
||||
cell := sp.Cell(ix, iy)
|
||||
cell := sp.GetCell(ix, iy)
|
||||
|
||||
if cell != nil {
|
||||
|
||||
rb := cell.Objects
|
||||
if len(tags) > 0 {
|
||||
|
||||
if cell.ContainsTags(tags...) {
|
||||
for _, obj := range cell.Objects {
|
||||
for i := rb.StFrameId; i < rb.EdFrameId; i++ {
|
||||
obj := rb.GetByFrameId(i).(*Object)
|
||||
if obj.HasTags(tags...) {
|
||||
return obj
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if cell.Occupied() {
|
||||
return cell.Objects[0]
|
||||
return rb.GetByFrameId(rb.StFrameId).(*Object)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -178,10 +183,13 @@ func (sp *Space) CheckCellsWorld(x, y, w, h float64, tags ...string) *Object {
|
||||
func (sp *Space) UnregisterAllObjects() {
|
||||
|
||||
for y := 0; y < len(sp.Cells); y++ {
|
||||
|
||||
for x := 0; x < len(sp.Cells[y]); x++ {
|
||||
cell := sp.Cells[y][x]
|
||||
sp.Remove(cell.Objects...)
|
||||
rb := cell.Objects
|
||||
for i := rb.StFrameId; i < rb.EdFrameId; i++ {
|
||||
o := rb.GetByFrameId(i).(*Object)
|
||||
sp.RemoveSingle(o)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -190,8 +198,9 @@ func (sp *Space) UnregisterAllObjects() {
|
||||
|
||||
// WorldToSpace converts from a world position (x, y) to a position in the Space (a grid-based position).
|
||||
func (sp *Space) WorldToSpace(x, y float64) (int, int) {
|
||||
fx := int(math.Floor(x / float64(sp.CellWidth)))
|
||||
fy := int(math.Floor(y / float64(sp.CellHeight)))
|
||||
// [WARNING] DON'T use "int(...)" syntax to convert float to int, it's not supported by go2cs!
|
||||
var fx int = (int)(Floor(x / float64(sp.CellWidth)))
|
||||
var fy int = (int)(Floor(y / float64(sp.CellHeight)))
|
||||
return fx, fy
|
||||
}
|
||||
|
||||
@@ -218,8 +227,8 @@ func (sp *Space) Width() int {
|
||||
func (sp *Space) CellsInLine(startX, startY, endX, endY int) []*Cell {
|
||||
|
||||
cells := []*Cell{}
|
||||
cell := sp.Cell(startX, startY)
|
||||
endCell := sp.Cell(endX, endY)
|
||||
cell := sp.GetCell(startX, startY)
|
||||
endCell := sp.GetCell(endX, endY)
|
||||
|
||||
if cell != nil && endCell != nil {
|
||||
|
||||
@@ -248,7 +257,7 @@ func (sp *Space) CellsInLine(startX, startY, endX, endY int) []*Cell {
|
||||
}
|
||||
|
||||
cx, cy := sp.WorldToSpace(p[0], p[1])
|
||||
c := sp.Cell(cx, cy)
|
||||
c := sp.GetCell(cx, cy)
|
||||
if c != cell {
|
||||
cell = c
|
||||
}
|
||||
|
@@ -1,8 +1,6 @@
|
||||
package resolv
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
import "math"
|
||||
|
||||
// Vector is the definition of a row vector that contains scalars as
|
||||
// 64 bit floats
|
||||
@@ -14,106 +12,13 @@ type Axis int
|
||||
const (
|
||||
// the consts below are used to represent vector axis, they are useful
|
||||
// to lookup values within the vector.
|
||||
X Axis = iota
|
||||
Y
|
||||
Z
|
||||
X Axis = 0
|
||||
Y Axis = 1
|
||||
Z Axis = 2
|
||||
)
|
||||
|
||||
// Clone a vector
|
||||
func Clone(v Vector) Vector {
|
||||
return v.Clone()
|
||||
}
|
||||
|
||||
// Clone a vector
|
||||
func (v Vector) Clone() Vector {
|
||||
clone := make(Vector, len(v))
|
||||
copy(clone, v)
|
||||
return clone
|
||||
}
|
||||
|
||||
// Add a vector with a vector or a set of vectors
|
||||
func Add(v1 Vector, vs ...Vector) Vector {
|
||||
return v1.Clone().Add(vs...)
|
||||
}
|
||||
|
||||
// Add a vector with a vector or a set of vectors
|
||||
func (v Vector) Add(vs ...Vector) Vector {
|
||||
dim := len(v)
|
||||
|
||||
for i := range vs {
|
||||
if len(vs[i]) > dim {
|
||||
axpyUnitaryTo(v, 1, v, vs[i][:dim])
|
||||
} else {
|
||||
axpyUnitaryTo(v, 1, v, vs[i])
|
||||
}
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Sub subtracts a vector with another vector or a set of vectors
|
||||
func Sub(v1 Vector, vs ...Vector) Vector {
|
||||
return v1.Clone().Sub(vs...)
|
||||
}
|
||||
|
||||
// Sub subtracts a vector with another vector or a set of vectors
|
||||
func (v Vector) Sub(vs ...Vector) Vector {
|
||||
dim := len(v)
|
||||
|
||||
for i := range vs {
|
||||
if len(vs[i]) > dim {
|
||||
axpyUnitaryTo(v, -1, vs[i][:dim], v)
|
||||
} else {
|
||||
axpyUnitaryTo(v, -1, vs[i], v)
|
||||
}
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Scale vector with a given size
|
||||
func Scale(v Vector, size float64) Vector {
|
||||
return v.Clone().Scale(size)
|
||||
}
|
||||
|
||||
// Scale vector with a given size
|
||||
func (v Vector) Scale(size float64) Vector {
|
||||
scalUnitaryTo(v, size, v)
|
||||
return v
|
||||
}
|
||||
|
||||
// Equal compares that two vectors are equal to each other
|
||||
func Equal(v1, v2 Vector) bool {
|
||||
return v1.Equal(v2)
|
||||
}
|
||||
|
||||
// Equal compares that two vectors are equal to each other
|
||||
func (v Vector) Equal(v2 Vector) bool {
|
||||
if len(v) != len(v2) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range v {
|
||||
if math.Abs(v[i]-v2[i]) > 1e-8 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Magnitude of a vector
|
||||
func Magnitude(v Vector) float64 {
|
||||
return v.Magnitude()
|
||||
}
|
||||
|
||||
// Magnitude of a vector
|
||||
func (v Vector) Magnitude() float64 {
|
||||
return math.Sqrt(v.Magnitude2())
|
||||
}
|
||||
|
||||
func (v Vector) Magnitude2() float64 {
|
||||
var result float64
|
||||
var result float64 = 0.
|
||||
|
||||
for _, scalar := range v {
|
||||
result += scalar * scalar
|
||||
@@ -122,129 +27,19 @@ func (v Vector) Magnitude2() float64 {
|
||||
return result
|
||||
}
|
||||
|
||||
// Unit returns a direction vector with the length of one.
|
||||
func Unit(v Vector) Vector {
|
||||
return v.Clone().Unit()
|
||||
}
|
||||
|
||||
// Unit returns a direction vector with the length of one.
|
||||
func (v Vector) Unit() Vector {
|
||||
l := v.Magnitude()
|
||||
|
||||
if l < 1e-8 {
|
||||
l2 := v.Magnitude2()
|
||||
if l2 < 1e-16 {
|
||||
return v
|
||||
}
|
||||
|
||||
for i := range v {
|
||||
l := math.Sqrt(l2)
|
||||
//inv := FastInvSqrt64(l2) // "Fast Inverse Square Root" is arch dependent, it's by far non-trivial to use it in Golang as well as make it feasible in the transpiled JavaScript.
|
||||
|
||||
for i := 0; i < len(v); i++ {
|
||||
v[i] = v[i] / l
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Dot product of two vectors
|
||||
func Dot(v1, v2 Vector) float64 {
|
||||
result, dim1, dim2 := 0., len(v1), len(v2)
|
||||
|
||||
if dim1 > dim2 {
|
||||
v2 = append(v2, make(Vector, dim1-dim2)...)
|
||||
}
|
||||
|
||||
if dim1 < dim2 {
|
||||
v1 = append(v1, make(Vector, dim2-dim1)...)
|
||||
}
|
||||
|
||||
for i := range v1 {
|
||||
result += v1[i] * v2[i]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Dot product of two vectors
|
||||
func (v Vector) Dot(v2 Vector) float64 {
|
||||
return Dot(v, v2)
|
||||
}
|
||||
|
||||
// Cross product of two vectors
|
||||
func Cross(v1, v2 Vector) Vector {
|
||||
return v1.Cross(v2)
|
||||
}
|
||||
|
||||
// Cross product of two vectors
|
||||
func (v Vector) Cross(v2 Vector) Vector {
|
||||
if len(v) != 3 || len(v2) != 3 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Vector{
|
||||
v[Y]*v2[Z] - v[Z]*v2[Y],
|
||||
v[Z]*v2[X] - v[X]*v2[Z],
|
||||
v[X]*v2[Z] - v[Z]*v2[X],
|
||||
}
|
||||
}
|
||||
|
||||
// Rotate is rotating a vector around a specified axis.
|
||||
// If no axis are specified, it will default to the Z axis.
|
||||
//
|
||||
// If a vector with more than 3-dimensions is rotated, it will cut the extra
|
||||
// dimensions and return a 3-dimensional vector.
|
||||
//
|
||||
// NOTE: the ...Axis is just syntactic sugar that allows the axis to not be
|
||||
// specified and default to Z, if multiple axis is passed the first will be
|
||||
// set as the rotation axis
|
||||
func Rotate(v Vector, angle float64, as ...Axis) Vector {
|
||||
return v.Clone().Rotate(angle, as...)
|
||||
}
|
||||
|
||||
// Rotate is rotating a vector around a specified axis.
|
||||
// If no axis are specified, it will default to the Z axis.
|
||||
//
|
||||
// If a vector with more than 3-dimensions is rotated, it will cut the extra
|
||||
// dimensions and return a 3-dimensional vector.
|
||||
//
|
||||
// NOTE: the ...Axis is just syntactic sugar that allows the axis to not be
|
||||
// specified and default to Z, if multiple axis is passed the first will be
|
||||
// set as the rotation axis
|
||||
func (v Vector) Rotate(angle float64, as ...Axis) Vector {
|
||||
axis, dim := Z, len(v)
|
||||
|
||||
if dim == 0 {
|
||||
return v
|
||||
}
|
||||
|
||||
if len(as) > 0 {
|
||||
axis = as[0]
|
||||
}
|
||||
|
||||
if dim == 1 && axis != Z {
|
||||
v = append(v, 0, 0)
|
||||
}
|
||||
|
||||
if (dim < 2 && axis == Z) || (dim == 2 && axis != Z) {
|
||||
v = append(v, 0)
|
||||
}
|
||||
|
||||
x, y := v[X], v[Y]
|
||||
|
||||
cos, sin := math.Cos(angle), math.Sin(angle)
|
||||
|
||||
switch axis {
|
||||
case X:
|
||||
z := v[Z]
|
||||
v[Y] = y*cos - z*sin
|
||||
v[Z] = y*sin + z*cos
|
||||
case Y:
|
||||
z := v[Z]
|
||||
v[X] = x*cos + z*sin
|
||||
v[Z] = -x*sin + z*cos
|
||||
case Z:
|
||||
v[X] = x*cos - y*sin
|
||||
v[Y] = x*sin + y*cos
|
||||
}
|
||||
|
||||
if dim > 3 {
|
||||
return v[:3]
|
||||
//v[i] = v[i] * inv
|
||||
}
|
||||
|
||||
return v
|
||||
@@ -252,7 +47,7 @@ func (v Vector) Rotate(angle float64, as ...Axis) Vector {
|
||||
|
||||
// X is corresponding to doing a v[0] lookup, if index 0 does not exist yet, a
|
||||
// 0 will be returned instead
|
||||
func (v Vector) X() float64 {
|
||||
func (v Vector) GetX() float64 {
|
||||
if len(v) < 1 {
|
||||
return 0.
|
||||
}
|
||||
@@ -262,7 +57,7 @@ func (v Vector) X() float64 {
|
||||
|
||||
// Y is corresponding to doing a v[1] lookup, if index 1 does not exist yet, a
|
||||
// 0 will be returned instead
|
||||
func (v Vector) Y() float64 {
|
||||
func (v Vector) GetY() float64 {
|
||||
if len(v) < 2 {
|
||||
return 0.
|
||||
}
|
||||
@@ -272,7 +67,7 @@ func (v Vector) Y() float64 {
|
||||
|
||||
// Z is corresponding to doing a v[2] lookup, if index 2 does not exist yet, a
|
||||
// 0 will be returned instead
|
||||
func (v Vector) Z() float64 {
|
||||
func (v Vector) GetZ() float64 {
|
||||
if len(v) < 3 {
|
||||
return 0.
|
||||
}
|
||||
|
Reference in New Issue
Block a user