mirror of
https://github.com/genxium/DelayNoMore
synced 2025-10-17 12:36:46 +00:00
Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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 | ||
|
5d92b339f6 | ||
|
642adff919 | ||
|
62c51b1838 | ||
|
2751569e0c | ||
|
d623916b3c | ||
|
efd070a11b | ||
|
d111de0a7a | ||
|
c2fa251e69 | ||
|
de16e8e8de | ||
|
365177a3af |
@@ -75,4 +75,18 @@ Instead of replacing all use of TCP by UDP, it's more reasonable to keep using T
|
||||
It's not a global consensus, but in practice many UDP communications are platform specific due to their paired asynchronous I/O choices, e.g. epoll in Linux and kqueue in BSD-ish. Of course there're many 3rd party higher level encapsulated tools for cross-platform use but that introduces extra debugging when things go wrong.
|
||||
|
||||
Therefore, the following plan doesn't assume use of any specific 3rd party encapsulation of UDP communication.
|
||||

|
||||

|
||||
|
||||
# Would using WebRTC for all frontends be a `UDP for all` solution?
|
||||
Theoretically yes.
|
||||
|
||||
## Plan to integrate WebRTC
|
||||
The actual integration of WebRTC to enable `browser v.s. native app w/ WebRTC` requires detailed planning :)
|
||||
|
||||
In my current implementation, there's only 1 backend process and it's responsible for all of the following things. The plan for integrating/migrating each item is written respectively.
|
||||
- TURN for UDP tunneling/relay
|
||||
- Some minor modification to [Room.PlayerSecondaryDownsyncSessionDict](https://github.com/genxium/DelayNoMore/blob/365177a3af6033f1cd629a4a4d59beb4557cc311/battle_srv/models/room.go#L126) should be enough to yield a WebRTC API friendly TURN. It's interesting that [though UDP based in transport layer, a WebRTC session is stateful and more similar to WebSocket in terms of API](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API).
|
||||
- STUN for UDP holepunching
|
||||
- Some minor modification to [Player.UdpAddr](https://github.com/genxium/DelayNoMore/blob/365177a3af6033f1cd629a4a4d59beb4557cc311/battle_srv/models/player.go#L56) should be enough to yield a WebRTC API friendly STUN.
|
||||
- reconnection recovery
|
||||
- Not sure whether or not I should separate this feature from STUN and TURN, but if I were to do so, [both `Room.RenderFrameBuffer` and `Room.InputsBuffer`](https://github.com/genxium/DelayNoMore/blob/365177a3af6033f1cd629a4a4d59beb4557cc311/battle_srv/models/room.go) should be moved to a shared fast I/O storage (e.g. using Redis) to achieve the same level of `High Availability` in design as STUN and TURN.
|
||||
|
26
README.md
26
README.md
@@ -6,10 +6,14 @@ This project is a demo for a websocket-based rollback netcode inspired by [GGPO]
|
||||
|
||||

|
||||
|
||||
(battle between 2 celluar 4G users using Android phones, [original video here](https://pan.baidu.com/s/1RL-9M-cK8cFS_Q8afMTrJA?pwd=ryzv))
|
||||
|
||||

|
||||
|
||||
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 (viewed by PC side)](https://pan.baidu.com/s/1IZVa5wVgAdeH6D-xsZYFUw?pwd=dgkj).
|
||||
- Browser vs `native app` is possible but in that case only websocket is used.
|
||||
- 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).
|
||||
- Browser vs `native app` is possible but in that case only websocket is used. For WebRTC integration plan please see [ConcerningEdgeCases](./ConcerningEdgeCases.md). You might also be interested in visiting [netplayjs](https://github.com/rameshvarun/netplayjs) to see how others use WebRTC for browser game synchronization as well.
|
||||
|
||||
|
||||
# Notable Features
|
||||
@@ -116,6 +120,24 @@ When building for native platforms, it's much more convenient to trigger the Coc
|
||||
```
|
||||
shell> cd <proj-root>
|
||||
shell> /path/to/CocosCreator.exe --path ./frontend --build "platform=win32;debug=true"
|
||||
shell> cd ./frontend/build/jsb-link/frameworks/runtime-src/proj.win32 && MSBUILD DelayNoMore.vcxproj -property:Configuration=Debug
|
||||
```
|
||||
or
|
||||
```
|
||||
shell> cd <proj-root>
|
||||
shell> /path/to/CocosCreator.exe --path ./frontend --build "platform=win32;debug=false"
|
||||
shell> cd ./frontend/build/jsb-link/frameworks/runtime-src/proj.win32 && MSBUILD DelayNoMore.vcxproj -property:Configuration=Release
|
||||
```
|
||||
|
||||
for release.
|
||||
|
||||
If `MSBUILD` command is not yet added to `PATH`, Use `Get-Command MSBUILD` in `Developer Command Prompt for VS 2017/2019` to see where the command should come from and add it to `PATH`.
|
||||
|
||||
Similarly for Android release build
|
||||
```
|
||||
shell> cd <proj-root>
|
||||
shell> /path/to/CocosCreator.exe --path ./frontend --build "platform=android;debug=false"
|
||||
shell> cd ./frontend/build/jsb-link/frameworks/runtime-src/proj.android-studio && ./gradlew assembleRelease
|
||||
```
|
||||
|
||||
### 2.4 CococCreator native build reloading
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
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,11 +43,11 @@ 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 (
|
||||
dnmshared => ../dnmshared
|
||||
jsexport => ../jsexport
|
||||
resolv => ../resolv_tailored
|
||||
jsexport => ../jsexport
|
||||
resolv => ../resolv_tailored
|
||||
)
|
||||
|
@@ -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,11 +47,10 @@ type Player struct {
|
||||
TutorialStage int `db:"tutorial_stage"`
|
||||
|
||||
// other in-battle info fields
|
||||
LastReceivedInputFrameId int32
|
||||
LastUdpReceivedInputFrameId int32
|
||||
LastSentInputFrameId int32
|
||||
AckingFrameId int32
|
||||
AckingInputFrameId int32
|
||||
LastConsecutiveRecvInputFrameId int32
|
||||
LastSentInputFrameId int32
|
||||
AckingFrameId int32
|
||||
AckingInputFrameId int32
|
||||
|
||||
UdpAddr *PeerUdpAddr
|
||||
BattleUdpTunnelAddr *net.UDPAddr // This addr is used by backend only, not visible to frontend
|
||||
|
@@ -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
|
||||
@@ -156,31 +156,39 @@ type Room struct {
|
||||
TmxPointsMap StrToVec2DListMap
|
||||
TmxPolygonsMap StrToPolygon2DListMap
|
||||
|
||||
rdfIdToActuallyUsedInput map[int32]*pb.InputFrameDownsync
|
||||
LastIndividuallyConfirmedInputList []uint64
|
||||
rdfIdToActuallyUsedInput map[int32]*pb.InputFrameDownsync
|
||||
allowUpdateInputFrameInPlaceUponDynamics bool
|
||||
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, 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)
|
||||
defer pR.onPlayerAdded(playerId, speciesId)
|
||||
|
||||
pPlayerFromDbInit.UdpAddr = nil
|
||||
pPlayerFromDbInit.BattleUdpTunnelAddr = nil
|
||||
@@ -188,8 +196,7 @@ func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, session *websocke
|
||||
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 +211,19 @@ func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, session *websocke
|
||||
})
|
||||
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 +238,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 +252,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 {
|
||||
@@ -416,24 +423,14 @@ func (pR *Room) StartBattle() {
|
||||
|
||||
pR.RenderFrameId = 0
|
||||
|
||||
for _, player := range pR.Players {
|
||||
speciesId := int(player.JoinIndex - 1) // FIXME: Hardcoded the values for now
|
||||
if player.JoinIndex == 1 {
|
||||
speciesId = 4096
|
||||
}
|
||||
chosenCh := battle.Characters[speciesId]
|
||||
pR.CharacterConfigsArr[player.JoinIndex-1] = chosenCh
|
||||
pR.SpeciesIdList[player.JoinIndex-1] = int32(speciesId)
|
||||
}
|
||||
Logger.Info("[StartBattle] ", zap.Any("roomId", pR.Id), zap.Any("roomState", pR.State), zap.Any("SpeciesIdList", pR.SpeciesIdList))
|
||||
|
||||
// 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"
|
||||
@@ -487,7 +484,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
|
||||
@@ -519,7 +516,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))
|
||||
}
|
||||
@@ -548,13 +545,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:
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -781,7 +778,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)
|
||||
@@ -804,10 +801,15 @@ func (pR *Room) OnDismissed() {
|
||||
pR.PlayerSignalToCloseDict = make(map[int32]SignalToCloseConnCbType)
|
||||
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.RenderCacheSize = 256
|
||||
pR.RenderFrameBuffer = resolv.NewRingBuffer(pR.RenderCacheSize)
|
||||
pR.InputsBuffer = resolv.NewRingBuffer((pR.RenderCacheSize >> 1) + 1)
|
||||
pR.rdfIdToActuallyUsedInput = make(map[int32]*pb.InputFrameDownsync)
|
||||
pR.allowUpdateInputFrameInPlaceUponDynamics = true
|
||||
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
|
||||
@@ -819,6 +821,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
|
||||
@@ -954,7 +974,7 @@ func (pR *Room) clearPlayerNetworkSession(playerId int32) {
|
||||
}
|
||||
}
|
||||
|
||||
func (pR *Room) onPlayerAdded(playerId int32) {
|
||||
func (pR *Room) onPlayerAdded(playerId int32, speciesId int) {
|
||||
pR.EffectivePlayerCount++
|
||||
|
||||
if 1 == pR.EffectivePlayerCount {
|
||||
@@ -966,8 +986,9 @@ func (pR *Room) onPlayerAdded(playerId int32) {
|
||||
pR.Players[playerId].JoinIndex = int32(index) + 1
|
||||
pR.JoinIndexBooleanArr[index] = true
|
||||
|
||||
speciesId := index // FIXME
|
||||
pR.SpeciesIdList[index] = int32(speciesId)
|
||||
chosenCh := battle.Characters[speciesId]
|
||||
pR.CharacterConfigsArr[index] = chosenCh
|
||||
pR.Players[playerId].Speed = chosenCh.Speed
|
||||
|
||||
// Lazily assign the initial position of "Player" for "RoomDownsyncFrame".
|
||||
@@ -1175,9 +1196,9 @@ 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)))
|
||||
continue
|
||||
}
|
||||
if clientInputFrameId > pR.InputsBuffer.EdFrameId {
|
||||
@@ -1193,19 +1214,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
|
||||
}
|
||||
@@ -1284,6 +1305,9 @@ func (pR *Room) forceConfirmationIfApplicable(prevRenderFrameId int32) uint64 {
|
||||
panic(fmt.Sprintf("inputFrameId=%v doesn't exist for roomId=%v! InputsBuffer=%v", j, pR.Id, pR.InputsBufferString(false)))
|
||||
}
|
||||
inputFrameDownsync := tmp.(*battle.InputFrameDownsync)
|
||||
if pR.allowUpdateInputFrameInPlaceUponDynamics {
|
||||
battle.UpdateInputFrameInPlaceUponDynamics(j, pR.Capacity, inputFrameDownsync.ConfirmedList, inputFrameDownsync.InputList, pR.LastIndividuallyConfirmedInputFrameId, pR.LastIndividuallyConfirmedInputList, int32(MAGIC_JOIN_INDEX_INVALID))
|
||||
}
|
||||
unconfirmedMask |= (allConfirmedMask ^ inputFrameDownsync.ConfirmedList)
|
||||
inputFrameDownsync.ConfirmedList = allConfirmedMask
|
||||
pR.onInputFrameDownsyncAllConfirmed(inputFrameDownsync, -1)
|
||||
@@ -1364,8 +1388,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) // "allowUpdateInputFrameInPlaceUponDynamics" is instead used when "forceConfirmationIfApplicable"
|
||||
pR.CurDynamicsRenderFrameId++
|
||||
}
|
||||
}
|
||||
@@ -1779,7 +1802,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
|
||||
|
@@ -50,7 +50,17 @@ func Serve(c *gin.Context) {
|
||||
|
||||
boundRoomId := 0
|
||||
expectedRoomId := 0
|
||||
speciesId := 0
|
||||
var err error
|
||||
if speciesIdStr, hasSpeciesId := c.GetQuery("speciesId"); hasSpeciesId {
|
||||
speciesId, err = strconv.Atoi(speciesIdStr)
|
||||
if err != nil {
|
||||
// TODO: Abort with specific message.
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if boundRoomIdStr, hasBoundRoomId := c.GetQuery("boundRoomId"); hasBoundRoomId {
|
||||
boundRoomId, err = strconv.Atoi(boundRoomIdStr)
|
||||
if err != nil {
|
||||
@@ -178,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, conn, signalToCloseConnOfThisPlayer) {
|
||||
playerSuccessfullyAddedToRoom = true
|
||||
} else {
|
||||
Logger.Warn("Failed to get:\n", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Any("forExpectedRoomId", expectedRoomId))
|
||||
playerSuccessfullyAddedToRoom = false
|
||||
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))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -219,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, 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
BIN
charts/Phone4g_battle_spedup.gif
Normal file
BIN
charts/Phone4g_battle_spedup.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.4 MiB |
@@ -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
|
||||
},
|
||||
|
@@ -48,13 +48,13 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"frame": 0.45,
|
||||
"frame": 0.4,
|
||||
"value": {
|
||||
"__uuid__": "487b65c3-44e3-4b0e-9350-e0d1c952785b"
|
||||
}
|
||||
},
|
||||
{
|
||||
"frame": 0.5,
|
||||
"frame": 0.4166666666666667,
|
||||
"value": {
|
||||
"__uuid__": "9a5357ae-a160-4198-a6d5-cc9631fde754"
|
||||
}
|
||||
|
@@ -59,6 +59,12 @@
|
||||
"__uuid__": "0ecf4a0c-0f13-42fa-a214-b4826acd8556"
|
||||
}
|
||||
},
|
||||
{
|
||||
"frame": 0.3,
|
||||
"value": {
|
||||
"__uuid__": "cabf9cb6-99ca-426d-9a23-95cdec6f06b9"
|
||||
}
|
||||
},
|
||||
{
|
||||
"frame": 0.3333333333333333,
|
||||
"value": {
|
||||
|
@@ -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,
|
||||
90.576,
|
||||
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,
|
||||
-68.939,
|
||||
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": ""
|
||||
},
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -88,7 +88,7 @@
|
||||
"__id__": 1
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_active": false,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 3
|
||||
|
@@ -77,12 +77,6 @@
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 49
|
||||
},
|
||||
{
|
||||
"__id__": 50
|
||||
},
|
||||
{
|
||||
"__id__": 51
|
||||
},
|
||||
@@ -91,6 +85,12 @@
|
||||
},
|
||||
{
|
||||
"__id__": 53
|
||||
},
|
||||
{
|
||||
"__id__": 54
|
||||
},
|
||||
{
|
||||
"__id__": 55
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -244,10 +244,10 @@
|
||||
"__uuid__": "670b477e-61a1-4778-879b-35913f7c79d2"
|
||||
},
|
||||
"boundRoomIdLabel": {
|
||||
"__id__": 26
|
||||
"__id__": 28
|
||||
},
|
||||
"countdownLabel": {
|
||||
"__id__": 33
|
||||
"__id__": 35
|
||||
},
|
||||
"resultPanelPrefab": {
|
||||
"__uuid__": "c4cfe3bd-c59e-4d5b-95cb-c933b120e184"
|
||||
@@ -261,26 +261,26 @@
|
||||
"countdownToBeginGamePrefab": {
|
||||
"__uuid__": "230eeb1f-e0f9-4a41-ab6c-05b3771cbf3e"
|
||||
},
|
||||
"playersInfoPrefab": {
|
||||
"__uuid__": "b4e519f4-e698-4403-9ff2-47b8dacb077e"
|
||||
},
|
||||
"forceBigEndianFloatingNumDecoding": false,
|
||||
"renderFrameIdLagTolerance": 4,
|
||||
"sendingQLabel": {
|
||||
"inputFrameFrontLabel": {
|
||||
"__id__": 13
|
||||
},
|
||||
"inputFrameDownsyncQLabel": {
|
||||
"sendingQLabel": {
|
||||
"__id__": 15
|
||||
},
|
||||
"peerInputFrameUpsyncQLabel": {
|
||||
"inputFrameDownsyncQLabel": {
|
||||
"__id__": 17
|
||||
},
|
||||
"rollbackFramesLabel": {
|
||||
"peerInputFrameUpsyncQLabel": {
|
||||
"__id__": 19
|
||||
},
|
||||
"skippedRenderFrameCntLabel": {
|
||||
"rollbackFramesLabel": {
|
||||
"__id__": 21
|
||||
},
|
||||
"skippedRenderFrameCntLabel": {
|
||||
"__id__": 23
|
||||
},
|
||||
"_id": "d12gkAmppNlIzqcRDELa91"
|
||||
},
|
||||
{
|
||||
@@ -292,13 +292,13 @@
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 43
|
||||
"__id__": 45
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 48
|
||||
"__id__": 50
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -360,20 +360,20 @@
|
||||
{
|
||||
"__id__": 6
|
||||
},
|
||||
{
|
||||
"__id__": 32
|
||||
},
|
||||
{
|
||||
"__id__": 34
|
||||
},
|
||||
{
|
||||
"__id__": 38
|
||||
"__id__": 36
|
||||
},
|
||||
{
|
||||
"__id__": 40
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 42
|
||||
"__id__": 44
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -442,7 +442,7 @@
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 31
|
||||
"__id__": 33
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -536,7 +536,7 @@
|
||||
"array": [
|
||||
0,
|
||||
0,
|
||||
216.50635094610968,
|
||||
216.05530045313827,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@@ -620,15 +620,18 @@
|
||||
},
|
||||
{
|
||||
"__id__": 22
|
||||
},
|
||||
{
|
||||
"__id__": 24
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 29
|
||||
"__id__": 31
|
||||
},
|
||||
{
|
||||
"__id__": 30
|
||||
"__id__": 32
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -681,7 +684,7 @@
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "sendingQ",
|
||||
"_name": "inputFrameFront",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 11
|
||||
@@ -739,7 +742,7 @@
|
||||
"_is3DNode": false,
|
||||
"_groupIndex": 0,
|
||||
"groupIndex": 0,
|
||||
"_id": "ack7XH+0lEj6chSUsKBLU5"
|
||||
"_id": "83n6IAj9tAkop6CA/MyM0A"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Label",
|
||||
@@ -771,11 +774,11 @@
|
||||
"_N$fontFamily": "Arial",
|
||||
"_N$overflow": 0,
|
||||
"_N$cacheMode": 0,
|
||||
"_id": "deCJfLuoFO36c/O4lXWJ1N"
|
||||
"_id": "a4CfeVmexNm7nULcCRHcVl"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "inputFrameDownsyncQ",
|
||||
"_name": "sendingQ",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 11
|
||||
@@ -833,7 +836,7 @@
|
||||
"_is3DNode": false,
|
||||
"_groupIndex": 0,
|
||||
"groupIndex": 0,
|
||||
"_id": "d9n+NRm9pA0YtKxvy4bW+U"
|
||||
"_id": "ack7XH+0lEj6chSUsKBLU5"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Label",
|
||||
@@ -865,11 +868,11 @@
|
||||
"_N$fontFamily": "Arial",
|
||||
"_N$overflow": 0,
|
||||
"_N$cacheMode": 0,
|
||||
"_id": "90NvjGFrpBFqqzl1WZczta"
|
||||
"_id": "deCJfLuoFO36c/O4lXWJ1N"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "peerInputFrameUpsyncQ",
|
||||
"_name": "inputFrameDownsyncQ",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 11
|
||||
@@ -927,7 +930,7 @@
|
||||
"_is3DNode": false,
|
||||
"_groupIndex": 0,
|
||||
"groupIndex": 0,
|
||||
"_id": "45FAmgRLZNNbLt/GcnTXVx"
|
||||
"_id": "d9n+NRm9pA0YtKxvy4bW+U"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Label",
|
||||
@@ -959,11 +962,11 @@
|
||||
"_N$fontFamily": "Arial",
|
||||
"_N$overflow": 0,
|
||||
"_N$cacheMode": 0,
|
||||
"_id": "42PRTrjEpDw6Z8N5yWFTpd"
|
||||
"_id": "90NvjGFrpBFqqzl1WZczta"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "rollbackFrames",
|
||||
"_name": "peerInputFrameUpsyncQ",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 11
|
||||
@@ -1021,7 +1024,7 @@
|
||||
"_is3DNode": false,
|
||||
"_groupIndex": 0,
|
||||
"groupIndex": 0,
|
||||
"_id": "f5SBs3b1pPNbHbnqVLYsHp"
|
||||
"_id": "45FAmgRLZNNbLt/GcnTXVx"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Label",
|
||||
@@ -1053,11 +1056,11 @@
|
||||
"_N$fontFamily": "Arial",
|
||||
"_N$overflow": 0,
|
||||
"_N$cacheMode": 0,
|
||||
"_id": "77aNARt1VATLsjIzwbqvkh"
|
||||
"_id": "42PRTrjEpDw6Z8N5yWFTpd"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "skippedCnt",
|
||||
"_name": "rollbackFrames",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 11
|
||||
@@ -1073,9 +1076,9 @@
|
||||
"_opacity": 255,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 243,
|
||||
"g": 225,
|
||||
"b": 11,
|
||||
"r": 255,
|
||||
"g": 255,
|
||||
"b": 255,
|
||||
"a": 255
|
||||
},
|
||||
"_contentSize": {
|
||||
@@ -1115,7 +1118,7 @@
|
||||
"_is3DNode": false,
|
||||
"_groupIndex": 0,
|
||||
"groupIndex": 0,
|
||||
"_id": "cdlF8Z8TZEdLRHQQ8T8qX7"
|
||||
"_id": "f5SBs3b1pPNbHbnqVLYsHp"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Label",
|
||||
@@ -1147,6 +1150,100 @@
|
||||
"_N$fontFamily": "Arial",
|
||||
"_N$overflow": 0,
|
||||
"_N$cacheMode": 0,
|
||||
"_id": "77aNARt1VATLsjIzwbqvkh"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "skippedCnt",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 11
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 23
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
"_opacity": 255,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 243,
|
||||
"g": 225,
|
||||
"b": 11,
|
||||
"a": 255
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 18.33,
|
||||
"height": 22.61
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
"y": 0.5
|
||||
},
|
||||
"_trs": {
|
||||
"__type__": "TypedArray",
|
||||
"ctor": "Float64Array",
|
||||
"array": [
|
||||
0,
|
||||
-24.355000000000018,
|
||||
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": "cdlF8Z8TZEdLRHQQ8T8qX7"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Label",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 22
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
{
|
||||
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
|
||||
}
|
||||
],
|
||||
"_useOriginalSize": false,
|
||||
"_string": "0",
|
||||
"_N$string": "0",
|
||||
"_fontSize": 22,
|
||||
"_lineHeight": 37,
|
||||
"_enableWrapText": true,
|
||||
"_N$file": {
|
||||
"__uuid__": "a564b3db-b8cb-48b4-952e-25bb56949116"
|
||||
},
|
||||
"_isSystemFontUsed": false,
|
||||
"_spacingX": 0,
|
||||
"_batchAsBitmap": false,
|
||||
"_N$horizontalAlign": 0,
|
||||
"_N$verticalAlign": 1,
|
||||
"_N$fontFamily": "Arial",
|
||||
"_N$overflow": 0,
|
||||
"_N$cacheMode": 0,
|
||||
"_id": "2flWrlaK1PeJMUB/in+S1W"
|
||||
},
|
||||
{
|
||||
@@ -1158,19 +1255,19 @@
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 23
|
||||
"__id__": 25
|
||||
},
|
||||
{
|
||||
"__id__": 25
|
||||
"__id__": 27
|
||||
}
|
||||
],
|
||||
"_active": false,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 27
|
||||
"__id__": 29
|
||||
},
|
||||
{
|
||||
"__id__": 28
|
||||
"__id__": 30
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -1226,13 +1323,13 @@
|
||||
"_name": "label",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 22
|
||||
"__id__": 24
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 24
|
||||
"__id__": 26
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -1288,7 +1385,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 23
|
||||
"__id__": 25
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [],
|
||||
@@ -1316,13 +1413,13 @@
|
||||
"_name": "BoundRoomIdLabel",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 22
|
||||
"__id__": 24
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 26
|
||||
"__id__": 28
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -1378,7 +1475,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 25
|
||||
"__id__": 27
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [],
|
||||
@@ -1406,7 +1503,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 22
|
||||
"__id__": 24
|
||||
},
|
||||
"_enabled": true,
|
||||
"_layoutSize": {
|
||||
@@ -1439,7 +1536,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 22
|
||||
"__id__": 24
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [],
|
||||
@@ -1561,7 +1658,7 @@
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 33
|
||||
"__id__": 35
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -1617,7 +1714,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 32
|
||||
"__id__": 34
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -1651,7 +1748,7 @@
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 35
|
||||
"__id__": 37
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
@@ -1709,16 +1806,16 @@
|
||||
"_name": "Background",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 34
|
||||
"__id__": 36
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 36
|
||||
"__id__": 38
|
||||
},
|
||||
{
|
||||
"__id__": 37
|
||||
"__id__": 39
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -1774,7 +1871,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 35
|
||||
"__id__": 37
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -1808,7 +1905,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 35
|
||||
"__id__": 37
|
||||
},
|
||||
"_enabled": true,
|
||||
"alignMode": 0,
|
||||
@@ -1839,7 +1936,7 @@
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 39
|
||||
"__id__": 41
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
@@ -1897,16 +1994,16 @@
|
||||
"_name": "Background",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 38
|
||||
"__id__": 40
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 40
|
||||
"__id__": 42
|
||||
},
|
||||
{
|
||||
"__id__": 41
|
||||
"__id__": 43
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -1962,7 +2059,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 39
|
||||
"__id__": 41
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -1996,7 +2093,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 39
|
||||
"__id__": 41
|
||||
},
|
||||
"_enabled": true,
|
||||
"alignMode": 0,
|
||||
@@ -2054,16 +2151,16 @@
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 44
|
||||
"__id__": 46
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 46
|
||||
"__id__": 48
|
||||
},
|
||||
{
|
||||
"__id__": 47
|
||||
"__id__": 49
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -2119,13 +2216,13 @@
|
||||
"_name": "Joystick",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 43
|
||||
"__id__": 45
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 45
|
||||
"__id__": 47
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -2181,7 +2278,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 44
|
||||
"__id__": 46
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -2215,7 +2312,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 43
|
||||
"__id__": 45
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -2249,7 +2346,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 43
|
||||
"__id__": 45
|
||||
},
|
||||
"_enabled": true,
|
||||
"alignMode": 0,
|
||||
@@ -2384,10 +2481,10 @@
|
||||
"__id__": 3
|
||||
},
|
||||
"stickhead": {
|
||||
"__id__": 44
|
||||
"__id__": 46
|
||||
},
|
||||
"base": {
|
||||
"__id__": 43
|
||||
"__id__": 45
|
||||
},
|
||||
"joyStickEps": 0.1,
|
||||
"magicLeanLowerBound": 0.414,
|
||||
@@ -2408,10 +2505,10 @@
|
||||
"linearMovingEps": 0.1,
|
||||
"scaleByEps": 0.0375,
|
||||
"btnA": {
|
||||
"__id__": 34
|
||||
"__id__": 36
|
||||
},
|
||||
"btnB": {
|
||||
"__id__": 38
|
||||
"__id__": 40
|
||||
},
|
||||
"_id": "e9oVYTr7ROlpp/IrNjBUmR"
|
||||
}
|
||||
|
@@ -461,7 +461,7 @@
|
||||
"array": [
|
||||
0,
|
||||
0,
|
||||
216.50635094610968,
|
||||
210.43877906529718,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
|
@@ -72,14 +72,11 @@
|
||||
"__id__": 3
|
||||
},
|
||||
{
|
||||
"__id__": 10
|
||||
"__id__": 9
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 49
|
||||
},
|
||||
{
|
||||
"__id__": 50
|
||||
},
|
||||
@@ -91,6 +88,9 @@
|
||||
},
|
||||
{
|
||||
"__id__": 53
|
||||
},
|
||||
{
|
||||
"__id__": 54
|
||||
}
|
||||
],
|
||||
"_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,
|
||||
@@ -276,20 +245,22 @@
|
||||
"gameRulePrefab": null,
|
||||
"findingPlayerPrefab": null,
|
||||
"countdownToBeginGamePrefab": null,
|
||||
"playersInfoPrefab": null,
|
||||
"forceBigEndianFloatingNumDecoding": false,
|
||||
"renderFrameIdLagTolerance": 4,
|
||||
"inputFrameFrontLabel": {
|
||||
"__id__": 13
|
||||
},
|
||||
"sendingQLabel": {
|
||||
"__id__": 14
|
||||
"__id__": 15
|
||||
},
|
||||
"inputFrameDownsyncQLabel": {
|
||||
"__id__": 16
|
||||
"__id__": 17
|
||||
},
|
||||
"peerInputFrameUpsyncQLabel": {
|
||||
"__id__": 18
|
||||
"__id__": 19
|
||||
},
|
||||
"rollbackFramesLabel": {
|
||||
"__id__": 20
|
||||
"__id__": 21
|
||||
},
|
||||
"skippedRenderFrameCntLabel": null,
|
||||
"_id": "e5xQdv12xLoIRr0b36Pie+"
|
||||
@@ -299,17 +270,17 @@
|
||||
"_name": "JoystickContainer",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 8
|
||||
"__id__": 7
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 43
|
||||
"__id__": 44
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 48
|
||||
"__id__": 49
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -365,26 +336,26 @@
|
||||
"_name": "Interactive",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 9
|
||||
"__id__": 8
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 7
|
||||
"__id__": 6
|
||||
},
|
||||
{
|
||||
"__id__": 32
|
||||
"__id__": 33
|
||||
},
|
||||
{
|
||||
"__id__": 34
|
||||
"__id__": 35
|
||||
},
|
||||
{
|
||||
"__id__": 38
|
||||
"__id__": 39
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 42
|
||||
"__id__": 43
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -440,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__": 31
|
||||
"__id__": 32
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -513,13 +484,13 @@
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 9
|
||||
"__id__": 8
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 11
|
||||
"__id__": 10
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -575,7 +546,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 10
|
||||
"__id__": 9
|
||||
},
|
||||
"_enabled": true,
|
||||
"_cullingMask": 4294967295,
|
||||
@@ -611,32 +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__": 24
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 30
|
||||
"__id__": 31
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -689,16 +663,16 @@
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "sendingQ",
|
||||
"_name": "inputFrameFront",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 12
|
||||
"__id__": 11
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 14
|
||||
"__id__": 13
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -725,7 +699,99 @@
|
||||
"ctor": "Float64Array",
|
||||
"array": [
|
||||
0,
|
||||
76.48,
|
||||
79.66666666666667,
|
||||
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": "16ecz642FAMIrtMtKWTI/3"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Label",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 12
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
{
|
||||
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
|
||||
}
|
||||
],
|
||||
"_useOriginalSize": false,
|
||||
"_string": "0",
|
||||
"_N$string": "0",
|
||||
"_fontSize": 22,
|
||||
"_lineHeight": 23,
|
||||
"_enableWrapText": true,
|
||||
"_N$file": null,
|
||||
"_isSystemFontUsed": true,
|
||||
"_spacingX": 0,
|
||||
"_batchAsBitmap": false,
|
||||
"_N$horizontalAlign": 0,
|
||||
"_N$verticalAlign": 1,
|
||||
"_N$fontFamily": "Arial",
|
||||
"_N$overflow": 0,
|
||||
"_N$cacheMode": 0,
|
||||
"_id": "e2W8Kja+VG7IEwuBQapscC"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "sendingQ",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 11
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 15
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
"_opacity": 255,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 255,
|
||||
"g": 255,
|
||||
"b": 255,
|
||||
"a": 255
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 12.24,
|
||||
"height": 28.98
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
"y": 0.5
|
||||
},
|
||||
"_trs": {
|
||||
"__type__": "TypedArray",
|
||||
"ctor": "Float64Array",
|
||||
"array": [
|
||||
0,
|
||||
47.8,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@@ -754,7 +820,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 13
|
||||
"__id__": 14
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -784,13 +850,13 @@
|
||||
"_name": "inputFrameDownsyncQ",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 12
|
||||
"__id__": 11
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 16
|
||||
"__id__": 17
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -817,7 +883,7 @@
|
||||
"ctor": "Float64Array",
|
||||
"array": [
|
||||
0,
|
||||
38.239999999999995,
|
||||
15.933333333333325,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@@ -846,7 +912,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 15
|
||||
"__id__": 16
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -876,13 +942,13 @@
|
||||
"_name": "peerInputFrameUpsyncQ",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 12
|
||||
"__id__": 11
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 18
|
||||
"__id__": 19
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -909,7 +975,7 @@
|
||||
"ctor": "Float64Array",
|
||||
"array": [
|
||||
0,
|
||||
-7.105427357601002e-15,
|
||||
-15.933333333333346,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@@ -938,7 +1004,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 17
|
||||
"__id__": 18
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -968,13 +1034,13 @@
|
||||
"_name": "rollbackFrames",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 12
|
||||
"__id__": 11
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 20
|
||||
"__id__": 21
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -1001,7 +1067,7 @@
|
||||
"ctor": "Float64Array",
|
||||
"array": [
|
||||
0,
|
||||
-38.24000000000001,
|
||||
-47.80000000000002,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@@ -1030,7 +1096,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 19
|
||||
"__id__": 20
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -1060,13 +1126,13 @@
|
||||
"_name": "skippedCnt",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 12
|
||||
"__id__": 11
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 22
|
||||
"__id__": 23
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -1093,7 +1159,7 @@
|
||||
"ctor": "Float64Array",
|
||||
"array": [
|
||||
0,
|
||||
-76.48000000000002,
|
||||
-79.66666666666669,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@@ -1122,7 +1188,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 21
|
||||
"__id__": 22
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -1154,23 +1220,23 @@
|
||||
"_name": "RoomIdIndicator",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 12
|
||||
"__id__": 11
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 24
|
||||
"__id__": 25
|
||||
},
|
||||
{
|
||||
"__id__": 26
|
||||
"__id__": 27
|
||||
}
|
||||
],
|
||||
"_active": false,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 28
|
||||
"__id__": 29
|
||||
},
|
||||
{
|
||||
"__id__": 29
|
||||
"__id__": 30
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -1226,13 +1292,13 @@
|
||||
"_name": "label",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 23
|
||||
"__id__": 24
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 25
|
||||
"__id__": 26
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -1288,7 +1354,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 24
|
||||
"__id__": 25
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [],
|
||||
@@ -1316,13 +1382,13 @@
|
||||
"_name": "BoundRoomIdLabel",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 23
|
||||
"__id__": 24
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 27
|
||||
"__id__": 28
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -1378,7 +1444,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 26
|
||||
"__id__": 27
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [],
|
||||
@@ -1406,7 +1472,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 23
|
||||
"__id__": 24
|
||||
},
|
||||
"_enabled": true,
|
||||
"_layoutSize": {
|
||||
@@ -1439,7 +1505,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 23
|
||||
"__id__": 24
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [],
|
||||
@@ -1469,7 +1535,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 12
|
||||
"__id__": 11
|
||||
},
|
||||
"_enabled": true,
|
||||
"_layoutSize": {
|
||||
@@ -1502,7 +1568,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 9
|
||||
"__id__": 8
|
||||
},
|
||||
"_enabled": true,
|
||||
"alignMode": 1,
|
||||
@@ -1529,13 +1595,13 @@
|
||||
"_name": "CountdownSeconds",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 8
|
||||
"__id__": 7
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 33
|
||||
"__id__": 34
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -1591,7 +1657,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 32
|
||||
"__id__": 33
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -1621,11 +1687,11 @@
|
||||
"_name": "BtnA",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 8
|
||||
"__id__": 7
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 35
|
||||
"__id__": 36
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
@@ -1683,16 +1749,16 @@
|
||||
"_name": "Background",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 34
|
||||
"__id__": 35
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 36
|
||||
"__id__": 37
|
||||
},
|
||||
{
|
||||
"__id__": 37
|
||||
"__id__": 38
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -1748,7 +1814,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 35
|
||||
"__id__": 36
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -1782,7 +1848,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 35
|
||||
"__id__": 36
|
||||
},
|
||||
"_enabled": true,
|
||||
"alignMode": 0,
|
||||
@@ -1809,11 +1875,11 @@
|
||||
"_name": "BtnB",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 8
|
||||
"__id__": 7
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 39
|
||||
"__id__": 40
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
@@ -1871,16 +1937,16 @@
|
||||
"_name": "Background",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 38
|
||||
"__id__": 39
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 40
|
||||
"__id__": 41
|
||||
},
|
||||
{
|
||||
"__id__": 41
|
||||
"__id__": 42
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -1936,7 +2002,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 39
|
||||
"__id__": 40
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -1970,7 +2036,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 39
|
||||
"__id__": 40
|
||||
},
|
||||
"_enabled": true,
|
||||
"alignMode": 0,
|
||||
@@ -1997,7 +2063,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 8
|
||||
"__id__": 7
|
||||
},
|
||||
"_enabled": true,
|
||||
"alignMode": 1,
|
||||
@@ -2024,20 +2090,20 @@
|
||||
"_name": "JoystickBG",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 7
|
||||
"__id__": 6
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 44
|
||||
"__id__": 45
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 46
|
||||
"__id__": 47
|
||||
},
|
||||
{
|
||||
"__id__": 47
|
||||
"__id__": 48
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -2093,13 +2159,13 @@
|
||||
"_name": "Joystick",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 43
|
||||
"__id__": 44
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 45
|
||||
"__id__": 46
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -2155,7 +2221,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 44
|
||||
"__id__": 45
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -2189,7 +2255,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 43
|
||||
"__id__": 44
|
||||
},
|
||||
"_enabled": true,
|
||||
"_materials": [
|
||||
@@ -2223,7 +2289,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 43
|
||||
"__id__": 44
|
||||
},
|
||||
"_enabled": true,
|
||||
"alignMode": 0,
|
||||
@@ -2250,7 +2316,7 @@
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 7
|
||||
"__id__": 6
|
||||
},
|
||||
"_enabled": true,
|
||||
"alignMode": 0,
|
||||
@@ -2352,16 +2418,16 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"translationListenerNode": {
|
||||
"__id__": 7
|
||||
"__id__": 6
|
||||
},
|
||||
"zoomingListenerNode": {
|
||||
"__id__": 3
|
||||
},
|
||||
"stickhead": {
|
||||
"__id__": 44
|
||||
"__id__": 45
|
||||
},
|
||||
"base": {
|
||||
"__id__": 43
|
||||
"__id__": 44
|
||||
},
|
||||
"joyStickEps": 0.1,
|
||||
"magicLeanLowerBound": 0.414,
|
||||
@@ -2382,10 +2448,10 @@
|
||||
"linearMovingEps": 0.1,
|
||||
"scaleByEps": 0.0375,
|
||||
"btnA": {
|
||||
"__id__": 34
|
||||
"__id__": 35
|
||||
},
|
||||
"btnB": {
|
||||
"__id__": 38
|
||||
"__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;
|
||||
|
41
frontend/assets/scripts/CharacterSelectCell.js
Normal file
41
frontend/assets/scripts/CharacterSelectCell.js
Normal file
@@ -0,0 +1,41 @@
|
||||
cc.Class({
|
||||
extends: cc.Component,
|
||||
properties: {
|
||||
panelNode: {
|
||||
type: cc.Node,
|
||||
default: null
|
||||
},
|
||||
chosenFlag: {
|
||||
type: cc.Sprite,
|
||||
default: null
|
||||
},
|
||||
avatarNode: {
|
||||
type: cc.Button,
|
||||
default: null
|
||||
},
|
||||
animNode: {
|
||||
type: cc.Node,
|
||||
default: null
|
||||
},
|
||||
speciesId: {
|
||||
type: cc.Integer,
|
||||
default: 0
|
||||
},
|
||||
},
|
||||
|
||||
ctor() {},
|
||||
|
||||
setInteractable(enabled) {
|
||||
this.avatarNode.interactable = enabled;
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
const avatarNodeClickEventHandler = new cc.Component.EventHandler();
|
||||
avatarNodeClickEventHandler.target = this.panelNode;
|
||||
avatarNodeClickEventHandler.component = "GameRule";
|
||||
avatarNodeClickEventHandler.handler = "onSpeciesSelected";
|
||||
avatarNodeClickEventHandler.customEventData = this.speciesId;
|
||||
this.avatarNode.clickEvents.push(avatarNodeClickEventHandler);
|
||||
},
|
||||
});
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"ver": "1.0.5",
|
||||
"uuid": "09e1bfed-132e-4ada-a68f-229a870db69e",
|
||||
"uuid": "6dd2c047-fa5c-4080-8221-27fabfd275d6",
|
||||
"isPlugin": false,
|
||||
"loadPluginInWeb": true,
|
||||
"loadPluginInNative": true,
|
@@ -47,7 +47,7 @@ cc.Class({
|
||||
},
|
||||
|
||||
hideExitButton() {
|
||||
if (null == this.exitBtnNode != null) {
|
||||
if (null == this.exitBtnNode) {
|
||||
return;
|
||||
}
|
||||
this.exitBtnNode.active = false;
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -10,15 +10,46 @@ cc.Class({
|
||||
type: cc.Node,
|
||||
default: null
|
||||
},
|
||||
characterSelectCells: {
|
||||
type: cc.Node,
|
||||
default: []
|
||||
},
|
||||
chosenSpeciesId: {
|
||||
type: cc.Integer,
|
||||
default: 0
|
||||
},
|
||||
loadingNode: {
|
||||
default: null,
|
||||
type: cc.Node
|
||||
},
|
||||
},
|
||||
|
||||
// LIFE-CYCLE CALLBACKS:
|
||||
onLoad() {
|
||||
const modeBtnClickEventHandler = new cc.Component.EventHandler();
|
||||
modeBtnClickEventHandler.target = this.mapNode;
|
||||
modeBtnClickEventHandler.component = "Map";
|
||||
modeBtnClickEventHandler.handler = "onGameRule1v1ModeClicked";
|
||||
this.modeButton.clickEvents.push(modeBtnClickEventHandler);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
onSpeciesSelected(evt, val) {
|
||||
for (let cell of this.characterSelectCells) {
|
||||
const comp = cell.getComponent("CharacterSelectCell");
|
||||
if (comp.speciesId != val) {
|
||||
comp.chosenFlag.node.active = false;
|
||||
} else {
|
||||
comp.chosenFlag.node.active = true;
|
||||
this.chosenSpeciesId = val;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onModeButtonClicked(evt) {
|
||||
for (let cell of this.characterSelectCells) {
|
||||
const comp = cell.getComponent("CharacterSelectCell");
|
||||
comp.setInteractable(false);
|
||||
}
|
||||
this.modeButton.node.active = false;
|
||||
this.loadingNode.active = true;
|
||||
this.loadingNode.runAction(
|
||||
cc.repeatForever(cc.rotateBy(1.0, 360))
|
||||
);
|
||||
this.mapNode.getComponent("Map").onGameRule1v1ModeClicked(this.chosenSpeciesId);
|
||||
},
|
||||
});
|
||||
|
@@ -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,
|
||||
@@ -354,6 +355,7 @@ cc.Class({
|
||||
self.loadingNode.getChildByName('loadingSprite').runAction(
|
||||
cc.repeatForever(cc.rotateBy(1.0, 360))
|
||||
);
|
||||
self.loadingNode.getChildByName('loadingLabel').active = true;
|
||||
cc.director.loadScene('default_map');
|
||||
} else {
|
||||
console.log("OnLoggedIn failed, about to remove `selfPlayer` in local cache.")
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@@ -5,6 +5,7 @@ var NetworkDoctor = function(capacity) {
|
||||
};
|
||||
|
||||
NetworkDoctor.prototype.reset = function(capacity) {
|
||||
this.inputFrameIdFront = 0;
|
||||
this.sendingQ = new RingBuffer(capacity);
|
||||
this.inputFrameDownsyncQ = new RingBuffer(capacity);
|
||||
this.peerInputFrameUpsyncQ = new RingBuffer(capacity);
|
||||
@@ -17,6 +18,10 @@ NetworkDoctor.prototype.reset = function(capacity) {
|
||||
this.rollbackFramesThreshold = 8; // Roughly the minimum "TurnAroundFramesToRecover".
|
||||
};
|
||||
|
||||
NetworkDoctor.prototype.logInputFrameIdFront = function(inputFrameId) {
|
||||
this.inputFrameIdFront = inputFrameId;
|
||||
};
|
||||
|
||||
NetworkDoctor.prototype.logSending = function(stFrameId, edFrameId) {
|
||||
this.sendingQ.put({
|
||||
i: stFrameId,
|
||||
@@ -50,7 +55,8 @@ NetworkDoctor.prototype.logRollbackFrames = function(x) {
|
||||
};
|
||||
|
||||
NetworkDoctor.prototype.stats = function() {
|
||||
let sendingFps = 0,
|
||||
let inputFrameIdFront = this.inputFrameIdFront,
|
||||
sendingFps = 0,
|
||||
srvDownsyncFps = 0,
|
||||
peerUpsyncFps = 0,
|
||||
rollbackFrames = this.immediateRollbackFrames;
|
||||
@@ -72,7 +78,7 @@ NetworkDoctor.prototype.stats = function() {
|
||||
const elapsedMillis = ed.t - st.t;
|
||||
peerUpsyncFps = Math.round(this.peerInputFrameUpsyncCnt * 1000 / elapsedMillis);
|
||||
}
|
||||
return [sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, this.skippedRenderFrameCnt];
|
||||
return [inputFrameIdFront, sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, this.skippedRenderFrameCnt];
|
||||
};
|
||||
|
||||
NetworkDoctor.prototype.logSkippedRenderFrameCnt = function() {
|
||||
@@ -80,31 +86,36 @@ NetworkDoctor.prototype.logSkippedRenderFrameCnt = function() {
|
||||
}
|
||||
|
||||
NetworkDoctor.prototype.isTooFast = function(mapIns) {
|
||||
const [sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, skippedRenderFrameCnt] = this.stats();
|
||||
const [inputFrameIdFront, sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, skippedRenderFrameCnt] = this.stats();
|
||||
if (sendingFps >= this.inputRateThreshold + 3) {
|
||||
// Don't send too fast
|
||||
console.log(`Sending too fast, sendingFps=${sendingFps}`);
|
||||
return true;
|
||||
if (CC_DEBUG) {
|
||||
// Printing of this message might induce a performance impact.
|
||||
// console.log(`Sending too fast, sendingFps=${sendingFps}`);
|
||||
}
|
||||
return [true, inputFrameIdFront, sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, skippedRenderFrameCnt];
|
||||
} else {
|
||||
const sendingFpsNormal = (sendingFps >= this.inputRateThreshold);
|
||||
// An outstanding lag within the "inputFrameDownsyncQ" will reduce "srvDownsyncFps", HOWEVER, a constant lag wouldn't impact "srvDownsyncFps"! In native platforms we might use PING value might help as a supplement information to confirm that the "selfPlayer" is not lagged within the time accounted by "inputFrameDownsyncQ".
|
||||
const recvFpsNormal = (srvDownsyncFps >= this.inputRateThreshold || peerUpsyncFps >= this.inputRateThreshold * (window.boundRoomCapacity - 1));
|
||||
if (sendingFpsNormal && recvFpsNormal) {
|
||||
let selfInputFrameIdFront = gopkgs.ConvertToNoDelayInputFrameId(mapIns.renderFrameId);
|
||||
let minInputFrameIdFront = Number.MAX_VALUE;
|
||||
for (let k = 0; k < window.boundRoomCapacity; ++k) {
|
||||
if (k + 1 == mapIns.selfPlayerInfo.JoinIndex) continue;
|
||||
if (mapIns.lastIndividuallyConfirmedInputFrameId[k] >= minInputFrameIdFront) continue;
|
||||
minInputFrameIdFront = mapIns.lastIndividuallyConfirmedInputFrameId[k];
|
||||
}
|
||||
if ((selfInputFrameIdFront > minInputFrameIdFront) && ((selfInputFrameIdFront - minInputFrameIdFront) > (mapIns.inputFrameUpsyncDelayTolerance + 1))) {
|
||||
if ((inputFrameIdFront > minInputFrameIdFront) && ((inputFrameIdFront - minInputFrameIdFront) > (mapIns.inputFrameUpsyncDelayTolerance + 1))) {
|
||||
// first comparison condition is to avoid numeric overflow
|
||||
console.log(`Game logic ticking too fast, selfInputFrameIdFront=${selfInputFrameIdFront}, minInputFrameIdFront=${minInputFrameIdFront}, inputFrameUpsyncDelayTolerance=${mapIns.inputFrameUpsyncDelayTolerance}`);
|
||||
return true;
|
||||
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}`);
|
||||
}
|
||||
return [true, inputFrameIdFront, sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, skippedRenderFrameCnt];
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return [false, inputFrameIdFront, sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, skippedRenderFrameCnt];
|
||||
};
|
||||
|
||||
module.exports = NetworkDoctor;
|
||||
|
@@ -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",
|
||||
|
@@ -58,10 +58,21 @@ window.getBoundRoomCapacityFromPersistentStorage = function() {
|
||||
return (null == boundRoomCapacityStr ? null : parseInt(boundRoomCapacityStr));
|
||||
};
|
||||
|
||||
window.getChosenSpeciesIdFromPersistentStorage = function() {
|
||||
const boundRoomIdExpiresAt = parseInt(cc.sys.localStorage.getItem("boundRoomIdExpiresAt"));
|
||||
if (!boundRoomIdExpiresAt || Date.now() >= boundRoomIdExpiresAt) {
|
||||
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
|
||||
return null;
|
||||
}
|
||||
const chosenSpeciesIdStr = cc.sys.localStorage.getItem("chosenSpeciesId");
|
||||
return (null == chosenSpeciesIdStr ? 0 : parseInt(chosenSpeciesIdStr));
|
||||
};
|
||||
|
||||
window.clearBoundRoomIdInBothVolatileAndPersistentStorage = function() {
|
||||
window.boundRoomId = null;
|
||||
cc.sys.localStorage.removeItem("boundRoomId");
|
||||
cc.sys.localStorage.removeItem("boundRoomCapacity");
|
||||
cc.sys.localStorage.removeItem("chosenSpeciesId");
|
||||
cc.sys.localStorage.removeItem("boundRoomIdExpiresAt");
|
||||
};
|
||||
|
||||
@@ -76,14 +87,15 @@ 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;
|
||||
window.boundRoomCapacity = resp.bciFrame.boundRoomCapacity;
|
||||
cc.sys.localStorage.setItem('boundRoomId', window.boundRoomId);
|
||||
cc.sys.localStorage.setItem('boundRoomCapacity', window.boundRoomCapacity);
|
||||
cc.sys.localStorage.setItem('chosenSpeciesId', window.chosenSpeciesId);
|
||||
cc.sys.localStorage.setItem('boundRoomIdExpiresAt', Date.now() + 10 * 60 * 1000); // Temporarily hardcoded, for `boundRoomId` only.
|
||||
}
|
||||
console.log(`Handle hb requirements #3`);
|
||||
@@ -97,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);
|
||||
@@ -109,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();
|
||||
@@ -179,6 +191,13 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
|
||||
}
|
||||
}
|
||||
|
||||
if (null == window.chosenSpeciesId) {
|
||||
window.chosenSpeciesId = getChosenSpeciesIdFromPersistentStorage();
|
||||
}
|
||||
if (null != window.chosenSpeciesId) {
|
||||
urlToConnect = urlToConnect + "&speciesId=" + window.chosenSpeciesId;
|
||||
}
|
||||
|
||||
const clientSession = new WebSocket(urlToConnect);
|
||||
clientSession.binaryType = 'arraybuffer'; // Make 'event.data' of 'onmessage' an "ArrayBuffer" instead of a "Blob"
|
||||
|
||||
@@ -230,9 +249,9 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
|
||||
const peerAddrList = resp.rdf.peerUdpAddrList;
|
||||
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"
|
||||
}, j*500);
|
||||
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"
|
||||
}, j * 500);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -269,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:
|
||||
@@ -286,9 +313,19 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
|
||||
console.warn(`${mapIns._stringifyRdfIdToActuallyUsedInput()}
|
||||
`);
|
||||
}
|
||||
window.clearLocalStorageAndBackToLoginScene(true);
|
||||
break;
|
||||
mapIns.popupSimplePressToGo("Disconnected unexpectedly, please retry", false, () => {
|
||||
window.clearLocalStorageAndBackToLoginScene(true);
|
||||
});
|
||||
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": "171e2c96-28b4-4225-bdcc-5e464f07d91a",
|
||||
"uuid": "02c5cdc1-9797-49ab-bc11-963215909926",
|
||||
"isPlugin": true,
|
||||
"loadPluginInWeb": true,
|
||||
"loadPluginInNative": true,
|
||||
|
7
frontend/build-templates/.cocos-project.json
Normal file
7
frontend/build-templates/.cocos-project.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"engine_version": "2.2.1",
|
||||
"has_native": true,
|
||||
"project_type": "js",
|
||||
"projectName": "DelayNoMore",
|
||||
"packageName": "org.genxium.delaynomore"
|
||||
}
|
@@ -18,11 +18,11 @@
|
||||
"from": "cocos/scripting/js-bindings/manual/jsb_module_register.cpp",
|
||||
"to": "frameworks/runtime-src/Classes/jsb_module_register.cpp"
|
||||
}, {
|
||||
"from": "frameworks/runtime-src/Classes/send_ring_buff.hpp",
|
||||
"to": "frameworks/runtime-src/Classes/send_ring_buff.hpp"
|
||||
"from": "frameworks/runtime-src/Classes/ring_buff.hpp",
|
||||
"to": "frameworks/runtime-src/Classes/ring_buff.hpp"
|
||||
}, {
|
||||
"from": "frameworks/runtime-src/Classes/send_ring_buff.cpp",
|
||||
"to": "frameworks/runtime-src/Classes/send_ring_buff.cpp"
|
||||
"from": "frameworks/runtime-src/Classes/ring_buff.cpp",
|
||||
"to": "frameworks/runtime-src/Classes/ring_buff.cpp"
|
||||
}, {
|
||||
"from": "frameworks/runtime-src/Classes/udp_session.hpp",
|
||||
"to": "frameworks/runtime-src/Classes/udp_session.hpp"
|
||||
|
@@ -1,5 +1,5 @@
|
||||
#include <string.h>
|
||||
#include "send_ring_buff.hpp"
|
||||
#include "ring_buff.hpp"
|
||||
|
||||
// Sending
|
||||
void SendRingBuff::put(BYTEC* const newBytes, size_t newBytesLen, PeerAddr* pNewPeerAddr) {
|
||||
@@ -32,35 +32,60 @@ 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;
|
||||
}
|
||||
slotEle->bytesLen = newBytesLen;
|
||||
memset(slotEle->ui8Arr, 0, sizeof slotEle->ui8Arr);
|
||||
for (int i = 0; i < newBytesLen; i++) {
|
||||
for (size_t i = 0; i < newBytesLen; i++) {
|
||||
*(slotEle->ui8Arr + i) = *(newBytes + i);
|
||||
}
|
||||
|
||||
// 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++;
|
||||
}
|
||||
@@ -77,7 +102,7 @@ bool RecvRingBuff::pop(RecvWork* out) {
|
||||
if (0 >= oldCnt) {
|
||||
// "pop" could be accessed by either "GameThread/pollUdpRecvRingBuff" or "UvRecvThread/put", thus we should be proactively guard against concurrent popping while "1 == cnt"
|
||||
++cnt;
|
||||
return NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
// When concurrent "pop"s reach here, over-popping is definitely avoided.
|
||||
@@ -99,4 +124,4 @@ bool RecvRingBuff::pop(RecvWork* out) {
|
||||
++cnt;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
|
||||
@@ -248,20 +246,41 @@ 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.
|
||||
|
||||
uv_async_send(&uvSendLoopStopSig);
|
||||
CCLOG("Signaling UvSendThread to end in GameThread...");
|
||||
uv_thread_join(&sendTid);
|
||||
free(udpSendSocket);
|
||||
free(sendLoop);
|
||||
delete sendRingBuff;
|
||||
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);
|
||||
free(udpSendSocket);
|
||||
free(sendLoop);
|
||||
delete sendRingBuff;
|
||||
|
||||
udpSendSocket = NULL;
|
||||
sendLoop = NULL;
|
||||
sendRingBuff = NULL;
|
||||
|
||||
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);
|
||||
free(udpRecvSocket);
|
||||
free(recvLoop);
|
||||
delete recvRingBuff;
|
||||
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);
|
||||
free(udpRecvSocket);
|
||||
free(recvLoop);
|
||||
delete recvRingBuff;
|
||||
|
||||
udpRecvSocket = NULL;
|
||||
recvLoop = NULL;
|
||||
recvRingBuff = NULL;
|
||||
|
||||
uv_mutex_destroy(&recvRingBuffLock);
|
||||
}
|
||||
|
||||
CCLOG("Closed udp session and dealloc all resources in GameThread...");
|
||||
|
||||
@@ -356,8 +375,10 @@ bool DelayNoMore::UdpSession::pollUdpRecvRingBuff() {
|
||||
while (true) {
|
||||
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"!
|
||||
@@ -381,4 +402,4 @@ bool DelayNoMore::UdpSession::pollUdpRecvRingBuff() {
|
||||
}
|
||||
//uv_mutex_unlock(&recvRingBuffLock);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#ifndef udp_session_hpp
|
||||
#define udp_session_hpp
|
||||
|
||||
#include "send_ring_buff.hpp"
|
||||
#include "ring_buff.hpp"
|
||||
|
||||
int const maxPeerCnt = 10;
|
||||
|
||||
|
@@ -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" />
|
||||
|
||||
|
@@ -15,7 +15,7 @@ LOCAL_SRC_FILES := hellojavascript/main.cpp \
|
||||
../../../Classes/jsb_module_register.cpp \
|
||||
../../../Classes/udp_session.cpp \
|
||||
../../../Classes/udp_session_bridge.cpp \
|
||||
../../../Classes/send_ring_buff.cpp
|
||||
../../../Classes/ring_buff.cpp
|
||||
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../../Classes
|
||||
|
||||
|
@@ -190,11 +190,11 @@ copy "$(ProjectDir)..\..\..\project.json" "$(OutDir)\" /Y</Command>
|
||||
<ClCompile Include="..\Classes\AppDelegate.cpp" />
|
||||
<ClCompile Include="..\Classes\udp_session.cpp" />
|
||||
<ClCompile Include="..\Classes\udp_session_bridge.cpp" />
|
||||
<ClCompile Include="..\Classes\send_ring_buff.cpp" />
|
||||
<ClCompile Include="..\Classes\ring_buff.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="main.h" />
|
||||
<ClInclude Include="..\Classes\send_ring_buff.hpp" />
|
||||
<ClInclude Include="..\Classes\ring_buff.hpp" />
|
||||
<ClInclude Include="..\Classes\udp_session.hpp" />
|
||||
<ClInclude Include="..\Classes\udp_session_bridge.hpp" />
|
||||
<ClInclude Include="..\Classes\AppDelegate.h" />
|
||||
|
@@ -22,7 +22,7 @@
|
||||
<ClCompile Include="..\Classes\jsb_module_register.cpp">
|
||||
<Filter>Classes</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\Classes\send_ring_buff.cpp">
|
||||
<ClCompile Include="..\Classes\ring_buff.cpp">
|
||||
<Filter>Classes</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\Classes\udp_session.cpp">
|
||||
@@ -40,7 +40,7 @@
|
||||
<Filter>win32</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="..\Classes\send_ring_buff.hpp">
|
||||
<ClInclude Include="..\Classes\ring_buff.hpp">
|
||||
<Filter>Classes</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Classes\udp_session.hpp">
|
||||
|
@@ -76,7 +76,7 @@
|
||||
"shelter_z_reducer",
|
||||
"shelter"
|
||||
],
|
||||
"last-module-event-record-time": 1675852779064,
|
||||
"last-module-event-record-time": 1677337364473,
|
||||
"simulator-orientation": false,
|
||||
"simulator-resolution": {
|
||||
"height": 640,
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -107,7 +107,7 @@ var Characters = map[int]*CharacterConfig{
|
||||
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
|
||||
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),
|
||||
|
||||
@@ -234,7 +234,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),
|
||||
@@ -332,7 +332,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),
|
||||
@@ -538,7 +538,7 @@ var skills = map[int]*Skill{
|
||||
HitboxSizeX: int32(float64(64) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
HitboxSizeY: int32(float64(48) * WORLD_TO_VIRTUAL_GRID_RATIO),
|
||||
BlowUp: false,
|
||||
ExplosionFrames: 10,
|
||||
ExplosionFrames: 30,
|
||||
SpeciesId: int32(1),
|
||||
},
|
||||
},
|
||||
@@ -781,10 +781,10 @@ var skills = map[int]*Skill{
|
||||
Hits: []interface{}{
|
||||
&MeleeBullet{
|
||||
Bullet: &BulletConfig{
|
||||
StartupFrames: int32(3),
|
||||
StartupFrames: int32(4),
|
||||
ActiveFrames: int32(20),
|
||||
HitStunFrames: int32(18),
|
||||
BlockStunFrames: int32(9),
|
||||
HitStunFrames: int32(9),
|
||||
BlockStunFrames: int32(5),
|
||||
Damage: int32(5),
|
||||
SelfLockVelX: NO_LOCK_VEL,
|
||||
SelfLockVelY: NO_LOCK_VEL,
|
||||
|
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
|
||||
}
|
212
jsexport/main.go
212
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,41 +106,111 @@ 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() {
|
||||
js.Global.Set("gopkgs", map[string]interface{}{
|
||||
"NewVec2DJs": NewVec2DJs,
|
||||
"NewPolygon2DJs": NewPolygon2DJs,
|
||||
"NewBarrierJs": NewBarrierJs,
|
||||
"NewPlayerDownsyncJs": NewPlayerDownsyncJs,
|
||||
"NewMeleeBulletJs": NewMeleeBulletJs,
|
||||
"NewFireballBulletJs": NewFireballBulletJs,
|
||||
"NewNpcPatrolCue": NewNpcPatrolCue,
|
||||
"NewRoomDownsyncFrameJs": NewRoomDownsyncFrameJs,
|
||||
"NewCollisionSpaceJs": NewCollisionSpaceJs,
|
||||
"NewInputFrameDownsync": NewInputFrameDownsync,
|
||||
"NewRingBufferJs": NewRingBufferJs,
|
||||
"GenerateRectColliderJs": GenerateRectColliderJs,
|
||||
"GenerateConvexPolygonColliderJs": GenerateConvexPolygonColliderJs,
|
||||
"GetCollisionSpaceObjsJs": GetCollisionSpaceObjsJs,
|
||||
"WorldToPolygonColliderBLPos": WorldToPolygonColliderBLPos, // No need to wrap primitive return types
|
||||
"PolygonColliderBLToWorldPos": PolygonColliderBLToWorldPos,
|
||||
"WorldToVirtualGridPos": WorldToVirtualGridPos,
|
||||
"VirtualGridToWorldPos": VirtualGridToWorldPos,
|
||||
"GetCharacterConfigsOrderedByJoinIndex": GetCharacterConfigsOrderedByJoinIndex,
|
||||
"NewVec2DJs": NewVec2DJs,
|
||||
"NewPolygon2DJs": NewPolygon2DJs,
|
||||
"NewBarrierJs": NewBarrierJs,
|
||||
"NewPlayerDownsyncJs": NewPlayerDownsyncJs,
|
||||
"NewMeleeBulletJs": NewMeleeBulletJs,
|
||||
"NewFireballBulletJs": NewFireballBulletJs,
|
||||
"NewNpcPatrolCue": NewNpcPatrolCue,
|
||||
"NewRoomDownsyncFrameJs": NewRoomDownsyncFrameJs,
|
||||
"NewCollisionSpaceJs": NewCollisionSpaceJs,
|
||||
"NewCollisionHolder": NewCollisionHolder,
|
||||
"NewInputFrameDownsync": NewInputFrameDownsync,
|
||||
"NewRingBufferJs": NewRingBufferJs,
|
||||
"GenerateConvexPolygonColliderJs": GenerateConvexPolygonColliderJs,
|
||||
"GetCollisionSpaceObjsJs": GetCollisionSpaceObjsJs,
|
||||
"WorldToPolygonColliderBLPos": WorldToPolygonColliderBLPos, // No need to wrap primitive return types
|
||||
"PolygonColliderBLToWorldPos": PolygonColliderBLToWorldPos,
|
||||
"WorldToVirtualGridPos": WorldToVirtualGridPos,
|
||||
"VirtualGridToWorldPos": VirtualGridToWorldPos,
|
||||
"GetCharacterConfigsOrderedByJoinIndex": GetCharacterConfigsOrderedByJoinIndex,
|
||||
"ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs": ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs,
|
||||
"ConvertToDelayedInputFrameId": ConvertToDelayedInputFrameId,
|
||||
"ConvertToNoDelayInputFrameId": ConvertToNoDelayInputFrameId,
|
||||
"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,3 +1,5 @@
|
||||
# TODO: For websocket traffic, use a "consistent hash" on "expectedRoomId" and "boundRoomId"!
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name tsrht.lokcol.com;
|
||||
|
@@ -2,8 +2,8 @@ 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.
|
||||
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 *RingBuffer // The Objects that a Cell contains.
|
||||
}
|
||||
|
||||
// newCell creates a new cell at the specified X and Y position. Should not be used directly.
|
||||
@@ -11,25 +11,27 @@ func newCell(x, y int) *Cell {
|
||||
return &Cell{
|
||||
X: x,
|
||||
Y: y,
|
||||
Objects: []*Object{},
|
||||
Objects: NewRingBuffer(16), // A single cell is so small thus wouldn't have many touching objects simultaneously
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
@@ -3,23 +3,39 @@ package resolv
|
||||
// Collision contains the results of an Object.Check() call, and represents a collision between an Object and cells that contain other Objects.
|
||||
// The Objects array indicate the Objects collided with.
|
||||
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.
|
||||
checkingObject *Object // The checking object
|
||||
dx, dy float64 // The delta the checking object was moving on that caused this collision
|
||||
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{},
|
||||
Objects: NewRingBuffer(16), // I don't expect it to exceed 10 actually
|
||||
Cells: NewRingBuffer(16),
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -38,8 +54,9 @@ func (cc *Collision) ObjectsByTags(tags ...string) []*Object {
|
||||
|
||||
objects := []*Object{}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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
|
||||
|
@@ -2,7 +2,6 @@ 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.
|
||||
@@ -10,21 +9,36 @@ 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 NewObjectSingleTag(x, y, w, h float64, tag string) *Object {
|
||||
o := &Object{
|
||||
X: x,
|
||||
Y: y,
|
||||
W: w,
|
||||
H: h,
|
||||
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!
|
||||
tags: []string{tag},
|
||||
ignoreList: map[*Object]bool{},
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
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{},
|
||||
X: x,
|
||||
Y: y,
|
||||
W: w,
|
||||
H: h,
|
||||
TouchingCells: NewRingBuffer(512),
|
||||
tags: []string{},
|
||||
ignoreList: map[*Object]bool{},
|
||||
}
|
||||
|
||||
if len(tags) > 0 {
|
||||
@@ -34,6 +48,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()...)
|
||||
@@ -59,7 +85,7 @@ func (obj *Object) Update() {
|
||||
|
||||
space := obj.Space
|
||||
|
||||
obj.Space.Remove(obj)
|
||||
obj.Space.RemoveSingle(obj)
|
||||
|
||||
obj.Space = space
|
||||
|
||||
@@ -73,7 +99,7 @@ func (obj *Object) Update() {
|
||||
|
||||
if c != nil {
|
||||
c.register(obj)
|
||||
obj.TouchingCells = append(obj.TouchingCells, c)
|
||||
obj.TouchingCells.Put(c)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -154,17 +180,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,13 +249,12 @@ 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 {
|
||||
@@ -253,63 +283,36 @@ func (obj *Object) Check(dx, dy float64, tags ...string) *Collision {
|
||||
|
||||
if c := obj.Space.Cell(x, y); c != nil {
|
||||
|
||||
for _, o := range c.Objects {
|
||||
|
||||
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 len(cc.Objects) == 0 {
|
||||
return nil
|
||||
if 0 >= cc.Objects.Cnt {
|
||||
return false
|
||||
}
|
||||
|
||||
/*
|
||||
// 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,4 +1,4 @@
|
||||
package battle
|
||||
package resolv
|
||||
|
||||
const (
|
||||
RING_BUFF_CONSECUTIVE_SET = int32(0)
|
||||
@@ -18,11 +18,26 @@ type RingBuffer struct {
|
||||
|
||||
func NewRingBuffer(n int32) *RingBuffer {
|
||||
return &RingBuffer{
|
||||
Ed: 0,
|
||||
St: 0,
|
||||
N: n,
|
||||
Cnt: 0,
|
||||
Eles: make([]interface{}, n),
|
||||
Ed: 0,
|
||||
St: 0,
|
||||
EdFrameId: 0,
|
||||
StFrameId: 0,
|
||||
N: n,
|
||||
Cnt: 0,
|
||||
Eles: make([]interface{}, n),
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,3 +137,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
|
||||
}
|
@@ -38,8 +38,9 @@ func (line *Line) Project(axis Vector) Vector {
|
||||
}
|
||||
|
||||
func (line *Line) Normal() Vector {
|
||||
v := line.Vector()
|
||||
return Vector{v[1], -v[0]}.Unit()
|
||||
dy := line.End[1] - line.Start[1]
|
||||
dx := line.End[0] - line.Start[0]
|
||||
return Vector{dy, -dx}.Unit()
|
||||
}
|
||||
|
||||
func (line *Line) Vector() Vector {
|
||||
@@ -124,7 +125,7 @@ func (line *Line) IntersectionPointsCircle(circle *Circle) []Vector {
|
||||
}
|
||||
|
||||
type ConvexPolygon struct {
|
||||
Points []Vector
|
||||
Points *RingBuffer
|
||||
X, Y float64
|
||||
Closed bool
|
||||
}
|
||||
@@ -134,67 +135,89 @@ 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{
|
||||
Points: NewRingBuffer(6), // I don't expected more points to be coped with in this particular game
|
||||
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 +226,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
|
||||
}
|
||||
@@ -275,12 +299,14 @@ func (cp *ConvexPolygon) Center() Vector {
|
||||
|
||||
pos := Vector{0, 0}
|
||||
|
||||
for _, v := range cp.Transformed() {
|
||||
vertices := cp.Transformed()
|
||||
for _, v := range vertices {
|
||||
pos.Add(v)
|
||||
}
|
||||
|
||||
pos[0] /= float64(len(cp.Transformed()))
|
||||
pos[1] /= float64(len(cp.Transformed()))
|
||||
denom := float64(len(vertices))
|
||||
pos[0] /= denom
|
||||
pos[1] /= denom
|
||||
|
||||
return pos
|
||||
|
||||
@@ -305,10 +331,10 @@ func (cp *ConvexPolygon) Project(axis Vector) Projection {
|
||||
|
||||
// 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
|
||||
|
||||
@@ -550,42 +576,6 @@ func (cp *ConvexPolygon) ContainedBy(otherShape Shape) bool {
|
||||
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 {
|
||||
|
@@ -30,7 +30,20 @@ func NewSpace(spaceWidth, spaceHeight, cellWidth, cellHeight int) *Space {
|
||||
|
||||
}
|
||||
|
||||
// [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 +63,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 +84,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
|
||||
|
||||
}
|
||||
@@ -80,16 +105,14 @@ func (sp *Space) Objects() []*Object {
|
||||
for cy := range sp.Cells {
|
||||
|
||||
for cx := range sp.Cells[cy] {
|
||||
|
||||
for _, o := range sp.Cells[cy][cx].Objects {
|
||||
|
||||
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,19 +123,13 @@ 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
|
||||
@@ -137,25 +154,23 @@ func (sp *Space) CheckCells(x, y, w, h int, tags ...string) *Object {
|
||||
cell := sp.Cell(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 +193,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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user