Compare commits

...

9 Commits

Author SHA1 Message Date
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
25 changed files with 3254 additions and 395 deletions

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

@@ -13,7 +13,7 @@ This project is a demo for a websocket-based rollback netcode inspired by [GGPO]
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.
- Browser vs `native app` is possible but in that case only websocket is used. For WebRTC integration plan please see [ConcerningEdgeCases](./ConcerningEdgeCases.md). You might also be interested in visiting [netplayjs](https://github.com/rameshvarun/netplayjs) to see how others use WebRTC for browser game synchronization as well.
# Notable Features
@@ -120,6 +120,24 @@ When building for native platforms, it's much more convenient to trigger the Coc
```
shell> cd <proj-root>
shell> /path/to/CocosCreator.exe --path ./frontend --build "platform=win32;debug=true"
shell> cd ./frontend/build/jsb-link/frameworks/runtime-src/proj.win32 && MSBUILD DelayNoMore.vcxproj -property:Configuration=Debug
```
or
```
shell> cd <proj-root>
shell> /path/to/CocosCreator.exe --path ./frontend --build "platform=win32;debug=false"
shell> cd ./frontend/build/jsb-link/frameworks/runtime-src/proj.win32 && MSBUILD DelayNoMore.vcxproj -property:Configuration=Release
```
for release.
If `MSBUILD` command is not yet added to `PATH`, Use `Get-Command MSBUILD` in `Developer Command Prompt for VS 2017/2019` to see where the command should come from and add it to `PATH`.
Similarly for Android release build
```
shell> cd <proj-root>
shell> /path/to/CocosCreator.exe --path ./frontend --build "platform=android;debug=false"
shell> cd ./frontend/build/jsb-link/frameworks/runtime-src/proj.android-studio && ./gradlew assembleRelease
```
### 2.4 CococCreator native build reloading

View File

@@ -168,7 +168,7 @@ 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 {
@@ -180,7 +180,7 @@ 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
@@ -416,15 +416,6 @@ 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"
@@ -954,7 +945,7 @@ func (pR *Room) clearPlayerNetworkSession(playerId int32) {
}
}
func (pR *Room) onPlayerAdded(playerId int32) {
func (pR *Room) onPlayerAdded(playerId int32, speciesId int) {
pR.EffectivePlayerCount++
if 1 == pR.EffectivePlayerCount {
@@ -966,8 +957,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".

View File

@@ -50,7 +50,17 @@ func Serve(c *gin.Context) {
boundRoomId := 0
expectedRoomId := 0
speciesId := 0
var err error
if speciesIdStr, hasSpeciesId := c.GetQuery("speciesId"); hasSpeciesId {
speciesId, err = strconv.Atoi(speciesIdStr)
if err != nil {
// TODO: Abort with specific message.
c.AbortWithStatus(http.StatusBadRequest)
return
}
}
if boundRoomIdStr, hasBoundRoomId := c.GetQuery("boundRoomId"); hasBoundRoomId {
boundRoomId, err = strconv.Atoi(boundRoomIdStr)
if err != nil {
@@ -195,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))
@@ -219,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))
}

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

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

View File

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

View File

@@ -77,12 +77,6 @@
],
"_active": true,
"_components": [
{
"__id__": 49
},
{
"__id__": 50
},
{
"__id__": 51
},
@@ -91,6 +85,12 @@
},
{
"__id__": 53
},
{
"__id__": 54
},
{
"__id__": 55
}
],
"_prefab": null,
@@ -244,10 +244,10 @@
"__uuid__": "670b477e-61a1-4778-879b-35913f7c79d2"
},
"boundRoomIdLabel": {
"__id__": 26
"__id__": 28
},
"countdownLabel": {
"__id__": 33
"__id__": 35
},
"resultPanelPrefab": {
"__uuid__": "c4cfe3bd-c59e-4d5b-95cb-c933b120e184"
@@ -261,26 +261,26 @@
"countdownToBeginGamePrefab": {
"__uuid__": "230eeb1f-e0f9-4a41-ab6c-05b3771cbf3e"
},
"playersInfoPrefab": {
"__uuid__": "b4e519f4-e698-4403-9ff2-47b8dacb077e"
},
"forceBigEndianFloatingNumDecoding": false,
"renderFrameIdLagTolerance": 4,
"sendingQLabel": {
"inputFrameFrontLabel": {
"__id__": 13
},
"inputFrameDownsyncQLabel": {
"sendingQLabel": {
"__id__": 15
},
"peerInputFrameUpsyncQLabel": {
"inputFrameDownsyncQLabel": {
"__id__": 17
},
"rollbackFramesLabel": {
"peerInputFrameUpsyncQLabel": {
"__id__": 19
},
"skippedRenderFrameCntLabel": {
"rollbackFramesLabel": {
"__id__": 21
},
"skippedRenderFrameCntLabel": {
"__id__": 23
},
"_id": "d12gkAmppNlIzqcRDELa91"
},
{
@@ -292,13 +292,13 @@
},
"_children": [
{
"__id__": 43
"__id__": 45
}
],
"_active": true,
"_components": [
{
"__id__": 48
"__id__": 50
}
],
"_prefab": null,
@@ -360,20 +360,20 @@
{
"__id__": 6
},
{
"__id__": 32
},
{
"__id__": 34
},
{
"__id__": 38
"__id__": 36
},
{
"__id__": 40
}
],
"_active": true,
"_components": [
{
"__id__": 42
"__id__": 44
}
],
"_prefab": null,
@@ -442,7 +442,7 @@
"_active": true,
"_components": [
{
"__id__": 31
"__id__": 33
}
],
"_prefab": null,
@@ -536,7 +536,7 @@
"array": [
0,
0,
216.50635094610968,
216.65450766436658,
0,
0,
0,
@@ -620,15 +620,18 @@
},
{
"__id__": 22
},
{
"__id__": 24
}
],
"_active": true,
"_components": [
{
"__id__": 29
"__id__": 31
},
{
"__id__": 30
"__id__": 32
}
],
"_prefab": null,
@@ -681,7 +684,7 @@
},
{
"__type__": "cc.Node",
"_name": "sendingQ",
"_name": "inputFrameFront",
"_objFlags": 0,
"_parent": {
"__id__": 11
@@ -739,7 +742,7 @@
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": "ack7XH+0lEj6chSUsKBLU5"
"_id": "83n6IAj9tAkop6CA/MyM0A"
},
{
"__type__": "cc.Label",
@@ -771,11 +774,11 @@
"_N$fontFamily": "Arial",
"_N$overflow": 0,
"_N$cacheMode": 0,
"_id": "deCJfLuoFO36c/O4lXWJ1N"
"_id": "a4CfeVmexNm7nULcCRHcVl"
},
{
"__type__": "cc.Node",
"_name": "inputFrameDownsyncQ",
"_name": "sendingQ",
"_objFlags": 0,
"_parent": {
"__id__": 11
@@ -833,7 +836,7 @@
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": "d9n+NRm9pA0YtKxvy4bW+U"
"_id": "ack7XH+0lEj6chSUsKBLU5"
},
{
"__type__": "cc.Label",
@@ -865,11 +868,11 @@
"_N$fontFamily": "Arial",
"_N$overflow": 0,
"_N$cacheMode": 0,
"_id": "90NvjGFrpBFqqzl1WZczta"
"_id": "deCJfLuoFO36c/O4lXWJ1N"
},
{
"__type__": "cc.Node",
"_name": "peerInputFrameUpsyncQ",
"_name": "inputFrameDownsyncQ",
"_objFlags": 0,
"_parent": {
"__id__": 11
@@ -927,7 +930,7 @@
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": "45FAmgRLZNNbLt/GcnTXVx"
"_id": "d9n+NRm9pA0YtKxvy4bW+U"
},
{
"__type__": "cc.Label",
@@ -959,11 +962,11 @@
"_N$fontFamily": "Arial",
"_N$overflow": 0,
"_N$cacheMode": 0,
"_id": "42PRTrjEpDw6Z8N5yWFTpd"
"_id": "90NvjGFrpBFqqzl1WZczta"
},
{
"__type__": "cc.Node",
"_name": "rollbackFrames",
"_name": "peerInputFrameUpsyncQ",
"_objFlags": 0,
"_parent": {
"__id__": 11
@@ -1021,7 +1024,7 @@
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": "f5SBs3b1pPNbHbnqVLYsHp"
"_id": "45FAmgRLZNNbLt/GcnTXVx"
},
{
"__type__": "cc.Label",
@@ -1053,11 +1056,11 @@
"_N$fontFamily": "Arial",
"_N$overflow": 0,
"_N$cacheMode": 0,
"_id": "77aNARt1VATLsjIzwbqvkh"
"_id": "42PRTrjEpDw6Z8N5yWFTpd"
},
{
"__type__": "cc.Node",
"_name": "skippedCnt",
"_name": "rollbackFrames",
"_objFlags": 0,
"_parent": {
"__id__": 11
@@ -1073,9 +1076,9 @@
"_opacity": 255,
"_color": {
"__type__": "cc.Color",
"r": 243,
"g": 225,
"b": 11,
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_contentSize": {
@@ -1115,7 +1118,7 @@
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": "cdlF8Z8TZEdLRHQQ8T8qX7"
"_id": "f5SBs3b1pPNbHbnqVLYsHp"
},
{
"__type__": "cc.Label",
@@ -1147,6 +1150,100 @@
"_N$fontFamily": "Arial",
"_N$overflow": 0,
"_N$cacheMode": 0,
"_id": "77aNARt1VATLsjIzwbqvkh"
},
{
"__type__": "cc.Node",
"_name": "skippedCnt",
"_objFlags": 0,
"_parent": {
"__id__": 11
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 23
}
],
"_prefab": null,
"_opacity": 255,
"_color": {
"__type__": "cc.Color",
"r": 243,
"g": 225,
"b": 11,
"a": 255
},
"_contentSize": {
"__type__": "cc.Size",
"width": 18.33,
"height": 22.61
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0.5
},
"_trs": {
"__type__": "TypedArray",
"ctor": "Float64Array",
"array": [
0,
-24.355000000000018,
0,
0,
0,
0,
1,
1,
1,
1
]
},
"_eulerAngles": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_skewX": 0,
"_skewY": 0,
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": "cdlF8Z8TZEdLRHQQ8T8qX7"
},
{
"__type__": "cc.Label",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 22
},
"_enabled": true,
"_materials": [
{
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
}
],
"_useOriginalSize": false,
"_string": "0",
"_N$string": "0",
"_fontSize": 22,
"_lineHeight": 37,
"_enableWrapText": true,
"_N$file": {
"__uuid__": "a564b3db-b8cb-48b4-952e-25bb56949116"
},
"_isSystemFontUsed": false,
"_spacingX": 0,
"_batchAsBitmap": false,
"_N$horizontalAlign": 0,
"_N$verticalAlign": 1,
"_N$fontFamily": "Arial",
"_N$overflow": 0,
"_N$cacheMode": 0,
"_id": "2flWrlaK1PeJMUB/in+S1W"
},
{
@@ -1158,19 +1255,19 @@
},
"_children": [
{
"__id__": 23
"__id__": 25
},
{
"__id__": 25
"__id__": 27
}
],
"_active": false,
"_components": [
{
"__id__": 27
"__id__": 29
},
{
"__id__": 28
"__id__": 30
}
],
"_prefab": null,
@@ -1226,13 +1323,13 @@
"_name": "label",
"_objFlags": 0,
"_parent": {
"__id__": 22
"__id__": 24
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 24
"__id__": 26
}
],
"_prefab": null,
@@ -1288,7 +1385,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 23
"__id__": 25
},
"_enabled": true,
"_materials": [],
@@ -1316,13 +1413,13 @@
"_name": "BoundRoomIdLabel",
"_objFlags": 0,
"_parent": {
"__id__": 22
"__id__": 24
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 26
"__id__": 28
}
],
"_prefab": null,
@@ -1378,7 +1475,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 25
"__id__": 27
},
"_enabled": true,
"_materials": [],
@@ -1406,7 +1503,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 22
"__id__": 24
},
"_enabled": true,
"_layoutSize": {
@@ -1439,7 +1536,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 22
"__id__": 24
},
"_enabled": true,
"_materials": [],
@@ -1561,7 +1658,7 @@
"_active": true,
"_components": [
{
"__id__": 33
"__id__": 35
}
],
"_prefab": null,
@@ -1617,7 +1714,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 32
"__id__": 34
},
"_enabled": true,
"_materials": [
@@ -1651,7 +1748,7 @@
},
"_children": [
{
"__id__": 35
"__id__": 37
}
],
"_active": true,
@@ -1709,16 +1806,16 @@
"_name": "Background",
"_objFlags": 0,
"_parent": {
"__id__": 34
"__id__": 36
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 36
"__id__": 38
},
{
"__id__": 37
"__id__": 39
}
],
"_prefab": null,
@@ -1774,7 +1871,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 35
"__id__": 37
},
"_enabled": true,
"_materials": [
@@ -1808,7 +1905,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 35
"__id__": 37
},
"_enabled": true,
"alignMode": 0,
@@ -1839,7 +1936,7 @@
},
"_children": [
{
"__id__": 39
"__id__": 41
}
],
"_active": true,
@@ -1897,16 +1994,16 @@
"_name": "Background",
"_objFlags": 0,
"_parent": {
"__id__": 38
"__id__": 40
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 40
"__id__": 42
},
{
"__id__": 41
"__id__": 43
}
],
"_prefab": null,
@@ -1962,7 +2059,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 39
"__id__": 41
},
"_enabled": true,
"_materials": [
@@ -1996,7 +2093,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 39
"__id__": 41
},
"_enabled": true,
"alignMode": 0,
@@ -2054,16 +2151,16 @@
},
"_children": [
{
"__id__": 44
"__id__": 46
}
],
"_active": true,
"_components": [
{
"__id__": 46
"__id__": 48
},
{
"__id__": 47
"__id__": 49
}
],
"_prefab": null,
@@ -2119,13 +2216,13 @@
"_name": "Joystick",
"_objFlags": 0,
"_parent": {
"__id__": 43
"__id__": 45
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 45
"__id__": 47
}
],
"_prefab": null,
@@ -2181,7 +2278,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 44
"__id__": 46
},
"_enabled": true,
"_materials": [
@@ -2215,7 +2312,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 43
"__id__": 45
},
"_enabled": true,
"_materials": [
@@ -2249,7 +2346,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 43
"__id__": 45
},
"_enabled": true,
"alignMode": 0,
@@ -2384,10 +2481,10 @@
"__id__": 3
},
"stickhead": {
"__id__": 44
"__id__": 46
},
"base": {
"__id__": 43
"__id__": 45
},
"joyStickEps": 0.1,
"magicLeanLowerBound": 0.414,
@@ -2408,10 +2505,10 @@
"linearMovingEps": 0.1,
"scaleByEps": 0.0375,
"btnA": {
"__id__": 34
"__id__": 36
},
"btnB": {
"__id__": 38
"__id__": 40
},
"_id": "e9oVYTr7ROlpp/IrNjBUmR"
}

View File

@@ -461,7 +461,7 @@
"array": [
0,
0,
216.50635094610968,
210.4441731196186,
0,
0,
0,

View File

@@ -77,12 +77,6 @@
],
"_active": true,
"_components": [
{
"__id__": 49
},
{
"__id__": 50
},
{
"__id__": 51
},
@@ -91,6 +85,12 @@
},
{
"__id__": 53
},
{
"__id__": 54
},
{
"__id__": 55
}
],
"_prefab": null,
@@ -276,21 +276,23 @@
"gameRulePrefab": null,
"findingPlayerPrefab": null,
"countdownToBeginGamePrefab": null,
"playersInfoPrefab": null,
"forceBigEndianFloatingNumDecoding": false,
"renderFrameIdLagTolerance": 4,
"sendingQLabel": {
"inputFrameFrontLabel": {
"__id__": 14
},
"inputFrameDownsyncQLabel": {
"sendingQLabel": {
"__id__": 16
},
"peerInputFrameUpsyncQLabel": {
"inputFrameDownsyncQLabel": {
"__id__": 18
},
"rollbackFramesLabel": {
"peerInputFrameUpsyncQLabel": {
"__id__": 20
},
"rollbackFramesLabel": {
"__id__": 22
},
"skippedRenderFrameCntLabel": null,
"_id": "e5xQdv12xLoIRr0b36Pie+"
},
@@ -303,13 +305,13 @@
},
"_children": [
{
"__id__": 43
"__id__": 45
}
],
"_active": true,
"_components": [
{
"__id__": 48
"__id__": 50
}
],
"_prefab": null,
@@ -371,20 +373,20 @@
{
"__id__": 7
},
{
"__id__": 32
},
{
"__id__": 34
},
{
"__id__": 38
"__id__": 36
},
{
"__id__": 40
}
],
"_active": true,
"_components": [
{
"__id__": 42
"__id__": 44
}
],
"_prefab": null,
@@ -453,7 +455,7 @@
"_active": true,
"_components": [
{
"__id__": 31
"__id__": 33
}
],
"_prefab": null,
@@ -547,7 +549,7 @@
"array": [
0,
0,
216.50635094610968,
210.4441731196186,
0,
0,
0,
@@ -631,12 +633,15 @@
},
{
"__id__": 23
},
{
"__id__": 25
}
],
"_active": true,
"_components": [
{
"__id__": 30
"__id__": 32
}
],
"_prefab": null,
@@ -689,7 +694,7 @@
},
{
"__type__": "cc.Node",
"_name": "sendingQ",
"_name": "inputFrameFront",
"_objFlags": 0,
"_parent": {
"__id__": 12
@@ -725,7 +730,7 @@
"ctor": "Float64Array",
"array": [
0,
76.48,
79.66666666666667,
0,
0,
0,
@@ -747,7 +752,7 @@
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": "6fOGsdJxxKMLM7+K16V2wq"
"_id": "16ecz642FAMIrtMtKWTI/3"
},
{
"__type__": "cc.Label",
@@ -777,11 +782,11 @@
"_N$fontFamily": "Arial",
"_N$overflow": 0,
"_N$cacheMode": 0,
"_id": "38SxePoOdHT4wrqnMdR0ql"
"_id": "e2W8Kja+VG7IEwuBQapscC"
},
{
"__type__": "cc.Node",
"_name": "inputFrameDownsyncQ",
"_name": "sendingQ",
"_objFlags": 0,
"_parent": {
"__id__": 12
@@ -817,7 +822,7 @@
"ctor": "Float64Array",
"array": [
0,
38.239999999999995,
47.8,
0,
0,
0,
@@ -839,7 +844,7 @@
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": "c7OaUtV+9JaL4UcCC0gp8r"
"_id": "6fOGsdJxxKMLM7+K16V2wq"
},
{
"__type__": "cc.Label",
@@ -869,11 +874,11 @@
"_N$fontFamily": "Arial",
"_N$overflow": 0,
"_N$cacheMode": 0,
"_id": "e22YW/mxlPRoYkJwMCezpE"
"_id": "38SxePoOdHT4wrqnMdR0ql"
},
{
"__type__": "cc.Node",
"_name": "peerInputFrameUpsyncQ",
"_name": "inputFrameDownsyncQ",
"_objFlags": 0,
"_parent": {
"__id__": 12
@@ -909,7 +914,7 @@
"ctor": "Float64Array",
"array": [
0,
-7.105427357601002e-15,
15.933333333333325,
0,
0,
0,
@@ -931,7 +936,7 @@
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": "3fAMol6MJN+IHH1Ckh13wR"
"_id": "c7OaUtV+9JaL4UcCC0gp8r"
},
{
"__type__": "cc.Label",
@@ -961,11 +966,11 @@
"_N$fontFamily": "Arial",
"_N$overflow": 0,
"_N$cacheMode": 0,
"_id": "3dO5iM/4VHVb21M3X82p0z"
"_id": "e22YW/mxlPRoYkJwMCezpE"
},
{
"__type__": "cc.Node",
"_name": "rollbackFrames",
"_name": "peerInputFrameUpsyncQ",
"_objFlags": 0,
"_parent": {
"__id__": 12
@@ -1001,7 +1006,99 @@
"ctor": "Float64Array",
"array": [
0,
-38.24000000000001,
-15.933333333333346,
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": "3fAMol6MJN+IHH1Ckh13wR"
},
{
"__type__": "cc.Label",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 19
},
"_enabled": true,
"_materials": [
{
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
}
],
"_useOriginalSize": false,
"_string": "0",
"_N$string": "0",
"_fontSize": 22,
"_lineHeight": 23,
"_enableWrapText": true,
"_N$file": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_batchAsBitmap": false,
"_N$horizontalAlign": 0,
"_N$verticalAlign": 1,
"_N$fontFamily": "Arial",
"_N$overflow": 0,
"_N$cacheMode": 0,
"_id": "3dO5iM/4VHVb21M3X82p0z"
},
{
"__type__": "cc.Node",
"_name": "rollbackFrames",
"_objFlags": 0,
"_parent": {
"__id__": 12
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 22
}
],
"_prefab": null,
"_opacity": 255,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_contentSize": {
"__type__": "cc.Size",
"width": 12.24,
"height": 28.98
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0.5
},
"_trs": {
"__type__": "TypedArray",
"ctor": "Float64Array",
"array": [
0,
-47.80000000000002,
0,
0,
0,
@@ -1030,7 +1127,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 19
"__id__": 21
},
"_enabled": true,
"_materials": [
@@ -1066,7 +1163,7 @@
"_active": true,
"_components": [
{
"__id__": 22
"__id__": 24
}
],
"_prefab": null,
@@ -1093,7 +1190,7 @@
"ctor": "Float64Array",
"array": [
0,
-76.48000000000002,
-79.66666666666669,
0,
0,
0,
@@ -1122,7 +1219,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 21
"__id__": 23
},
"_enabled": true,
"_materials": [
@@ -1158,19 +1255,19 @@
},
"_children": [
{
"__id__": 24
"__id__": 26
},
{
"__id__": 26
"__id__": 28
}
],
"_active": false,
"_components": [
{
"__id__": 28
"__id__": 30
},
{
"__id__": 29
"__id__": 31
}
],
"_prefab": null,
@@ -1226,13 +1323,13 @@
"_name": "label",
"_objFlags": 0,
"_parent": {
"__id__": 23
"__id__": 25
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 25
"__id__": 27
}
],
"_prefab": null,
@@ -1288,7 +1385,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 24
"__id__": 26
},
"_enabled": true,
"_materials": [],
@@ -1316,13 +1413,13 @@
"_name": "BoundRoomIdLabel",
"_objFlags": 0,
"_parent": {
"__id__": 23
"__id__": 25
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 27
"__id__": 29
}
],
"_prefab": null,
@@ -1378,7 +1475,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 26
"__id__": 28
},
"_enabled": true,
"_materials": [],
@@ -1406,7 +1503,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 23
"__id__": 25
},
"_enabled": true,
"_layoutSize": {
@@ -1439,7 +1536,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 23
"__id__": 25
},
"_enabled": true,
"_materials": [],
@@ -1535,7 +1632,7 @@
"_active": true,
"_components": [
{
"__id__": 33
"__id__": 35
}
],
"_prefab": null,
@@ -1591,7 +1688,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 32
"__id__": 34
},
"_enabled": true,
"_materials": [
@@ -1625,7 +1722,7 @@
},
"_children": [
{
"__id__": 35
"__id__": 37
}
],
"_active": true,
@@ -1683,16 +1780,16 @@
"_name": "Background",
"_objFlags": 0,
"_parent": {
"__id__": 34
"__id__": 36
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 36
"__id__": 38
},
{
"__id__": 37
"__id__": 39
}
],
"_prefab": null,
@@ -1748,7 +1845,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 35
"__id__": 37
},
"_enabled": true,
"_materials": [
@@ -1782,7 +1879,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 35
"__id__": 37
},
"_enabled": true,
"alignMode": 0,
@@ -1813,7 +1910,7 @@
},
"_children": [
{
"__id__": 39
"__id__": 41
}
],
"_active": true,
@@ -1871,16 +1968,16 @@
"_name": "Background",
"_objFlags": 0,
"_parent": {
"__id__": 38
"__id__": 40
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 40
"__id__": 42
},
{
"__id__": 41
"__id__": 43
}
],
"_prefab": null,
@@ -1936,7 +2033,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 39
"__id__": 41
},
"_enabled": true,
"_materials": [
@@ -1970,7 +2067,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 39
"__id__": 41
},
"_enabled": true,
"alignMode": 0,
@@ -2028,16 +2125,16 @@
},
"_children": [
{
"__id__": 44
"__id__": 46
}
],
"_active": true,
"_components": [
{
"__id__": 46
"__id__": 48
},
{
"__id__": 47
"__id__": 49
}
],
"_prefab": null,
@@ -2093,13 +2190,13 @@
"_name": "Joystick",
"_objFlags": 0,
"_parent": {
"__id__": 43
"__id__": 45
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 45
"__id__": 47
}
],
"_prefab": null,
@@ -2155,7 +2252,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 44
"__id__": 46
},
"_enabled": true,
"_materials": [
@@ -2189,7 +2286,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 43
"__id__": 45
},
"_enabled": true,
"_materials": [
@@ -2223,7 +2320,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 43
"__id__": 45
},
"_enabled": true,
"alignMode": 0,
@@ -2358,10 +2455,10 @@
"__id__": 3
},
"stickhead": {
"__id__": 44
"__id__": 46
},
"base": {
"__id__": 43
"__id__": 45
},
"joyStickEps": 0.1,
"magicLeanLowerBound": 0.414,
@@ -2382,10 +2479,10 @@
"linearMovingEps": 0.1,
"scaleByEps": 0.0375,
"btnA": {
"__id__": 34
"__id__": 36
},
"btnB": {
"__id__": 38
"__id__": 40
},
"_id": "e9oVYTr7ROlpp/IrNjBUmR"
}

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

@@ -0,0 +1,9 @@
{
"ver": "1.0.5",
"uuid": "6dd2c047-fa5c-4080-8221-27fabfd275d6",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@@ -47,7 +47,7 @@ cc.Class({
},
hideExitButton() {
if (null == this.exitBtnNode != null) {
if (null == this.exitBtnNode) {
return;
}
this.exitBtnNode.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

@@ -354,6 +354,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.")

View File

@@ -103,10 +103,6 @@ cc.Class({
type: cc.Prefab,
default: null
},
playersInfoPrefab: {
type: cc.Prefab,
default: null
},
forceBigEndianFloatingNumDecoding: {
default: false,
},
@@ -114,6 +110,10 @@ cc.Class({
type: cc.Integer,
default: 4 // implies (renderFrameIdLagTolerance >> inputScaleFrames) count of inputFrameIds
},
inputFrameFrontLabel: {
type: cc.Label,
default: null
},
sendingQLabel: {
type: cc.Label,
default: null
@@ -280,13 +280,6 @@ cc.Class({
}
},
onManualRejoinRequired(labelString) {
const self = this;
self.battleState = ALL_BATTLE_STATES.NONE; // Effectively stops "update(dt)"
self.showPopupInCanvas(self.gameRuleNode);
self.popupSimplePressToGo(labelString, false);
},
popupSimplePressToGo(labelString, hideYesButton) {
const self = this;
self.state = ALL_MAP_STATES.SHOWING_MODAL_POPUP;
@@ -392,20 +385,12 @@ cc.Class({
self.rdfIdToActuallyUsedInput = new Map();
self.networkDoctor = new NetworkDoctor(20);
self.allowSkippingRenderFrameFlag = true;
self.skipRenderFrameFlag = false;
self.allowRollbackOnPeerUpsync = true;
self.countdownNanos = null;
if (self.countdownLabel) {
self.countdownLabel.string = "";
}
if (self.playersInfoNode) {
safelyAddChild(self.widgetsAboveAllNode, self.playersInfoNode);
}
if (self.findingPlayerNode) {
safelyAddChild(self.widgetsAboveAllNode, self.findingPlayerNode);
}
},
initDebugDrawers() {
@@ -508,8 +493,6 @@ cc.Class({
const findingPlayerScriptIns = self.findingPlayerNode.getComponent("FindingPlayer");
findingPlayerScriptIns.init(self);
self.playersInfoNode = cc.instantiate(self.playersInfoPrefab);
self.countdownToBeginGameNode = cc.instantiate(self.countdownToBeginGamePrefab);
self.countdownToBeginGameNode.width = self.canvasNode.width;
self.countdownToBeginGameNode.height = self.canvasNode.height;
@@ -551,6 +534,11 @@ cc.Class({
tiledMapIns.tmxAsset = null;
mapNode.removeAllChildren();
self._resetCurrentMatch();
if (self.countdownLabel) {
self.countdownLabel.string = "";
}
self.hideGameRuleNode();
self.showFindingPlayerGUI(null);
tiledMapIns.tmxAsset = tmxAsset;
const newMapSize = tiledMapIns.getMapSize();
@@ -597,7 +585,6 @@ cc.Class({
self.initAfterWSConnected = () => {
const self = window.mapIns;
self.hideGameRuleNode();
self.transitToState(ALL_MAP_STATES.WAITING);
self._inputControlEnabled = false;
}
@@ -643,7 +630,8 @@ cc.Class({
if (null == self.gameRuleNode) {
return;
}
self.gameRuleNode.active = false;
//self.gameRuleNode.active = false;
self.gameRuleNode.setPosition(cc.v2(Number.MAX_VALUE, Number.MAX_VALUE));
},
enableInputControls() {
@@ -718,14 +706,6 @@ cc.Class({
self.chConfigsOrderedByJoinIndex = gopkgs.GetCharacterConfigsOrderedByJoinIndex(pbRdf.speciesIdList);
self._initPlayerRichInfoDict(rdf.PlayersArr);
// Show the top status indicators for IN_BATTLE
if (self.playersInfoNode) {
const playersInfoScriptIns = self.playersInfoNode.getComponent("PlayersInfo");
for (let i in pbRdf.playersArr) {
playersInfoScriptIns.updateData(pbRdf.playersArr[i]);
}
}
if (shouldForceDumping1 || shouldForceDumping2 || shouldForceResync) {
// In fact, not having "window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet" should already imply that "self.renderFrameId <= rdf.id", but here we double check and log the anomaly
@@ -923,12 +903,15 @@ cc.Class({
--------------------------------------------------------
*/
// The actual rollback-and-chase would later be executed in update(dt).
console.log(`Mismatched input detected, resetting chaserRenderFrameId: ${self.chaserRenderFrameId}->${renderFrameId1} by
if (CC_DEBUG) {
// Printing of this message might induce a performance impact.
console.log(`Mismatched input detected, resetting chaserRenderFrameId: ${self.chaserRenderFrameId}->${renderFrameId1} by
firstPredictedYetIncorrectInputFrameId: ${firstPredictedYetIncorrectInputFrameId}
lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}
recentInputCache=${self._stringifyRecentInputCache(false)}
batchInputFrameIdRange=[${batch[0].inputFrameId}, ${batch[batch.length - 1].inputFrameId}]
fromUDP=${fromUDP}`);
}
self.chaserRenderFrameId = renderFrameId1;
let rollbackFrames = (self.renderFrameId - self.chaserRenderFrameId);
if (0 > rollbackFrames) {
@@ -986,7 +969,7 @@ fromUDP=${fromUDP}`);
//console.log(`Updated encoded input of peerJoinIndex=${peerJoinIndex} to ${peerEncodedInput} for inputFrameId=${inputFrameId}/renderedInputFrameIdUpper=${renderedInputFrameIdUpper} from ${JSON.stringify(inputFrame)}; newInputFrameDownsyncLocal=${self.gopkgsInputFrameDownsyncStr(newInputFrameDownsyncLocal)}; existingInputFrame=${self.gopkgsInputFrameDownsyncStr(existingInputFrame)}`);
self.recentInputCache.SetByFrameId(newInputFrameDownsyncLocal, inputFrameId);
if (self.allowRollbackOnPeerUpsync) {
if (true == self.allowRollbackOnPeerUpsync) {
// Reaching here implies that "true == self.allowRollbackOnPeerUpsync".
// Shall we update the "chaserRenderFrameId" if the rendered history was wrong? It doesn't seem to impact eventual correctness if we allow the update of "chaserRenderFrameId" upon "inputFrameId <= renderedInputFrameIdUpper" here, however UDP upsync doesn't reserve order from a same sender and there might be multiple other senders, hence it might result in unnecessarily frequent chasing.
if (
@@ -1009,12 +992,7 @@ fromUDP=${fromUDP}`);
onPlayerAdded(rdf /* pb.RoomDownsyncFrame */ ) {
const self = this;
// Update the "finding player" GUI and show it if not previously present
if (!self.findingPlayerNode.parent) {
self.showPopupInCanvas(self.findingPlayerNode);
}
let findingPlayerScriptIns = self.findingPlayerNode.getComponent("FindingPlayer");
findingPlayerScriptIns.updatePlayersInfo(rdf.playersArr);
self.showFindingPlayerGUI(rdf);
},
onBattleStopped() {
@@ -1034,9 +1012,6 @@ fromUDP=${fromUDP}`);
resultPanelScriptIns.showPlayerInfo(self.playerRichInfoDict);
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
self.showPopupInCanvas(resultPanelNode);
// Clear player info
self.playersInfoNode.getComponent("PlayersInfo").clearInfo();
},
spawnPlayerNode(joinIndex, vx, vy, playerDownsyncInfo) {
@@ -1077,7 +1052,7 @@ fromUDP=${fromUDP}`);
Kindly note that Significantly different network bandwidths or delay fluctuations would result in frequent [type#1 forceConfirmation] too, but CAUSE FROM DIFFERENT LOCAL "update(dt)" RATE SHOULD BE THE FIRST TO INVESTIGATE AND ELIMINATE -- because we have control on it, but no one has control on the internet.
*/
if (self.skipRenderFrameFlag) {
if (self.allowSkippingRenderFrameFlag && self.skipRenderFrameFlag) {
self.networkDoctor.logSkippedRenderFrameCnt();
self.skipRenderFrameFlag = false;
return;
@@ -1092,6 +1067,7 @@ fromUDP=${fromUDP}`);
currSelfInput = null;
if (gopkgs.ShouldGenerateInputFrameUpsync(self.renderFrameId)) {
[prevSelfInput, currSelfInput] = self.getOrPrefabInputFrameUpsync(noDelayInputFrameId, true);
self.networkDoctor.logInputFrameIdFront(noDelayInputFrameId);
}
const delayedInputFrameId = gopkgs.ConvertToDelayedInputFrameId(self.renderFrameId);
@@ -1143,21 +1119,22 @@ fromUDP=${fromUDP}`);
console.warn(`Mismatched render frame@rdf.id=${rdf.Id} w/ inputFrameId=${delayedInputFrameId}:
rdf=${JSON.stringify(rdf)}
othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame)}`);
// closeWSConnection(constants.RET_CODE.CLIENT_MISMATCHED_RENDER_FRAME, "");
// self.onManualRejoinRequired("[DEBUG] CLIENT_MISMATCHED_RENDER_FRAME");
rdf = othersForcedDownsyncRenderFrame;
self.othersForcedDownsyncRenderFrameDict.delete(rdf.Id);
}
}
self.applyRoomDownsyncFrameDynamics(rdf, prevRdf);
self.showDebugBoundaries(rdf);
if (self.showNetworkDoctorInfo) {
self.showNetworkDoctorLabels();
}
++self.renderFrameId; // [WARNING] It's important to increment the renderFrameId AFTER all the operations above!!!
self.lastRenderFrameIdTriggeredAt = performance.now();
let t3 = performance.now();
self.skipRenderFrameFlag = self.networkDoctor.isTooFast(self);
const [skipRenderFrameFlag, inputFrameIdFront, sendingFps, srvDownsyncFps, peerUpsyncFps, doctorRollbackFrames, skippedRenderFrameCnt] = self.networkDoctor.isTooFast(self);
if (self.allowSkippingRenderFrameFlag) {
self.skipRenderFrameFlag = skipRenderFrameFlag;
}
if (self.showNetworkDoctorInfo) {
self.showNetworkDoctorLabels(inputFrameIdFront, sendingFps, srvDownsyncFps, peerUpsyncFps, doctorRollbackFrames, skippedRenderFrameCnt);
}
} catch (err) {
console.error("Error during Map.update", err);
self.onBattleStopped(); // TODO: Popup to ask player to refresh browser
@@ -1230,11 +1207,11 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
self.enableInputControls();
},
onGameRule1v1ModeClicked(evt, cb) {
onGameRule1v1ModeClicked(chosenSpeciesId) {
const self = this;
self.battleState = ALL_BATTLE_STATES.WAITING;
window.chosenSpeciesId = chosenSpeciesId; // TODO: Find a better way to pass it into "self.initAfterWSConnected"!
window.initPersistentSessionClient(self.initAfterWSConnected, null /* Deliberately NOT passing in any `expectedRoomId`. -- YFLu */ );
self.hideGameRuleNode();
},
showPopupInCanvas(toShowNode) {
@@ -1246,6 +1223,18 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
setLocalZOrder(toShowNode, 10);
},
showFindingPlayerGUI(rdf) {
const self = this;
// Update the "finding player" GUI and show it if not previously present
if (!self.findingPlayerNode.parent) {
self.showPopupInCanvas(self.findingPlayerNode);
}
if (null != rdf) {
const findingPlayerScriptIns = self.findingPlayerNode.getComponent("FindingPlayer");
findingPlayerScriptIns.updatePlayersInfo(rdf.playersArr);
}
},
hideFindingPlayersGUI(rdf) {
const self = this;
// [WARNING] "cc.Node.removeChild" would trigger massive update of rendering nodes, thus a performance impact at the beginning of battle, avoid it by just moving the widget to infinitely far away!
@@ -1258,13 +1247,6 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
const self = this;
const players = rdf.playersArr;
// Show the top status indicators for IN_BATTLE
if (self.playersInfoNode) {
const playersInfoScriptIns = self.playersInfoNode.getComponent("PlayersInfo");
for (let i in players) {
playersInfoScriptIns.updateData(players[i]);
}
}
console.log("Calling `onBattleReadyToStart` with:", players);
if (self.findingPlayerNode) {
const findingPlayerScriptIns = self.findingPlayerNode.getComponent("FindingPlayer");
@@ -1306,7 +1288,7 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
}
for (let k in rdf.MeleeBullets) {
const meleeBullet = rdf.MeleeBullets[k];
const isExploding = (window.BULLET_STATE.Exploding == meleeBullet.BlState);
const isExploding = (window.BULLET_STATE.Exploding == meleeBullet.BlState && meleeBullet.FramesInBlState < meleeBullet.Bullet.ExplosionFrames);
if (isExploding) {
let pqNode = self.cachedFireballs.popAny(meleeBullet.BattleAttr.BulletLocalId);
let speciesName = `MeleeExplosion`;
@@ -1651,11 +1633,13 @@ actuallyUsedinputList:{${self.inputFrameDownsyncStr(actuallyUsedInputClone)}}`);
}
},
showNetworkDoctorLabels() {
showNetworkDoctorLabels(inputFrameIdFront, sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, skippedRenderFrameCnt) {
const self = this;
const [sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, skippedRenderFrameCnt] = self.networkDoctor.stats();
if (self.inputFrameFrontLabel) {
self.inputFrameFrontLabel.string = `inputFrameId front: ${inputFrameIdFront}`;
}
if (self.sendingQLabel) {
self.sendingQLabel.string = `${sendingFps} fps sending`;
self.sendingQLabel.string = `fps sending: ${sendingFps}`;
if (sendingFps < self.networkDoctor.inputRateThreshold) {
self.sendingQLabel.node.color = cc.Color.RED;
} else {
@@ -1663,7 +1647,7 @@ actuallyUsedinputList:{${self.inputFrameDownsyncStr(actuallyUsedInputClone)}}`);
}
}
if (self.inputFrameDownsyncQLabel) {
self.inputFrameDownsyncQLabel.string = `${srvDownsyncFps} fps srv-downsync`;
self.inputFrameDownsyncQLabel.string = `fps srv-downsync: ${srvDownsyncFps}`;
if (srvDownsyncFps < self.networkDoctor.inputRateThreshold) {
self.inputFrameDownsyncQLabel.node.color = cc.Color.RED;
} else {
@@ -1671,7 +1655,7 @@ actuallyUsedinputList:{${self.inputFrameDownsyncStr(actuallyUsedInputClone)}}`);
}
}
if (self.peerInputFrameUpsyncQLabel) {
self.peerInputFrameUpsyncQLabel.string = `${peerUpsyncFps} fps peer-upsync`;
self.peerInputFrameUpsyncQLabel.string = `fps peer-upsync: ${peerUpsyncFps}`;
if (peerUpsyncFps > self.networkDoctor.peerUpsyncFps) {
self.peerInputFrameUpsyncQLabel.node.color = cc.Color.RED;
} else {
@@ -1687,7 +1671,7 @@ actuallyUsedinputList:{${self.inputFrameDownsyncStr(actuallyUsedInputClone)}}`);
}
}
if (self.skippedRenderFrameCntLabel) {
self.skippedRenderFrameCntLabel.string = `${skippedRenderFrameCnt} frames skipped`
self.skippedRenderFrameCntLabel.string = `frames skipped: ${skippedRenderFrameCnt}`
}
},
});

View File

@@ -5,6 +5,7 @@ var NetworkDoctor = function(capacity) {
};
NetworkDoctor.prototype.reset = function(capacity) {
this.inputFrameIdFront = 0;
this.sendingQ = new RingBuffer(capacity);
this.inputFrameDownsyncQ = new RingBuffer(capacity);
this.peerInputFrameUpsyncQ = new RingBuffer(capacity);
@@ -17,6 +18,10 @@ NetworkDoctor.prototype.reset = function(capacity) {
this.rollbackFramesThreshold = 8; // Roughly the minimum "TurnAroundFramesToRecover".
};
NetworkDoctor.prototype.logInputFrameIdFront = function(inputFrameId) {
this.inputFrameIdFront = inputFrameId;
};
NetworkDoctor.prototype.logSending = function(stFrameId, edFrameId) {
this.sendingQ.put({
i: stFrameId,
@@ -50,7 +55,8 @@ NetworkDoctor.prototype.logRollbackFrames = function(x) {
};
NetworkDoctor.prototype.stats = function() {
let sendingFps = 0,
let inputFrameIdFront = this.inputFrameIdFront,
sendingFps = 0,
srvDownsyncFps = 0,
peerUpsyncFps = 0,
rollbackFrames = this.immediateRollbackFrames;
@@ -72,7 +78,7 @@ NetworkDoctor.prototype.stats = function() {
const elapsedMillis = ed.t - st.t;
peerUpsyncFps = Math.round(this.peerInputFrameUpsyncCnt * 1000 / elapsedMillis);
}
return [sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, this.skippedRenderFrameCnt];
return [inputFrameIdFront, sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, this.skippedRenderFrameCnt];
};
NetworkDoctor.prototype.logSkippedRenderFrameCnt = function() {
@@ -80,31 +86,36 @@ NetworkDoctor.prototype.logSkippedRenderFrameCnt = function() {
}
NetworkDoctor.prototype.isTooFast = function(mapIns) {
const [sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, skippedRenderFrameCnt] = this.stats();
const [inputFrameIdFront, sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, skippedRenderFrameCnt] = this.stats();
if (sendingFps >= this.inputRateThreshold + 3) {
// Don't send too fast
console.log(`Sending too fast, sendingFps=${sendingFps}`);
return true;
if (CC_DEBUG) {
// Printing of this message might induce a performance impact.
console.log(`Sending too fast, sendingFps=${sendingFps}`);
}
return [true, inputFrameIdFront, sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, skippedRenderFrameCnt];
} else {
const sendingFpsNormal = (sendingFps >= this.inputRateThreshold);
// An outstanding lag within the "inputFrameDownsyncQ" will reduce "srvDownsyncFps", HOWEVER, a constant lag wouldn't impact "srvDownsyncFps"! In native platforms we might use PING value might help as a supplement information to confirm that the "selfPlayer" is not lagged within the time accounted by "inputFrameDownsyncQ".
const recvFpsNormal = (srvDownsyncFps >= this.inputRateThreshold || peerUpsyncFps >= this.inputRateThreshold * (window.boundRoomCapacity - 1));
if (sendingFpsNormal && recvFpsNormal) {
let selfInputFrameIdFront = gopkgs.ConvertToNoDelayInputFrameId(mapIns.renderFrameId);
let minInputFrameIdFront = Number.MAX_VALUE;
for (let k = 0; k < window.boundRoomCapacity; ++k) {
if (k + 1 == mapIns.selfPlayerInfo.JoinIndex) continue;
if (mapIns.lastIndividuallyConfirmedInputFrameId[k] >= minInputFrameIdFront) continue;
minInputFrameIdFront = mapIns.lastIndividuallyConfirmedInputFrameId[k];
}
if ((selfInputFrameIdFront > minInputFrameIdFront) && ((selfInputFrameIdFront - minInputFrameIdFront) > (mapIns.inputFrameUpsyncDelayTolerance + 1))) {
if ((inputFrameIdFront > minInputFrameIdFront) && ((inputFrameIdFront - minInputFrameIdFront) > (mapIns.inputFrameUpsyncDelayTolerance + 1))) {
// first comparison condition is to avoid numeric overflow
console.log(`Game logic ticking too fast, selfInputFrameIdFront=${selfInputFrameIdFront}, minInputFrameIdFront=${minInputFrameIdFront}, inputFrameUpsyncDelayTolerance=${mapIns.inputFrameUpsyncDelayTolerance}`);
return true;
if (CC_DEBUG) {
// Printing of this message might induce a performance impact.
console.log(`Game logic ticking too fast, selfInputFrameIdFront=${inputFrameIdFront}, minInputFrameIdFront=${minInputFrameIdFront}, inputFrameUpsyncDelayTolerance=${mapIns.inputFrameUpsyncDelayTolerance}`);
}
return [true, inputFrameIdFront, sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, skippedRenderFrameCnt];
}
}
}
return false;
return [false, inputFrameIdFront, sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, skippedRenderFrameCnt];
};
module.exports = NetworkDoctor;

View File

@@ -58,10 +58,21 @@ window.getBoundRoomCapacityFromPersistentStorage = function() {
return (null == boundRoomCapacityStr ? null : parseInt(boundRoomCapacityStr));
};
window.getChosenSpeciesIdFromPersistentStorage = function() {
const boundRoomIdExpiresAt = parseInt(cc.sys.localStorage.getItem("boundRoomIdExpiresAt"));
if (!boundRoomIdExpiresAt || Date.now() >= boundRoomIdExpiresAt) {
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
return null;
}
const chosenSpeciesIdStr = cc.sys.localStorage.getItem("chosenSpeciesId");
return (null == chosenSpeciesIdStr ? 0 : parseInt(chosenSpeciesIdStr));
};
window.clearBoundRoomIdInBothVolatileAndPersistentStorage = function() {
window.boundRoomId = null;
cc.sys.localStorage.removeItem("boundRoomId");
cc.sys.localStorage.removeItem("boundRoomCapacity");
cc.sys.localStorage.removeItem("chosenSpeciesId");
cc.sys.localStorage.removeItem("boundRoomIdExpiresAt");
};
@@ -84,6 +95,7 @@ window.handleHbRequirements = function(resp) {
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`);
@@ -179,6 +191,13 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
}
}
if (null == window.chosenSpeciesId) {
window.chosenSpeciesId = getChosenSpeciesIdFromPersistentStorage();
}
if (null != window.chosenSpeciesId) {
urlToConnect = urlToConnect + "&speciesId=" + window.chosenSpeciesId;
}
const clientSession = new WebSocket(urlToConnect);
clientSession.binaryType = 'arraybuffer'; // Make 'event.data' of 'onmessage' an "ArrayBuffer" instead of a "Blob"
@@ -230,9 +249,9 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
const peerAddrList = resp.rdf.peerUdpAddrList;
console.log(`Got DOWNSYNC_MSG_ACT_PEER_UDP_ADDR peerAddrList=${JSON.stringify(peerAddrList)}; boundRoomCapacity=${window.boundRoomCapacity}`);
for (let j = 0; j < 3; ++j) {
setTimeout(()=> {
DelayNoMore.UdpSession.upsertPeerUdpAddr(peerAddrList, window.boundRoomCapacity, window.mapIns.selfPlayerInfo.JoinIndex); // In C++ impl it actually broadcasts the peer-punching message to all known peers within "window.boundRoomCapacity"
}, j*500);
setTimeout(() => {
DelayNoMore.UdpSession.upsertPeerUdpAddr(peerAddrList, window.boundRoomCapacity, window.mapIns.selfPlayerInfo.JoinIndex); // In C++ impl it actually broadcasts the peer-punching message to all known peers within "window.boundRoomCapacity"
}, j * 500);
}
}
break;

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
{
"ver": "1.0.5",
"uuid": "171e2c96-28b4-4225-bdcc-5e464f07d91a",
"uuid": "eeaa56f4-bd6c-4208-bec4-6ab1aa39ca93",
"isPlugin": true,
"loadPluginInWeb": true,
"loadPluginInNative": true,

View File

@@ -51,7 +51,7 @@ void RecvRingBuff::put(char* newBytes, size_t newBytesLen) {
}
slotEle->bytesLen = newBytesLen;
memset(slotEle->ui8Arr, 0, sizeof slotEle->ui8Arr);
for (int i = 0; i < newBytesLen; i++) {
for (size_t i = 0; i < newBytesLen; i++) {
*(slotEle->ui8Arr + i) = *(newBytes + i);
}

View File

@@ -716,7 +716,7 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
} else if stoppingFromWalking {
thatPlayerInNextFrame.FramesToRecover = chConfig.InertiaFramesToRecover
} else {
thatPlayerInNextFrame.FramesToRecover = (chConfig.InertiaFramesToRecover >> 1)
thatPlayerInNextFrame.FramesToRecover = ((chConfig.InertiaFramesToRecover >> 1) + (chConfig.InertiaFramesToRecover >> 2))
}
} else {
thatPlayerInNextFrame.CapturedByInertia = false
@@ -757,8 +757,19 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
if 0 >= thatPlayerInNextFrame.Hp && 0 == thatPlayerInNextFrame.FramesToRecover {
// Revive from Dying
newVx, newVy = currPlayerDownsync.RevivalVirtualGridX, currPlayerDownsync.RevivalVirtualGridY
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_GET_UP1
thatPlayerInNextFrame.FramesInChState = ATK_CHARACTER_STATE_GET_UP1
thatPlayerInNextFrame.FramesToRecover = chConfig.GetUpFramesToRecover
thatPlayerInNextFrame.FramesInvinsible = chConfig.GetUpInvinsibleFrames
thatPlayerInNextFrame.Hp = currPlayerDownsync.MaxHp
// Hardcoded initial character orientation/facing
if 0 == (thatPlayerInNextFrame.JoinIndex % 2) {
thatPlayerInNextFrame.DirX = -2
thatPlayerInNextFrame.DirY = 0
} else {
thatPlayerInNextFrame.DirX = +2
thatPlayerInNextFrame.DirY = 0
}
}
if jumpedOrNotList[i] {
// We haven't proceeded with "OnWall" calculation for "thatPlayerInNextFrame", thus use "currPlayerDownsync.OnWall" for checking
@@ -899,8 +910,12 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
if collision := playerCollider.Check(0, 0); nil != collision {
for _, obj := range collision.Objects {
isBarrier, isAnotherPlayer, isBullet := false, false, false
switch obj.Data.(type) {
switch v := obj.Data.(type) {
case *PlayerDownsync:
if ATK_CHARACTER_STATE_DYING == v.CharacterState {
// ignore collision with dying player
continue
}
isAnotherPlayer = true
case *MeleeBullet, *FireballBullet:
isBullet = true
@@ -912,6 +927,7 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
// ignore bullets for this step
continue
}
bShape := obj.Shape.(*resolv.ConvexPolygon)
overlapped, pushbackX, pushbackY, overlapResult := calcPushbacks(0, 0, playerShape, bShape)
if !overlapped {
@@ -1013,113 +1029,83 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
collision := bulletCollider.Check(0, 0)
bulletCollider.Space.Remove(bulletCollider) // Make sure that the bulletCollider is always removed for each renderFrame
exploded := false
if nil != collision {
switch v := bulletCollider.Data.(type) {
case *MeleeBullet:
bulletShape := bulletCollider.Shape.(*resolv.ConvexPolygon)
offender := currRenderFrame.PlayersArr[v.BattleAttr.OffenderJoinIndex-1]
for _, obj := range collision.Objects {
defenderShape := obj.Shape.(*resolv.ConvexPolygon)
switch t := obj.Data.(type) {
case *PlayerDownsync:
if v.BattleAttr.OffenderJoinIndex == t.JoinIndex {
continue
}
overlapped, _, _, _ := calcPushbacks(0, 0, bulletShape, defenderShape)
if !overlapped {
continue
}
exploded = true
if _, existent := invinsibleSet[t.CharacterState]; existent {
continue
}
if 0 < t.FramesInvinsible {
continue
}
xfac := int32(1) // By now, straight Punch offset doesn't respect "y-axis"
if 0 > offender.DirX {
xfac = -xfac
}
atkedPlayerInNextFrame := nextRenderFramePlayers[t.JoinIndex-1]
atkedPlayerInNextFrame.Hp -= v.Bullet.Damage
if 0 >= atkedPlayerInNextFrame.Hp {
// [WARNING] We don't have "dying in air" animation for now, and for better graphical recognition, play the same dying animation even in air
atkedPlayerInNextFrame.Hp = 0
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_DYING
atkedPlayerInNextFrame.FramesToRecover = DYING_FRAMES_TO_RECOVER
} else {
pushbackVelX, pushbackVelY := xfac*v.Bullet.PushbackVelX, v.Bullet.PushbackVelY
atkedPlayerInNextFrame.VelX = pushbackVelX
atkedPlayerInNextFrame.VelY = pushbackVelY
if v.Bullet.BlowUp {
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_BLOWN_UP1
} else {
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATKED1
}
oldFramesToRecover := nextRenderFramePlayers[t.JoinIndex-1].FramesToRecover
if v.Bullet.HitStunFrames > oldFramesToRecover {
atkedPlayerInNextFrame.FramesToRecover = v.Bullet.HitStunFrames
}
}
}
}
case *FireballBullet:
bulletShape := bulletCollider.Shape.(*resolv.ConvexPolygon)
offender := currRenderFrame.PlayersArr[v.BattleAttr.OffenderJoinIndex-1]
for _, obj := range collision.Objects {
defenderShape := obj.Shape.(*resolv.ConvexPolygon)
switch t := obj.Data.(type) {
case *PlayerDownsync:
if v.BattleAttr.OffenderJoinIndex == t.JoinIndex {
continue
}
overlapped, _, _, _ := calcPushbacks(0, 0, bulletShape, defenderShape)
if !overlapped {
continue
}
exploded = true
if _, existent := invinsibleSet[t.CharacterState]; existent {
continue
}
if 0 < t.FramesInvinsible {
continue
}
xfac := int32(1) // By now, straight Punch offset doesn't respect "y-axis"
if 0 > offender.DirX {
xfac = -xfac
}
atkedPlayerInNextFrame := nextRenderFramePlayers[t.JoinIndex-1]
atkedPlayerInNextFrame.Hp -= v.Bullet.Damage
if 0 >= atkedPlayerInNextFrame.Hp {
// [WARNING] We don't have "dying in air" animation for now, and for better graphical recognition, play the same dying animation even in air
atkedPlayerInNextFrame.Hp = 0
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_DYING
atkedPlayerInNextFrame.FramesToRecover = DYING_FRAMES_TO_RECOVER
} else {
pushbackVelX, pushbackVelY := xfac*v.Bullet.PushbackVelX, v.Bullet.PushbackVelY
atkedPlayerInNextFrame.VelX = pushbackVelX
atkedPlayerInNextFrame.VelY = pushbackVelY
if v.Bullet.BlowUp {
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_BLOWN_UP1
} else {
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATKED1
}
oldFramesToRecover := nextRenderFramePlayers[t.JoinIndex-1].FramesToRecover
if v.Bullet.HitStunFrames > oldFramesToRecover {
atkedPlayerInNextFrame.FramesToRecover = v.Bullet.HitStunFrames
}
}
default:
exploded = true
explodedOnAnotherPlayer := false
if nil == collision {
continue
}
var bulletStaticAttr *BulletConfig = nil
var bulletBattleAttr *BulletBattleAttr = nil
switch v := bulletCollider.Data.(type) {
case *MeleeBullet:
bulletStaticAttr = v.Bullet
bulletBattleAttr = v.BattleAttr
case *FireballBullet:
bulletStaticAttr = v.Bullet
bulletBattleAttr = v.BattleAttr
}
bulletShape := bulletCollider.Shape.(*resolv.ConvexPolygon)
offender := currRenderFrame.PlayersArr[bulletBattleAttr.OffenderJoinIndex-1]
for _, obj := range collision.Objects {
defenderShape := obj.Shape.(*resolv.ConvexPolygon)
switch t := obj.Data.(type) {
case *PlayerDownsync:
if bulletBattleAttr.OffenderJoinIndex == t.JoinIndex {
continue
}
overlapped, _, _, _ := calcPushbacks(0, 0, bulletShape, defenderShape)
if !overlapped {
continue
}
if _, existent := invinsibleSet[t.CharacterState]; existent {
continue
}
if 0 < t.FramesInvinsible {
continue
}
exploded = true
explodedOnAnotherPlayer = true
xfac := int32(1) // By now, straight Punch offset doesn't respect "y-axis"
if 0 > offender.DirX {
xfac = -xfac
}
atkedPlayerInNextFrame := nextRenderFramePlayers[t.JoinIndex-1]
atkedPlayerInNextFrame.Hp -= bulletStaticAttr.Damage
if 0 >= atkedPlayerInNextFrame.Hp {
// [WARNING] We don't have "dying in air" animation for now, and for better graphical recognition, play the same dying animation even in air
atkedPlayerInNextFrame.Hp = 0
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_DYING
atkedPlayerInNextFrame.FramesToRecover = DYING_FRAMES_TO_RECOVER
} else {
pushbackVelX, pushbackVelY := xfac*bulletStaticAttr.PushbackVelX, bulletStaticAttr.PushbackVelY
atkedPlayerInNextFrame.VelX = pushbackVelX
atkedPlayerInNextFrame.VelY = pushbackVelY
if bulletStaticAttr.BlowUp {
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_BLOWN_UP1
} else {
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATKED1
}
oldFramesToRecover := nextRenderFramePlayers[t.JoinIndex-1].FramesToRecover
if bulletStaticAttr.HitStunFrames > oldFramesToRecover {
atkedPlayerInNextFrame.FramesToRecover = bulletStaticAttr.HitStunFrames
}
}
default:
exploded = true
}
}
if exploded {
switch v := bulletCollider.Data.(type) {
case *MeleeBullet:
v.BlState = BULLET_EXPLODING
v.FramesInBlState = 0
if explodedOnAnotherPlayer {
v.FramesInBlState = 0
} else {
// When hitting a barrier, don't play explosion anim
v.FramesInBlState = v.Bullet.ExplosionFrames + 1
}
//fmt.Printf("melee exploded @currRenderFrame.Id=%d, bulletLocalId=%d, blState=%d\n", currRenderFrame.Id, v.BattleAttr.BulletLocalId, v.BlState)
case *FireballBullet:
v.BlState = BULLET_EXPLODING

View File

@@ -538,7 +538,7 @@ var skills = map[int]*Skill{
HitboxSizeX: int32(float64(64) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxSizeY: int32(float64(48) * WORLD_TO_VIRTUAL_GRID_RATIO),
BlowUp: false,
ExplosionFrames: 10,
ExplosionFrames: 30,
SpeciesId: int32(1),
},
},
@@ -781,10 +781,10 @@ var skills = map[int]*Skill{
Hits: []interface{}{
&MeleeBullet{
Bullet: &BulletConfig{
StartupFrames: int32(3),
StartupFrames: int32(4),
ActiveFrames: int32(20),
HitStunFrames: int32(18),
BlockStunFrames: int32(9),
HitStunFrames: int32(9),
BlockStunFrames: int32(5),
Damage: int32(5),
SelfLockVelX: NO_LOCK_VEL,
SelfLockVelY: NO_LOCK_VEL,

View File

@@ -1,3 +1,5 @@
# TODO: For websocket traffic, use a "consistent hash" on "expectedRoomId" and "boundRoomId"!
server {
listen 80;
server_name tsrht.lokcol.com;