mirror of
https://github.com/genxium/DelayNoMore
synced 2024-12-26 03:39:00 +00:00
Integrated basic holepunching for multiplayer codebase.
This commit is contained in:
parent
5df545e168
commit
8536521136
@ -37,6 +37,8 @@ type mysqlConf struct {
|
|||||||
|
|
||||||
type sioConf struct {
|
type sioConf struct {
|
||||||
HostAndPort string `json:"hostAndPort"`
|
HostAndPort string `json:"hostAndPort"`
|
||||||
|
UdpHost string `json:"udpHost"`
|
||||||
|
UdpPort int `json:"udpPort"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type botServerConf struct {
|
type botServerConf struct {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
{
|
{
|
||||||
"hostAndPort": "0.0.0.0:9992"
|
"hostAndPort": "0.0.0.0:9992",
|
||||||
|
"udpHost": "0.0.0.0",
|
||||||
|
"udpPort": 3000
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,8 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/robfig/cron"
|
"github.com/robfig/cron"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -34,7 +36,7 @@ func main() {
|
|||||||
env_tools.MergeTestPlayerAccounts()
|
env_tools.MergeTestPlayerAccounts()
|
||||||
}
|
}
|
||||||
models.InitRoomHeapManager()
|
models.InitRoomHeapManager()
|
||||||
startScheduler()
|
// startScheduler()
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
setRouter(router)
|
setRouter(router)
|
||||||
|
|
||||||
@ -54,6 +56,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
Logger.Info("Listening and serving HTTP on", zap.Any("Conf.Sio.HostAndPort", Conf.Sio.HostAndPort))
|
Logger.Info("Listening and serving HTTP on", zap.Any("Conf.Sio.HostAndPort", Conf.Sio.HostAndPort))
|
||||||
}()
|
}()
|
||||||
|
go startUdpServer()
|
||||||
var gracefulStop = make(chan os.Signal)
|
var gracefulStop = make(chan os.Signal)
|
||||||
signal.Notify(gracefulStop, syscall.SIGTERM)
|
signal.Notify(gracefulStop, syscall.SIGTERM)
|
||||||
signal.Notify(gracefulStop, syscall.SIGINT)
|
signal.Notify(gracefulStop, syscall.SIGINT)
|
||||||
@ -114,3 +117,26 @@ func startScheduler() {
|
|||||||
//c.AddFunc("*/1 * * * * *", FuncName)
|
//c.AddFunc("*/1 * * * * *", FuncName)
|
||||||
c.Start()
|
c.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func startUdpServer() {
|
||||||
|
conn, err := net.ListenUDP("udp", &net.UDPAddr{
|
||||||
|
Port: Conf.Sio.UdpPort,
|
||||||
|
IP: net.ParseIP(Conf.Sio.UdpHost),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer conn.Close()
|
||||||
|
Logger.Info(fmt.Sprintf("Udp server 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("received: %d bytes from %s\n", rlen, remote))
|
||||||
|
ws.HandleUdpHolePunchingForPlayer(message[0:rlen], remote)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -50,6 +50,8 @@ type Player struct {
|
|||||||
LastSentInputFrameId int32
|
LastSentInputFrameId int32
|
||||||
AckingFrameId int32
|
AckingFrameId int32
|
||||||
AckingInputFrameId int32
|
AckingInputFrameId int32
|
||||||
|
|
||||||
|
UdpAddr *PeerUdpAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExistPlayerByName(name string) (bool, error) {
|
func ExistPlayerByName(name string) (bool, error) {
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"jsexport/battle"
|
"jsexport/battle"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"resolv"
|
"resolv"
|
||||||
@ -32,6 +33,7 @@ const (
|
|||||||
DOWNSYNC_MSG_ACT_BATTLE_STOPPED = int32(3)
|
DOWNSYNC_MSG_ACT_BATTLE_STOPPED = int32(3)
|
||||||
DOWNSYNC_MSG_ACT_FORCED_RESYNC = int32(4)
|
DOWNSYNC_MSG_ACT_FORCED_RESYNC = int32(4)
|
||||||
DOWNSYNC_MSG_ACT_PEER_INPUT_BATCH = int32(5)
|
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_READY_TO_START = int32(-1)
|
||||||
DOWNSYNC_MSG_ACT_BATTLE_START = int32(0)
|
DOWNSYNC_MSG_ACT_BATTLE_START = int32(0)
|
||||||
@ -176,6 +178,7 @@ func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, session *websocke
|
|||||||
|
|
||||||
defer pR.onPlayerAdded(playerId)
|
defer pR.onPlayerAdded(playerId)
|
||||||
|
|
||||||
|
pPlayerFromDbInit.UdpAddr = nil
|
||||||
pPlayerFromDbInit.AckingFrameId = -1
|
pPlayerFromDbInit.AckingFrameId = -1
|
||||||
pPlayerFromDbInit.AckingInputFrameId = -1
|
pPlayerFromDbInit.AckingInputFrameId = -1
|
||||||
pPlayerFromDbInit.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED
|
pPlayerFromDbInit.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED
|
||||||
@ -215,6 +218,7 @@ func (pR *Room) ReAddPlayerIfPossible(pTmpPlayerInstance *Player, session *webso
|
|||||||
*/
|
*/
|
||||||
defer pR.onPlayerReAdded(playerId)
|
defer pR.onPlayerReAdded(playerId)
|
||||||
pEffectiveInRoomPlayerInstance := pR.Players[playerId]
|
pEffectiveInRoomPlayerInstance := pR.Players[playerId]
|
||||||
|
pEffectiveInRoomPlayerInstance.UdpAddr = nil
|
||||||
pEffectiveInRoomPlayerInstance.AckingFrameId = -1
|
pEffectiveInRoomPlayerInstance.AckingFrameId = -1
|
||||||
pEffectiveInRoomPlayerInstance.AckingInputFrameId = -1
|
pEffectiveInRoomPlayerInstance.AckingInputFrameId = -1
|
||||||
pEffectiveInRoomPlayerInstance.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED
|
pEffectiveInRoomPlayerInstance.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED
|
||||||
@ -1068,7 +1072,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))
|
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 playerDownsyncSession, existent := pR.PlayerDownsyncSessionDict[playerId]; existent {
|
||||||
if err := playerDownsyncSession.WriteMessage(websocket.BinaryMessage, theBytes); nil != err {
|
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))
|
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))
|
||||||
@ -1607,3 +1613,53 @@ 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@ -256,17 +257,19 @@ func Serve(c *gin.Context) {
|
|||||||
SpaceOffsetX: pRoom.SpaceOffsetX,
|
SpaceOffsetX: pRoom.SpaceOffsetX,
|
||||||
SpaceOffsetY: pRoom.SpaceOffsetY,
|
SpaceOffsetY: pRoom.SpaceOffsetY,
|
||||||
|
|
||||||
RenderCacheSize: pRoom.RenderCacheSize,
|
RenderCacheSize: pRoom.RenderCacheSize,
|
||||||
CollisionMinStep: pRoom.CollisionMinStep,
|
CollisionMinStep: pRoom.CollisionMinStep,
|
||||||
|
BoundRoomCapacity: int32(pRoom.Capacity),
|
||||||
|
|
||||||
FrameDataLoggingEnabled: pRoom.FrameDataLoggingEnabled,
|
FrameDataLoggingEnabled: pRoom.FrameDataLoggingEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := &pb.WsResp{
|
resp := &pb.WsResp{
|
||||||
Ret: int32(Constants.RetCode.Ok),
|
Ret: int32(Constants.RetCode.Ok),
|
||||||
EchoedMsgId: int32(0),
|
EchoedMsgId: int32(0),
|
||||||
Act: models.DOWNSYNC_MSG_ACT_HB_REQ,
|
Act: models.DOWNSYNC_MSG_ACT_HB_REQ,
|
||||||
BciFrame: bciFrame,
|
BciFrame: bciFrame,
|
||||||
|
PeerJoinIndex: pThePlayer.JoinIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Debug("Sending downsync HeartbeatRequirements:", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Any("resp", resp))
|
Logger.Debug("Sending downsync HeartbeatRequirements:", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Any("resp", resp))
|
||||||
@ -432,12 +435,12 @@ func HandleSecondaryWsSessionForPlayer(c *gin.Context) {
|
|||||||
playerId, err := models.GetPlayerIdByToken(token)
|
playerId, err := models.GetPlayerIdByToken(token)
|
||||||
if err != nil || playerId == 0 {
|
if err != nil || playerId == 0 {
|
||||||
// TODO: Abort with specific message.
|
// 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)
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
return
|
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)
|
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -482,3 +485,32 @@ func HandleSecondaryWsSessionForPlayer(c *gin.Context) {
|
|||||||
|
|
||||||
pRoom.SetSecondarySession(int32(playerId), conn, signalToCloseConnOfThisPlayer)
|
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("Udp session 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("Udp session 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("Udp session playerLogin record not found for:", zap.Any("intAuthToken", token))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Info("Udp session 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)
|
||||||
|
}
|
||||||
|
@ -77,22 +77,12 @@ message WsReq {
|
|||||||
HeartbeatUpsync hb = 8;
|
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.
|
|
||||||
}
|
|
||||||
|
|
||||||
message InputsBufferSnapshot {
|
message InputsBufferSnapshot {
|
||||||
int32 refRenderFrameId = 1;
|
int32 refRenderFrameId = 1;
|
||||||
uint64 unconfirmedMask = 2;
|
uint64 unconfirmedMask = 2;
|
||||||
repeated InputFrameDownsync toSendInputFrameDownsyncs = 3;
|
repeated InputFrameDownsync toSendInputFrameDownsyncs = 3;
|
||||||
bool shouldForceResync = 4;
|
bool shouldForceResync = 4;
|
||||||
int32 peerJoinIndex = 5; // Only used when "WsResp.peerJoinIndex" is used.
|
int32 peerJoinIndex = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message MeleeBullet {
|
message MeleeBullet {
|
||||||
@ -191,10 +181,23 @@ message BattleColliderInfo {
|
|||||||
double spaceOffsetX = 11;
|
double spaceOffsetX = 11;
|
||||||
double spaceOffsetY = 12;
|
double spaceOffsetY = 12;
|
||||||
int32 collisionMinStep = 13;
|
int32 collisionMinStep = 13;
|
||||||
|
int32 boundRoomCapacity = 14;
|
||||||
|
|
||||||
bool frameDataLoggingEnabled = 1024;
|
bool frameDataLoggingEnabled = 1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message HolePunchUpsync {
|
||||||
|
string intAuthToken = 1;
|
||||||
|
int32 boundRoomId = 2;
|
||||||
|
int32 authKey = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PeerUdpAddr {
|
||||||
|
string ip = 1;
|
||||||
|
int32 port = 2;
|
||||||
|
int32 authKey = 3;
|
||||||
|
}
|
||||||
|
|
||||||
message RoomDownsyncFrame {
|
message RoomDownsyncFrame {
|
||||||
int32 id = 1;
|
int32 id = 1;
|
||||||
repeated PlayerDownsync playersArr = 2;
|
repeated PlayerDownsync playersArr = 2;
|
||||||
@ -207,11 +210,15 @@ message RoomDownsyncFrame {
|
|||||||
repeated int32 speciesIdList = 1026;
|
repeated int32 speciesIdList = 1026;
|
||||||
|
|
||||||
int32 bulletLocalIdCounter = 1027;
|
int32 bulletLocalIdCounter = 1027;
|
||||||
|
repeated PeerUdpAddr peerUdpAddrList = 1028;
|
||||||
}
|
}
|
||||||
|
|
||||||
message HolePunchUpsync {
|
message WsResp {
|
||||||
int32 joinIndex = 1;
|
int32 ret = 1;
|
||||||
string intAuthToken = 2;
|
int32 echoedMsgId = 2;
|
||||||
int32 boundRoomId = 3;
|
int32 act = 3;
|
||||||
int32 authKey = 4;
|
RoomDownsyncFrame rdf = 4;
|
||||||
|
repeated InputFrameDownsync inputFrameDownsyncBatch = 5;
|
||||||
|
BattleColliderInfo bciFrame = 6;
|
||||||
|
int32 peerJoinIndex = 7;
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ cc.Class({
|
|||||||
|
|
||||||
exitBtnOnClick(evt) {
|
exitBtnOnClick(evt) {
|
||||||
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
|
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
|
||||||
window.closeWSConnection();
|
window.closeWSConnection(constants.RET_CODE.UNKNOWN_ERROR, "");
|
||||||
cc.director.loadScene('login');
|
cc.director.loadScene('login');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -336,7 +336,6 @@ cc.Class({
|
|||||||
|
|
||||||
self.recentRenderCache = new RingBuffer(self.renderCacheSize);
|
self.recentRenderCache = new RingBuffer(self.renderCacheSize);
|
||||||
|
|
||||||
self.selfPlayerInfo = null; // This field is kept for distinguishing "self" and "others".
|
|
||||||
self.recentInputCache = gopkgs.NewRingBufferJs((self.renderCacheSize >> 1) + 1);
|
self.recentInputCache = gopkgs.NewRingBufferJs((self.renderCacheSize >> 1) + 1);
|
||||||
|
|
||||||
self.gopkgsCollisionSys = gopkgs.NewCollisionSpaceJs((self.spaceOffsetX << 1), (self.spaceOffsetY << 1), self.collisionMinStep, self.collisionMinStep);
|
self.gopkgsCollisionSys = gopkgs.NewCollisionSpaceJs((self.spaceOffsetX << 1), (self.spaceOffsetY << 1), self.collisionMinStep, self.collisionMinStep);
|
||||||
@ -500,7 +499,7 @@ cc.Class({
|
|||||||
const fullPathOfTmxFile = cc.js.formatStr("map/%s/map", parsedBattleColliderInfo.stageName);
|
const fullPathOfTmxFile = cc.js.formatStr("map/%s/map", parsedBattleColliderInfo.stageName);
|
||||||
cc.loader.loadRes(fullPathOfTmxFile, cc.TiledMapAsset, (err, tmxAsset) => {
|
cc.loader.loadRes(fullPathOfTmxFile, cc.TiledMapAsset, (err, tmxAsset) => {
|
||||||
if (null != err) {
|
if (null != err) {
|
||||||
console.error(err);
|
console.error(`Error occurred when loading tiled stage ${parsedBattleColliderInfo.stageName}`, err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -549,10 +548,6 @@ cc.Class({
|
|||||||
const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter);
|
const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter);
|
||||||
self.gopkgsCollisionSysMap[collisionBarrierIndex] = newBarrierCollider;
|
self.gopkgsCollisionSysMap[collisionBarrierIndex] = newBarrierCollider;
|
||||||
}
|
}
|
||||||
self.selfPlayerInfo = JSON.parse(cc.sys.localStorage.getItem('selfPlayer'));
|
|
||||||
Object.assign(self.selfPlayerInfo, {
|
|
||||||
Id: self.selfPlayerInfo.playerId
|
|
||||||
});
|
|
||||||
self.initDebugDrawers();
|
self.initDebugDrawers();
|
||||||
const reqData = window.pb.protos.WsReq.encode({
|
const reqData = window.pb.protos.WsReq.encode({
|
||||||
msgId: Date.now(),
|
msgId: Date.now(),
|
||||||
@ -920,7 +915,7 @@ batchInputFrameIdRange=[${batch[0].inputFrameId}, ${batch[batch.length - 1].inpu
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self._stringifyRdfIdToActuallyUsedInput();
|
self._stringifyRdfIdToActuallyUsedInput();
|
||||||
window.closeWSConnection(constants.RET_CODE.BATTLE_STOPPED);
|
window.closeWSConnection(constants.RET_CODE.BATTLE_STOPPED, "");
|
||||||
self.battleState = ALL_BATTLE_STATES.IN_SETTLEMENT;
|
self.battleState = ALL_BATTLE_STATES.IN_SETTLEMENT;
|
||||||
self.countdownNanos = null;
|
self.countdownNanos = null;
|
||||||
if (self.musicEffectManagerScriptIns) {
|
if (self.musicEffectManagerScriptIns) {
|
||||||
@ -1273,24 +1268,6 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
|
|||||||
}
|
}
|
||||||
const j = gopkgs.ConvertToDelayedInputFrameId(i);
|
const j = gopkgs.ConvertToDelayedInputFrameId(i);
|
||||||
const delayedInputFrame = self.recentInputCache.GetByFrameId(j);
|
const delayedInputFrame = self.recentInputCache.GetByFrameId(j);
|
||||||
/*
|
|
||||||
const prevJ = gopkgs.ConvertToDelayedInputFrameId(i - 1);
|
|
||||||
const prevDelayedInputFrame = self.recentInputCache.GetByFrameId(prevJ);
|
|
||||||
const prevBtnALevel = (null == prevDelayedInputFrame ? 0 : ((prevDelayedInputFrame.InputList[self.selfPlayerInfo.JoinIndex - 1] >> 4) & 1));
|
|
||||||
const btnALevel = ((delayedInputFrame.InputList[self.selfPlayerInfo.JoinIndex - 1] >> 4) & 1);
|
|
||||||
if (
|
|
||||||
ATK_CHARACTER_STATE.Atk1[0] == currRdf.PlayersArr[self.selfPlayerInfo.JoinIndex - 1].CharacterState
|
|
||||||
||
|
|
||||||
ATK_CHARACTER_STATE.Atk2[0] == currRdf.PlayersArr[self.selfPlayerInfo.JoinIndex - 1].CharacterState
|
|
||||||
) {
|
|
||||||
console.log(`rdf.Id=${i}, (btnALevel,j)=(${btnALevel},${j}), (prevBtnALevel,prevJ) is (${prevBtnALevel},${prevJ}), in cancellable atk!`);
|
|
||||||
}
|
|
||||||
if (btnALevel > 0) {
|
|
||||||
if (btnALevel > prevBtnALevel) {
|
|
||||||
console.log(`rdf.Id=${i}, rising edge of btnA triggered`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (self.frameDataLoggingEnabled) {
|
if (self.frameDataLoggingEnabled) {
|
||||||
const actuallyUsedInputClone = delayedInputFrame.InputList.slice();
|
const actuallyUsedInputClone = delayedInputFrame.InputList.slice();
|
||||||
@ -1336,7 +1313,7 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
|
|||||||
|
|
||||||
const selfPlayerId = self.selfPlayerInfo.Id;
|
const selfPlayerId = self.selfPlayerInfo.Id;
|
||||||
if (selfPlayerId == playerId) {
|
if (selfPlayerId == playerId) {
|
||||||
self.selfPlayerInfo.JoinIndex = immediatePlayerInfo.JoinIndex;
|
self.selfPlayerInfo.JoinIndex = immediatePlayerInfo.JoinIndex; // Update here in case of any change during WAITING phase
|
||||||
nodeAndScriptIns[1].showArrowTipNode();
|
nodeAndScriptIns[1].showArrowTipNode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,13 +156,12 @@ cc.Class({
|
|||||||
cc.log(`#2 Js called back by CPP: onUdpMessage: ${JSON.stringify(echoed)}`);
|
cc.log(`#2 Js called back by CPP: onUdpMessage: ${JSON.stringify(echoed)}`);
|
||||||
};
|
};
|
||||||
const res1 = DelayNoMore.UdpSession.openUdpSession(8888 + self.selfPlayerInfo.JoinIndex);
|
const res1 = DelayNoMore.UdpSession.openUdpSession(8888 + self.selfPlayerInfo.JoinIndex);
|
||||||
const holePunchDate = window.pb.protos.HolePunchUpsync.encode({
|
const holePunchData = window.pb.protos.HolePunchUpsync.encode({
|
||||||
joinIndex: self.selfPlayerInfo.JoinIndex,
|
|
||||||
boundRoomId: 22,
|
boundRoomId: 22,
|
||||||
intAuthToken: "foobar",
|
intAuthToken: "foobar",
|
||||||
authKey: Math.floor(Math.random() * 65535),
|
authKey: Math.floor(Math.random() * 65535),
|
||||||
}).finish()
|
}).finish()
|
||||||
const res2 = DelayNoMore.UdpSession.punchToServer("127.0.0.1", 3000, holePunchDate);
|
const res2 = DelayNoMore.UdpSession.punchToServer("127.0.0.1", 3000, holePunchData);
|
||||||
const res3 = DelayNoMore.UdpSession.upsertPeerUdpAddr(self.selfPlayerInfo.JoinIndex, "192.168.31.194", 6789, 123456, 2, self.selfPlayerInfo.JoinIndex);
|
const res3 = DelayNoMore.UdpSession.upsertPeerUdpAddr(self.selfPlayerInfo.JoinIndex, "192.168.31.194", 6789, 123456, 2, self.selfPlayerInfo.JoinIndex);
|
||||||
//const res4 = DelayNoMore.UdpSession.closeUdpSession();
|
//const res4 = DelayNoMore.UdpSession.closeUdpSession();
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ window.DOWNSYNC_MSG_ACT_INPUT_BATCH = 2;
|
|||||||
window.DOWNSYNC_MSG_ACT_BATTLE_STOPPED = 3;
|
window.DOWNSYNC_MSG_ACT_BATTLE_STOPPED = 3;
|
||||||
window.DOWNSYNC_MSG_ACT_FORCED_RESYNC = 4;
|
window.DOWNSYNC_MSG_ACT_FORCED_RESYNC = 4;
|
||||||
window.DOWNSYNC_MSG_ACT_PEER_INPUT_BATCH = 5;
|
window.DOWNSYNC_MSG_ACT_PEER_INPUT_BATCH = 5;
|
||||||
|
window.DOWNSYNC_MSG_ACT_PEER_UDP_ADDR = 6;
|
||||||
|
|
||||||
window.sendSafely = function(msgStr) {
|
window.sendSafely = function(msgStr) {
|
||||||
/**
|
/**
|
||||||
@ -46,9 +47,19 @@ window.getBoundRoomIdFromPersistentStorage = function() {
|
|||||||
return cc.sys.localStorage.getItem("boundRoomId");
|
return cc.sys.localStorage.getItem("boundRoomId");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
window.getBoundRoomCapacityFromPersistentStorage = function() {
|
||||||
|
const boundRoomIdExpiresAt = parseInt(cc.sys.localStorage.getItem("boundRoomIdExpiresAt"));
|
||||||
|
if (!boundRoomIdExpiresAt || Date.now() >= boundRoomIdExpiresAt) {
|
||||||
|
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return cc.sys.localStorage.getItem("boundRoomCapacity");
|
||||||
|
};
|
||||||
|
|
||||||
window.clearBoundRoomIdInBothVolatileAndPersistentStorage = function() {
|
window.clearBoundRoomIdInBothVolatileAndPersistentStorage = function() {
|
||||||
window.boundRoomId = null;
|
window.boundRoomId = null;
|
||||||
cc.sys.localStorage.removeItem("boundRoomId");
|
cc.sys.localStorage.removeItem("boundRoomId");
|
||||||
|
cc.sys.localStorage.removeItem("boundRoomCapacity");
|
||||||
cc.sys.localStorage.removeItem("boundRoomIdExpiresAt");
|
cc.sys.localStorage.removeItem("boundRoomIdExpiresAt");
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -57,20 +68,44 @@ window.clearSelfPlayer = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
window.boundRoomId = getBoundRoomIdFromPersistentStorage();
|
window.boundRoomId = getBoundRoomIdFromPersistentStorage();
|
||||||
|
window.boundRoomCapacity = getBoundRoomCapacityFromPersistentStorage();
|
||||||
window.handleHbRequirements = function(resp) {
|
window.handleHbRequirements = function(resp) {
|
||||||
|
console.log(`Handle hb requirements #1`);
|
||||||
if (constants.RET_CODE.OK != resp.ret) return;
|
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.boundRoomId = resp.bciFrame.boundRoomId;
|
||||||
|
window.boundRoomCapacity = resp.bciFrame.boundRoomCapacity;
|
||||||
cc.sys.localStorage.setItem('boundRoomId', window.boundRoomId);
|
cc.sys.localStorage.setItem('boundRoomId', window.boundRoomId);
|
||||||
|
cc.sys.localStorage.setItem('boundRoomCapacity', window.boundRoomCapacity);
|
||||||
cc.sys.localStorage.setItem('boundRoomIdExpiresAt', Date.now() + 10 * 60 * 1000); // Temporarily hardcoded, for `boundRoomId` only.
|
cc.sys.localStorage.setItem('boundRoomIdExpiresAt', Date.now() + 10 * 60 * 1000); // Temporarily hardcoded, for `boundRoomId` only.
|
||||||
}
|
}
|
||||||
|
console.log(`Handle hb requirements #3`);
|
||||||
if (window.handleBattleColliderInfo) {
|
if (window.handleBattleColliderInfo) {
|
||||||
if (!cc.sys.isNative) {
|
|
||||||
window.initSecondarySession(null, window.boundRoomId);
|
|
||||||
}
|
|
||||||
window.handleBattleColliderInfo(resp.bciFrame);
|
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`);
|
||||||
|
const res1 = DelayNoMore.UdpSession.openUdpSession(8888 + window.mapIns.selfPlayerInfo.JoinIndex);
|
||||||
|
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 res2 = DelayNoMore.UdpSession.punchToServer(backendAddress.HOST, 3000, holePunchData);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function _uint8ToBase64(uint8Arr) {
|
function _uint8ToBase64(uint8Arr) {
|
||||||
@ -128,6 +163,7 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
|
|||||||
urlToConnect = urlToConnect + "&expectedRoomId=" + expectedRoomId;
|
urlToConnect = urlToConnect + "&expectedRoomId=" + expectedRoomId;
|
||||||
} else {
|
} else {
|
||||||
window.boundRoomId = getBoundRoomIdFromPersistentStorage();
|
window.boundRoomId = getBoundRoomIdFromPersistentStorage();
|
||||||
|
window.boundRoomCapacity = getBoundRoomCapacityFromPersistentStorage();
|
||||||
if (null != window.boundRoomId) {
|
if (null != window.boundRoomId) {
|
||||||
console.log("initPersistentSessionClient with boundRoomId == " + boundRoomId);
|
console.log("initPersistentSessionClient with boundRoomId == " + boundRoomId);
|
||||||
urlToConnect = urlToConnect + "&boundRoomId=" + window.boundRoomId;
|
urlToConnect = urlToConnect + "&boundRoomId=" + window.boundRoomId;
|
||||||
@ -178,6 +214,16 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
|
|||||||
}
|
}
|
||||||
mapIns.onRoomDownsyncFrame(resp.rdf, resp.inputFrameDownsyncBatch);
|
mapIns.onRoomDownsyncFrame(resp.rdf, resp.inputFrameDownsyncBatch);
|
||||||
break;
|
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;
|
||||||
|
const peerAddr = peerAddrList[peerJoinIndex - 1];
|
||||||
|
console.log(`Got DOWNSYNC_MSG_ACT_PEER_UDP_ADDR peerAddr=${peerAddr}; boundRoomCapacity=${window.boundRoomCapacity}, mapIns.selfPlayerInfo=${window.mapIns.selfPlayerInfo}`);
|
||||||
|
DelayNoMore.UdpSession.upsertPeerUdpAddr(peerJoinIndex, peerAddr.ip, peerAddr.port, peerAddr.authKey, window.boundRoomCapacity, window.mapIns.selfPlayerInfo.JoinIndex);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -224,6 +270,9 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (cc.sys.isNative) {
|
||||||
|
DelayNoMore.UdpSession.closeUdpSession();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -234,7 +283,7 @@ window.clearLocalStorageAndBackToLoginScene = function(shouldRetainBoundRoomIdIn
|
|||||||
window.mapIns.musicEffectManagerScriptIns.stopAllMusic();
|
window.mapIns.musicEffectManagerScriptIns.stopAllMusic();
|
||||||
}
|
}
|
||||||
|
|
||||||
window.closeWSConnection();
|
window.closeWSConnection(constants.RET_CODE.UNKNOWN_ERROR, "");
|
||||||
window.clearSelfPlayer();
|
window.clearSelfPlayer();
|
||||||
if (true != shouldRetainBoundRoomIdInBothVolatileAndPersistentStorage) {
|
if (true != shouldRetainBoundRoomIdInBothVolatileAndPersistentStorage) {
|
||||||
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
|
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -25,6 +25,7 @@
|
|||||||
<PlatformToolset Condition="'$(VisualStudioVersion)' == '14.0' and exists('$(MSBuildProgramFiles32)\Microsoft SDKs\Windows\v7.1A')">v140_xp</PlatformToolset>
|
<PlatformToolset Condition="'$(VisualStudioVersion)' == '14.0' and exists('$(MSBuildProgramFiles32)\Microsoft SDKs\Windows\v7.1A')">v140_xp</PlatformToolset>
|
||||||
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
|
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
|
||||||
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0' and exists('$(MSBuildProgramFiles32)\Microsoft SDKs\Windows\v7.1A')">v140_xp</PlatformToolset>
|
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0' and exists('$(MSBuildProgramFiles32)\Microsoft SDKs\Windows\v7.1A')">v140_xp</PlatformToolset>
|
||||||
|
<PlatformToolset>v140</PlatformToolset>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||||
<ConfigurationType>Application</ConfigurationType>
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
@ -17,8 +17,7 @@
|
|||||||
},
|
},
|
||||||
"encryptJs": false,
|
"encryptJs": false,
|
||||||
"excludeScenes": [
|
"excludeScenes": [
|
||||||
"92160186-3e0d-4e0a-ae20-97286170ba58",
|
"8491a86c-bec9-4813-968a-128ca01639e0"
|
||||||
"2ff474d9-0c9e-4fe3-87ec-fbff7cae85b4"
|
|
||||||
],
|
],
|
||||||
"fb-instant-games": {},
|
"fb-instant-games": {},
|
||||||
"includeSDKBox": false,
|
"includeSDKBox": false,
|
||||||
@ -38,7 +37,7 @@
|
|||||||
"REMOTE_SERVER_ROOT": "",
|
"REMOTE_SERVER_ROOT": "",
|
||||||
"orientation": "portrait"
|
"orientation": "portrait"
|
||||||
},
|
},
|
||||||
"startScene": "8491a86c-bec9-4813-968a-128ca01639e0",
|
"startScene": "2ff474d9-0c9e-4fe3-87ec-fbff7cae85b4",
|
||||||
"title": "DelayNoMore",
|
"title": "DelayNoMore",
|
||||||
"webOrientation": "landscape",
|
"webOrientation": "landscape",
|
||||||
"wechatgame": {
|
"wechatgame": {
|
||||||
@ -58,5 +57,5 @@
|
|||||||
"packageName": "org.genxium.delaynomore"
|
"packageName": "org.genxium.delaynomore"
|
||||||
},
|
},
|
||||||
"win32": {},
|
"win32": {},
|
||||||
"includeAnySDK": null
|
"includeAnySDK": false
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@
|
|||||||
"shelter_z_reducer",
|
"shelter_z_reducer",
|
||||||
"shelter"
|
"shelter"
|
||||||
],
|
],
|
||||||
"last-module-event-record-time": 1673930863015,
|
"last-module-event-record-time": 1674632533161,
|
||||||
"simulator-orientation": false,
|
"simulator-orientation": false,
|
||||||
"simulator-resolution": {
|
"simulator-resolution": {
|
||||||
"height": 640,
|
"height": 640,
|
||||||
|
Loading…
Reference in New Issue
Block a user