Compare commits

...

84 Commits

Author SHA1 Message Date
genxium
618531f5c6 Minor fix. 2023-02-18 13:21:34 +08:00
genxium
8345d55e76 Updated collision constant setup. 2023-02-18 11:46:01 +08:00
genxium
a48e2f3cc0 Fixed bullet cloning. 2023-02-18 11:13:33 +08:00
yflu
83419a6f23 Fixed debug boundary drawing. 2023-02-17 22:44:21 +08:00
genxium
b19549b0a8 Enhanced inplace rdf update. 2023-02-17 18:54:51 +08:00
genxium
60866674b5 Fixed multiplayer mode for new dynamics calculation. 2023-02-17 15:38:37 +08:00
genxium
a4941c1273 Minor fix. 2023-02-17 14:35:42 +08:00
genxium
2b304eaa75 A temp dirty commit having mysterious left moving players. 2023-02-17 12:26:07 +08:00
genxium
c6b98855af Reduced externalization cost. 2023-02-16 22:23:12 +08:00
genxium
4e0928cb1b Drafted inplace memory version of game dynamics. 2023-02-16 17:13:09 +08:00
genxium
fb42533f55 Temp update to remove use of MakeFullWrapper for reducing redundant CPU time in profiling. 2023-02-16 10:14:37 +08:00
genxium
9dff989e02 Drafted inplace_ring_buff. 2023-02-15 21:34:27 +08:00
genxium
5b7f35b874 Applied ringbuff to resolv_tailored for reducing memory usage. 2023-02-15 15:38:20 +08:00
genxium
2d179d0cdf Minor update. 2023-02-14 23:27:21 +08:00
genxium
f4b303eb91 Started preparation of in-place clone utilities in jsexport. 2023-02-14 15:38:21 +08:00
genxium
5d92b339f6 Enhanced logging efficiency. 2023-02-13 15:37:13 +08:00
genxium
642adff919 Minor fix. 2023-02-13 11:52:47 +08:00
genxium
62c51b1838 Enhanced pre-battle start GUI. 2023-02-13 10:34:56 +08:00
genxium
2751569e0c Fixed data path for character select. 2023-02-12 23:04:20 +08:00
genxium
d623916b3c Minor fix. 2023-02-12 22:10:42 +08:00
genxium
efd070a11b Merge branch 'main' of github.com:genxium/DelayNoMore 2023-02-12 18:46:13 +08:00
genxium
d111de0a7a Drafted character selection. 2023-02-12 18:45:57 +08:00
yflu
c2fa251e69 Updated documentation. 2023-02-12 12:10:20 +08:00
genxium
de16e8e8de Simplified bullet handling. 2023-02-11 12:08:01 +08:00
genxium
365177a3af Renamed CPP files. 2023-02-10 11:38:21 +08:00
genxium
b79e2dc935 Enhanced UDP message callback handling. 2023-02-09 23:34:00 +08:00
genxium
7b0c807496 Drafted enhancement of UDP message callback. 2023-02-09 10:18:23 +08:00
genxium
5c611b626d Minor fix. 2023-02-08 19:28:40 +08:00
genxium
38149279bd Drafted hp handling. 2023-02-08 18:32:59 +08:00
genxium
a762c563d9 Updated skill sets and animations. 2023-02-07 11:31:59 +08:00
genxium
6a0d729dee Added more animation resources. 2023-02-06 12:02:44 +08:00
genxium
f10389bf55 Minor update. 2023-02-05 20:24:09 +08:00
genxium
6b3d1ed49a Enhanced UI handling upon battle started. 2023-02-05 18:44:37 +08:00
genxium
d25bb5ff10 Enhanced inertia handling. 2023-02-05 11:27:26 +08:00
genxium
d38d4b4ec9 Updated README. 2023-02-05 00:16:09 +08:00
genxium
03828db6ff Reverted input scale. 2023-02-04 21:41:58 +08:00
genxium
917fca2bcd Improved on wall dynamics. 2023-02-04 18:33:49 +08:00
genxium
680e4f1f59 Updated README. 2023-02-03 22:56:58 +08:00
genxium
f367609276 Fixes for rollback on UDP peer upsync. 2023-02-03 22:06:03 +08:00
genxium
70ae4a4c92 Fixed socket binding compatibility issue for Android and Windows. 2023-02-03 09:51:56 +08:00
genxium
6f561bea87 Fixed build for Android. 2023-02-03 00:21:07 +08:00
genxium
70a86c27b0 Enhancement for libuv thread safety. 2023-02-02 21:53:43 +08:00
genxium
b0f37d2237 Minor update. 2023-02-01 23:41:08 +08:00
genxium
09376b827d Reverted magic constants. 2023-02-01 23:27:27 +08:00
genxium
d560392c79 Minor fix. 2023-02-01 23:17:55 +08:00
genxium
c75f642011 Minor update again. 2023-02-01 22:55:27 +08:00
genxium
5cfcac6cf6 Minor update. 2023-02-01 21:44:33 +08:00
genxium
d37ebd4c33 Updated README. 2023-02-01 18:59:14 +08:00
genxium
1d138b17c3 Fixes for UDP p2p packets handling in frontend input buffer. 2023-02-01 17:43:15 +08:00
genxium
851678e2f3 Minor fix. 2023-02-01 13:09:17 +08:00
genxium
2fb6fd6bea Updated CameraTracker. 2023-01-31 23:11:46 +08:00
genxium
e3440a2a06 Fixes for UDP use in input prediction. 2023-01-31 22:39:21 +08:00
genxium
8de2d6e4e7 Enhancement for type#1 force-confirmation trigger. 2023-01-31 09:57:37 +08:00
genxium
ba2dd0b22e Added thread-safety comments for libuv codes. 2023-01-30 23:41:22 +08:00
genxium
754610d31b Updated documentation. 2023-01-30 09:56:19 +08:00
genxium
a35de9b83c Enhanced backend udp tunnel workflow. 2023-01-30 09:21:06 +08:00
genxium
2b6cb57050 Enabled backend udp tunnel forwarding. 2023-01-30 00:25:11 +08:00
genxium
677e76179c Minor fix. 2023-01-29 22:38:12 +08:00
genxium
c65c122f45 Added front-to-back UDP channel punching. 2023-01-29 18:20:48 +08:00
genxium
b5530b352b Updated pb files. 2023-01-29 13:10:19 +08:00
genxium
4e638fb2ec Preparing in battle udp tunnel on backend. 2023-01-29 12:42:48 +08:00
genxium
7c454130db Minor fix. 2023-01-28 23:50:32 +08:00
genxium
5863f88435 Minor update. 2023-01-28 12:55:22 +08:00
genxium
bbf07fe518 Fixed Android segfault on mysterious udp reception callback. 2023-01-28 10:52:58 +08:00
genxium
26660d75d2 Minor fix. 2023-01-28 00:13:30 +08:00
genxium
76cdbc8f1f Updated trouble shooting doc. 2023-01-28 00:10:10 +08:00
genxium
4097a8da75 Fixed libuv multithreading. 2023-01-27 22:51:34 +08:00
genxium
e7bf6ec16b Added basic input upsync via udp. 2023-01-26 21:34:29 +08:00
genxium
7ab983949c Fixes in udp_session type usage for cross-platform use. 2023-01-26 12:43:38 +08:00
genxium
2028f8277d Fixed Android building setup. 2023-01-26 00:08:46 +08:00
genxium
60bb74169e Fixed libuv resource deallocation. 2023-01-25 23:47:54 +08:00
genxium
8536521136 Integrated basic holepunching for multiplayer codebase. 2023-01-25 18:26:13 +08:00
genxium
5df545e168 Fixes for frontend hole punching compatibility. 2023-01-25 11:57:59 +08:00
genxium
6bc3feab58 Fixed part of Cpp to Js callback scopes. 2023-01-24 21:30:58 +08:00
genxium
e21e1b840f Drafted udp holepunching upsync pathway. 2023-01-24 12:00:49 +08:00
genxium
ef345e0e48 Fine tune for rollback params. 2023-01-24 10:08:34 +08:00
genxium
0168e2182e Further updates on win32 build templates. 2023-01-23 23:18:21 +08:00
genxium
58b06f6a10 Updated win32 build templates. 2023-01-23 12:22:34 +08:00
genxium
58e60a789f Preparation for native app build. 2023-01-22 23:05:32 +08:00
genxium
b5b43bb596 Minor update. 2023-01-22 14:32:32 +08:00
genxium
59767c1ed5 Added network doctor stats. 2023-01-22 11:39:27 +08:00
genxium
1c6ad5c8f8 Minor fix. 2023-01-21 23:03:55 +08:00
genxium
34e0893eb8 Added network doctor for frontend. 2023-01-21 22:53:41 +08:00
genxium
cc7524becd Updated README. 2023-01-21 11:59:01 +08:00
123 changed files with 18164 additions and 4936 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
.vs
**/.vs
battle_srv/test_cases/test_cases
battle_srv/test_cases/tests
*.pid

View File

@@ -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.
![UDP_secondary_session](./charts/UDPEssentials.jpg)
![UDP_secondary_session](./charts/UDPEssentials.jpg)
# 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.

View File

@@ -1,21 +1,20 @@
# Preface
This project is a demo for a websocket-based rollback netcode inspired by [GGPO](https://github.com/pond3r/ggpo/blob/master/doc/README.md). 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) -- not necessarily correct but that's indeed a question to face :)
This project is a demo for a websocket-based rollback netcode inspired by [GGPO](https://github.com/pond3r/ggpo/blob/master/doc/README.md).
The following video is recorded over INTERNET using an input delay of 4 frames and it feels SMOOTH when playing! Please also checkout these demo videos
- [source video of the first gif](https://pan.baidu.com/s/1ML6hNupaPHPJRd5rcTvQvw?pwd=8ruc)
- [phone v.s. PC over internet battle#1](https://pan.baidu.com/s/1NuGxuMwrV_jalcToyUZPLg?pwd=kfkr)
- [phone v.s. PC over internet battle#2](https://pan.baidu.com/s/1kMiFdwDHyJpZJ0GGU1Y3eA?pwd=46gd)
- [PC Wifi viewing Phone 4g](https://pan.baidu.com/s/1PJEtC9iB_fcabMWhbx2oAg?pwd=tp7k)
- [PC Wifi viewing Phone Wifi (over internet of course)](https://pan.baidu.com/s/108rvC1CcUdiQeMauXWsLJg?pwd=mn39)
[Demo recorded over INTERNET (Phone-Wifi v.s. PC-Wifi UDP holepunched) using an input delay of 6 frames](https://pan.baidu.com/s/1UArwqDShLoPjYppjjqsTqQ?pwd=10wc), and it feels SMOOTH when playing!
to see how this demo carries out a full 60fps synchronization with the help of _batched input upsync/downsync_ for satisfying network I/O performance.
![Merged_cut_annotated_spedup](./charts/Merged_cut_annotated_spedup.gif)
![gif_demo_1](./charts/internet_fireball_explosion_wallmove_spedup.gif)
(battle between 2 celluar 4G users using Android phones, [original video here](https://pan.baidu.com/s/1RL-9M-cK8cFS_Q8afMTrJA?pwd=ryzv))
![gif_demo_2](./charts/jump_sync_spedup.gif)
![Phone4g_battle_spedup](./charts/Phone4g_battle_spedup.gif)
As lots of feedbacks ask for a discussion on using UDP instead, I tried to summarize my personal opinion about it in [ConcerningEdgeCases](./ConcerningEdgeCases.md) -- **since v0.9.25, the project is actually equipped with UDP capabilities as follows**.
- When using the so called `native apps` on `Android` and `Windows` (I'm working casually hard to support `iOS` next), the frontends will try to use UDP hole-punching w/ the help of backend as a registry. If UDP hole-punching is working, the rollback is often less than `turn-around frames to recover` and thus not noticeable, being much better than using websocket alone. This video shows how the UDP holepunched p2p performs for [Phone-Wifi v.s. PC-Wifi (viewed by PC side)](https://pan.baidu.com/s/1K6704bJKlrSBTVqGcXhajA?pwd=l7ok).
- If UDP hole-punching is not working, e.g. for Symmetric NAT like in 4G/5G cellular network, the frontends will use backend as a UDP tunnel (or relay, whatever you like to call it). This video shows how the UDP tunnel performs for [Phone-4G v.s. PC-Wifi (merged view@v0.9.34, excellent synchronization)](https://pan.baidu.com/s/1yeIrN5TSf6_av_8-N3vdVg?pwd=7tzw).
- 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.
All gifs are sped up to ~1.5x for file size reduction, kindly note that animations are resumed from a partial progress!
# Notable Features
- Backend dynamics toggle via [Room.BackendDynamicsEnabled](https://github.com/genxium/DelayNoMore/blob/v0.9.14/battle_srv/models/room.go#L786)
@@ -27,7 +26,7 @@ _(how input delay roughly works)_
![input_delay_intro](./charts/InputDelayIntro.jpg)
_(how rollback-and-chase in this project roughly works, kindly note that by the current implementation, each frontend only maintains a `lastAllConfirmedInputFrameId` for all the other peers, because the backend only downsyncs all-confirmed inputFrames, see [markConfirmationIfApplicable](https://github.com/genxium/DelayNoMore/blob/v0.9.14/battle_srv/models/room.go#L1085) for more information -- if a serverless peer-to-peer communication is seriously needed here, consider porting [markConfirmationIfApplicable](https://github.com/genxium/DelayNoMore/blob/v0.9.14/battle_srv/models/room.go#L1085) into frontend for maintaining `lastAllConfirmedInputFrameId` under chaotic reception order of inputFrames from peers)_
_(how rollback-and-chase in this project roughly works)_
![server_clients](./charts/ServerClients.jpg)
![rollback_and_chase_intro](./charts/RollbackAndChase.jpg)
@@ -111,3 +110,58 @@ Moreover, in practice I found that to spot sync anomalies, the following tools a
- Detection of [prediction mismatch on the frontend](https://github.com/genxium/DelayNoMore/blob/v0.9.19/frontend/assets/scripts/Map.js#L842).
- Detection of [type#1 forceConfirmation on the backend](https://github.com/genxium/DelayNoMore/blob/v0.9.19/battle_srv/models/room.go#L1246).
- Detection of [type#2 forceConfirmation on the backend](https://github.com/genxium/DelayNoMore/blob/v0.9.19/battle_srv/models/room.go#L1259).
There's also some useful information displayed on the frontend when `true == Map.showNetworkDoctorInfo`.
![networkstats](./charts/networkstats.png)
### 2.3 WIN32 platform tool versioning
![visual_studio](./charts/VisualStudioSetup.png)
When building for native platforms, it's much more convenient to trigger the CocosCreator project forming frmo CLI, e.g.
```
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
CocosCreator doesn't have perfect file cache management during native project building, use "Developer Tools > Reload" to reset the IDE status upon mysterious errors.
![ccc_reload](./charts/NativeBuildReload.png)
Another issue is with the package name, see the screenshot below, kindly note that after successfully building with a blank package name, you can then re-fill the desired package name and build again!
![ccc_package_name](./charts/PackageNameIssueInCcc.png)
### 2.5 Checking UDP port binding result
__*nix__
```
netstat -anp | grep <your_port>
```
__Windows__
```
netstat -ano | grep <your_port>
```
### 2.6 Checking native code crash on non-rooted Android phone
```
DeveloperOs> adb bugreport ./logs.zip
# The file "logs.zip" will be automatically pulled to current folder of the DeveloperOS, copy "logs/FS/data/tomestones" out of the zip, then use the binary "$NDK_ROOT/ndk-stack" to analyze whichever tombstone you're interested in, for example, I often use the following
DeveloperOs> ${NDK_ROOT}/ndk-stack.cmd -sym \path\to\DelayNoMore\frontend\build\jsb-link\frameworks\runtime-src\proj.android-studio\app\build\intermediates\ndkBuild\debug\obj\local\arm64-v8a -dump \path\to\tombstones\tombstone_03
# The param "-sym \path\to\objs" tells "ndk-stack" to decode "tombstone_03" with symbols provided by all the files inside that "\path\to\objs".
```

View File

@@ -37,6 +37,8 @@ type mysqlConf struct {
type sioConf struct {
HostAndPort string `json:"hostAndPort"`
UdpHost string `json:"udpHost"`
UdpPort int `json:"udpPort"`
}
type botServerConf struct {

View File

@@ -1,3 +1,5 @@
{
"hostAndPort": "0.0.0.0:9992"
"hostAndPort": "0.0.0.0:9992",
"udpHost": "0.0.0.0",
"udpPort": 3000
}

View File

@@ -23,6 +23,8 @@ import (
"github.com/gin-gonic/gin"
"github.com/robfig/cron"
"go.uber.org/zap"
"net"
)
func main() {
@@ -34,7 +36,7 @@ func main() {
env_tools.MergeTestPlayerAccounts()
}
models.InitRoomHeapManager()
startScheduler()
// startScheduler()
router := gin.Default()
setRouter(router)
@@ -54,6 +56,7 @@ func main() {
}
Logger.Info("Listening and serving HTTP on", zap.Any("Conf.Sio.HostAndPort", Conf.Sio.HostAndPort))
}()
go startGrandUdpServer()
var gracefulStop = make(chan os.Signal)
signal.Notify(gracefulStop, syscall.SIGTERM)
signal.Notify(gracefulStop, syscall.SIGINT)
@@ -114,3 +117,33 @@ func startScheduler() {
//c.AddFunc("*/1 * * * * *", FuncName)
c.Start()
}
func startGrandUdpServer() {
conn, err := net.ListenUDP("udp", &net.UDPAddr{
Port: Conf.Sio.UdpPort,
IP: net.ParseIP(Conf.Sio.UdpHost),
})
if err != nil {
panic(err)
}
defer func() {
conn.Close()
if r := recover(); r != nil {
Logger.Error("`GrandUdpServer`, recovery spot#1, recovered from: ", zap.Any("panic", r))
}
Logger.Info(fmt.Sprintf("The `GrandUdpServer` is stopped"))
}()
Logger.Info(fmt.Sprintf("`GrandUdpServer` started at %s", conn.LocalAddr().String()))
for {
message := make([]byte, 2046)
rlen, remote, err := conn.ReadFromUDP(message[:])
if err != nil {
panic(err)
}
Logger.Info(fmt.Sprintf("`GrandUdpServer` received: %d bytes from %s\n", rlen, remote))
ws.HandleUdpHolePunchingForPlayer(message[0:rlen], remote)
}
}

View File

@@ -22,34 +22,36 @@ func toPbRoomDownsyncFrame(rdf *battle.RoomDownsyncFrame) *pb.RoomDownsyncFrame
for i, last := range rdf.PlayersArr {
pbPlayer := &pb.PlayerDownsync{
Id: last.Id,
VirtualGridX: last.VirtualGridX,
VirtualGridY: last.VirtualGridY,
DirX: last.DirX,
DirY: last.DirY,
VelX: last.VelX,
VelY: last.VelY,
FramesToRecover: last.FramesToRecover,
FramesInChState: last.FramesInChState,
ActiveSkillId: last.ActiveSkillId,
ActiveSkillHit: last.ActiveSkillHit,
FramesInvinsible: last.FramesInvinsible,
Speed: last.Speed,
BattleState: last.BattleState,
CharacterState: last.CharacterState,
InAir: last.InAir,
OnWall: last.OnWall,
OnWallNormX: last.OnWallNormX,
OnWallNormY: last.OnWallNormY,
CapturedByInertia: last.CapturedByInertia,
JoinIndex: last.JoinIndex,
BulletTeamId: last.BulletTeamId,
ChCollisionTeamId: last.ChCollisionTeamId,
Hp: last.Hp,
MaxHp: last.MaxHp,
ColliderRadius: last.ColliderRadius,
Score: last.Score,
Removed: last.Removed,
Id: last.Id,
VirtualGridX: last.VirtualGridX,
VirtualGridY: last.VirtualGridY,
DirX: last.DirX,
DirY: last.DirY,
VelX: last.VelX,
VelY: last.VelY,
FramesToRecover: last.FramesToRecover,
FramesInChState: last.FramesInChState,
ActiveSkillId: last.ActiveSkillId,
ActiveSkillHit: last.ActiveSkillHit,
FramesInvinsible: last.FramesInvinsible,
Speed: last.Speed,
BattleState: last.BattleState,
CharacterState: last.CharacterState,
InAir: last.InAir,
OnWall: last.OnWall,
OnWallNormX: last.OnWallNormX,
OnWallNormY: last.OnWallNormY,
CapturedByInertia: last.CapturedByInertia,
JoinIndex: last.JoinIndex,
BulletTeamId: last.BulletTeamId,
ChCollisionTeamId: last.ChCollisionTeamId,
Hp: last.Hp,
MaxHp: last.MaxHp,
RevivalVirtualGridX: last.RevivalVirtualGridX,
RevivalVirtualGridY: last.RevivalVirtualGridY,
ColliderRadius: last.ColliderRadius,
Score: last.Score,
Removed: last.Removed,
}
ret.PlayersArr[i] = pbPlayer
}
@@ -147,32 +149,36 @@ func toPbPlayers(modelInstances map[int32]*Player, withMetaInfo bool) []*pb.Play
for _, last := range modelInstances {
pbPlayer := &pb.PlayerDownsync{
Id: last.Id,
VirtualGridX: last.VirtualGridX,
VirtualGridY: last.VirtualGridY,
DirX: last.DirX,
DirY: last.DirY,
VelX: last.VelX,
VelY: last.VelY,
FramesToRecover: last.FramesToRecover,
FramesInChState: last.FramesInChState,
ActiveSkillId: last.ActiveSkillId,
ActiveSkillHit: last.ActiveSkillHit,
FramesInvinsible: last.FramesInvinsible,
Speed: last.Speed,
BattleState: last.BattleState,
CharacterState: last.CharacterState,
InAir: last.InAir,
OnWall: last.OnWall,
OnWallNormX: last.OnWallNormX,
OnWallNormY: last.OnWallNormY,
CapturedByInertia: last.CapturedByInertia,
JoinIndex: last.JoinIndex,
BulletTeamId: last.BulletTeamId,
ChCollisionTeamId: last.ChCollisionTeamId,
ColliderRadius: last.ColliderRadius,
Score: last.Score,
Removed: last.Removed,
Id: last.Id,
VirtualGridX: last.VirtualGridX,
VirtualGridY: last.VirtualGridY,
DirX: last.DirX,
DirY: last.DirY,
VelX: last.VelX,
VelY: last.VelY,
FramesToRecover: last.FramesToRecover,
FramesInChState: last.FramesInChState,
ActiveSkillId: last.ActiveSkillId,
ActiveSkillHit: last.ActiveSkillHit,
FramesInvinsible: last.FramesInvinsible,
Speed: last.Speed,
BattleState: last.BattleState,
CharacterState: last.CharacterState,
InAir: last.InAir,
OnWall: last.OnWall,
OnWallNormX: last.OnWallNormX,
OnWallNormY: last.OnWallNormY,
CapturedByInertia: last.CapturedByInertia,
JoinIndex: last.JoinIndex,
BulletTeamId: last.BulletTeamId,
ChCollisionTeamId: last.ChCollisionTeamId,
Hp: last.Hp,
MaxHp: last.MaxHp,
RevivalVirtualGridX: last.RevivalVirtualGridX,
RevivalVirtualGridY: last.RevivalVirtualGridY,
ColliderRadius: last.ColliderRadius,
Score: last.Score,
Removed: last.Removed,
}
if withMetaInfo {
pbPlayer.Name = last.Name
@@ -193,34 +199,36 @@ func toJsPlayers(modelInstances map[int32]*Player) []*battle.PlayerDownsync {
for _, last := range modelInstances {
toRet[last.JoinIndex-1] = &battle.PlayerDownsync{
Id: last.Id,
VirtualGridX: last.VirtualGridX,
VirtualGridY: last.VirtualGridY,
DirX: last.DirX,
DirY: last.DirY,
VelX: last.VelX,
VelY: last.VelY,
FramesToRecover: last.FramesToRecover,
FramesInChState: last.FramesInChState,
ActiveSkillId: last.ActiveSkillId,
ActiveSkillHit: last.ActiveSkillHit,
FramesInvinsible: last.FramesInvinsible,
Speed: last.Speed,
BattleState: last.BattleState,
CharacterState: last.CharacterState,
JoinIndex: last.JoinIndex,
BulletTeamId: last.BulletTeamId,
ChCollisionTeamId: last.ChCollisionTeamId,
Hp: last.Hp,
MaxHp: last.MaxHp,
ColliderRadius: last.ColliderRadius,
InAir: last.InAir,
OnWall: last.OnWall,
OnWallNormX: last.OnWallNormX,
OnWallNormY: last.OnWallNormY,
CapturedByInertia: last.CapturedByInertia,
Score: last.Score,
Removed: last.Removed,
Id: last.Id,
VirtualGridX: last.VirtualGridX,
VirtualGridY: last.VirtualGridY,
DirX: last.DirX,
DirY: last.DirY,
VelX: last.VelX,
VelY: last.VelY,
FramesToRecover: last.FramesToRecover,
FramesInChState: last.FramesInChState,
ActiveSkillId: last.ActiveSkillId,
ActiveSkillHit: last.ActiveSkillHit,
FramesInvinsible: last.FramesInvinsible,
Speed: last.Speed,
BattleState: last.BattleState,
CharacterState: last.CharacterState,
JoinIndex: last.JoinIndex,
BulletTeamId: last.BulletTeamId,
ChCollisionTeamId: last.ChCollisionTeamId,
Hp: last.Hp,
MaxHp: last.MaxHp,
RevivalVirtualGridX: last.RevivalVirtualGridX,
RevivalVirtualGridY: last.RevivalVirtualGridY,
ColliderRadius: last.ColliderRadius,
InAir: last.InAir,
OnWall: last.OnWall,
OnWallNormX: last.OnWallNormX,
OnWallNormY: last.OnWallNormY,
CapturedByInertia: last.CapturedByInertia,
Score: last.Score,
Removed: last.Removed,
}
}

View File

@@ -8,6 +8,7 @@ import (
sq "github.com/Masterminds/squirrel"
"github.com/jmoiron/sqlx"
"go.uber.org/zap"
"net"
)
type PlayerBattleState struct {
@@ -46,10 +47,15 @@ type Player struct {
TutorialStage int `db:"tutorial_stage"`
// other in-battle info fields
LastReceivedInputFrameId int32
LastSentInputFrameId int32
AckingFrameId int32
AckingInputFrameId int32
LastReceivedInputFrameId int32
LastUdpReceivedInputFrameId int32
LastSentInputFrameId int32
AckingFrameId int32
AckingInputFrameId int32
UdpAddr *PeerUdpAddr
BattleUdpTunnelAddr *net.UDPAddr // This addr is used by backend only, not visible to frontend
BattleUdpTunnelAuthKey int32
}
func ExistPlayerByName(name string) (bool, error) {

View File

@@ -13,6 +13,7 @@ import (
"io/ioutil"
"jsexport/battle"
"math/rand"
"net"
"os"
"path/filepath"
"resolv"
@@ -32,6 +33,7 @@ const (
DOWNSYNC_MSG_ACT_BATTLE_STOPPED = int32(3)
DOWNSYNC_MSG_ACT_FORCED_RESYNC = int32(4)
DOWNSYNC_MSG_ACT_PEER_INPUT_BATCH = int32(5)
DOWNSYNC_MSG_ACT_PEER_UDP_ADDR = int32(6)
DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START = int32(-1)
DOWNSYNC_MSG_ACT_BATTLE_START = int32(0)
@@ -133,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]
RenderFrameBuffer *battle.RingBuffer // Indices are STRICTLY consecutive
InputsBuffer *resolv.RingBuffer // Indices are STRICTLY consecutive
InputsBufferLock sync.Mutex // Guards [InputsBuffer, LatestPlayerUpsyncedInputFrameId, LastAllConfirmedInputFrameId, LastAllConfirmedInputList, LastAllConfirmedInputFrameIdWithChange, LastIndividuallyConfirmedInputList, player.LastReceivedInputFrameId, player.LastUdpReceivedInputFrameId]
RenderFrameBuffer *resolv.RingBuffer // Indices are STRICTLY consecutive
LatestPlayerUpsyncedInputFrameId int32
LastAllConfirmedInputFrameId int32
LastAllConfirmedInputFrameIdWithChange int32
@@ -156,13 +158,23 @@ type Room struct {
rdfIdToActuallyUsedInput map[int32]*pb.InputFrameDownsync
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) bool {
playerId := pPlayerFromDbInit.Id
// TODO: Any thread-safety concern for accessing "pR" here?
if RoomBattleStateIns.IDLE != pR.State && RoomBattleStateIns.WAITING != pR.State {
@@ -174,12 +186,16 @@ func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, session *websocke
return false
}
defer pR.onPlayerAdded(playerId)
defer pR.onPlayerAdded(playerId, speciesId)
pPlayerFromDbInit.UdpAddr = nil
pPlayerFromDbInit.BattleUdpTunnelAddr = nil
pPlayerFromDbInit.BattleUdpTunnelAuthKey = rand.Int31()
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.BattleState = PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK
pPlayerFromDbInit.ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded
@@ -215,9 +231,13 @@ func (pR *Room) ReAddPlayerIfPossible(pTmpPlayerInstance *Player, session *webso
*/
defer pR.onPlayerReAdded(playerId)
pEffectiveInRoomPlayerInstance := pR.Players[playerId]
pEffectiveInRoomPlayerInstance.UdpAddr = nil
pEffectiveInRoomPlayerInstance.BattleUdpTunnelAddr = nil
pEffectiveInRoomPlayerInstance.BattleUdpTunnelAuthKey = rand.Int31()
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!
pEffectiveInRoomPlayerInstance.BattleState = PlayerBattleStateIns.READDED_PENDING_BATTLE_COLLIDER_ACK
pEffectiveInRoomPlayerInstance.ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded
@@ -315,8 +335,8 @@ func (pR *Room) InputsBufferString(allDetails bool) string {
// Appending of the array of strings can be very SLOW due to on-demand heap allocation! Use this printing with caution.
s := make([]string, 0)
s = append(s, fmt.Sprintf("{renderFrameId: %v, stInputFrameId: %v, edInputFrameId: %v, lastAllConfirmedInputFrameIdWithChange: %v, lastAllConfirmedInputFrameId: %v}", pR.RenderFrameId, pR.InputsBuffer.StFrameId, pR.InputsBuffer.EdFrameId, pR.LastAllConfirmedInputFrameIdWithChange, pR.LastAllConfirmedInputFrameId))
for playerId, player := range pR.PlayersArr {
s = append(s, fmt.Sprintf("{playerId: %v, ackingFrameId: %v, ackingInputFrameId: %v, lastSentInputFrameId: %v}", playerId, player.AckingFrameId, player.AckingInputFrameId, player.LastSentInputFrameId))
for _, player := range pR.PlayersArr {
s = append(s, fmt.Sprintf("{playerId: %v, ackingFrameId: %v, ackingInputFrameId: %v, lastSentInputFrameId: %v}", player.Id, player.AckingFrameId, player.AckingInputFrameId, player.LastSentInputFrameId))
}
for i := pR.InputsBuffer.StFrameId; i < pR.InputsBuffer.EdFrameId; i++ {
tmp := pR.InputsBuffer.GetByFrameId(i)
@@ -402,24 +422,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"
@@ -562,7 +572,7 @@ func (pR *Room) StartBattle() {
})
}
func (pR *Room) OnBattleCmdReceived(pReq *pb.WsReq) {
func (pR *Room) OnBattleCmdReceived(pReq *pb.WsReq, fromUDP bool) {
/*
[WARNING] This function "OnBattleCmdReceived" could be called by different ws sessions and thus from different threads!
@@ -607,20 +617,21 @@ func (pR *Room) OnBattleCmdReceived(pReq *pb.WsReq) {
//Logger.Debug(fmt.Sprintf("OnBattleCmdReceived-InputsBufferLock unlocked: roomId=%v, fromPlayerId=%v", pR.Id, playerId))
}()
inputsBufferSnapshot := pR.markConfirmationIfApplicable(inputFrameUpsyncBatch, playerId, player)
inputsBufferSnapshot := pR.markConfirmationIfApplicable(inputFrameUpsyncBatch, playerId, player, fromUDP)
if nil != inputsBufferSnapshot {
pR.downsyncToAllPlayers(inputsBufferSnapshot)
} else {
// no new all-confirmed
toSendInputFrameDownsyncs := pR.cloneInputsBuffer(inputFrameUpsyncBatch[0].InputFrameId, inputFrameUpsyncBatch[len(inputFrameUpsyncBatch)-1].InputFrameId+1)
} /*else {
// FIXME: Enable this block after we can proactively detect whether there's any "secondary ws session player" in the battle to avoid waste of resource in creating the snapshot
// no new all-confirmed
toSendInputFrameDownsyncs := pR.cloneInputsBuffer(inputFrameUpsyncBatch[0].InputFrameId, inputFrameUpsyncBatch[len(inputFrameUpsyncBatch)-1].InputFrameId+1)
inputsBufferSnapshot = &pb.InputsBufferSnapshot{
ToSendInputFrameDownsyncs: toSendInputFrameDownsyncs,
PeerJoinIndex: player.JoinIndex,
}
//Logger.Info(fmt.Sprintf("OnBattleCmdReceived no new all-confirmed: roomId=%v, fromPlayerId=%v, forming peer broadcasting snapshot=%v", pR.Id, playerId, inputsBufferSnapshot))
pR.broadcastPeerUpsyncForBetterPrediction(inputsBufferSnapshot)
}
inputsBufferSnapshot = &pb.InputsBufferSnapshot{
ToSendInputFrameDownsyncs: toSendInputFrameDownsyncs,
PeerJoinIndex: player.JoinIndex,
}
//Logger.Info(fmt.Sprintf("OnBattleCmdReceived no new all-confirmed: roomId=%v, fromPlayerId=%v, forming peer broadcasting snapshot=%v", pR.Id, playerId, inputsBufferSnapshot))
pR.broadcastPeerUpsyncForBetterPrediction(inputsBufferSnapshot)
}*/
}
func (pR *Room) onInputFrameDownsyncAllConfirmed(inputFrameDownsync *battle.InputFrameDownsync, playerId int32) {
@@ -662,6 +673,10 @@ func (pR *Room) StopBattleForSettlement() {
if RoomBattleStateIns.IN_BATTLE != pR.State {
return
}
pR.BattleUdpTunnelLock.Lock()
pR.BattleUdpTunnel.Close()
pR.BattleUdpTunnelLock.Unlock()
pR.State = RoomBattleStateIns.STOPPING_BATTLE_FOR_SETTLEMENT
Logger.Info("Stopping the `battleMainLoop` for:", zap.Any("roomId", pR.Id))
pR.RenderFrameId++
@@ -762,7 +777,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)
@@ -785,9 +800,9 @@ 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.LastIndividuallyConfirmedInputList = make([]uint64, pR.Capacity)
@@ -798,7 +813,25 @@ func (pR *Room) OnDismissed() {
pR.RenderFrameId = 0
pR.CurDynamicsRenderFrameId = 0
pR.NstDelayFrames = 16
pR.NstDelayFrames = 24
pR.collisionHolder = resolv.NewCollision()
pR.effPushbacks = make([]*battle.Vec2D, pR.Capacity)
for i := 0; i < len(pR.effPushbacks); 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
@@ -806,20 +839,26 @@ func (pR *Room) OnDismissed() {
dilutedServerFps := float64(58.0) // Don't set this value too small, otherwise we might miss force confirmation needs for slow tickers!
pR.dilutedRollbackEstimatedDtNanos = int64(float64(pR.RollbackEstimatedDtNanos) * float64(serverFps) / dilutedServerFps)
pR.BattleDurationFrames = int32(60 * serverFps)
//pR.BattleDurationFrames = int32(20 * serverFps)
pR.BattleDurationNanos = int64(pR.BattleDurationFrames) * (pR.RollbackEstimatedDtNanos + 1)
pR.InputFrameUpsyncDelayTolerance = battle.ConvertToNoDelayInputFrameId(pR.NstDelayFrames) - 1 // this value should be strictly smaller than (NstDelayFrames >> InputScaleFrames), otherwise "type#1 forceConfirmation" might become a lag avalanche
pR.MaxChasingRenderFramesPerUpdate = 4 // Don't set this value too high to avoid exhausting frontend CPU within a single frame
pR.MaxChasingRenderFramesPerUpdate = 9 // Don't set this value too high to avoid exhausting frontend CPU within a single frame, roughly as the "turn-around frames to recover" is empirically OK
pR.BackendDynamicsEnabled = true // [WARNING] When "false", recovery upon reconnection wouldn't work!
pR.ForceAllResyncOnAnyActiveSlowTicker = true // See tradeoff discussion in "downsyncToAllPlayers"
pR.FrameDataLoggingEnabled = false // [WARNING] DON'T ENABLE ON LONG BATTLE DURATION! It consumes A LOT OF MEMORY!
pR.BattleUdpTunnelLock.Lock()
pR.BattleUdpTunnel = nil
pR.BattleUdpTunnelAddr = nil
pR.BattleUdpTunnelLock.Unlock()
pR.ChooseStage()
pR.EffectivePlayerCount = 0
// [WARNING] It's deliberately ordered such that "pR.State = RoomBattleStateIns.IDLE" is put AFTER all the refreshing operations above.
pR.State = RoomBattleStateIns.IDLE
go pR.startBattleUdpTunnel() // Would reassign "pR.BattleUdpTunnel"
pR.updateScore()
Logger.Info("The room is completely dismissed(all playerDownsyncChan closed):", zap.Any("roomId", pR.Id))
@@ -929,7 +968,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 {
@@ -941,8 +980,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".
@@ -955,7 +995,10 @@ func (pR *Room) onPlayerAdded(playerId int32) {
if nil == playerPos {
panic(fmt.Sprintf("onPlayerAdded error, nil == playerPos, roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v", pR.Id, playerId, pR.State, pR.EffectivePlayerCount))
}
pR.Players[playerId].VirtualGridX, pR.Players[playerId].VirtualGridY = battle.WorldToVirtualGridPos(playerPos.X, playerPos.Y)
pR.Players[playerId].RevivalVirtualGridX, pR.Players[playerId].RevivalVirtualGridY = battle.WorldToVirtualGridPos(playerPos.X, playerPos.Y)
pR.Players[playerId].VirtualGridX, pR.Players[playerId].VirtualGridY = pR.Players[playerId].RevivalVirtualGridX, pR.Players[playerId].RevivalVirtualGridY
pR.Players[playerId].MaxHp = 100 // Hardcoded for now
pR.Players[playerId].Hp = pR.Players[playerId].MaxHp
// Hardcoded initial character orientation/facing
if 0 == (pR.Players[playerId].JoinIndex % 2) {
pR.Players[playerId].DirX = -2
@@ -1068,7 +1111,9 @@ func (pR *Room) sendSafely(roomDownsyncFrame *pb.RoomDownsyncFrame, toSendInputF
panic(fmt.Sprintf("Error marshaling downsync message: roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v", pR.Id, playerId, pR.State, pR.EffectivePlayerCount))
}
if MAGIC_JOIN_INDEX_DEFAULT == peerJoinIndex {
shouldUseSecondaryWsSession := (MAGIC_JOIN_INDEX_DEFAULT != peerJoinIndex && DOWNSYNC_MSG_ACT_INPUT_BATCH == act) // FIXME: Simplify the condition
//Logger.Info(fmt.Sprintf("shouldUseSecondaryWsSession=%v: roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v", shouldUseSecondaryWsSession, pR.Id, playerId, pR.State, pR.EffectivePlayerCount))
if !shouldUseSecondaryWsSession {
if playerDownsyncSession, existent := pR.PlayerDownsyncSessionDict[playerId]; existent {
if err := playerDownsyncSession.WriteMessage(websocket.BinaryMessage, theBytes); nil != err {
panic(fmt.Sprintf("Error sending primary downsync message: roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v, err=%v", pR.Id, playerId, pR.State, pR.EffectivePlayerCount, err))
@@ -1135,7 +1180,7 @@ func (pR *Room) getOrPrefabInputFrameDownsync(inputFrameId int32) *battle.InputF
return currInputFrameDownsync
}
func (pR *Room) markConfirmationIfApplicable(inputFrameUpsyncBatch []*pb.InputFrameUpsync, playerId int32, player *Player) *pb.InputsBufferSnapshot {
func (pR *Room) markConfirmationIfApplicable(inputFrameUpsyncBatch []*pb.InputFrameUpsync, playerId int32, player *Player, fromUDP bool) *pb.InputsBufferSnapshot {
// [WARNING] This function MUST BE called while "pR.InputsBufferLock" is locked!
// Step#1, put the received "inputFrameUpsyncBatch" into "pR.InputsBuffer"
for _, inputFrameUpsync := range inputFrameUpsyncBatch {
@@ -1146,6 +1191,7 @@ func (pR *Room) markConfirmationIfApplicable(inputFrameUpsyncBatch []*pb.InputFr
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)))
continue
}
@@ -1158,11 +1204,25 @@ func (pR *Room) markConfirmationIfApplicable(inputFrameUpsyncBatch []*pb.InputFr
targetInputFrameDownsync.InputList[player.JoinIndex-1] = inputFrameUpsync.Encoded
targetInputFrameDownsync.ConfirmedList |= uint64(1 << uint32(player.JoinIndex-1))
player.LastReceivedInputFrameId = clientInputFrameId
pR.LastIndividuallyConfirmedInputList[player.JoinIndex-1] = inputFrameUpsync.Encoded
if false == fromUDP {
/*
[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"!
if clientInputFrameId > pR.LatestPlayerUpsyncedInputFrameId {
pR.LatestPlayerUpsyncedInputFrameId = clientInputFrameId
Moreover, only ws session upsyncs should advance "player.LastReceivedInputFrameId" & "pR.LatestPlayerUpsyncedInputFrameId".
Kindly note that the updates of "player.LastReceivedInputFrameId" could be discrete before and after reconnection.
*/
player.LastReceivedInputFrameId = 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
// 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
}
}
@@ -1229,6 +1289,7 @@ func (pR *Room) forceConfirmationIfApplicable(prevRenderFrameId int32) uint64 {
totPlayerCnt := uint32(pR.Capacity)
allConfirmedMask := uint64((1 << totPlayerCnt) - 1)
unconfirmedMask := uint64(0)
// As "pR.LastAllConfirmedInputFrameId" can be advanced by UDP but "pR.LatestPlayerUpsyncedInputFrameId" could only be advanced by ws session, when the following condition is met we know that the slow ticker is really in trouble!
if pR.LatestPlayerUpsyncedInputFrameId > (pR.LastAllConfirmedInputFrameId + pR.InputFrameUpsyncDelayTolerance + 1) {
// Type#1 check whether there's a significantly slow ticker among players
oldLastAllConfirmedInputFrameId := pR.LastAllConfirmedInputFrameId
@@ -1318,8 +1379,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.CurDynamicsRenderFrameId++
}
}
@@ -1350,13 +1410,13 @@ func (pR *Room) printBarrier(barrierCollider *resolv.Object) {
}
func (pR *Room) doBattleMainLoopPerTickBackendDynamicsWithProperLocking(prevRenderFrameId int32, pDynamicsDuration *int64) {
Logger.Debug(fmt.Sprintf("doBattleMainLoopPerTickBackendDynamicsWithProperLocking-InputsBufferLock to about lock: roomId=%v", pR.Id))
//Logger.Debug(fmt.Sprintf("doBattleMainLoopPerTickBackendDynamicsWithProperLocking-InputsBufferLock to about lock: roomId=%v", pR.Id))
pR.InputsBufferLock.Lock()
Logger.Debug(fmt.Sprintf("doBattleMainLoopPerTickBackendDynamicsWithProperLocking-InputsBufferLock locked: roomId=%v", pR.Id))
//Logger.Debug(fmt.Sprintf("doBattleMainLoopPerTickBackendDynamicsWithProperLocking-InputsBufferLock locked: roomId=%v", pR.Id))
defer func() {
pR.InputsBufferLock.Unlock()
Logger.Debug(fmt.Sprintf("doBattleMainLoopPerTickBackendDynamicsWithProperLocking-InputsBufferLock unlocked: roomId=%v", pR.Id))
//Logger.Debug(fmt.Sprintf("doBattleMainLoopPerTickBackendDynamicsWithProperLocking-InputsBufferLock unlocked: roomId=%v", pR.Id))
}()
if ok, thatRenderFrameId := battle.ShouldPrefabInputFrameDownsync(prevRenderFrameId, pR.RenderFrameId); ok {
@@ -1607,3 +1667,144 @@ func (pR *Room) SetSecondarySession(playerId int32, session *websocket.Conn, sig
}
}
}
func (pR *Room) UpdatePeerUdpAddrList(playerId int32, peerAddr *net.UDPAddr, pReq *pb.HolePunchUpsync) {
// TODO: There's a chance that by now "player.JoinIndex" is not yet determined, use a lock to sync
if player, ok := pR.Players[playerId]; ok && MAGIC_JOIN_INDEX_DEFAULT != player.JoinIndex {
playerBattleState := atomic.LoadInt32(&(player.BattleState))
switch playerBattleState {
case PlayerBattleStateIns.DISCONNECTED, PlayerBattleStateIns.LOST, PlayerBattleStateIns.EXPELLED_DURING_GAME, PlayerBattleStateIns.EXPELLED_IN_DISMISSAL:
// Kindly note that "PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK, PlayerBattleStateIns.READDED_PENDING_BATTLE_COLLIDER_ACK" are allowed
return
}
if _, existent := pR.PlayerDownsyncSessionDict[playerId]; existent {
player.UdpAddr = &pb.PeerUdpAddr{
Ip: peerAddr.IP.String(),
Port: int32(peerAddr.Port),
AuthKey: pReq.AuthKey,
}
Logger.Info(fmt.Sprintf("UpdatePeerUdpAddrList done for roomId=%v, playerId=%d, peerAddr=%s", pR.Id, playerId, peerAddr))
peerJoinIndex := player.JoinIndex
peerUdpAddrList := make([]*pb.PeerUdpAddr, pR.Capacity, pR.Capacity)
for _, otherPlayer := range pR.Players {
if MAGIC_JOIN_INDEX_DEFAULT == otherPlayer.JoinIndex {
// TODO: Again this shouldn't happen, apply proper locking
continue
}
// In case of highly concurrent update that might occur while later marshalling, use the ptr of a copy
peerUdpAddrList[otherPlayer.JoinIndex-1] = &pb.PeerUdpAddr{
Ip: otherPlayer.UdpAddr.Ip,
Port: otherPlayer.UdpAddr.Port,
AuthKey: otherPlayer.UdpAddr.AuthKey,
}
}
// Broadcast this new UDP addr to all the existing players
for otherPlayerId, otherPlayer := range pR.Players {
otherPlayerBattleState := atomic.LoadInt32(&(otherPlayer.BattleState))
switch otherPlayerBattleState {
case PlayerBattleStateIns.DISCONNECTED, PlayerBattleStateIns.LOST, PlayerBattleStateIns.EXPELLED_DURING_GAME, PlayerBattleStateIns.EXPELLED_IN_DISMISSAL:
continue
}
Logger.Info(fmt.Sprintf("Downsyncing peerUdpAddrList for roomId=%v, playerId=%d", pR.Id, otherPlayerId))
pR.sendSafely(&pb.RoomDownsyncFrame{
PeerUdpAddrList: peerUdpAddrList,
}, nil, DOWNSYNC_MSG_ACT_PEER_UDP_ADDR, otherPlayerId, false, peerJoinIndex)
}
}
}
}
func (pR *Room) startBattleUdpTunnel() {
defer func() {
if r := recover(); r != nil {
Logger.Error("`BattleUdpTunnel` recovery spot#1, recovered from: ", zap.Any("roomId", pR.Id), zap.Any("panic", r))
}
Logger.Info(fmt.Sprintf("`BattleUdpTunnel` stopped for (roomId=%d)@renderFrameId=%v", pR.Id, pR.RenderFrameId))
}()
pR.BattleUdpTunnelLock.Lock()
conn, err := net.ListenUDP("udp", &net.UDPAddr{
Port: 0,
IP: net.ParseIP(Conf.Sio.UdpHost),
})
if nil != err {
// No need to close the "conn" upon error here
pR.BattleUdpTunnelLock.Unlock()
panic(err)
}
pR.BattleUdpTunnel = conn
switch v := conn.LocalAddr().(type) {
case (*net.UDPAddr):
pR.BattleUdpTunnelAddr = &pb.PeerUdpAddr{
Ip: Conf.Sio.UdpHost,
Port: int32(v.Port),
AuthKey: 0, // To be determined for each specific player upon joining and sent to it by BattleColliderInfo
}
}
pR.BattleUdpTunnelLock.Unlock()
defer func() {
if r := recover(); r != nil {
Logger.Warn("`BattleUdpTunnel` recovery spot#2, recovered from: ", zap.Any("roomId", pR.Id), zap.Any("panic", r))
}
Logger.Info(fmt.Sprintf("`BattleUdpTunnel` closed for (roomId=%d)@renderFrameId=%v", pR.Id, pR.RenderFrameId))
}()
Logger.Info(fmt.Sprintf("`BattleUdpTunnel` started for roomId=%d at %s", pR.Id, conn.LocalAddr().String()))
for {
message := make([]byte, 128)
rlen, remote, err := conn.ReadFromUDP(message[:]) // Would be unblocked when "conn.Close()" is called from another thread/goroutine, reference https://pkg.go.dev/net@go1.18.6#PacketConn
if nil != err {
// Should proceed to close the "conn" upon error here, if "conn" is already closed it'd just throw another error to be catched by "spot#2"
conn.Close()
panic(err)
}
pReq := new(pb.WsReq)
bytes := message[0:rlen]
if unmarshalErr := proto.Unmarshal(bytes, pReq); nil != unmarshalErr {
Logger.Warn(fmt.Sprintf("`BattleUdpTunnel` for roomId=%d failed to unmarshal %d bytes", pR.Id, rlen), zap.Error(unmarshalErr))
continue
}
playerId := pReq.PlayerId
//Logger.Info(fmt.Sprintf("`BattleUdpTunnel` for roomId=%d received decoded WsReq:", pR.Id), zap.Any("pReq", pReq))
if player, exists1 := pR.Players[playerId]; exists1 {
authKey := pReq.AuthKey
if authKey != player.BattleUdpTunnelAuthKey {
Logger.Warn(fmt.Sprintf("`BattleUdpTunnel` for roomId=%d received %d bytes for playerId=%d from %s, but (incomingAuthKey:%d != playerBattleUdpTunnelAuthKey:%d)\n", pR.Id, rlen, playerId, remote, authKey, player.BattleUdpTunnelAuthKey))
continue
}
if _, existent := pR.PlayerDownsyncSessionDict[playerId]; existent {
player.BattleUdpTunnelAddr = remote
//Logger.Info(fmt.Sprintf("`BattleUdpTunnel` for roomId=%d updated battleUdpAddr for playerId=%d to be %s\n", pR.Id, playerId, remote))
nowBattleState := atomic.LoadInt32(&pR.State)
if RoomBattleStateIns.IN_BATTLE == nowBattleState {
batch := pReq.InputFrameUpsyncBatch
if nil != batch && 0 < len(batch) {
peerJoinIndex := pReq.JoinIndex
// Broadcast to every other player in the same room/battle
for _, otherPlayer := range pR.PlayersArr {
if otherPlayer.JoinIndex == peerJoinIndex {
continue
}
_, wrerr := conn.WriteTo(bytes, otherPlayer.BattleUdpTunnelAddr)
if nil != wrerr {
//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
}
}
} else {
Logger.Warn(fmt.Sprintf("`BattleUdpTunnel` for roomId=%d received validated %d bytes for playerId=%d from %s, but primary downsync session for it doesn't exist\n", pR.Id, rlen, playerId, remote))
}
} else {
Logger.Warn(fmt.Sprintf("`BattleUdpTunnel` for roomId=%d received invalid %d bytes for playerId=%d from %s, but it doesn't belong to this room!\n", pR.Id, rlen, playerId, remote))
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,7 @@ import (
"github.com/golang/protobuf/proto"
"github.com/gorilla/websocket"
"go.uber.org/zap"
"net"
"net/http"
"strconv"
"sync/atomic"
@@ -49,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 {
@@ -194,7 +205,7 @@ func Serve(c *gin.Context) {
if pRoom.ReAddPlayerIfPossible(pPlayer, conn, signalToCloseConnOfThisPlayer) {
playerSuccessfullyAddedToRoom = true
} else if pRoom.AddPlayerIfPossible(pPlayer, conn, signalToCloseConnOfThisPlayer) {
} else if pRoom.AddPlayerIfPossible(pPlayer, speciesId, conn, signalToCloseConnOfThisPlayer) {
playerSuccessfullyAddedToRoom = true
} else {
Logger.Warn("Failed to get:\n", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Any("forExpectedRoomId", expectedRoomId))
@@ -218,7 +229,7 @@ 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)
res := pRoom.AddPlayerIfPossible(pPlayer, speciesId, conn, signalToCloseConnOfThisPlayer)
if !res {
signalToCloseConnOfThisPlayer(Constants.RetCode.PlayerNotAddableToRoom, fmt.Sprintf("AddPlayerIfPossible returns false for roomId == %v, playerId == %v!", pRoom.Id, playerId))
}
@@ -256,17 +267,25 @@ func Serve(c *gin.Context) {
SpaceOffsetX: pRoom.SpaceOffsetX,
SpaceOffsetY: pRoom.SpaceOffsetY,
RenderCacheSize: pRoom.RenderCacheSize,
CollisionMinStep: pRoom.CollisionMinStep,
RenderCacheSize: pRoom.RenderCacheSize,
CollisionMinStep: pRoom.CollisionMinStep,
BoundRoomCapacity: int32(pRoom.Capacity),
BattleUdpTunnel: &pb.PeerUdpAddr{
Ip: pRoom.BattleUdpTunnelAddr.Ip,
Port: pRoom.BattleUdpTunnelAddr.Port,
AuthKey: pThePlayer.BattleUdpTunnelAuthKey,
},
FrameDataLoggingEnabled: pRoom.FrameDataLoggingEnabled,
}
resp := &pb.WsResp{
Ret: int32(Constants.RetCode.Ok),
EchoedMsgId: int32(0),
Act: models.DOWNSYNC_MSG_ACT_HB_REQ,
BciFrame: bciFrame,
Ret: int32(Constants.RetCode.Ok),
EchoedMsgId: int32(0),
Act: models.DOWNSYNC_MSG_ACT_HB_REQ,
BciFrame: bciFrame,
PeerJoinIndex: pThePlayer.JoinIndex,
}
Logger.Debug("Sending downsync HeartbeatRequirements:", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Any("resp", resp))
@@ -379,7 +398,7 @@ func Serve(c *gin.Context) {
startOrFeedHeartbeatWatchdog(conn)
case models.UPSYNC_MSG_ACT_PLAYER_CMD:
startOrFeedHeartbeatWatchdog(conn)
pRoom.OnBattleCmdReceived(pReq)
pRoom.OnBattleCmdReceived(pReq, false)
case models.UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK:
res := pRoom.OnPlayerBattleColliderAcked(int32(playerId))
if false == res {
@@ -432,12 +451,12 @@ func HandleSecondaryWsSessionForPlayer(c *gin.Context) {
playerId, err := models.GetPlayerIdByToken(token)
if err != nil || playerId == 0 {
// TODO: Abort with specific message.
Logger.Warn("Secondary ws session playerLogin record not found for ws authentication:", zap.Any("intAuthToken", token))
Logger.Warn("Secondary ws session playerLogin record not found:", zap.Any("intAuthToken", token))
c.AbortWithStatus(http.StatusBadRequest)
return
}
Logger.Info("Secondary ws session playerLogin record has been found for ws authentication:", zap.Any("playerId", playerId), zap.Any("intAuthToken", token), zap.Any("boundRoomId", boundRoomId))
Logger.Info("Secondary ws session playerLogin record has been found:", zap.Any("playerId", playerId), zap.Any("intAuthToken", token), zap.Any("boundRoomId", boundRoomId))
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
@@ -482,3 +501,32 @@ func HandleSecondaryWsSessionForPlayer(c *gin.Context) {
pRoom.SetSecondarySession(int32(playerId), conn, signalToCloseConnOfThisPlayer)
}
func HandleUdpHolePunchingForPlayer(message []byte, peerAddr *net.UDPAddr) {
pReq := new(pb.HolePunchUpsync)
if unmarshalErr := proto.Unmarshal(message, pReq); nil != unmarshalErr {
Logger.Error("`GrandUdpServer` failed to unmarshal", zap.Error(unmarshalErr))
return
}
token := pReq.IntAuthToken
boundRoomId := pReq.BoundRoomId
pRoom, existent := (*models.RoomMapManagerIns)[int32(boundRoomId)]
// Deliberately querying playerId after querying room, because the former is against persistent storage and could be slow!
if !existent {
Logger.Warn("`GrandUdpServer` failed to get:\n", zap.Any("intAuthToken", token), zap.Any("forBoundRoomId", boundRoomId))
return
}
// TODO: Wrap the following 2 stmts by sql transaction!
playerId, err := models.GetPlayerIdByToken(token)
if err != nil || playerId == 0 {
// TODO: Abort with specific message.
Logger.Warn("`GrandUdpServer` playerLogin record not found for:", zap.Any("intAuthToken", token))
return
}
Logger.Info("`GrandUdpServer` playerLogin record has been found:", zap.Any("playerId", playerId), zap.Any("intAuthToken", token), zap.Any("boundRoomId", boundRoomId), zap.Any("peerAddr", peerAddr))
pRoom.UpdatePeerUdpAddrList(int32(playerId), peerAddr, pReq)
}

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 417 KiB

After

Width:  |  Height:  |  Size: 472 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 MiB

BIN
charts/networkstats.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

View File

@@ -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)
}

View File

@@ -1,66 +0,0 @@
function NetworkDoctor(serverFps, clientUpsyncFps) {
this.serverFps = serverFps;
this.clientUpsyncFps = clientUpsyncFps;
this.millisPerServerFrame = parseInt(1000 / this.serverFps);
this._tooLongSinceLastFrameDiffReceivedThreshold = (this.millisPerServerFrame << 6);
this.setupFps = function(fps) {
this.serverFps = this.clientUpsyncFps = fps;
this.millisPerServerFrame = parseInt(1000 / this.serverFps);
this._tooLongSinceLastFrameDiffReceivedThreshold = (this.millisPerServerFrame << 6);
}
this._lastFrameDiffRecvTime = null;
this._tooLongSinceLastFrameDiffReceived = function() {
if (undefined === this._lastFrameDiffRecvTime || null === this._lastFrameDiffRecvTime) return false;
return (this._tooLongSinceLastFrameDiffReceivedThreshold <= (Date.now() - this._lastFrameDiffRecvTime));
};
this._consecutiveALittleLongFrameDiffReceivedIntervalCount = 0;
this._consecutiveALittleLongFrameDiffReceivedIntervalCountThreshold = 120;
this.onNewFrameDiffReceived = function(frameDiff) {
var now = Date.now();
if (undefined !== this._lastFrameDiffRecvTime && null !== this._lastFrameDiffRecvTime) {
var intervalFromLastFrameDiff = (now - this._lastFrameDiffRecvTime);
if ((this.millisPerServerFrame << 5) < intervalFromLastFrameDiff) {
++this._consecutiveALittleLongFrameDiffReceivedIntervalCount;
console.log('Medium delay, intervalFromLastFrameDiff is', intervalFromLastFrameDiff);
} else {
this._consecutiveALittleLongFrameDiffReceivedIntervalCount = 0;
}
}
this._lastFrameDiffRecvTime = now;
};
this._networkComplaintPrefix = "\nNetwork is not good >_<\n";
this.generateNetworkComplaint = function(excludeTypeConstantALittleLongFrameDiffReceivedInterval, excludeTypeTooLongSinceLastFrameDiffReceived) {
if (this.hasBattleStopped) return null;
var shouldComplain = false;
var ret = this._networkComplaintPrefix;
if (true != excludeTypeConstantALittleLongFrameDiffReceivedInterval && this._consecutiveALittleLongFrameDiffReceivedIntervalCountThreshold <= this._consecutiveALittleLongFrameDiffReceivedIntervalCount) {
this._consecutiveALittleLongFrameDiffReceivedIntervalCount = 0;
ret += "\nConstantly having a little long recv interval.\n";
shouldComplain = true;
}
if (true != excludeTypeTooLongSinceLastFrameDiffReceived && this._tooLongSinceLastFrameDiffReceived()) {
ret += "\nToo long since last received frameDiff.\n";
shouldComplain = true;
}
return (shouldComplain ? ret : null);
};
this.hasBattleStopped = false;
this.onBattleStopped = function() {
this.hasBattleStopped = true;
};
this.isClientSessionConnected = function() {
if (!window.game) return false;
if (!window.game.clientSession) return false;
return window.game.clientSession.connected;
};
}
window.NetworkDoctor = NetworkDoctor;

View File

@@ -198,3 +198,6 @@ window.getOrCreateAnimationClipForGid = function(gid, tiledMapInfo, tilesElListU
animationClip: animClip,
};
};
// Node.js, this is a workaround to avoid accessing the non-existent "TextDecoder class" from "jsexport.js".
window.fs = function() {};

File diff suppressed because one or more lines are too long

View File

@@ -3,7 +3,7 @@
"_name": "Fireball1Explosion",
"_objFlags": 0,
"_native": "",
"_duration": 0.26666666666666666,
"_duration": 0.5,
"sample": 60,
"speed": 1,
"wrapMode": 1,
@@ -30,19 +30,19 @@
}
},
{
"frame": 0.15,
"frame": 0.16666666666666666,
"value": {
"__uuid__": "8b566f26-b34d-4da6-bdaa-078358a5b685"
}
},
{
"frame": 0.2,
"frame": 0.31666666666666665,
"value": {
"__uuid__": "6ec5f75d-307e-4292-b667-cbbb5a52c2f6"
}
},
{
"frame": 0.25,
"frame": 0.48333333333333334,
"value": {
"__uuid__": "d89977f1-d927-4a08-9591-9feb1daf68c8"
}

View File

@@ -0,0 +1,85 @@
{
"__type__": "cc.AnimationClip",
"_name": "Fireball2Explosion",
"_objFlags": 0,
"_native": "",
"_duration": 0.5,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "a1979f05-3ecc-4d70-9ea9-7822e35602c3"
}
},
{
"frame": 0.05,
"value": {
"__uuid__": "850884ca-2e6a-4d04-94d9-fd929ac33942"
}
},
{
"frame": 0.08333333333333333,
"value": {
"__uuid__": "88b9c254-1fd8-451f-902e-4a43a7ef5d51"
}
},
{
"frame": 0.13333333333333333,
"value": {
"__uuid__": "566342a3-cfde-44c9-afbe-7d9469653ccb"
}
},
{
"frame": 0.18333333333333332,
"value": {
"__uuid__": "d1620a98-de62-4069-8910-122f361d22a4"
}
},
{
"frame": 0.23333333333333334,
"value": {
"__uuid__": "2e9ed070-e592-4e77-8fa1-c5250deb006b"
}
},
{
"frame": 0.2833333333333333,
"value": {
"__uuid__": "a3e8357d-39da-42e8-b26b-f5ae7a68aed7"
}
},
{
"frame": 0.3333333333333333,
"value": {
"__uuid__": "3f3cb45c-732d-4bea-89b4-5495fb0d2c37"
}
},
{
"frame": 0.38333333333333336,
"value": {
"__uuid__": "d7aeb01a-4e04-4037-a2c4-ba72f45f69f3"
}
},
{
"frame": 0.43333333333333335,
"value": {
"__uuid__": "fe4a97a0-1207-4b81-a541-c2da0bf0a6f3"
}
},
{
"frame": 0.48333333333333334,
"value": {
"__uuid__": "97014ab9-8bdd-4b71-9f61-0639327f9159"
}
}
]
}
}
},
"events": []
}

View File

@@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "14b92f5c-af81-416a-a162-e5822d20fe68",
"subMetas": {}
}

View File

@@ -0,0 +1,79 @@
{
"__type__": "cc.AnimationClip",
"_name": "Fireball3Explosion",
"_objFlags": 0,
"_native": "",
"_duration": 0.5,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "0e003318-f8c2-40f7-b144-140b5ca1e46a"
}
},
{
"frame": 0.06666666666666667,
"value": {
"__uuid__": "9c2b0cc2-9a52-4052-b796-cd6c6bd940d4"
}
},
{
"frame": 0.11666666666666667,
"value": {
"__uuid__": "76fe0c09-d2d6-432d-bacb-20d297eb4966"
}
},
{
"frame": 0.2,
"value": {
"__uuid__": "0735a7ff-0e50-472a-b0f9-8e2cc97be7e7"
}
},
{
"frame": 0.2833333333333333,
"value": {
"__uuid__": "993199a8-54a9-40d1-8d2f-12bf16af934c"
}
},
{
"frame": 0.35,
"value": {
"__uuid__": "5d8d9ffc-b4d6-4518-9946-953929ec055c"
}
},
{
"frame": 0.38333333333333336,
"value": {
"__uuid__": "6501ae08-b0ff-43ad-b5c5-cb6dc67f989d"
}
},
{
"frame": 0.4166666666666667,
"value": {
"__uuid__": "616cfa00-1dba-4a71-8141-36774933b6a9"
}
},
{
"frame": 0.45,
"value": {
"__uuid__": "4b296e86-2e96-4276-b6de-6a6b22530344"
}
},
{
"frame": 0.48333333333333334,
"value": {
"__uuid__": "f9cc8e37-c9c2-4f20-9d7e-4533c4e859fe"
}
}
]
}
}
},
"events": []
}

View File

@@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "0dbb90ed-a08a-448c-b06e-4831260e9213",
"subMetas": {}
}

View File

@@ -0,0 +1,97 @@
{
"__type__": "cc.AnimationClip",
"_name": "Fireball2",
"_objFlags": 0,
"_native": "",
"_duration": 0.21666666666666667,
"sample": 60,
"speed": 1,
"wrapMode": 2,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "db4c7e6f-9bee-4e7a-8628-d41b8bcaff42"
}
},
{
"frame": 0.016666666666666666,
"value": {
"__uuid__": "42796576-72b3-49c2-8c5a-ea946fbe1525"
}
},
{
"frame": 0.03333333333333333,
"value": {
"__uuid__": "0aa5a52f-a92a-4a4a-b49b-aee2b5a3eb55"
}
},
{
"frame": 0.05,
"value": {
"__uuid__": "0a7b5e41-acdc-4af3-beff-0a42aca9f91a"
}
},
{
"frame": 0.06666666666666667,
"value": {
"__uuid__": "de0b22b7-65ca-455f-bcd1-2ddd6cc114e2"
}
},
{
"frame": 0.08333333333333333,
"value": {
"__uuid__": "e9ce1383-9e3d-4d44-9f80-ab5fa2224138"
}
},
{
"frame": 0.1,
"value": {
"__uuid__": "5b22df7e-414b-44a3-989f-640c5b9417b9"
}
},
{
"frame": 0.11666666666666667,
"value": {
"__uuid__": "f459615c-70a4-421b-b649-a28460332364"
}
},
{
"frame": 0.13333333333333333,
"value": {
"__uuid__": "c2723b9d-fbd8-4524-a0dd-b110451e4e32"
}
},
{
"frame": 0.15,
"value": {
"__uuid__": "4286b3d1-fea2-41fd-8829-7635f546def4"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "4e0d6419-62df-4382-893e-dd7cc47f7770"
}
},
{
"frame": 0.18333333333333332,
"value": {
"__uuid__": "f0cd9259-b323-4fba-ad8b-02d5e56c2cd4"
}
},
{
"frame": 0.2,
"value": {
"__uuid__": "0193b66d-06bb-49f9-b2f5-51fff8b16015"
}
}
]
}
}
},
"events": []
}

View File

@@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "d2c65ac4-a5b3-411e-8d2d-18d3980649d7",
"subMetas": {}
}

View File

@@ -0,0 +1,73 @@
{
"__type__": "cc.AnimationClip",
"_name": "Fireball3",
"_objFlags": 0,
"_native": "",
"_duration": 0.5666666666666667,
"sample": 60,
"speed": 1,
"wrapMode": 2,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "6af65d40-470c-47de-8b3d-f53c3923bf90"
}
},
{
"frame": 0.05,
"value": {
"__uuid__": "ebf64819-79a5-4366-bf70-08f3b1c6114c"
}
},
{
"frame": 0.13333333333333333,
"value": {
"__uuid__": "e03d879b-5227-4c11-a4b9-0a426967d28a"
}
},
{
"frame": 0.2,
"value": {
"__uuid__": "a1aa0c83-4a34-43ae-9a8f-56189808df68"
}
},
{
"frame": 0.26666666666666666,
"value": {
"__uuid__": "3cc28dd0-2518-4162-a39d-4e4b19f9d60b"
}
},
{
"frame": 0.35,
"value": {
"__uuid__": "1b41f500-c55b-4cbf-a040-287b6cc0e958"
}
},
{
"frame": 0.43333333333333335,
"value": {
"__uuid__": "cfa24c51-0ad4-4e3b-b571-c5500002d6e9"
}
},
{
"frame": 0.5,
"value": {
"__uuid__": "d4a46a6a-401c-4694-a192-0a7b3ce6f603"
}
},
{
"frame": 0.55,
"value": {
"__uuid__": "c88c5293-9f21-4a1a-a6b6-649e403dc7a2"
}
}
]
}
}
},
"events": []
}

View File

@@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "6aef5812-d16c-4da1-96a3-a38ac227c823",
"subMetas": {}
}

View File

@@ -0,0 +1,73 @@
{
"__type__": "cc.AnimationClip",
"_name": "Atk4",
"_objFlags": 0,
"_native": "",
"_duration": 1.0166666666666666,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "da597a30-22da-4053-b4ee-1cfa27980a75"
}
},
{
"frame": 0.08333333333333333,
"value": {
"__uuid__": "b3604b4c-426f-4843-bb76-f09a9687950d"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "24b51487-6c91-42d9-bd12-afbbf70f2e4b"
}
},
{
"frame": 0.2833333333333333,
"value": {
"__uuid__": "c318ad71-7a5e-43b0-8098-b7a34a6e6fbe"
}
},
{
"frame": 0.38333333333333336,
"value": {
"__uuid__": "85d6d8d7-81cf-4369-a501-6ad72d70f5a2"
}
},
{
"frame": 0.5,
"value": {
"__uuid__": "42b76eaf-db36-4835-9072-893337c83425"
}
},
{
"frame": 0.6,
"value": {
"__uuid__": "152f23a1-f70f-4db6-bb28-68625aef930f"
}
},
{
"frame": 0.8833333333333333,
"value": {
"__uuid__": "9c907eb5-84ab-4fa9-9404-9085f29706cc"
}
},
{
"frame": 1,
"value": {
"__uuid__": "74f0ffc8-cc25-4fcf-a6d8-bf093daba9ca"
}
}
]
}
}
},
"events": []
}

View File

@@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "2aef91f9-ef47-4bb4-bf43-5441723aa639",
"subMetas": {}
}

View File

@@ -0,0 +1,103 @@
{
"__type__": "cc.AnimationClip",
"_name": "Dying",
"_objFlags": 0,
"_native": "",
"_duration": 0.5333333333333333,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "700f93f9-ef84-4cb2-b759-f39ceac1c1d1"
}
},
{
"frame": 0.016666666666666666,
"value": {
"__uuid__": "5b4ea047-594e-4d0b-8e08-e24117bf1e67"
}
},
{
"frame": 0.05,
"value": {
"__uuid__": "a822576c-d2eb-4c17-8969-03dd1da5a93e"
}
},
{
"frame": 0.1,
"value": {
"__uuid__": "85e92afc-4359-4a8d-bdfa-958a6134cd6a"
}
},
{
"frame": 0.15,
"value": {
"__uuid__": "88d6e560-1b65-4d78-949c-cbc0e67d33cc"
}
},
{
"frame": 0.2,
"value": {
"__uuid__": "9ac16319-c1af-41d1-910b-99cbfd6230b2"
}
},
{
"frame": 0.23333333333333334,
"value": {
"__uuid__": "2a6b168a-458f-4d19-a985-9b00cc6e37e8"
}
},
{
"frame": 0.26666666666666666,
"value": {
"__uuid__": "f8482295-dc0d-4265-be56-0b0a9f6f6b9b"
}
},
{
"frame": 0.31666666666666665,
"value": {
"__uuid__": "0d4a314c-119a-46b9-8dce-dbaacf2523e5"
}
},
{
"frame": 0.36666666666666664,
"value": {
"__uuid__": "18d4ff6c-6b57-461b-8588-03521fafc9d1"
}
},
{
"frame": 0.4,
"value": {
"__uuid__": "f25280f2-442a-4ad7-914e-0f96cbf108f5"
}
},
{
"frame": 0.45,
"value": {
"__uuid__": "ccccb669-d44d-4a5c-89a1-9aba9476ce12"
}
},
{
"frame": 0.48333333333333334,
"value": {
"__uuid__": "4a45c23d-7bc8-4c5e-b761-ac1100b12a09"
}
},
{
"frame": 0.5166666666666667,
"value": {
"__uuid__": "1c4359c5-b303-403d-82ad-8d5e6ae6ec99"
}
}
]
}
}
},
"events": []
}

View File

@@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "ac90c9b8-3b06-4866-89ce-2c953a9d5a9a",
"subMetas": {}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 175 KiB

View File

@@ -0,0 +1,61 @@
{
"__type__": "cc.AnimationClip",
"_name": "Dashing",
"_objFlags": 0,
"_native": "",
"_duration": 0.18333333333333332,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "ec69a078-4153-49e1-9450-656942c2a567"
}
},
{
"frame": 0.016666666666666666,
"value": {
"__uuid__": "bbf23710-9dc6-4bbb-9565-df8848819d07"
}
},
{
"frame": 0.03333333333333333,
"value": {
"__uuid__": "cc7b4103-1d6b-44c1-8e0c-ee1c49052837"
}
},
{
"frame": 0.05,
"value": {
"__uuid__": "90409bfe-7b6c-4eab-953b-ea630585fad4"
}
},
{
"frame": 0.06666666666666667,
"value": {
"__uuid__": "9614dc2a-9bfe-4b85-9aa6-d7d62feec82b"
}
},
{
"frame": 0.15,
"value": {
"__uuid__": "c326e3c0-140f-457b-a086-fe95c025d576"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "8e2d7c5b-452d-44db-b0b4-8ee03b36e7f2"
}
}
]
}
}
},
"events": []
}

View File

@@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "337d57ad-118c-40e2-be90-2aa1505c152b",
"subMetas": {}
}

View File

@@ -0,0 +1,115 @@
{
"__type__": "cc.AnimationClip",
"_name": "Dying",
"_objFlags": 0,
"_native": "",
"_duration": 0.5333333333333333,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "6d1cd049-7a44-4dcb-9018-4f0fbbf3fdf8"
}
},
{
"frame": 0.03333333333333333,
"value": {
"__uuid__": "50245b04-bcb1-4488-951c-49944c1037da"
}
},
{
"frame": 0.06666666666666667,
"value": {
"__uuid__": "7a6721bb-2321-4947-832f-9a317565ea88"
}
},
{
"frame": 0.11666666666666667,
"value": {
"__uuid__": "c3553a29-e04a-42e2-8b46-82aa85706e26"
}
},
{
"frame": 0.15,
"value": {
"__uuid__": "e221838e-740f-45b1-8fd5-80d4ab8563c3"
}
},
{
"frame": 0.2,
"value": {
"__uuid__": "37ebbd1d-9a18-4514-8331-1358a59cab83"
}
},
{
"frame": 0.23333333333333334,
"value": {
"__uuid__": "b4a9ee91-4315-4fb9-9900-6d763406c81d"
}
},
{
"frame": 0.26666666666666666,
"value": {
"__uuid__": "e5388e53-5268-4f54-9a93-f6506db5b77b"
}
},
{
"frame": 0.3,
"value": {
"__uuid__": "078814c3-90e2-4b17-a90b-d2046df9a351"
}
},
{
"frame": 0.3333333333333333,
"value": {
"__uuid__": "c605bf48-9cc5-41f1-8ace-a273298f7b21"
}
},
{
"frame": 0.38333333333333336,
"value": {
"__uuid__": "5b5083ca-8fca-4827-9b76-eaa08685b031"
}
},
{
"frame": 0.4166666666666667,
"value": {
"__uuid__": "b454af6f-9e07-4b34-952b-eca69dc13d5e"
}
},
{
"frame": 0.45,
"value": {
"__uuid__": "af921d09-a72e-4b48-8585-ba72377ba410"
}
},
{
"frame": 0.48333333333333334,
"value": {
"__uuid__": "733d339e-ed74-49ab-8955-641d21528fcc"
}
},
{
"frame": 0.5,
"value": {
"__uuid__": "b7335bea-2985-4331-92c2-08c4c2a5ec86"
}
},
{
"frame": 0.5166666666666667,
"value": {
"__uuid__": "0ae606ea-93c0-4815-9e24-62c5fb59decc"
}
}
]
}
}
},
"events": []
}

View File

@@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "657d4193-2224-44ea-94f7-0305a9f2b322",
"subMetas": {}
}

View File

@@ -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"
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 KiB

After

Width:  |  Height:  |  Size: 314 KiB

View File

@@ -0,0 +1,91 @@
{
"__type__": "cc.AnimationClip",
"_name": "Atk4",
"_objFlags": 0,
"_native": "",
"_duration": 0.5333333333333333,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "a9a10466-1e80-4fb8-9c32-2019ee2c988d"
}
},
{
"frame": 0.05,
"value": {
"__uuid__": "e0e3907f-520c-4c4c-991a-ec554e24f368"
}
},
{
"frame": 0.1,
"value": {
"__uuid__": "e4bec6fe-db19-4cf6-a8cc-bfcc3e892d5e"
}
},
{
"frame": 0.15,
"value": {
"__uuid__": "c36ceda7-2e5d-42f4-ae7b-02064348a1c2"
}
},
{
"frame": 0.18333333333333332,
"value": {
"__uuid__": "07004da9-abd4-4a05-baee-447235dcdf2d"
}
},
{
"frame": 0.23333333333333334,
"value": {
"__uuid__": "dd047451-9715-4e68-9ae5-4e4556007190"
}
},
{
"frame": 0.2833333333333333,
"value": {
"__uuid__": "7b2acb5e-3ee8-4c26-b950-f201346cefde"
}
},
{
"frame": 0.31666666666666665,
"value": {
"__uuid__": "b378b873-fae7-49dd-8581-15136046e2f1"
}
},
{
"frame": 0.36666666666666664,
"value": {
"__uuid__": "845b1de6-648f-422a-8289-98222175b787"
}
},
{
"frame": 0.4166666666666667,
"value": {
"__uuid__": "df09902a-52d8-4dec-9d05-62d3428c4625"
}
},
{
"frame": 0.4666666666666667,
"value": {
"__uuid__": "da55a31c-ce4a-4003-a119-8c76fd6d1a80"
}
},
{
"frame": 0.5166666666666667,
"value": {
"__uuid__": "bd3f63fb-6d6d-47d2-9d96-2b58292fccfa"
}
}
]
}
}
},
"events": []
}

View File

@@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "168df303-4b6a-4376-940c-3d36fa9e98d8",
"subMetas": {}
}

View File

@@ -0,0 +1,55 @@
{
"__type__": "cc.AnimationClip",
"_name": "Dashing",
"_objFlags": 0,
"_native": "",
"_duration": 0.18333333333333332,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "b8c177cf-013e-4936-a031-2d3480cf975b"
}
},
{
"frame": 0.03333333333333333,
"value": {
"__uuid__": "ae2d8041-e7ee-4300-b3d6-3e85b146f33c"
}
},
{
"frame": 0.06666666666666667,
"value": {
"__uuid__": "f0518811-8fc9-4f9c-9ec4-401abdb3917d"
}
},
{
"frame": 0.1,
"value": {
"__uuid__": "3117e445-fe0f-425f-83af-5b719bf8a009"
}
},
{
"frame": 0.13333333333333333,
"value": {
"__uuid__": "f9d00d7d-2143-4893-be61-32cf1490c9f2"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "daff32df-5e22-4d4e-94d2-6e4522a02138"
}
}
]
}
}
},
"events": []
}

View File

@@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "d7b6d7c4-d2b5-49c6-bbcb-d8d80f52ae7e",
"subMetas": {}
}

View File

@@ -0,0 +1,61 @@
{
"__type__": "cc.AnimationClip",
"_name": "Dying",
"_objFlags": 0,
"_native": "",
"_duration": 0.45,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "0b31e6af-6d24-4915-b87b-772c6eb10ca7"
}
},
{
"frame": 0.06666666666666667,
"value": {
"__uuid__": "c597fb09-4621-4d1f-abf9-6484405a6330"
}
},
{
"frame": 0.13333333333333333,
"value": {
"__uuid__": "8c8be852-b65d-41d8-800f-04cbb3cad094"
}
},
{
"frame": 0.2,
"value": {
"__uuid__": "f9522b47-812e-4020-845a-5d9f6d9aca90"
}
},
{
"frame": 0.26666666666666666,
"value": {
"__uuid__": "1ff63b81-49d8-4d68-9526-5f0dc4c88ef0"
}
},
{
"frame": 0.3333333333333333,
"value": {
"__uuid__": "4e96b6fd-2cd1-412b-98a8-7f22040af589"
}
},
{
"frame": 0.43333333333333335,
"value": {
"__uuid__": "a827896b-00b5-4385-9648-2c40414b29c3"
}
}
]
}
}
},
"events": []
}

View File

@@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "86706adc-e079-4997-883b-3e269d223065",
"subMetas": {}
}

View File

@@ -59,6 +59,12 @@
"__uuid__": "0ecf4a0c-0f13-42fa-a214-b4826acd8556"
}
},
{
"frame": 0.3,
"value": {
"__uuid__": "cabf9cb6-99ca-426d-9a23-95cdec6f06b9"
}
},
{
"frame": 0.3333333333333333,
"value": {

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 227 KiB

View File

@@ -0,0 +1,25 @@
{
"__type__": "cc.AnimationClip",
"_name": "OnWall",
"_objFlags": 0,
"_native": "",
"_duration": 0.016666666666666666,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "d5c1e6b4-1048-43e2-96f9-801dc23cf418"
}
}
]
}
}
},
"events": []
}

View File

@@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "57358699-1d1b-44db-898c-df0c3ce9aab0",
"subMetas": {}
}

View File

@@ -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="40" tilewidth="16" tileheight="16" infinite="0" nextlayerid="8" 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="7" name="Ground" width="128" height="40">
<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=
eJzt2k0OgjAQQGECccMOo+5JvIlx484LeP9jaAxNSEOhlMGZOG/xbfyl81rc2FRV1QAAAAAAAAAAAADAD9zxpd1Bs7/2NWjzPAPPa2cGvtfODHyvnRn4XjszKFv7KdOW6zoKvYb+9vpf6W+G5f5zfZeep7/82kM3qf6Hj25C3DhFYwb/pqT/VlKfQ///7z91f+g2fj/9y/tLWbqfp7rvsQ/ob0tue6l9YHEGlvv3Qq+hv77x2tuBdv/SPVC6P7z3v0XCc/HjQZ8p9X5rwgy0W2j1rwfh/L8+Hgm1gNQZlfjsku/2fv7j/uPfgPFjUv010T/d/xH1niLd47KDNfcc+s/3aRdml2Opv/R+ov+6/ufB3Ny2nP+5PbD3+c/Zn/RP95f8zaW/Pdr/u7ZCu4Nm/6dz9Pdtz/5vZSq2hg==
</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>

View File

@@ -37,6 +37,8 @@ message PlayerDownsync {
int32 onWallNormY = 28;
bool capturedByInertia = 29; // like "inAir", its by design a standalone field only inferred by the calc result of "applyInputFrameDownsyncDynamicsOnSingleRenderFrame" instead of "characterState"
int32 revivalVirtualGridX = 30;
int32 revivalVirtualGridY = 31;
string name = 997;
string displayName = 998;
@@ -73,18 +75,9 @@ message WsReq {
int32 joinIndex = 4;
int32 ackingFrameId = 5;
int32 ackingInputFrameId = 6;
repeated InputFrameUpsync inputFrameUpsyncBatch = 7;
HeartbeatUpsync hb = 8;
}
message WsResp {
int32 ret = 1;
int32 echoedMsgId = 2;
int32 act = 3;
RoomDownsyncFrame rdf = 4;
repeated InputFrameDownsync inputFrameDownsyncBatch = 5;
BattleColliderInfo bciFrame = 6;
int32 peerJoinIndex = 7; // Only used when "InputsBufferSnapshot.peerJoinIndex" is used.
int32 authKey = 7;
repeated InputFrameUpsync inputFrameUpsyncBatch = 8;
HeartbeatUpsync hb = 9;
}
message InputsBufferSnapshot {
@@ -92,7 +85,7 @@ message InputsBufferSnapshot {
uint64 unconfirmedMask = 2;
repeated InputFrameDownsync toSendInputFrameDownsyncs = 3;
bool shouldForceResync = 4;
int32 peerJoinIndex = 5; // Only used when "WsResp.peerJoinIndex" is used.
int32 peerJoinIndex = 5;
}
message MeleeBullet {
@@ -175,6 +168,18 @@ message FireballBullet {
int32 speed = 1005;
}
message HolePunchUpsync {
string intAuthToken = 1;
int32 boundRoomId = 2;
int32 authKey = 3;
}
message PeerUdpAddr {
string ip = 1;
int32 port = 2;
int32 authKey = 3;
}
message BattleColliderInfo {
string stageName = 1;
@@ -191,6 +196,8 @@ message BattleColliderInfo {
double spaceOffsetX = 11;
double spaceOffsetY = 12;
int32 collisionMinStep = 13;
int32 boundRoomCapacity = 14;
PeerUdpAddr battleUdpTunnel = 15;
bool frameDataLoggingEnabled = 1024;
}
@@ -207,4 +214,15 @@ message RoomDownsyncFrame {
repeated int32 speciesIdList = 1026;
int32 bulletLocalIdCounter = 1027;
repeated PeerUdpAddr peerUdpAddrList = 1028;
}
message WsResp {
int32 ret = 1;
int32 echoedMsgId = 2;
int32 act = 3;
RoomDownsyncFrame rdf = 4;
repeated InputFrameDownsync inputFrameDownsyncBatch = 5;
BattleColliderInfo bciFrame = 6;
int32 peerJoinIndex = 7;
}

View File

@@ -25,16 +25,19 @@
},
{
"__id__": 8
},
{
"__id__": 22
}
],
"_active": true,
"_components": [
{
"__id__": 22
"__id__": 29
}
],
"_prefab": {
"__id__": 23
"__id__": 30
},
"_opacity": 255,
"_color": {
@@ -324,8 +327,8 @@
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"g": 0,
"b": 0,
"a": 255
},
"_contentSize": {
@@ -483,12 +486,21 @@
{
"__uuid__": "e8247e2a-1b5b-4618-86f8-224b25246b55"
},
null,
null,
null,
{
"__uuid__": "168df303-4b6a-4376-940c-3d36fa9e98d8"
},
null,
{
"__uuid__": "d7b6d7c4-d2b5-49c6-bbcb-d8d80f52ae7e"
},
{
"__uuid__": "57358699-1d1b-44db-898c-df0c3ce9aab0"
},
{
"__uuid__": "6e1139d4-03dd-4bd4-9510-606e94f629fe"
},
{
"__uuid__": "86706adc-e079-4997-883b-3e269d223065"
}
],
"playOnLoad": false,
@@ -653,7 +665,9 @@
{
"__uuid__": "9b500cb0-8048-4715-81db-cc975c914225"
},
null,
{
"__uuid__": "2aef91f9-ef47-4bb4-bf43-5441723aa639"
},
null,
{
"__uuid__": "38b2c892-347b-4009-93f8-65b2ab1614f0"
@@ -663,6 +677,9 @@
},
{
"__uuid__": "e906322d-a08b-4477-a2e9-98acd42fa034"
},
{
"__uuid__": "ac90c9b8-3b06-4866-89ce-2c953a9d5a9a"
}
],
"playOnLoad": false,
@@ -833,10 +850,15 @@
{
"__uuid__": "0abbd156-980e-475e-9994-3c958bd913fc"
},
null,
{
"__uuid__": "337d57ad-118c-40e2-be90-2aa1505c152b"
},
null,
{
"__uuid__": "edd23b2f-1caa-4836-88a7-e4af1f26743e"
},
{
"__uuid__": "657d4193-2224-44ea-94f7-0305a9f2b322"
}
],
"playOnLoad": false,
@@ -892,6 +914,244 @@
"fileId": "7aN7Gcc/tBw5EGlTJVBj2+",
"sync": false
},
{
"__type__": "cc.Node",
"_name": "HpBar",
"_objFlags": 0,
"_parent": {
"__id__": 1
},
"_children": [
{
"__id__": 23
}
],
"_active": true,
"_components": [
{
"__id__": 26
},
{
"__id__": 27
}
],
"_prefab": {
"__id__": 28
},
"_opacity": 255,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_contentSize": {
"__type__": "cc.Size",
"width": 50,
"height": 8
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_trs": {
"__type__": "TypedArray",
"ctor": "Float64Array",
"array": [
0,
42.256,
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": "bar",
"_objFlags": 0,
"_parent": {
"__id__": 22
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 24
}
],
"_prefab": {
"__id__": 25
},
"_opacity": 255,
"_color": {
"__type__": "cc.Color",
"r": 10,
"g": 252,
"b": 0,
"a": 255
},
"_contentSize": {
"__type__": "cc.Size",
"width": 50,
"height": 8
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0.5
},
"_trs": {
"__type__": "TypedArray",
"ctor": "Float64Array",
"array": [
-25,
0,
0,
0,
0,
0,
1,
1,
1,
1
]
},
"_eulerAngles": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_skewX": 0,
"_skewY": 0,
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": ""
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 23
},
"_enabled": true,
"_materials": [
{
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
}
],
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
"_spriteFrame": {
"__uuid__": "67e68bc9-dad5-4ad9-a2d8-7e03d458e32f"
},
"_type": 1,
"_sizeMode": 0,
"_fillType": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_atlas": null,
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__uuid__": "59bff7a2-23e1-4d69-bce7-afb37eae196a"
},
"fileId": "1b5Rz5KABPK5Nv1wogghs6",
"sync": false
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 22
},
"_enabled": true,
"_materials": [
{
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
}
],
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
"_spriteFrame": {
"__uuid__": "88e79fd5-96b4-4a77-a1f4-312467171014"
},
"_type": 1,
"_sizeMode": 0,
"_fillType": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_atlas": null,
"_id": ""
},
{
"__type__": "cc.ProgressBar",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 22
},
"_enabled": true,
"_N$totalLength": 50,
"_N$barSprite": {
"__id__": 24
},
"_N$mode": 0,
"_N$progress": 1,
"_N$reverse": false,
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__uuid__": "59bff7a2-23e1-4d69-bce7-afb37eae196a"
},
"fileId": "1cDdFO9Z5KYIcRR52ZmtqO",
"sync": false
},
{
"__type__": "b74b05YDqZFRo4OkZRFZX8k",
"_name": "",
@@ -910,6 +1170,9 @@
"coordLabel": {
"__id__": 3
},
"hpBar": {
"__id__": 27
},
"_id": ""
},
{

View File

@@ -59,7 +59,7 @@
"ctor": "Float64Array",
"array": [
480,
320,
480,
0,
0,
0,
@@ -242,7 +242,7 @@
"ctor": "Float64Array",
"array": [
0,
128,
-9.924,
0,
0,
0,

View File

@@ -24,11 +24,11 @@
"_active": true,
"_components": [
{
"__id__": 12
"__id__": 20
}
],
"_prefab": {
"__id__": 13
"__id__": 21
},
"_opacity": 255,
"_color": {
@@ -90,12 +90,18 @@
},
{
"__id__": 7
},
{
"__id__": 11
},
{
"__id__": 15
}
],
"_active": true,
"_components": [],
"_prefab": {
"__id__": 11
"__id__": 19
},
"_opacity": 255,
"_color": {
@@ -211,38 +217,6 @@
"groupIndex": 0,
"_id": ""
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 3
},
"_enabled": true,
"_materials": [
{
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
}
],
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
"_spriteFrame": null,
"_type": 0,
"_sizeMode": 1,
"_fillType": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_atlas": {
"__uuid__": "6dcd5722-8ef9-47fd-9520-861d2713e274"
},
"_id": ""
},
{
"__type__": "cc.Animation",
"_name": "",
@@ -265,6 +239,34 @@
"playOnLoad": false,
"_id": ""
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 3
},
"_enabled": true,
"_materials": [],
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
"_spriteFrame": null,
"_type": 0,
"_sizeMode": 1,
"_fillType": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_atlas": {
"__uuid__": "6dcd5722-8ef9-47fd-9520-861d2713e274"
},
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
@@ -278,7 +280,7 @@
},
{
"__type__": "cc.Node",
"_name": "MeleeExplosion",
"_name": "Fireball2",
"_objFlags": 0,
"_parent": {
"__id__": 2
@@ -314,6 +316,258 @@
"x": 0.5,
"y": 0.5
},
"_trs": {
"__type__": "TypedArray",
"ctor": "Float64Array",
"array": [
0,
0,
0,
0,
0,
0,
1,
0.5,
0.5,
1
]
},
"_eulerAngles": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_skewX": 0,
"_skewY": 0,
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": ""
},
{
"__type__": "cc.Animation",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 7
},
"_enabled": true,
"_defaultClip": null,
"_clips": [
{
"__uuid__": "d2c65ac4-a5b3-411e-8d2d-18d3980649d7"
},
{
"__uuid__": "14b92f5c-af81-416a-a162-e5822d20fe68"
}
],
"playOnLoad": false,
"_id": ""
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 7
},
"_enabled": true,
"_materials": [],
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
"_spriteFrame": null,
"_type": 0,
"_sizeMode": 1,
"_fillType": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_atlas": {
"__uuid__": "725c90f9-56f8-48ea-9159-4d2949cd3ce0"
},
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__uuid__": "d92d4831-cd65-4eb5-90bd-b77021aec35b"
},
"fileId": "b0ZpleOHlFqIjwc8HDI9df",
"sync": false
},
{
"__type__": "cc.Node",
"_name": "Fireball3",
"_objFlags": 0,
"_parent": {
"__id__": 2
},
"_children": [],
"_active": false,
"_components": [
{
"__id__": 12
},
{
"__id__": 13
}
],
"_prefab": {
"__id__": 14
},
"_opacity": 255,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_contentSize": {
"__type__": "cc.Size",
"width": 0,
"height": 0
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_trs": {
"__type__": "TypedArray",
"ctor": "Float64Array",
"array": [
0,
0,
0,
0,
0,
0,
1,
1,
1,
1
]
},
"_eulerAngles": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_skewX": 0,
"_skewY": 0,
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": ""
},
{
"__type__": "cc.Animation",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 11
},
"_enabled": true,
"_defaultClip": null,
"_clips": [
{
"__uuid__": "6aef5812-d16c-4da1-96a3-a38ac227c823"
},
{
"__uuid__": "0dbb90ed-a08a-448c-b06e-4831260e9213"
}
],
"playOnLoad": false,
"_id": ""
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 11
},
"_enabled": true,
"_materials": [],
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
"_spriteFrame": null,
"_type": 0,
"_sizeMode": 1,
"_fillType": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_atlas": {
"__uuid__": "579bc0c1-f5e2-4a5d-889b-9d567e53b0e6"
},
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__uuid__": "d92d4831-cd65-4eb5-90bd-b77021aec35b"
},
"fileId": "03W6UmKHVAz4hCpMvTCpP9",
"sync": false
},
{
"__type__": "cc.Node",
"_name": "MeleeExplosion",
"_objFlags": 0,
"_parent": {
"__id__": 2
},
"_children": [],
"_active": false,
"_components": [
{
"__id__": 16
},
{
"__id__": 17
}
],
"_prefab": {
"__id__": 18
},
"_opacity": 255,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_contentSize": {
"__type__": "cc.Size",
"width": 0,
"height": 0
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_trs": {
"__type__": "TypedArray",
"ctor": "Float64Array",
@@ -348,7 +602,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 7
"__id__": 15
},
"_enabled": true,
"_defaultClip": null,
@@ -371,7 +625,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 7
"__id__": 15
},
"_enabled": true,
"_materials": [],

File diff suppressed because it is too large Load Diff

View File

@@ -88,7 +88,7 @@
"__id__": 1
},
"_children": [],
"_active": true,
"_active": false,
"_components": [
{
"__id__": 3

File diff suppressed because it is too large Load Diff

View File

@@ -72,22 +72,25 @@
"__id__": 3
},
{
"__id__": 7
"__id__": 5
},
{
"__id__": 9
},
{
"__id__": 11
}
],
"_active": true,
"_components": [
{
"__id__": 60
},
{
"__id__": 61
},
{
"__id__": 62
},
{
"__id__": 63
},
{
"__id__": 64
}
],
"_prefab": null,
@@ -138,6 +141,102 @@
"groupIndex": 0,
"_id": "88kscZWXFCIZtNFekdSP/o"
},
{
"__type__": "cc.Node",
"_name": "Background",
"_objFlags": 0,
"_parent": {
"__id__": 2
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 4
}
],
"_prefab": null,
"_opacity": 255,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_contentSize": {
"__type__": "cc.Size",
"width": 1280,
"height": 960
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_trs": {
"__type__": "TypedArray",
"ctor": "Float64Array",
"array": [
0,
0,
0,
0,
0,
0,
1,
1,
1,
1
]
},
"_eulerAngles": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_skewX": 0,
"_skewY": 0,
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": "c8r+ISXVhBZarylAeQMAQK"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 3
},
"_enabled": true,
"_materials": [
{
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
}
],
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
"_spriteFrame": {
"__uuid__": "637f31c2-c53e-4dec-ae11-d56c0c6177ad"
},
"_type": 0,
"_sizeMode": 0,
"_fillType": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_atlas": {
"__uuid__": "030d9286-e8a2-40cf-98f8-baf713f0b8c4"
},
"_id": "6dhdFxELpEwZ4TjXC+WnyS"
},
{
"__type__": "cc.Node",
"_name": "Decorations",
@@ -147,13 +246,13 @@
},
"_children": [
{
"__id__": 4
"__id__": 6
}
],
"_active": true,
"_components": [
{
"__id__": 6
"__id__": 8
}
],
"_prefab": null,
@@ -209,13 +308,13 @@
"_name": "Logo",
"_objFlags": 0,
"_parent": {
"__id__": 3
"__id__": 5
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 5
"__id__": 7
}
],
"_prefab": null,
@@ -271,7 +370,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 4
"__id__": 6
},
"_enabled": true,
"_materials": [
@@ -301,7 +400,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 3
"__id__": 5
},
"_enabled": true,
"alignMode": 0,
@@ -334,7 +433,7 @@
"_active": true,
"_components": [
{
"__id__": 8
"__id__": 10
}
],
"_prefab": null,
@@ -362,7 +461,7 @@
"array": [
0,
0,
210.43934936178934,
209.66956379694378,
0,
0,
0,
@@ -390,7 +489,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 7
"__id__": 9
},
"_enabled": true,
"_cullingMask": 4294967295,
@@ -430,13 +529,13 @@
},
"_children": [
{
"__id__": 10
"__id__": 12
}
],
"_active": true,
"_components": [
{
"__id__": 59
"__id__": 61
}
],
"_prefab": null,
@@ -492,41 +591,41 @@
"_name": "InteractiveControls",
"_objFlags": 0,
"_parent": {
"__id__": 9
"__id__": 11
},
"_children": [
{
"__id__": 11
},
{
"__id__": 19
"__id__": 13
},
{
"__id__": 21
},
{
"__id__": 30
"__id__": 23
},
{
"__id__": 32
},
{
"__id__": 41
"__id__": 34
},
{
"__id__": 48
"__id__": 43
},
{
"__id__": 54
"__id__": 50
},
{
"__id__": 56
},
{
"__id__": 58
}
],
"_active": true,
"_components": [
{
"__id__": 58
"__id__": 60
}
],
"_prefab": null,
@@ -582,23 +681,23 @@
"_name": "phoneCountryCodeInput",
"_objFlags": 0,
"_parent": {
"__id__": 10
"__id__": 12
},
"_children": [
{
"__id__": 12
},
{
"__id__": 14
},
{
"__id__": 16
},
{
"__id__": 18
}
],
"_active": true,
"_components": [
{
"__id__": 18
"__id__": 20
}
],
"_prefab": null,
@@ -654,13 +753,13 @@
"_name": "BACKGROUND_SPRITE",
"_objFlags": 0,
"_parent": {
"__id__": 11
"__id__": 13
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 13
"__id__": 15
}
],
"_prefab": null,
@@ -716,7 +815,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 12
"__id__": 14
},
"_enabled": true,
"_materials": [
@@ -748,13 +847,13 @@
"_name": "TEXT_LABEL",
"_objFlags": 0,
"_parent": {
"__id__": 11
"__id__": 13
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 15
"__id__": 17
}
],
"_prefab": null,
@@ -810,7 +909,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 14
"__id__": 16
},
"_enabled": true,
"_materials": [
@@ -840,13 +939,13 @@
"_name": "PLACEHOLDER_LABEL",
"_objFlags": 0,
"_parent": {
"__id__": 11
"__id__": 13
},
"_children": [],
"_active": false,
"_components": [
{
"__id__": 17
"__id__": 19
}
],
"_prefab": null,
@@ -902,7 +1001,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 16
"__id__": 18
},
"_enabled": true,
"_materials": [],
@@ -928,7 +1027,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 11
"__id__": 13
},
"_enabled": true,
"_useOriginalSize": false,
@@ -941,13 +1040,13 @@
"editingDidEnded": [],
"editingReturn": [],
"_N$textLabel": {
"__id__": 15
},
"_N$placeholderLabel": {
"__id__": 17
},
"_N$placeholderLabel": {
"__id__": 19
},
"_N$background": {
"__id__": 13
"__id__": 15
},
"_N$inputFlag": 5,
"_N$inputMode": 3,
@@ -959,13 +1058,13 @@
"_name": "phoneLabel",
"_objFlags": 0,
"_parent": {
"__id__": 10
"__id__": 12
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 20
"__id__": 22
}
],
"_prefab": null,
@@ -1021,7 +1120,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 19
"__id__": 21
},
"_enabled": true,
"_materials": [
@@ -1051,23 +1150,23 @@
"_name": "phoneNumberInput",
"_objFlags": 0,
"_parent": {
"__id__": 10
"__id__": 12
},
"_children": [
{
"__id__": 22
},
{
"__id__": 24
},
{
"__id__": 26
},
{
"__id__": 28
}
],
"_active": true,
"_components": [
{
"__id__": 29
"__id__": 31
}
],
"_prefab": null,
@@ -1123,13 +1222,13 @@
"_name": "BACKGROUND_SPRITE",
"_objFlags": 0,
"_parent": {
"__id__": 21
"__id__": 23
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 23
"__id__": 25
}
],
"_prefab": null,
@@ -1185,7 +1284,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 22
"__id__": 24
},
"_enabled": true,
"_materials": [
@@ -1217,13 +1316,13 @@
"_name": "TEXT_LABEL",
"_objFlags": 0,
"_parent": {
"__id__": 21
"__id__": 23
},
"_children": [],
"_active": false,
"_components": [
{
"__id__": 25
"__id__": 27
}
],
"_prefab": null,
@@ -1279,7 +1378,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 24
"__id__": 26
},
"_enabled": true,
"_materials": [],
@@ -1305,16 +1404,16 @@
"_name": "PLACEHOLDER_LABEL",
"_objFlags": 0,
"_parent": {
"__id__": 21
"__id__": 23
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 27
"__id__": 29
},
{
"__id__": 28
"__id__": 30
}
],
"_prefab": null,
@@ -1370,7 +1469,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 26
"__id__": 28
},
"_enabled": true,
"_materials": [
@@ -1400,7 +1499,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 26
"__id__": 28
},
"_enabled": true,
"_dataID": "login.hint.phoneInputHint",
@@ -1411,7 +1510,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 21
"__id__": 23
},
"_enabled": true,
"_useOriginalSize": false,
@@ -1424,13 +1523,13 @@
"editingDidEnded": [],
"editingReturn": [],
"_N$textLabel": {
"__id__": 25
},
"_N$placeholderLabel": {
"__id__": 27
},
"_N$placeholderLabel": {
"__id__": 29
},
"_N$background": {
"__id__": 23
"__id__": 25
},
"_N$inputFlag": 5,
"_N$inputMode": 1,
@@ -1442,13 +1541,13 @@
"_name": " smsLoginCaptchaLabel",
"_objFlags": 0,
"_parent": {
"__id__": 10
"__id__": 12
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 31
"__id__": 33
}
],
"_prefab": null,
@@ -1504,7 +1603,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 30
"__id__": 32
},
"_enabled": true,
"_materials": [
@@ -1534,23 +1633,23 @@
"_name": "smsLoginCaptchaInput",
"_objFlags": 0,
"_parent": {
"__id__": 10
"__id__": 12
},
"_children": [
{
"__id__": 33
},
{
"__id__": 35
},
{
"__id__": 37
},
{
"__id__": 39
}
],
"_active": true,
"_components": [
{
"__id__": 40
"__id__": 42
}
],
"_prefab": null,
@@ -1606,13 +1705,13 @@
"_name": "BACKGROUND_SPRITE",
"_objFlags": 0,
"_parent": {
"__id__": 32
"__id__": 34
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 34
"__id__": 36
}
],
"_prefab": null,
@@ -1668,7 +1767,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 33
"__id__": 35
},
"_enabled": true,
"_materials": [
@@ -1700,13 +1799,13 @@
"_name": "TEXT_LABEL",
"_objFlags": 0,
"_parent": {
"__id__": 32
"__id__": 34
},
"_children": [],
"_active": false,
"_components": [
{
"__id__": 36
"__id__": 38
}
],
"_prefab": null,
@@ -1762,7 +1861,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 35
"__id__": 37
},
"_enabled": true,
"_materials": [],
@@ -1788,16 +1887,16 @@
"_name": "PLACEHOLDER_LABEL",
"_objFlags": 0,
"_parent": {
"__id__": 32
"__id__": 34
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 38
"__id__": 40
},
{
"__id__": 39
"__id__": 41
}
],
"_prefab": null,
@@ -1853,7 +1952,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 37
"__id__": 39
},
"_enabled": true,
"_materials": [
@@ -1883,7 +1982,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 37
"__id__": 39
},
"_enabled": true,
"_dataID": "login.hint.captchaInputHint",
@@ -1894,7 +1993,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 32
"__id__": 34
},
"_enabled": true,
"_useOriginalSize": false,
@@ -1907,13 +2006,13 @@
"editingDidEnded": [],
"editingReturn": [],
"_N$textLabel": {
"__id__": 36
},
"_N$placeholderLabel": {
"__id__": 38
},
"_N$placeholderLabel": {
"__id__": 40
},
"_N$background": {
"__id__": 34
"__id__": 36
},
"_N$inputFlag": 5,
"_N$inputMode": 2,
@@ -1925,20 +2024,20 @@
"_name": "smsLoginCaptchaButton",
"_objFlags": 0,
"_parent": {
"__id__": 10
"__id__": 12
},
"_children": [
{
"__id__": 42
"__id__": 44
},
{
"__id__": 44
"__id__": 46
}
],
"_active": true,
"_components": [
{
"__id__": 46
"__id__": 48
}
],
"_prefab": null,
@@ -1994,13 +2093,13 @@
"_name": "smsGetCaptcha",
"_objFlags": 0,
"_parent": {
"__id__": 41
"__id__": 43
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 43
"__id__": 45
}
],
"_prefab": null,
@@ -2056,7 +2155,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 42
"__id__": 44
},
"_enabled": true,
"_materials": [
@@ -2090,13 +2189,13 @@
"_name": "captchaLabel",
"_objFlags": 0,
"_parent": {
"__id__": 41
"__id__": 43
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 45
"__id__": 47
}
],
"_prefab": null,
@@ -2152,7 +2251,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 44
"__id__": 46
},
"_enabled": true,
"_materials": [
@@ -2182,7 +2281,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 41
"__id__": 43
},
"_enabled": true,
"_normalMaterial": null,
@@ -2191,7 +2290,7 @@
"zoomScale": 1.1,
"clickEvents": [
{
"__id__": 47
"__id__": 49
}
],
"_N$interactable": true,
@@ -2257,7 +2356,7 @@
"__uuid__": "29158224-f8dd-4661-a796-1ffab537140e"
},
"_N$target": {
"__id__": 41
"__id__": 43
},
"_id": "96L9tbxW9DHJHU5hUi9oNc"
},
@@ -2276,20 +2375,20 @@
"_name": "loginButton",
"_objFlags": 0,
"_parent": {
"__id__": 10
"__id__": 12
},
"_children": [
{
"__id__": 49
"__id__": 51
}
],
"_active": true,
"_components": [
{
"__id__": 51
"__id__": 53
},
{
"__id__": 52
"__id__": 54
}
],
"_prefab": null,
@@ -2345,13 +2444,13 @@
"_name": "loginLabel",
"_objFlags": 0,
"_parent": {
"__id__": 48
"__id__": 50
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 50
"__id__": 52
}
],
"_prefab": null,
@@ -2407,7 +2506,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 49
"__id__": 51
},
"_enabled": true,
"_materials": [
@@ -2437,7 +2536,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 48
"__id__": 50
},
"_enabled": true,
"_materials": [
@@ -2471,7 +2570,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 48
"__id__": 50
},
"_enabled": true,
"_normalMaterial": null,
@@ -2480,7 +2579,7 @@
"zoomScale": 1.2,
"clickEvents": [
{
"__id__": 53
"__id__": 55
}
],
"_N$interactable": true,
@@ -2546,7 +2645,7 @@
"__uuid__": "29158224-f8dd-4661-a796-1ffab537140e"
},
"_N$target": {
"__id__": 48
"__id__": 50
},
"_id": "0dQu3M5+hO8Ia4FD67Br39"
},
@@ -2565,13 +2664,13 @@
"_name": "phoneNumberTips",
"_objFlags": 0,
"_parent": {
"__id__": 10
"__id__": 12
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 55
"__id__": 57
}
],
"_prefab": null,
@@ -2627,7 +2726,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 54
"__id__": 56
},
"_enabled": true,
"_materials": [
@@ -2657,13 +2756,13 @@
"_name": "captchaTips",
"_objFlags": 0,
"_parent": {
"__id__": 10
"__id__": 12
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 57
"__id__": 59
}
],
"_prefab": null,
@@ -2719,7 +2818,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 56
"__id__": 58
},
"_enabled": true,
"_materials": [
@@ -2749,7 +2848,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 10
"__id__": 12
},
"_enabled": true,
"alignMode": 1,
@@ -2776,7 +2875,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 9
"__id__": 11
},
"_enabled": true,
"alignMode": 0,
@@ -2854,37 +2953,37 @@
"__id__": 2
},
"backgroundNode": {
"__id__": 9
},
"interactiveControls": {
"__id__": 10
},
"phoneLabel": {
"__id__": 19
},
"smsLoginCaptchaLabel": {
"__id__": 30
},
"phoneCountryCodeInput": {
"__id__": 11
},
"phoneNumberInput": {
"interactiveControls": {
"__id__": 12
},
"phoneLabel": {
"__id__": 21
},
"phoneNumberTips": {
"__id__": 54
},
"smsLoginCaptchaInput": {
"smsLoginCaptchaLabel": {
"__id__": 32
},
"smsLoginCaptchaButton": {
"__id__": 41
"phoneCountryCodeInput": {
"__id__": 13
},
"captchaTips": {
"phoneNumberInput": {
"__id__": 23
},
"phoneNumberTips": {
"__id__": 56
},
"smsLoginCaptchaInput": {
"__id__": 34
},
"smsLoginCaptchaButton": {
"__id__": 43
},
"captchaTips": {
"__id__": 58
},
"loginButton": {
"__id__": 48
"__id__": 50
},
"smsWaitCountdownPrefab": {
"__uuid__": "2c0101b8-c15a-4501-9fce-cd5a014af8bf"

File diff suppressed because it is too large Load Diff

View File

@@ -19,6 +19,7 @@ window.ATK_CHARACTER_STATE = {
Dashing: [15, "Dashing"],
OnWall: [16, "OnWall"],
TurnAround1: [17, "TurnAround1"],
Dying: [18, "Dying"],
};
window.ATK_CHARACTER_STATE_ARR = [];
@@ -63,9 +64,6 @@ cc.Class({
ctor() {
this.speciesName = null;
this.hp = 100;
this.maxHp = 100;
this.inAir = true;
},
setSpecies(speciesName) {
@@ -75,27 +73,27 @@ cc.Class({
onLoad() {
BaseCharacter.prototype.onLoad.call(this);
this.effAnimNode = this.animNode.getChildByName(this.speciesName);
this.animComp = this.effAnimNode.getComponent(dragonBones.ArmatureDisplay);
if (!this.animComp) {
this.animComp = this.effAnimNode.getComponent(cc.Animation);
}
//this.animComp = this.effAnimNode.getComponent(dragonBones.ArmatureDisplay);
//if (!this.animComp) {
this.animComp = this.effAnimNode.getComponent(cc.Animation);
//}
this.effAnimNode.active = true;
},
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);
@@ -107,26 +105,25 @@ cc.Class({
let playingAnimName = null;
let underlyingAnimationCtrl = null;
if (this.animComp instanceof dragonBones.ArmatureDisplay) {
underlyingAnimationCtrl = this.animComp._armature.animation; // ALWAYS use the dragonBones api instead of ccc's wrapper!
playingAnimName = underlyingAnimationCtrl.lastAnimationName;
} else {
underlyingAnimationCtrl = this.animComp.currentClip;
playingAnimName = (!underlyingAnimationCtrl ? null : underlyingAnimationCtrl.name);
}
//if (this.animComp instanceof dragonBones.ArmatureDisplay) {
// underlyingAnimationCtrl = this.animComp._armature.animation; // ALWAYS use the dragonBones api instead of ccc's wrapper!
// playingAnimName = underlyingAnimationCtrl.lastAnimationName;
//} else {
underlyingAnimationCtrl = this.animComp.currentClip;
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)}`);
return;
}
if (this.animComp instanceof dragonBones.ArmatureDisplay) {
this._interruptPlayingAnimAndPlayNewAnimDragonBones(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl, playingAnimName, chConfig);
} else {
this._interruptPlayingAnimAndPlayNewAnimFrameAnim(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, playingAnimName, chConfig);
}
//if (this.animComp instanceof dragonBones.ArmatureDisplay) {
// this._interruptPlayingAnimAndPlayNewAnimDragonBones(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl, playingAnimName, chConfig);
//} else {
this._interruptPlayingAnimAndPlayNewAnimFrameAnim(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, playingAnimName, chConfig);
//}
},
_interruptPlayingAnimAndPlayNewAnimDragonBones(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl, playingAnimName, chConfig) {
@@ -150,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?
}

View File

@@ -8,7 +8,7 @@ cc.Class({
},
speed: {
type: cc.Float,
default: 500
default: 100
},
},
@@ -25,10 +25,19 @@ 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;
self.mapNode.setPosition(cc.v2().sub(selfPlayerNode.position));
const dst = cc.v2().sub(selfPlayerNode.position);
const pDiff = dst.sub(self.mapNode.position);
const stepLength = dt * self.speed;
if (stepLength > pDiff.mag()) {
self.mapNode.setPosition(dst);
} else {
pDiff.normalizeSelf();
const newMapPos = self.mapNode.position.add(pDiff.mul(dt * self.speed));
self.mapNode.setPosition(newMapPos);
}
}
});

View 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);
},
});

View File

@@ -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,

View File

@@ -10,7 +10,11 @@ cc.Class({
coordLabel: {
type: cc.Label,
default: null
}
},
hpBar: {
type: cc.ProgressBar,
default: null
},
},
onLoad() {

View File

@@ -23,7 +23,8 @@ cc.Class({
// LIFE-CYCLE CALLBACKS:
onLoad() {},
init() {
init(mapIns) {
this.mapIns = mapIns;
if (null != this.firstPlayerInfoNode) {
this.firstPlayerInfoNode.active = false;
}
@@ -46,16 +47,17 @@ cc.Class({
},
hideExitButton() {
if (null == this.exitBtnNode != null) {
if (null == this.exitBtnNode) {
return;
}
this.exitBtnNode.active = false;
},
exitBtnOnClick(evt) {
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
window.closeWSConnection();
cc.director.loadScene('login');
this.mapIns.hideFindingPlayersGUI();
cc.log(`FindingPlayers.exitBtnOnClick`);
window.closeWSConnection(constants.RET_CODE.BATTLE_STOPPED, "");
window.clearLocalStorageAndBackToLoginScene(false);
},
updatePlayersInfo(playerMetas) {

View File

@@ -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;
}
}

View File

@@ -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);
},
});

View File

@@ -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

View File

@@ -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);
}
},
});

View File

@@ -0,0 +1,121 @@
const RingBuffer = require('./RingBuffer');
var NetworkDoctor = function(capacity) {
this.reset(capacity);
};
NetworkDoctor.prototype.reset = function(capacity) {
this.inputFrameIdFront = 0;
this.sendingQ = new RingBuffer(capacity);
this.inputFrameDownsyncQ = new RingBuffer(capacity);
this.peerInputFrameUpsyncQ = new RingBuffer(capacity);
this.peerInputFrameUpsyncCnt = 0;
this.immediateRollbackFrames = 0;
this.skippedRenderFrameCnt = 0;
this.inputRateThreshold = gopkgs.ConvertToNoDelayInputFrameId(60);
this.peerUpsyncThreshold = 8;
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,
j: edFrameId,
t: Date.now()
});
};
NetworkDoctor.prototype.logInputFrameDownsync = function(stFrameId, edFrameId) {
this.inputFrameDownsyncQ.put({
i: stFrameId,
j: edFrameId,
t: Date.now()
});
};
NetworkDoctor.prototype.logPeerInputFrameUpsync = function(stFrameId, edFrameId) {
const firstPopped = this.peerInputFrameUpsyncQ.put({
i: stFrameId,
j: edFrameId,
t: Date.now()
});
if (null != firstPopped) {
this.peerInputFrameUpsyncCnt -= (firstPopped.j - firstPopped.i + 1);
}
this.peerInputFrameUpsyncCnt += (edFrameId - stFrameId + 1);
};
NetworkDoctor.prototype.logRollbackFrames = function(x) {
this.immediateRollbackFrames = x;
};
NetworkDoctor.prototype.stats = function() {
let inputFrameIdFront = this.inputFrameIdFront,
sendingFps = 0,
srvDownsyncFps = 0,
peerUpsyncFps = 0,
rollbackFrames = this.immediateRollbackFrames;
if (1 < this.sendingQ.cnt) {
const st = this.sendingQ.getByFrameId(this.sendingQ.stFrameId);
const ed = this.sendingQ.getByFrameId(this.sendingQ.edFrameId - 1);
const elapsedMillis = ed.t - st.t;
sendingFps = Math.round((ed.j - st.i) * 1000 / elapsedMillis);
}
if (1 < this.inputFrameDownsyncQ.cnt) {
const st = this.inputFrameDownsyncQ.getByFrameId(this.inputFrameDownsyncQ.stFrameId);
const ed = this.inputFrameDownsyncQ.getByFrameId(this.inputFrameDownsyncQ.edFrameId - 1);
const elapsedMillis = ed.t - st.t;
srvDownsyncFps = Math.round((ed.j - st.i) * 1000 / elapsedMillis);
}
if (1 < this.peerInputFrameUpsyncQ.cnt) {
const st = this.peerInputFrameUpsyncQ.getByFrameId(this.peerInputFrameUpsyncQ.stFrameId);
const ed = this.peerInputFrameUpsyncQ.getByFrameId(this.peerInputFrameUpsyncQ.edFrameId - 1);
const elapsedMillis = ed.t - st.t;
peerUpsyncFps = Math.round(this.peerInputFrameUpsyncCnt * 1000 / elapsedMillis);
}
return [inputFrameIdFront, sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, this.skippedRenderFrameCnt];
};
NetworkDoctor.prototype.logSkippedRenderFrameCnt = function() {
this.skippedRenderFrameCnt += 1;
}
NetworkDoctor.prototype.isTooFast = function(mapIns) {
const [inputFrameIdFront, sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, skippedRenderFrameCnt] = this.stats();
if (sendingFps >= this.inputRateThreshold + 3) {
// Don't send too fast
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 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 ((inputFrameIdFront > minInputFrameIdFront) && ((inputFrameIdFront - minInputFrameIdFront) > (mapIns.inputFrameUpsyncDelayTolerance + 1))) {
// first comparison condition is to avoid numeric overflow
if (CC_DEBUG) {
// Printing of this message might induce a performance impact.
// console.log(`Game logic ticking too fast, selfInputFrameIdFront=${inputFrameIdFront}, minInputFrameIdFront=${minInputFrameIdFront}, inputFrameUpsyncDelayTolerance=${mapIns.inputFrameUpsyncDelayTolerance}`);
}
return [true, inputFrameIdFront, sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, skippedRenderFrameCnt];
}
}
}
return [false, inputFrameIdFront, sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, skippedRenderFrameCnt];
};
module.exports = NetworkDoctor;

View File

@@ -1,6 +1,6 @@
{
"ver": "1.0.5",
"uuid": "477c07c3-0d50-4d55-96f0-6eaf9f25e2da",
"uuid": "affd726a-02f0-4079-aace-39fe525d7478",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,

View File

@@ -11,11 +11,13 @@ cc.Class({
},
onLoad() {
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 = false;
const mapNode = self.node;
const canvasNode = mapNode.parent;
@@ -31,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;
@@ -71,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);
@@ -96,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, 1];
const speciesIdList = [1, 0];
const chConfigsOrderedByJoinIndex = gopkgs.GetCharacterConfigsOrderedByJoinIndex(speciesIdList);
const startRdf = window.pb.protos.RoomDownsyncFrame.create({
@@ -107,7 +110,9 @@ cc.Class({
joinIndex: 1,
virtualGridX: p1Vpos[0],
virtualGridY: p1Vpos[1],
speed: chConfigsOrderedByJoinIndex[0].Speed,
revivalVirtualGridX: p1Vpos[0],
revivalVirtualGridY: p1Vpos[1],
speed: chConfigsOrderedByJoinIndex[0].GetSpeed(),
colliderRadius: colliderRadiusV[0],
characterState: window.ATK_CHARACTER_STATE.InAirIdle1NoJump[0],
framesToRecover: 0,
@@ -117,13 +122,17 @@ cc.Class({
velY: 0,
inAir: true,
onWall: false,
hp: 100,
maxHp: 100,
}),
window.pb.protos.PlayerDownsync.create({
id: 11,
joinIndex: 2,
virtualGridX: p2Vpos[0],
virtualGridY: p2Vpos[1],
speed: chConfigsOrderedByJoinIndex[1].Speed,
revivalVirtualGridX: p2Vpos[0],
revivalVirtualGridY: p2Vpos[1],
speed: chConfigsOrderedByJoinIndex[1].GetSpeed(),
colliderRadius: colliderRadiusV[0],
characterState: window.ATK_CHARACTER_STATE.InAirIdle1NoJump[0],
framesToRecover: 0,
@@ -133,15 +142,46 @@ cc.Class({
velY: 0,
inAir: true,
onWall: false,
hp: 100,
maxHp: 100,
}),
],
speciesIdList: speciesIdList,
});
self.selfPlayerInfo = {
Id: 10,
JoinIndex: 1,
id: 10,
joinIndex: 1,
};
if (cc.sys.isNative) {
window.onUdpMessage = (args) => {
const len = args.length;
const ui8Arr = new Uint8Array(len);
for (let i = 0; i < len; i++) {
ui8Arr[i] = args.charCodeAt(i);
}
cc.log(`#1 Js called back by CPP: onUdpMessage: args=${args}, typeof(args)=${typeof (args)}, argslen=${args.length}, ui8Arr=${ui8Arr}`);
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 holePunchData = window.pb.protos.HolePunchUpsync.encode({
boundRoomId: 22,
intAuthToken: "foobar",
authKey: Math.floor(Math.random() * 65535),
}).finish()
//const res2 = DelayNoMore.UdpSession.punchToServer("127.0.0.1", 3000, holePunchData, 19999, holePunchData);
const res3 = DelayNoMore.UdpSession.upsertPeerUdpAddr([window.pb.protos.PeerUdpAddr.create({
ip: "192.168.31.194",
port: 6789,
authKey: 123456,
}), window.pb.protos.PeerUdpAddr.create({
ip: "192.168.1.101",
port: 8771,
authKey: 654321,
})], 2, self.selfPlayerInfo.JoinIndex);
//const res4 = DelayNoMore.UdpSession.closeUdpSession();
}
self.onRoomDownsyncFrame(startRdf);
self.battleState = ALL_BATTLE_STATES.IN_BATTLE;
@@ -152,19 +192,13 @@ cc.Class({
update(dt) {
const self = this;
if (ALL_BATTLE_STATES.IN_BATTLE == self.battleState) {
const elapsedMillisSinceLastFrameIdTriggered = performance.now() - self.lastRenderFrameIdTriggeredAt;
if (elapsedMillisSinceLastFrameIdTriggered < self.tooFastDtIntervalMillis) {
// [WARNING] We should avoid a frontend ticking too fast to prevent cheating, as well as ticking too slow to cause a "resync avalanche" that impacts user experience!
// console.debug("Avoiding too fast frame@renderFrameId=", self.renderFrameId, ": elapsedMillisSinceLastFrameIdTriggered=", elapsedMillisSinceLastFrameIdTriggered);
//return;
}
try {
let st = performance.now();
let prevSelfInput = null,
currSelfInput = null;
const noDelayInputFrameId = gopkgs.ConvertToNoDelayInputFrameId(self.renderFrameId); // It's important that "inputDelayFrames == 0" here
if (gopkgs.ShouldGenerateInputFrameUpsync(self.renderFrameId)) {
const prevAndCurrInputs = self.getOrPrefabInputFrameUpsync(noDelayInputFrameId);
const prevAndCurrInputs = self.getOrPrefabInputFrameUpsync(noDelayInputFrameId, true);
prevSelfInput = prevAndCurrInputs[0];
currSelfInput = prevAndCurrInputs[1];
}

View File

@@ -13,9 +13,12 @@ var RingBuffer = function(capacity) {
};
RingBuffer.prototype.put = function(item) {
let firstPopped = null;
while (0 < this.cnt && this.cnt >= this.n) {
// Make room for the new element
this.pop();
const popped = this.pop();
if (null == firstPopped)
firstPopped = popped;
}
this.eles[this.ed] = item
this.edFrameId++;
@@ -24,6 +27,7 @@ RingBuffer.prototype.put = function(item) {
if (this.ed >= this.n) {
this.ed -= this.n; // Deliberately not using "%" operator for performance concern
}
return firstPopped;
};
RingBuffer.prototype.pop = function() {

View File

@@ -12,6 +12,7 @@ window.DOWNSYNC_MSG_ACT_INPUT_BATCH = 2;
window.DOWNSYNC_MSG_ACT_BATTLE_STOPPED = 3;
window.DOWNSYNC_MSG_ACT_FORCED_RESYNC = 4;
window.DOWNSYNC_MSG_ACT_PEER_INPUT_BATCH = 5;
window.DOWNSYNC_MSG_ACT_PEER_UDP_ADDR = 6;
window.sendSafely = function(msgStr) {
/**
@@ -33,7 +34,7 @@ window.closeWSConnection = function(code, reason) {
console.log(`"window.clientSession" is already closed or destroyed.`);
return;
}
console.log(`Closing "window.clientSession" from the client-side.`);
console.log(`Closing "window.clientSession" from the client-side with code=${code}.`);
window.clientSession.close(code, reason);
}
@@ -43,12 +44,35 @@ window.getBoundRoomIdFromPersistentStorage = function() {
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
return null;
}
return cc.sys.localStorage.getItem("boundRoomId");
const boundRoomIdStr = cc.sys.localStorage.getItem("boundRoomId");
return (null == boundRoomIdStr ? null : parseInt(boundRoomIdStr));
};
window.getBoundRoomCapacityFromPersistentStorage = function() {
const boundRoomIdExpiresAt = parseInt(cc.sys.localStorage.getItem("boundRoomIdExpiresAt"));
if (!boundRoomIdExpiresAt || Date.now() >= boundRoomIdExpiresAt) {
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
return null;
}
const boundRoomCapacityStr = cc.sys.localStorage.getItem("boundRoomCapacity");
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");
};
@@ -57,18 +81,52 @@ window.clearSelfPlayer = function() {
};
window.boundRoomId = getBoundRoomIdFromPersistentStorage();
window.boundRoomCapacity = getBoundRoomCapacityFromPersistentStorage();
window.handleHbRequirements = function(resp) {
console.log(`Handle hb requirements #1`);
if (constants.RET_CODE.OK != resp.ret) return;
if (null == window.boundRoomId) {
// 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;
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`);
if (window.handleBattleColliderInfo) {
window.initSecondarySession(null, window.boundRoomId);
window.handleBattleColliderInfo(resp.bciFrame);
}
console.log(`Handle hb requirements #4`);
if (!cc.sys.isNative) {
console.log(`Handle hb requirements #5, web`);
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);
window.mapIns.selfPlayerInfo.udpTunnelAuthKey = resp.bciFrame.battleUdpTunnel.authKey;
const intAuthToken = window.mapIns.selfPlayerInfo.intAuthToken;
const authKey = Math.floor(Math.random() * 65535);
window.mapIns.selfPlayerInfo.authKey = authKey;
const holePunchData = window.pb.protos.HolePunchUpsync.encode({
boundRoomId: window.boundRoomId,
intAuthToken: intAuthToken,
authKey: authKey,
}).finish();
const udpTunnelHolePunchData = window.pb.protos.WsReq.encode({
msgId: Date.now(),
playerId: window.mapIns.selfPlayerInfo.id,
act: window.UPSYNC_MSG_ACT_PLAYER_CMD,
authKey: resp.bciFrame.battleUdpTunnel.authKey,
}).finish();
const res2 = DelayNoMore.UdpSession.punchToServer(backendAddress.HOST, 3000, holePunchData, resp.bciFrame.battleUdpTunnel.port, udpTunnelHolePunchData);
}
};
function _uint8ToBase64(uint8Arr) {
@@ -126,12 +184,20 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
urlToConnect = urlToConnect + "&expectedRoomId=" + expectedRoomId;
} else {
window.boundRoomId = getBoundRoomIdFromPersistentStorage();
window.boundRoomCapacity = getBoundRoomCapacityFromPersistentStorage();
if (null != window.boundRoomId) {
console.log("initPersistentSessionClient with boundRoomId == " + boundRoomId);
urlToConnect = urlToConnect + "&boundRoomId=" + window.boundRoomId;
}
}
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"
@@ -176,6 +242,19 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
}
mapIns.onRoomDownsyncFrame(resp.rdf, resp.inputFrameDownsyncBatch);
break;
case window.DOWNSYNC_MSG_ACT_PEER_UDP_ADDR:
console.warn(`Got DOWNSYNC_MSG_ACT_PEER_UDP_ADDR resp=${JSON.stringify(resp, null, 2)}`);
if (cc.sys.isNative) {
const peerJoinIndex = resp.peerJoinIndex;
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);
}
}
break;
default:
break;
}
@@ -192,13 +271,21 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
clientSession.onclose = function(evt) {
// [WARNING] The callback "onclose" might be called AFTER the webpage is refreshed with "1001 == evt.code".
console.warn(`The WS clientSession is closed: evt=${JSON.stringify(evt)}, evt.code=${evt.code}`);
if (cc.sys.isNative) {
if (mapIns.frameDataLoggingEnabled) {
console.warn(`${mapIns._stringifyRdfIdToActuallyUsedInput()}
`);
}
DelayNoMore.UdpSession.closeUdpSession();
}
switch (evt.code) {
case constants.RET_CODE.CLIENT_MISMATCHED_RENDER_FRAME:
break;
case constants.RET_CODE.BATTLE_STOPPED:
// deliberately do nothing
if (mapIns.frameDataLoggingEnabled) {
console.warn(`${mapIns._stringifyRdfIdToActuallyUsedInput()}`);
console.warn(`${mapIns._stringifyRdfIdToActuallyUsedInput()}
`);
}
break;
case constants.RET_CODE.PLAYER_NOT_ADDABLE_TO_ROOM:
@@ -213,9 +300,10 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
case constants.RET_CODE.MYSQL_ERROR:
case constants.RET_CODE.PLAYER_NOT_FOUND:
case constants.RET_CODE.PLAYER_CHEATING:
case 1006: // Peer(i.e. the backend) gone unexpectedly
case 1006: // Peer(i.e. the backend) gone unexpectedly, but not working for "cc.sys.isNative"
if (mapIns.frameDataLoggingEnabled) {
console.warn(`${mapIns._stringifyRdfIdToActuallyUsedInput()}`);
console.warn(`${mapIns._stringifyRdfIdToActuallyUsedInput()}
`);
}
window.clearLocalStorageAndBackToLoginScene(true);
break;
@@ -227,16 +315,15 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
window.clearLocalStorageAndBackToLoginScene = function(shouldRetainBoundRoomIdInBothVolatileAndPersistentStorage) {
console.warn("+++++++ Calling `clearLocalStorageAndBackToLoginScene`");
window.clearSelfPlayer();
if (true != shouldRetainBoundRoomIdInBothVolatileAndPersistentStorage) {
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
}
if (window.mapIns && window.mapIns.musicEffectManagerScriptIns) {
window.mapIns.musicEffectManagerScriptIns.stopAllMusic();
}
window.closeWSConnection();
window.clearSelfPlayer();
if (true != shouldRetainBoundRoomIdInBothVolatileAndPersistentStorage) {
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
}
cc.director.loadScene('login');
};
@@ -274,7 +361,7 @@ window.initSecondarySession = function(onopenCb, boundRoomId) {
//console.log(`Got non-empty onmessage decoded: resp.act=${resp.act}`);
switch (resp.act) {
case window.DOWNSYNC_MSG_ACT_PEER_INPUT_BATCH:
mapIns.onPeerInputFrameUpsync(resp.peerJoinIndex, resp.inputFrameDownsyncBatch);
mapIns.onPeerInputFrameUpsync(resp.peerJoinIndex, resp.inputFrameDownsyncBatch, false);
break;
default:
break;

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
{
"ver": "1.0.5",
"uuid": "40edd08e-316c-44b8-a50f-bd173554c554",
"uuid": "02c5cdc1-9797-49ab-bc11-963215909926",
"isPlugin": true,
"loadPluginInWeb": true,
"loadPluginInNative": true,

View File

@@ -0,0 +1,7 @@
{
"engine_version": "2.2.1",
"has_native": true,
"project_type": "js",
"projectName": "DelayNoMore",
"packageName": "org.genxium.delaynomore"
}

View File

@@ -0,0 +1,81 @@
{
"do_default": {
"exclude_from_template": [
"frameworks/runtime-src"
]
},
"do_add_native_support": {
"append_from_template": {
"from": "frameworks/runtime-src",
"to": "frameworks/runtime-src",
"exclude": [
"proj.win32/Debug.win32",
"proj.win32/Release.win32",
"proj.win32/DelayNoMore.sdf"
]
},
"append_file": [{
"from": "cocos/scripting/js-bindings/manual/jsb_module_register.cpp",
"to": "frameworks/runtime-src/Classes/jsb_module_register.cpp"
}, {
"from": "frameworks/runtime-src/Classes/ring_buff.hpp",
"to": "frameworks/runtime-src/Classes/ring_buff.hpp"
}, {
"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"
}, {
"from": "frameworks/runtime-src/Classes/udp_session.cpp",
"to": "frameworks/runtime-src/Classes/udp_session.cpp"
}, {
"from": "frameworks/runtime-src/Classes/udp_session_bridge.hpp",
"to": "frameworks/runtime-src/Classes/udp_session_bridge.hpp"
}, {
"from": "frameworks/runtime-src/Classes/udp_session_bridge.cpp",
"to": "frameworks/runtime-src/Classes/udp_session_bridge.cpp"
}],
"project_rename": {
"src_project_name": "DelayNoMore",
"files": [
"frameworks/runtime-src/proj.win32/PROJECT_NAME.vcxproj",
"frameworks/runtime-src/proj.win32/PROJECT_NAME.vcxproj.filters",
"frameworks/runtime-src/proj.win32/PROJECT_NAME.vcxproj.user",
"frameworks/runtime-src/proj.win32/PROJECT_NAME.sln"
]
},
"project_replace_project_name": {
"src_project_name": "DelayNoMore",
"files": [
"frameworks/runtime-src/proj.win32/PROJECT_NAME.vcxproj",
"frameworks/runtime-src/proj.win32/PROJECT_NAME.vcxproj.filters",
"frameworks/runtime-src/proj.win32/PROJECT_NAME.vcxproj.user",
"frameworks/runtime-src/proj.win32/PROJECT_NAME.sln",
"frameworks/runtime-src/proj.win32/main.cpp",
"frameworks/runtime-src/proj.android-studio/settings.gradle",
"frameworks/runtime-src/proj.android-studio/app/res/values/strings.xml",
"frameworks/runtime-src/Classes/AppDelegate.cpp"
]
},
"project_replace_package_name": {
"src_package_name": "org.genxium.delaynomore",
"files": [
"frameworks/runtime-src/proj.android-studio/app/build.gradle",
"frameworks/runtime-src/proj.android-studio/app/AndroidManifest.xml"
]
},
"project_replace_mac_bundleid": {
"src_bundle_id": "org.genxium.delaynomore",
"files": [
"frameworks/runtime-src/proj.ios_mac/mac/Info.plist"
]
},
"project_replace_ios_bundleid": {
"src_bundle_id": "org.genxium.delaynomore",
"files": [
"frameworks/runtime-src/proj.ios_mac/ios/Info.plist"
]
}
}
}

View File

@@ -0,0 +1,93 @@
/****************************************************************************
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "AppDelegate.h"
#include "cocos2d.h"
//#include "cocos/audio/include/AudioEngine.h"
#include "cocos/scripting/js-bindings/manual/jsb_module_register.hpp"
#include "cocos/scripting/js-bindings/manual/jsb_global.h"
#include "cocos/scripting/js-bindings/jswrapper/SeApi.h"
#include "cocos/scripting/js-bindings/event/EventDispatcher.h"
#include "cocos/scripting/js-bindings/manual/jsb_classtype.hpp"
#include "udp_session_bridge.hpp"
USING_NS_CC;
AppDelegate::AppDelegate(int width, int height) : Application("Cocos Game", width, height)
{
}
AppDelegate::~AppDelegate()
{
}
bool AppDelegate::applicationDidFinishLaunching()
{
se::ScriptEngine *se = se::ScriptEngine::getInstance();
jsb_set_xxtea_key("");
jsb_init_file_operation_delegate();
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
// Enable debugger here
jsb_enable_debugger("0.0.0.0", 6086, false);
#endif
se->setExceptionCallback([](const char *location, const char *message, const char *stack) {
// Send exception information to server like Tencent Bugly.
});
jsb_register_all_modules();
se->addRegisterCallback(registerUdpSession);
se->start();
se::AutoHandleScope hs;
jsb_run_script("jsb-adapter/jsb-builtin.js");
jsb_run_script("main.js");
se->addAfterCleanupHook([]() {
JSBClassType::destroy();
});
return true;
}
// This function will be called when the app is inactive. When comes a phone call,it's be invoked too
void AppDelegate::applicationDidEnterBackground()
{
EventDispatcher::dispatchEnterBackgroundEvent();
// Ensure that handle AudioEngine enter background after all enter background events are handled
//AudioEngine::onEnterBackground();
}
// this function will be called when the app is active again
void AppDelegate::applicationWillEnterForeground()
{
// Ensure that handle AudioEngine enter foreground before all enter foreground events are handled
//AudioEngine::onEnterForeground();
EventDispatcher::dispatchEnterForegroundEvent();
}

View File

@@ -0,0 +1,102 @@
#include <string.h>
#include "ring_buff.hpp"
// Sending
void SendRingBuff::put(BYTEC* const newBytes, size_t newBytesLen, PeerAddr* pNewPeerAddr) {
while (0 < cnt && cnt >= n) {
// Make room for the new element
this->pop();
}
eles[ed].bytesLen = newBytesLen;
memset(eles[ed].bytes, 0, sizeof eles[ed].bytes);
memcpy(eles[ed].bytes, newBytes, newBytesLen);
eles[ed].peerAddr = *(pNewPeerAddr);
ed++;
cnt++;
if (ed >= n) {
ed -= n; // Deliberately not using "%" operator for performance concern
}
}
SendWork* SendRingBuff::pop() {
if (0 == cnt) {
return NULL;
}
SendWork* ret = &(eles[st]);
cnt--;
st++;
if (st >= n) {
st -= n;
}
return ret;
}
// Recving
void RecvRingBuff::put(char* newBytes, size_t newBytesLen) {
RecvWork* slotEle = (&eles[ed.load()]); // Save for later update
int oldCnt = cnt.load();
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) {
// 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();
++tried;
}
if (n <= oldCnt && !ed.compare_exchange_weak(oldSt, oldSt) && 3 == tried) {
// Failed silently, UDP packet can be dropped.
return;
}
slotEle->bytesLen = newBytesLen;
memset(slotEle->ui8Arr, 0, sizeof slotEle->ui8Arr);
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
}
// Only increment cnt when the putting of new element is fully done.
cnt++;
}
bool RecvRingBuff::pop(RecvWork* out) {
int oldCnt = std::atomic_fetch_sub(&cnt, 1);
/*
[WARNING]
After here, two cases should be taken care of.
1. If "n == oldCnt", we need guard against "put" to avoid contaminating "ret" by the "putting".
2. If "0 >= oldCnt", we need guard against another "pop" to avoid over-popping.
*/
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 false;
}
// When concurrent "pop"s reach here, over-popping is definitely avoided.
int oldSt = st.load();
if (out) {
RecvWork* src = (&eles[oldSt]);
memset(out->ui8Arr, 0, sizeof out->ui8Arr);
memcpy(out->ui8Arr, src, src->bytesLen);
out->bytesLen = src->bytesLen;
}
int newSt = oldSt + 1;
if (newSt >= n) {
newSt -= n;
}
if (st.compare_exchange_weak(oldSt, newSt)) {
return true;
} else {
// Failed concurrent access should recover the "cnt"
++cnt;
return false;
}
}

View File

@@ -0,0 +1,74 @@
#ifndef send_ring_buff_hpp
#define send_ring_buff_hpp
#include "uv/uv.h"
#define __SSIZE_T // Otherwise "ssize_t" would have conflicting macros error that stops compiling
#include <atomic>
int const RING_BUFF_CONSECUTIVE_SET = 0;
int const RING_BUFF_NON_CONSECUTIVE_SET = 1;
int const RING_BUFF_FAILED_TO_SET = 2;
typedef char BYTEC;
typedef char const CHARC;
int const maxUdpPayloadBytes = 128;
int const maxBuffedMsgs = 512;
struct PeerAddr {
struct sockaddr_in sockAddrIn;
uint32_t authKey;
};
class SendWork {
public:
BYTEC bytes[maxUdpPayloadBytes]; // Wasting some RAM here thus no need for explicit recursive destruction
size_t bytesLen;
PeerAddr peerAddr;
};
// [WARNING] This class is specific to "SendWork", designed and implemented only to use in multithreading env and save heap alloc/dealloc timecomplexity, it's by no means comparable to the Golang or JavaScript versions!
class SendRingBuff {
public:
int ed, st, n, cnt;
SendWork eles[maxBuffedMsgs]; // preallocated on stack to save heap alloc/dealloc time
SendRingBuff(int newN) {
this->n = newN;
this->st = this->ed = this->cnt = 0;
}
void put(BYTEC* const newBytes, size_t newBytesLen, PeerAddr* pNewPeerAddr);
// Sending is always sequential in UvSendThread, no need to return a copy of "SendWork" instance
SendWork* pop();
};
// TODO: Move "RecvXxxx" to a dedicated class.
class RecvWork {
public:
uint8_t ui8Arr[maxUdpPayloadBytes]; // Wasting some RAM here thus no need for explicit recursive destruction
size_t bytesLen;
};
/*
[WARNING] This class is specific to "RecvWork"; its "put" and "pop" methods are designed to be thread-safe & lock-free for our particular case, i.e. only concurrent access from "UvRecvThread" & "GameThread", in a sense more sophisticated than the Golang or JavaScript versions.
There's yet no plan to support thread-safe & lock-free "getByFrameId/setByFrameId" -- being thread-safe is easy by use of mutex, which is very SLOWWWWW when used in 60fps race-conditions.
The generic "thread-safe, lock-free ring buffer or circular buffer" is a big problem, widely discussed over the internet and in literatures, search "lock-free circular buffer" for more information.
*/
class RecvRingBuff {
public:
int n;
std::atomic_int ed, st, cnt;
RecvWork eles[maxBuffedMsgs]; // preallocated on stack to save heap alloc/dealloc time
RecvRingBuff(int newN) {
this->n = newN;
this->st = this->ed = this->cnt = 0;
}
void put(char* newBytes, size_t newBytesLen);
bool pop(RecvWork* out);
};
#endif

View File

@@ -0,0 +1,384 @@
#include "udp_session.hpp"
#include "base/ccMacros.h"
#include "cocos/platform/CCApplication.h"
#include "cocos/base/CCScheduler.h"
#include "cocos/scripting/js-bindings/jswrapper/SeApi.h"
int const punchServerCnt = 3;
int const punchPeerCnt = 3;
int const broadcastUpsyncCnt = 2;
uv_udp_t *udpRecvSocket = NULL, *udpSendSocket = NULL;
uv_thread_t recvTid, sendTid;
uv_async_t uvRecvLoopStopSig, uvSendLoopStopSig, uvSendLoopTriggerSig;
uv_loop_t *recvLoop = NULL, *sendLoop = NULL;
uv_mutex_t sendRingBuffLock; // used along with "uvSendLoopTriggerSig" as a "uv_cond_t"
SendRingBuff* sendRingBuff = NULL;
uv_mutex_t recvRingBuffLock;
RecvRingBuff* recvRingBuff = NULL;
char SRV_IP[256];
int SRV_PORT = 0;
int UDP_TUNNEL_SRV_PORT = 0;
struct PeerAddr udpPunchingServerAddr, udpTunnelAddr;
struct PeerAddr peerAddrList[maxPeerCnt];
bool peerPunchedMarks[maxPeerCnt];
void _onRead(uv_udp_t* req, ssize_t nread, uv_buf_t const* buf, struct sockaddr const* addr, unsigned flags) {
if (nread < 0) {
CCLOGERROR("Read error %s", uv_err_name(nread));
uv_close((uv_handle_t*)req, NULL);
free(buf->base);
return;
}
struct sockaddr_in const* sockAddr = (struct sockaddr_in const*)addr;
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
char ip[INET_ADDRSTRLEN];
memset(ip, 0, sizeof ip);
int port = 0;
if (NULL != addr) {
// The null check for "addr" is necessary, on Android there'd be such mysterious call to "_onRead"!
switch (addr->sa_family) {
case AF_INET: {
uv_inet_ntop(sockAddr->sin_family, &(sockAddr->sin_addr), ip, INET_ADDRSTRLEN);
port = ntohs(sockAddr->sin_port);
break;
}
default:
break;
}
} else {
CCLOG("UDP received %u bytes from unknown sender", nread);
}
#endif
if (6 == nread) {
// Peer holepunching
for (int i = 0; i < maxPeerCnt; i++) {
if (peerAddrList[i].sockAddrIn.sin_addr.s_addr != sockAddr->sin_addr.s_addr) continue;
if (peerAddrList[i].sockAddrIn.sin_port != sockAddr->sin_port) continue;
peerPunchedMarks[i] = true;
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
CCLOG("UDP received peer-holepunching from %s:%d", ip, port);
#endif
break;
}
} else if (0 < nread) {
// Non-holepunching; the previously used "cocos2d::Application::getInstance()->getScheduler()->performFunctionInCocosThread(...)" approach was so non-deterministic in terms of the lag till GameThread actually recognizes this latest received packet due to scheduler uncertainty -- and was also heavier in RAM due to lambda usage
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
CCLOG("UDP received %u bytes inputFrameUpsync from %s:%d", nread, ip, port);
#endif
//uv_mutex_lock(&recvRingBuffLock);
recvRingBuff->put(buf->base, nread);
//uv_mutex_unlock(&recvRingBuffLock);
}
free(buf->base);
/*
// [WARNING] Don't call either of the following statements! They will make "_onRead" no longer called for incoming packets!
//uv_udp_recv_stop(req);
//uv_close((uv_handle_t*)req, NULL);
*/
}
static void _allocBuffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
(void)handle;
buf->base = (char*)malloc(suggested_size);
buf->len = suggested_size;
}
void _onUvStopSig(uv_async_t* handle) {
if (!handle) return;
uv_stop(handle->loop);
CCLOG("UDP loop %p is signaled to stop in UvXxxxThread", handle->loop);
}
void _afterSend(uv_udp_send_t* req, int status) {
if (req) {
free(req);
}
if (status) {
CCLOGERROR("uv_udp_send_cb error: %s\n", uv_strerror(status));
}
}
void _onUvSthNewToSend(uv_async_t* handle) {
bool hasNext = true;
while (NULL != handle && true == hasNext) {
SendWork* work = NULL;
uv_mutex_lock(&sendRingBuffLock);
work = sendRingBuff->pop();
if (NULL == work) {
hasNext = false;
}
/*
[WARNING] The following "uv_udp_try_send" might block I / O for a long time, hence unlock "as soon as possible" to avoid blocking the "GameThread" which is awaiting to acquire this mutex!
There's a very small chance where "sendRingBuff->put(...)" could contaminate the just popped "work" in "sendRingBuff->eles", thus "sendRingBuff->n" is made quite large to avoid that, moreover in terms of protecting "work" we're also unlocking "as late as possible"!
*/
uv_mutex_unlock(&sendRingBuffLock);
if (NULL != work) {
// [WARNING] If "uv_udp_send" is to be used instead of "uv_udp_try_send", as UvSendThread will always be terminated from GameThread, it's a MUST to use the following heap-alloc form to initialize "uv_udp_send_t* req" such that "_afterSend" is guaranteed to be called, otherwise "int uvRunRet2 = uv_run(l, UV_RUN_DEFAULT);" for UvSendThread would block forever due to residual active handles.
uv_udp_send_t* req = (uv_udp_send_t*)malloc(sizeof(uv_udp_send_t));
uv_buf_t sendBuffer = uv_buf_init(work->bytes, work->bytesLen);
uv_udp_send(req, udpSendSocket, &sendBuffer, 1, (struct sockaddr const*)&(work->peerAddr.sockAddrIn), _afterSend);
//uv_buf_t sendBuffer = uv_buf_init(work->bytes, work->bytesLen);
//uv_udp_try_send(udpSendSocket, &sendBuffer, 1, (struct sockaddr const*)&(work->peerAddr.sockAddrIn));
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
char ip[INET_ADDRSTRLEN];
memset(ip, 0, sizeof ip);
uv_inet_ntop(work->peerAddr.sockAddrIn.sin_family, &(work->peerAddr.sockAddrIn.sin_addr), ip, INET_ADDRSTRLEN);
int port = ntohs(work->peerAddr.sockAddrIn.sin_port);
CCLOG("UDP sent %d bytes to %s:%d", sendBuffer.len, ip, port);
#endif
}
}
}
void _onWalkCleanup(uv_handle_t* handle, void* data) {
(void)data;
uv_close(handle, NULL);
}
void startRecvLoop(void* arg) {
uv_loop_t* l = (uv_loop_t*)arg;
int uvRunRet1 = uv_run(l, UV_RUN_DEFAULT);
CCLOG("UDP recv loop is ended in UvRecvThread, uvRunRet1=%d", uvRunRet1);
uv_walk(l, _onWalkCleanup, NULL);
CCLOG("UDP recv loop is walked in UvRecvThread");
int uvRunRet2 = uv_run(l, UV_RUN_DEFAULT);
CCLOG("UDP recv loop is run after walking in UvRecvThread, uvRunRet2=%d", uvRunRet2);
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) {
uv_loop_t* l = (uv_loop_t*)arg;
int uvRunRet1 = uv_run(l, UV_RUN_DEFAULT);
CCLOG("UDP send loop is ended in UvSendThread, uvRunRet1=%d", uvRunRet1);
uv_walk(l, _onWalkCleanup, NULL);
CCLOG("UDP send loop is walked in UvSendThread");
int uvRunRet2 = uv_run(l, UV_RUN_DEFAULT);
CCLOG("UDP send loop is run after walking in UvSendThread, uvRunRet2=%d", uvRunRet2);
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) {
sendLoop = uv_loop_new();
udpSendSocket = (uv_udp_t*)malloc(sizeof(uv_udp_t));
int sendSockInitRes = uv_udp_init(sendLoop, udpSendSocket); // "uv_udp_init" must precede that of "uv_udp_bind" for successful binding!
int sendBindRes = uv_udp_bind(udpSendSocket, pUdpAddr, UV_UDP_REUSEADDR);
if (0 != sendBindRes) {
CCLOGERROR("Failed to bind send; sendSockInitRes=%d, sendBindRes=%d, reason=%s", sendSockInitRes, sendBindRes, uv_strerror(sendBindRes));
exit(-1);
}
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);
return sendBindRes;
}
bool initRecvLoop(struct sockaddr const* pUdpAddr) {
recvLoop = uv_loop_new();
udpRecvSocket = (uv_udp_t*)malloc(sizeof(uv_udp_t));
int recvSockInitRes = uv_udp_init(recvLoop, udpRecvSocket);
int recvbindRes = uv_udp_bind(udpRecvSocket, pUdpAddr, UV_UDP_REUSEADDR);
if (0 != recvbindRes) {
CCLOGERROR("Failed to bind recv; recvSockInitRes=%d, recvbindRes=%d, reason=%s", recvSockInitRes, recvbindRes, uv_strerror(recvbindRes));
exit(-1);
}
uv_udp_recv_start(udpRecvSocket, _allocBuffer, _onRead);
uv_async_init(recvLoop, &uvRecvLoopStopSig, _onUvStopSig);
return recvbindRes;
}
bool DelayNoMore::UdpSession::openUdpSession(int port) {
struct sockaddr_in udpAddr;
uv_ip4_addr("0.0.0.0", port, &udpAddr);
struct sockaddr const* pUdpAddr = (struct sockaddr const*)&udpAddr;
memset(peerPunchedMarks, false, sizeof(peerPunchedMarks));
for (int i = 0; i < maxPeerCnt; i++) {
peerAddrList[i].authKey = -1; // hardcoded for now
memset((char*)&peerAddrList[i].sockAddrIn, 0, sizeof(peerAddrList[i].sockAddrIn));
}
/*
[WARNING] On Android, the libuv documentation of "UV_UDP_REUSEADDR" is true, i.e. only the socket that binds later on the same port will be triggered the recv callback; however on Windows, experiment shows that the exact reverse is true instead.
It's feasible to use a same socket instance for both receiving and sending in different threads, however not knowing the exact thread-safety concerns for "uv_udp_send/uv_udp_try_send" & "uv recv callback" stops me from doing so, I'd prefer to stick to using different socket instances in different threads.
*/
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
initSendLoop(pUdpAddr);
initRecvLoop(pUdpAddr);
#else
initRecvLoop(pUdpAddr);
initSendLoop(pUdpAddr);
#endif
CCLOG("About to open UDP session at port=%d; recvLoop=%p, sendLoop=%p...", port, recvLoop, sendLoop);
uv_thread_create(&recvTid, startRecvLoop, recvLoop);
uv_thread_create(&sendTid, startSendLoop, sendLoop);
CCLOG("Finished opening UDP session at port=%d", port);
return true;
}
bool DelayNoMore::UdpSession::closeUdpSession() {
CCLOG("About to close udp session and dealloc all resources...");
uv_async_send(&uvSendLoopStopSig);
CCLOG("Signaling UvSendThread to end in GameThread...");
uv_thread_join(&sendTid);
free(udpSendSocket);
free(sendLoop);
delete sendRingBuff;
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;
CCLOG("Closed udp session and dealloc all resources in GameThread...");
return true;
}
bool DelayNoMore::UdpSession::punchToServer(CHARC* const srvIp, int const srvPort, BYTEC* const bytes, size_t bytesLen, int const udpTunnelSrvPort, BYTEC* const udpTunnelBytes, size_t udpTunnelBytesBytesLen) {
memset(SRV_IP, 0, sizeof SRV_IP);
memcpy(SRV_IP, srvIp, strlen(srvIp));
SRV_PORT = srvPort;
UDP_TUNNEL_SRV_PORT = udpTunnelSrvPort;
struct sockaddr_in udpPunchingServerDestAddr;
uv_ip4_addr(SRV_IP, SRV_PORT, &udpPunchingServerDestAddr);
udpPunchingServerAddr.sockAddrIn = udpPunchingServerDestAddr;
struct sockaddr_in udpTunnelDestAddr;
uv_ip4_addr(SRV_IP, UDP_TUNNEL_SRV_PORT, &udpTunnelDestAddr);
udpTunnelAddr.sockAddrIn = udpTunnelDestAddr;
/*
Libuv is really inconvenient here, neither "uv_queue_work" nor "uv_async_init" is threadsafe(http ://docs.libuv.org/en/v1.x/threadpool.html#c.uv_queue_work)! What's the point of such a queue? It's even more difficult than writing my own implementation -- again a threadsafe RingBuff could be used to the rescue, yet I'd like to investigate more into how to make the following threadsafe APIs with minimum cross-platform C++ codes
- _sendMessage(...), should be both non-blocking & threadsafe, called from GameThread
- _onRead(...), should be called first in UvRecvThread in an edge-triggered manner like idiomatic "epoll" or "kqueue", then dispatch the received message to GameThread by a threadsafe RingBuff
*/
uv_mutex_lock(&sendRingBuffLock);
sendRingBuff->put(bytes, bytesLen, &udpPunchingServerAddr);
sendRingBuff->put(udpTunnelBytes, udpTunnelBytesBytesLen, &udpTunnelAddr);
uv_mutex_unlock(&sendRingBuffLock);
uv_async_send(&uvSendLoopTriggerSig);
return true;
}
bool DelayNoMore::UdpSession::upsertPeerUdpAddr(struct PeerAddr* newPeerAddrList, int roomCapacity, int selfJoinIndex) {
// Call timer for multiple sendings from JavaScript?
CCLOG("upsertPeerUdpAddr called by js for roomCapacity=%d, selfJoinIndex=%d.", roomCapacity, selfJoinIndex);
uv_mutex_lock(&sendRingBuffLock);
for (int i = 0; i < roomCapacity; i++) {
if (i == selfJoinIndex - 1) continue;
struct PeerAddr* cand = (newPeerAddrList + i);
if (NULL == cand || 0 == cand->sockAddrIn.sin_port) continue; // Not initialized
peerAddrList[i].sockAddrIn = cand->sockAddrIn;
peerAddrList[i].authKey = cand->authKey;
sendRingBuff->put("foobar", 6, &(peerAddrList[i])); // Content hardcoded for now
}
uv_mutex_unlock(&sendRingBuffLock);
uv_async_send(&uvSendLoopTriggerSig);
return true;
}
bool DelayNoMore::UdpSession::broadcastInputFrameUpsync(BYTEC* const bytes, size_t bytesLen, int roomCapacity, int selfJoinIndex) {
uv_mutex_lock(&sendRingBuffLock);
// Might want to send several times for better arrival rate
for (int j = 0; j < broadcastUpsyncCnt; j++) {
int peerPunchedCnt = 0;
for (int i = 0; i < roomCapacity; i++) {
if (i + 1 == selfJoinIndex) {
continue;
}
if (0 == peerAddrList[i].sockAddrIn.sin_port) {
// Peer addr not initialized
continue;
}
if (false == peerPunchedMarks[i]) {
// Not punched yet, save some bandwidth
continue;
}
sendRingBuff->put(bytes, bytesLen, &(peerAddrList[i]));
++peerPunchedCnt;
}
if (peerPunchedCnt + 1 < roomCapacity) {
// Send to room udp tunnel in case of ANY hole punching failure
sendRingBuff->put(bytes, bytesLen, &udpTunnelAddr);
}
}
uv_mutex_unlock(&sendRingBuffLock);
uv_async_send(&uvSendLoopTriggerSig);
return true;
}
bool DelayNoMore::UdpSession::pollUdpRecvRingBuff() {
// This function is called by GameThread 60 fps.
//uv_mutex_lock(&recvRingBuffLock);
while (true) {
RecvWork f;
bool res = recvRingBuff->pop(&f);
if (!res) return false;
// [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"!
se::Value onUdpMessageCb;
se::ScriptEngine::getInstance()->getGlobalObject()->getProperty("onUdpMessage", &onUdpMessageCb);
if (onUdpMessageCb.isObject() && onUdpMessageCb.toObject()->isFunction()) {
//CCLOG("UDP received %d bytes upsync -- 1", nread);
se::Object* const gameThreadMsg = se::Object::createTypedArray(se::Object::TypedArrayType::UINT8, f.ui8Arr, f.bytesLen);
//CCLOG("UDP received %d bytes upsync -- 2", nread);
se::ValueArray args = { se::Value(gameThreadMsg) };
// Temporarily assume that the "this" ptr within callback is NULL.
bool ok = onUdpMessageCb.toObject()->call(args, NULL);
if (!ok) {
se::ScriptEngine::getInstance()->clearException();
}
//CCLOG("UDP received %d bytes upsync -- 3", nread);
gameThreadMsg->decRef(); // Reference http://docs.cocos.com/creator/2.2/manual/en/advanced-topics/JSB2.0-learning.html#seobject
//CCLOG("UDP received %d bytes upsync -- 4", nread);
}
}
//uv_mutex_unlock(&recvRingBuffLock);
return true;
}

View File

@@ -0,0 +1,20 @@
#ifndef udp_session_hpp
#define udp_session_hpp
#include "ring_buff.hpp"
int const maxPeerCnt = 10;
namespace DelayNoMore {
class UdpSession {
public:
static bool openUdpSession(int port);
static bool closeUdpSession();
static bool upsertPeerUdpAddr(struct PeerAddr* newPeerAddrList, int roomCapacity, int selfJoinIndex);
//static bool clearPeerUDPAddrList();
static bool punchToServer(CHARC* const srvIp, int const srvPort, BYTEC* const bytes, size_t bytesLen, int const udpTunnelSrvPort, BYTEC* const udpTunnelBytes, size_t udpTunnelBytesBytesLen);
static bool broadcastInputFrameUpsync(BYTEC* const bytes, size_t bytesLen, int roomCapacity, int selfJoinIndex);
static bool pollUdpRecvRingBuff();
};
}
#endif

View File

@@ -0,0 +1,184 @@
#include "udp_session.hpp"
#include "base/ccMacros.h"
#include "scripting/js-bindings/manual/jsb_conversions.hpp"
bool openUdpSession(se::State& s) {
const auto& args = s.args();
size_t argc = args.size();
CC_UNUSED bool ok = true;
if (1 == argc && args[0].isNumber()) {
SE_PRECONDITION2(ok, false, "openUdpSession: Error processing arguments");
int port = args[0].toInt32();
return DelayNoMore::UdpSession::openUdpSession(port);
}
SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d; or wrong arg type!", (int)argc, 1);
return false;
}
SE_BIND_FUNC(openUdpSession)
bool punchToServer(se::State& s) {
const auto& args = s.args();
size_t argc = args.size();
CC_UNUSED bool ok = true;
if (5 == argc && args[0].isString() && args[1].isNumber() && args[2].isObject() && args[2].toObject()->isTypedArray()
&& args[3].isNumber() && args[4].isObject() && args[4].toObject()->isTypedArray()
) {
SE_PRECONDITION2(ok, false, "punchToServer: Error processing arguments");
CHARC* srvIp = args[0].toString().c_str();
int srvPort = args[1].toInt32();
BYTEC bytes[maxUdpPayloadBytes];
memset(bytes, 0, sizeof bytes);
se::Object* obj = args[2].toObject();
size_t sz = 0;
uint8_t* ptr = NULL;
obj->getTypedArrayData(&ptr, &sz);
for (size_t i = 0; i < sz; i++) {
bytes[i] = (char)(*(ptr + i));
}
int udpTunnelSrvPort = args[3].toInt32();
BYTEC udpTunnelBytes[maxUdpPayloadBytes];
memset(udpTunnelBytes, 0, sizeof udpTunnelBytes);
se::Object* udpTunnelObj = args[4].toObject();
size_t udpTunnelSz = 0;
uint8_t* udpTunnelPtr = NULL;
udpTunnelObj->getTypedArrayData(&udpTunnelPtr, &udpTunnelSz);
for (size_t i = 0; i < udpTunnelSz; i++) {
udpTunnelBytes[i] = (char)(*(udpTunnelPtr + i));
}
CCLOG("Should punch %s:%d by %d bytes; should punch udp tunnel %s:%d by %d bytes.", srvIp, srvPort, sz, srvIp, udpTunnelSrvPort, udpTunnelSz);
return DelayNoMore::UdpSession::punchToServer(srvIp, srvPort, bytes, sz, udpTunnelSrvPort, udpTunnelBytes, udpTunnelSz);
}
SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d; or wrong arg type!", (int)argc, 5);
return false;
}
SE_BIND_FUNC(punchToServer)
bool broadcastInputFrameUpsync(se::State& s) {
const auto& args = s.args();
size_t argc = args.size();
CC_UNUSED bool ok = true;
if (3 == argc && args[0].toObject()->isTypedArray() && args[1].isNumber() && args[2].isNumber()) {
SE_PRECONDITION2(ok, false, "broadcastInputFrameUpsync: Error processing arguments");
BYTEC bytes[1024];
memset(bytes, 0, sizeof bytes);
se::Object* obj = args[0].toObject();
size_t sz = 0;
uint8_t* ptr = NULL;
obj->getTypedArrayData(&ptr, &sz);
for (size_t i = 0; i < sz; i++) {
bytes[i] = (char)(*(ptr + i));
}
int roomCapacity = args[1].toInt32();
int selfJoinIndex = args[2].toInt32();
CCLOG("Should broadcastInputFrameUpsync %u bytes; roomCapacity=%d, selfJoinIndex=%d.", sz, roomCapacity, selfJoinIndex);
return DelayNoMore::UdpSession::broadcastInputFrameUpsync(bytes, sz, roomCapacity, selfJoinIndex);
}
SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d; or wrong arg type!", (int)argc, 3);
return false;
}
SE_BIND_FUNC(broadcastInputFrameUpsync)
bool closeUdpSession(se::State& s) {
const auto& args = s.args();
size_t argc = args.size();
CC_UNUSED bool ok = true;
if (0 == argc) {
SE_PRECONDITION2(ok, false, "closeUdpSession: Error processing arguments");
return DelayNoMore::UdpSession::closeUdpSession();
}
SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 0);
return false;
}
SE_BIND_FUNC(closeUdpSession)
struct PeerAddr newPeerAddrList[maxPeerCnt];
bool upsertPeerUdpAddr(se::State& s) {
const auto& args = s.args();
size_t argc = args.size();
CC_UNUSED bool ok = true;
if (3 == argc && args[0].isObject() && args[0].toObject()->isArray() && args[1].isNumber() && args[2].isNumber()) {
SE_PRECONDITION2(ok, false, "upsertPeerUdpAddr: Error processing arguments");
int roomCapacity = args[1].toInt32();
int selfJoinIndex = args[2].toInt32();
se::Object* newPeerAddrValArr = args[0].toObject();
for (int i = 0; i < roomCapacity; i++) {
se::Value newPeerAddrVal;
newPeerAddrValArr->getArrayElement(i, &newPeerAddrVal);
se::Object* newPeerAddrObj = newPeerAddrVal.toObject();
se::Value newIp, newPort, newAuthKey;
newPeerAddrObj->getProperty("ip", &newIp);
newPeerAddrObj->getProperty("port", &newPort);
newPeerAddrObj->getProperty("authKey", &newAuthKey);
uv_ip4_addr(newIp.toString().c_str(), newPort.toInt32(), &(newPeerAddrList[i].sockAddrIn));
newPeerAddrList[i].authKey = newAuthKey.toInt32();
}
return DelayNoMore::UdpSession::upsertPeerUdpAddr(newPeerAddrList, roomCapacity, selfJoinIndex);
}
SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d; or wrong arg type!", (int)argc, 3);
return false;
}
SE_BIND_FUNC(upsertPeerUdpAddr)
bool pollUdpRecvRingBuff(se::State& s) {
const auto& args = s.args();
size_t argc = args.size();
CC_UNUSED bool ok = true;
if (0 == argc) {
SE_PRECONDITION2(ok, false, "upsertPeerUdpAddr: Error processing arguments");
return DelayNoMore::UdpSession::pollUdpRecvRingBuff();
}
SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d; or wrong arg type!", (int)argc, 0);
return false;
}
SE_BIND_FUNC(pollUdpRecvRingBuff)
static bool udpSessionFinalize(se::State& s) {
CCLOGINFO("jsbindings: finalizing JS object %p (DelayNoMore::UdpSession)", s.nativeThisObject());
auto iter = se::NonRefNativePtrCreatedByCtorMap::find(s.nativeThisObject());
if (iter != se::NonRefNativePtrCreatedByCtorMap::end()) {
se::NonRefNativePtrCreatedByCtorMap::erase(iter);
DelayNoMore::UdpSession* cobj = (DelayNoMore::UdpSession*)s.nativeThisObject();
delete cobj;
}
return true;
}
SE_BIND_FINALIZE_FUNC(udpSessionFinalize)
se::Object* __jsb_udp_session_proto = nullptr;
se::Class* __jsb_udp_session_class = nullptr;
bool registerUdpSession(se::Object* obj) {
// Get the ns
se::Value nsVal;
if (!obj->getProperty("DelayNoMore", &nsVal))
{
se::HandleObject jsobj(se::Object::createPlainObject());
nsVal.setObject(jsobj);
obj->setProperty("DelayNoMore", nsVal);
}
se::Object* ns = nsVal.toObject();
auto cls = se::Class::create("UdpSession", ns, nullptr, nullptr);
cls->defineStaticFunction("openUdpSession", _SE(openUdpSession));
cls->defineStaticFunction("punchToServer", _SE(punchToServer));
cls->defineStaticFunction("broadcastInputFrameUpsync", _SE(broadcastInputFrameUpsync));
cls->defineStaticFunction("closeUdpSession", _SE(closeUdpSession));
cls->defineStaticFunction("upsertPeerUdpAddr", _SE(upsertPeerUdpAddr));
cls->defineStaticFunction("pollUdpRecvRingBuff", _SE(pollUdpRecvRingBuff));
cls->defineFinalizeFunction(_SE(udpSessionFinalize));
cls->install();
JSBClassType::registerClass<DelayNoMore::UdpSession>(cls);
__jsb_udp_session_proto = cls->getProto();
__jsb_udp_session_class = cls;
se::ScriptEngine::getInstance()->clearException();
return true;
}

View File

@@ -0,0 +1,19 @@
#ifndef udp_session_bridge_hpp
#define udp_session_bridge_hpp
#pragma once
#include "base/ccConfig.h"
#include "cocos/scripting/js-bindings/jswrapper/SeApi.h"
extern se::Object* __jsb_udp_session_proto;
extern se::Class* __jsb_udp_session_class;
bool registerUdpSession(se::Object* obj);
SE_DECLARE_FUNC(openUdpSession);
SE_DECLARE_FUNC(punchToServer);
SE_DECLARE_FUNC(broadcastInputFrameUpsync);
SE_DECLARE_FUNC(closeUdpSession);
SE_DECLARE_FUNC(upsertPeerUdpAddr);
SE_DECLARE_FUNC(pollUdpRecvRingBuff);
#endif

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.genxium.delaynomore"
android:installLocation="auto">
<uses-feature android:glEsVersion="0x00020000" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<!-- TODO: Remove "usesCleartextTraffic" in production -->
<application
android:allowBackup="true"
android:label="@string/app_name"
android:usesCleartextTraffic="true"
android:icon="@mipmap/ic_launcher">
<!-- Tell Cocos2dxActivity the name of our .so -->
<meta-data android:name="android.app.lib_name"
android:value="cocos2djs" />
<activity
android:name="org.cocos2dx.javascript.AppActivity"
android:screenOrientation="sensorLandscape"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/app_name"
android:usesCleartextTraffic="true"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

Some files were not shown because too many files have changed in this diff Show More