Merge pull request #15 from genxium/jumping_impl

Added jumping sync.
This commit is contained in:
Wing 2022-12-15 15:41:57 +08:00 committed by GitHub
commit c5b26d716e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 3381 additions and 14431 deletions

View File

@ -1,3 +1,7 @@
# What to be concerned for internet syncing
1. Server received too late (solution: force confirmation)
2. Client received too late (solution: prediction and frame chasing, big impact on user experience because the graphics will be inconsistent if mismatches occur too often)
# Potential avalanche from local lag # Potential avalanche from local lag
Under the current "input delay" algorithm, the lag of a single player would cause all the other players to receive outdated commands, e.g. when at a certain moment Under the current "input delay" algorithm, the lag of a single player would cause all the other players to receive outdated commands, e.g. when at a certain moment
- player#1: renderFrameId = 100, significantly lagged due to local CPU overheated - player#1: renderFrameId = 100, significantly lagged due to local CPU overheated
@ -9,7 +13,7 @@ players #2, #3 #4 would receive "outdated(in their subjective feelings) but all-
In a "no-server & p2p" setup, I couldn't think of a proper way to cope with such edge case. Solely on the frontend we could only mitigate the impact to players #2, #3, #4, e.g. a potential lag due to "large range of frame-chasing" is proactively avoided in `<proj-root>/frontend/assets/scripts/Map.js, function update(dt)`. In a "no-server & p2p" setup, I couldn't think of a proper way to cope with such edge case. Solely on the frontend we could only mitigate the impact to players #2, #3, #4, e.g. a potential lag due to "large range of frame-chasing" is proactively avoided in `<proj-root>/frontend/assets/scripts/Map.js, function update(dt)`.
However in a "server as authority" setup, the server could force confirming an inputFrame without player#1's upsync, and notify player#1 to apply a "roomDownsyncFrame" as well as drop all its outdated local inputFrames. To be fair, **a "p2p" setup can reduce round-trip to single-trip**, but w/o a point of authority in such case player#1 needs a way to recognize the slowness (e.g. check the received peer inputs) and ticks faster for a while to catch up; in contrast in a "server as authority" setup, the server could force confirming an inputFrame without player#1's upsync, and notify player#1 to apply a "roomDownsyncFrame" as well as drop all its outdated local inputFrames.
# Start up frames # Start up frames
renderFrameId | generatedInputFrameId | toApplyInputFrameId renderFrameId | generatedInputFrameId | toApplyInputFrameId

View File

@ -2,11 +2,11 @@
This project is a demo for a websocket-based rollback netcode inspired by [GGPO](https://github.com/pond3r/ggpo/blob/master/doc/README.md). This project is a demo for a websocket-based rollback netcode inspired by [GGPO](https://github.com/pond3r/ggpo/blob/master/doc/README.md).
_(the following gif is sped up to ~1.33x for file size reduction, kindly note that around ~8s countdown, the attack animation is resumed from a partial progress)_ _(the following gif is sped up to ~1.5x for file size reduction, kindly note that animations are resumed from a partial progress)_
![gif_demo](./charts/smooth_melee_attack_spedup.gif) ![gif_demo](./charts/jump_sync_spedup.gif)
Please also checkout [this demo video](https://pan.baidu.com/s/172AmIKxbFgGXZzWVqxNUPA?pwd=e2tp) to see how this demo carries out a full 60fps synchronization with the help of _batched input upsync/downsync_ for satisfying network I/O performance. Please also checkout [this demo video](https://pan.baidu.com/s/1Lmot9cb0pYylfUvC8G4fDg?pwd=ia97) to see how this demo carries out a full 60fps synchronization with the help of _batched input upsync/downsync_ for satisfying network I/O performance.
The video mainly shows the following features. The video mainly shows the following features.
- The backend receives inputs from frontend peers and broadcasts back for synchronization. - The backend receives inputs from frontend peers and broadcasts back for synchronization.

View File

@ -17,12 +17,16 @@ func toPbPlayers(modelInstances map[int32]*Player, withMetaInfo bool) map[int32]
VirtualGridY: last.VirtualGridY, VirtualGridY: last.VirtualGridY,
DirX: last.DirX, DirX: last.DirX,
DirY: last.DirY, DirY: last.DirY,
ColliderRadius: last.ColliderRadius, VelX: last.VelX,
VelY: last.VelY,
Speed: last.Speed, Speed: last.Speed,
BattleState: last.BattleState, BattleState: last.BattleState,
CharacterState: last.CharacterState,
InAir: last.InAir,
JoinIndex: last.JoinIndex,
ColliderRadius: last.ColliderRadius,
Score: last.Score, Score: last.Score,
Removed: last.Removed, Removed: last.Removed,
JoinIndex: last.JoinIndex,
} }
if withMetaInfo { if withMetaInfo {
toRet[k].Name = last.Name toRet[k].Name = last.Name

View File

@ -61,10 +61,13 @@ const (
) )
const ( const (
ATK_CHARACTER_STATE_IDLE1 = 0 ATK_CHARACTER_STATE_IDLE1 = 0
ATK_CHARACTER_STATE_WALKING = 1 ATK_CHARACTER_STATE_WALKING = 1
ATK_CHARACTER_STATE_ATK1 = 2 ATK_CHARACTER_STATE_ATK1 = 2
ATK_CHARACTER_STATE_ATKED1 = 3 ATK_CHARACTER_STATE_ATKED1 = 3
ATK_CHARACTER_STATE_INAIR_IDLE1 = 4
ATK_CHARACTER_STATE_INAIR_ATK1 = 5
ATK_CHARACTER_STATE_INAIR_ATKED1 = 6
) )
const ( const (
@ -198,6 +201,7 @@ func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, session *websocke
pPlayerFromDbInit.BattleState = PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK pPlayerFromDbInit.BattleState = PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK
pPlayerFromDbInit.Speed = pR.PlayerDefaultSpeed // Hardcoded pPlayerFromDbInit.Speed = pR.PlayerDefaultSpeed // Hardcoded
pPlayerFromDbInit.ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded pPlayerFromDbInit.ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded
pPlayerFromDbInit.InAir = true // Hardcoded
pR.Players[playerId] = pPlayerFromDbInit pR.Players[playerId] = pPlayerFromDbInit
pR.PlayerDownsyncSessionDict[playerId] = session pR.PlayerDownsyncSessionDict[playerId] = session
@ -229,6 +233,7 @@ func (pR *Room) ReAddPlayerIfPossible(pTmpPlayerInstance *Player, session *webso
pEffectiveInRoomPlayerInstance.BattleState = PlayerBattleStateIns.READDED_PENDING_BATTLE_COLLIDER_ACK pEffectiveInRoomPlayerInstance.BattleState = PlayerBattleStateIns.READDED_PENDING_BATTLE_COLLIDER_ACK
pEffectiveInRoomPlayerInstance.Speed = pR.PlayerDefaultSpeed // Hardcoded pEffectiveInRoomPlayerInstance.Speed = pR.PlayerDefaultSpeed // Hardcoded
pEffectiveInRoomPlayerInstance.ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded pEffectiveInRoomPlayerInstance.ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded
pEffectiveInRoomPlayerInstance.InAir = true // Hardcoded
pR.PlayerDownsyncSessionDict[playerId] = session pR.PlayerDownsyncSessionDict[playerId] = session
pR.PlayerSignalToCloseDict[playerId] = signalToCloseConnOfThisPlayer pR.PlayerSignalToCloseDict[playerId] = signalToCloseConnOfThisPlayer
@ -419,7 +424,7 @@ func (pR *Room) StartBattle() {
stCalculation := utils.UnixtimeNano() stCalculation := utils.UnixtimeNano()
elapsedNanosSinceLastFrameIdTriggered := stCalculation - pR.LastRenderFrameIdTriggeredAt elapsedNanosSinceLastFrameIdTriggered := stCalculation - pR.LastRenderFrameIdTriggeredAt
if elapsedNanosSinceLastFrameIdTriggered < pR.RollbackEstimatedDtNanos { if elapsedNanosSinceLastFrameIdTriggered < pR.RollbackEstimatedDtNanos {
Logger.Warn(fmt.Sprintf("renderFrameId=%v@roomId=%v: Is backend running too fast? elapsedNanosSinceLastFrameIdTriggered=%v", pR.RenderFrameId, pR.Id, elapsedNanosSinceLastFrameIdTriggered)) Logger.Debug(fmt.Sprintf("renderFrameId=%v@roomId=%v: Is backend running too fast? elapsedNanosSinceLastFrameIdTriggered=%v", pR.RenderFrameId, pR.Id, elapsedNanosSinceLastFrameIdTriggered))
} }
if pR.RenderFrameId > pR.BattleDurationFrames { if pR.RenderFrameId > pR.BattleDurationFrames {
@ -430,24 +435,6 @@ func (pR *Room) StartBattle() {
return return
} }
if 0 == pR.RenderFrameId {
for _, player := range pR.PlayersArr {
playerId := player.Id
thatPlayerBattleState := atomic.LoadInt32(&(player.BattleState)) // Might be changed in "OnPlayerDisconnected/OnPlayerLost" from other threads
// [WARNING] DON'T try to send any message to an inactive player!
switch thatPlayerBattleState {
case PlayerBattleStateIns.DISCONNECTED:
case PlayerBattleStateIns.LOST:
case PlayerBattleStateIns.EXPELLED_DURING_GAME:
case PlayerBattleStateIns.EXPELLED_IN_DISMISSAL:
continue
}
kickoffFrame := pR.RenderFrameBuffer.GetByFrameId(0).(*RoomDownsyncFrame)
pR.sendSafely(kickoffFrame, nil, DOWNSYNC_MSG_ACT_BATTLE_START, playerId, true)
}
Logger.Info(fmt.Sprintf("In `battleMainLoop` for roomId=%v sent out kickoffFrame", pR.Id))
}
/* /*
[WARNING] [WARNING]
Golang "time.Sleep" is known to be taking longer than specified time to wake up at millisecond granularity, as discussed in https://github.com/golang/go/issues/44343 Golang "time.Sleep" is known to be taking longer than specified time to wake up at millisecond granularity, as discussed in https://github.com/golang/go/issues/44343
@ -457,6 +444,24 @@ func (pR *Room) StartBattle() {
nextRenderFrameId := int32((totalElapsedNanos + pR.dilutedRollbackEstimatedDtNanos - 1) / pR.dilutedRollbackEstimatedDtNanos) // fast ceiling nextRenderFrameId := int32((totalElapsedNanos + pR.dilutedRollbackEstimatedDtNanos - 1) / pR.dilutedRollbackEstimatedDtNanos) // fast ceiling
toSleepNanos := int64(0) toSleepNanos := int64(0)
if nextRenderFrameId > pR.RenderFrameId { if nextRenderFrameId > pR.RenderFrameId {
if 0 == pR.RenderFrameId {
// It's important to send kickoff frame iff "0 == pR.RenderFrameId && nextRenderFrameId > pR.RenderFrameId", otherwise it might send duplicate kickoff frames
for _, player := range pR.PlayersArr {
playerId := player.Id
thatPlayerBattleState := atomic.LoadInt32(&(player.BattleState)) // Might be changed in "OnPlayerDisconnected/OnPlayerLost" from other threads
// [WARNING] DON'T try to send any message to an inactive player!
switch thatPlayerBattleState {
case PlayerBattleStateIns.DISCONNECTED:
case PlayerBattleStateIns.LOST:
case PlayerBattleStateIns.EXPELLED_DURING_GAME:
case PlayerBattleStateIns.EXPELLED_IN_DISMISSAL:
continue
}
kickoffFrame := pR.RenderFrameBuffer.GetByFrameId(0).(*RoomDownsyncFrame)
pR.sendSafely(kickoffFrame, nil, DOWNSYNC_MSG_ACT_BATTLE_START, playerId, true)
}
Logger.Info(fmt.Sprintf("In `battleMainLoop` for roomId=%v sent out kickoffFrame", pR.Id))
}
prevRenderFrameId := pR.RenderFrameId prevRenderFrameId := pR.RenderFrameId
pR.RenderFrameId = nextRenderFrameId pR.RenderFrameId = nextRenderFrameId
@ -563,12 +568,12 @@ func (pR *Room) OnBattleCmdReceived(pReq *WsReq) {
atomic.StoreInt32(&(player.AckingFrameId), ackingFrameId) atomic.StoreInt32(&(player.AckingFrameId), ackingFrameId)
atomic.StoreInt32(&(player.AckingInputFrameId), ackingInputFrameId) atomic.StoreInt32(&(player.AckingInputFrameId), ackingInputFrameId)
Logger.Debug(fmt.Sprintf("OnBattleCmdReceived-InputsBufferLock about to lock: roomId=%v, fromPlayerId=%v", pR.Id, playerId)) //Logger.Debug(fmt.Sprintf("OnBattleCmdReceived-InputsBufferLock about to lock: roomId=%v, fromPlayerId=%v", pR.Id, playerId))
pR.InputsBufferLock.Lock() pR.InputsBufferLock.Lock()
Logger.Debug(fmt.Sprintf("OnBattleCmdReceived-InputsBufferLock locked: roomId=%v, fromPlayerId=%v", pR.Id, playerId)) //Logger.Debug(fmt.Sprintf("OnBattleCmdReceived-InputsBufferLock locked: roomId=%v, fromPlayerId=%v", pR.Id, playerId))
defer func() { defer func() {
pR.InputsBufferLock.Unlock() pR.InputsBufferLock.Unlock()
Logger.Debug(fmt.Sprintf("OnBattleCmdReceived-InputsBufferLock unlocked: roomId=%v, fromPlayerId=%v", pR.Id, playerId)) //Logger.Debug(fmt.Sprintf("OnBattleCmdReceived-InputsBufferLock unlocked: roomId=%v, fromPlayerId=%v", pR.Id, playerId))
}() }()
inputsBufferSnapshot := pR.markConfirmationIfApplicable(inputFrameUpsyncBatch, playerId, player) inputsBufferSnapshot := pR.markConfirmationIfApplicable(inputFrameUpsyncBatch, playerId, player)
@ -749,7 +754,7 @@ func (pR *Room) OnDismissed() {
pR.RollbackEstimatedDtNanos = 16666666 // A little smaller than the actual per frame time, just for logging FAST FRAME pR.RollbackEstimatedDtNanos = 16666666 // A little smaller than the actual per frame time, just for logging FAST FRAME
dilutedServerFps := float64(55.0) dilutedServerFps := float64(55.0)
pR.dilutedRollbackEstimatedDtNanos = int64(float64(pR.RollbackEstimatedDtNanos) * float64(pR.ServerFps) / dilutedServerFps) pR.dilutedRollbackEstimatedDtNanos = int64(float64(pR.RollbackEstimatedDtNanos) * float64(pR.ServerFps) / dilutedServerFps)
pR.BattleDurationFrames = 30 * pR.ServerFps pR.BattleDurationFrames = 60 * pR.ServerFps
pR.BattleDurationNanos = int64(pR.BattleDurationFrames) * (pR.RollbackEstimatedDtNanos + 1) pR.BattleDurationNanos = int64(pR.BattleDurationFrames) * (pR.RollbackEstimatedDtNanos + 1)
pR.InputFrameUpsyncDelayTolerance = 2 pR.InputFrameUpsyncDelayTolerance = 2
pR.MaxChasingRenderFramesPerUpdate = 8 pR.MaxChasingRenderFramesPerUpdate = 8
@ -770,7 +775,7 @@ func (pR *Room) OnDismissed() {
}, },
HitboxOffset: float64(12.0), // should be about the radius of the PlayerCollider HitboxOffset: float64(12.0), // should be about the radius of the PlayerCollider
HitboxSize: &Vec2D{ HitboxSize: &Vec2D{
X: float64(23.0), X: float64(24.0),
Y: float64(32.0), Y: float64(32.0),
}, },
@ -782,6 +787,12 @@ func (pR *Room) OnDismissed() {
Damage: int32(5), Damage: int32(5),
} }
pR.SnapIntoPlatformOverlap = float64(0.1)
pR.SnapIntoPlatformThreshold = float64(0.5)
pR.JumpingInitVelY = int32(float64(7) * pR.WorldToVirtualGridRatio)
pR.GravityX = 0
pR.GravityY = -int32(float64(0.5) * pR.WorldToVirtualGridRatio) // makes all "playerCollider.Y" a multiple of 0.5 in all cases
pR.ChooseStage() pR.ChooseStage()
pR.EffectivePlayerCount = 0 pR.EffectivePlayerCount = 0
@ -1277,7 +1288,10 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
VirtualGridY: currPlayerDownsync.VirtualGridY, VirtualGridY: currPlayerDownsync.VirtualGridY,
DirX: currPlayerDownsync.DirX, DirX: currPlayerDownsync.DirX,
DirY: currPlayerDownsync.DirY, DirY: currPlayerDownsync.DirY,
VelX: currPlayerDownsync.VelX,
VelY: currPlayerDownsync.VelY,
CharacterState: currPlayerDownsync.CharacterState, CharacterState: currPlayerDownsync.CharacterState,
InAir: true,
Speed: currPlayerDownsync.Speed, Speed: currPlayerDownsync.Speed,
BattleState: currPlayerDownsync.BattleState, BattleState: currPlayerDownsync.BattleState,
Score: currPlayerDownsync.Score, Score: currPlayerDownsync.Score,
@ -1292,30 +1306,108 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
} }
} }
toRet := &RoomDownsyncFrame{ nextRenderFrameMeleeBullets := make([]*MeleeBullet, 0, len(currRenderFrame.MeleeBullets)) // Is there any better way to reduce malloc/free impact, e.g. smart prediction for fixed memory allocation?
Id: currRenderFrame.Id + 1, effPushbacks := make([]Vec2D, pR.Capacity)
Players: nextRenderFramePlayers, hardPushbackNorms := make([][]Vec2D, pR.Capacity)
CountdownNanos: (pR.BattleDurationNanos - int64(currRenderFrame.Id)*pR.RollbackEstimatedDtNanos),
MeleeBullets: make([]*MeleeBullet, 0), // Is there any better way to reduce malloc/free impact, e.g. smart prediction for fixed memory allocation? // 1. Process player inputs
if nil != delayedInputFrame {
var delayedInputFrameForPrevRenderFrame *InputFrameDownsync = nil
tmp := pR.InputsBuffer.GetByFrameId(pR.ConvertToInputFrameId(currRenderFrame.Id-1, pR.InputDelayFrames))
if nil != tmp {
delayedInputFrameForPrevRenderFrame = tmp.(*InputFrameDownsync)
}
inputList := delayedInputFrame.InputList
for _, player := range pR.PlayersArr {
playerId := player.Id
joinIndex := player.JoinIndex
currPlayerDownsync, thatPlayerInNextFrame := currRenderFrame.Players[playerId], nextRenderFramePlayers[playerId]
if 0 < thatPlayerInNextFrame.FramesToRecover {
continue
}
decodedInput := pR.decodeInput(inputList[joinIndex-1])
prevBtnALevel, prevBtnBLevel := int32(0), int32(0)
if nil != delayedInputFrameForPrevRenderFrame {
prevDecodedInput := pR.decodeInput(delayedInputFrameForPrevRenderFrame.InputList[joinIndex-1])
prevBtnALevel = prevDecodedInput.BtnALevel
prevBtnBLevel = prevDecodedInput.BtnBLevel
}
if decodedInput.BtnBLevel > prevBtnBLevel {
characStateAlreadyInAir := false
if ATK_CHARACTER_STATE_INAIR_IDLE1 == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_INAIR_ATK1 == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_INAIR_ATKED1 == thatPlayerInNextFrame.CharacterState {
characStateAlreadyInAir = true
}
characStateIsInterruptWaivable := false
if ATK_CHARACTER_STATE_IDLE1 == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_WALKING == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_INAIR_IDLE1 == thatPlayerInNextFrame.CharacterState {
characStateIsInterruptWaivable = true
}
if !characStateAlreadyInAir && characStateIsInterruptWaivable {
thatPlayerInNextFrame.VelY = pR.JumpingInitVelY
if 1 == currPlayerDownsync.JoinIndex {
Logger.Info(fmt.Sprintf("playerId=%v, joinIndex=%v jumped at {renderFrame.id: %d, virtualX: %d, virtualY: %d, nextVelX: %d, nextVelY: %d}, delayedInputFrame.id=%d", playerId, joinIndex, currRenderFrame.Id, currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, delayedInputFrame.InputFrameId))
}
}
}
if decodedInput.BtnALevel > prevBtnALevel {
punchSkillId := int32(1)
punchConfig := pR.MeleeSkillConfig[punchSkillId]
var newMeleeBullet MeleeBullet = *punchConfig
newMeleeBullet.BattleLocalId = pR.BulletBattleLocalIdCounter
pR.BulletBattleLocalIdCounter += 1
newMeleeBullet.OffenderJoinIndex = joinIndex
newMeleeBullet.OffenderPlayerId = playerId
newMeleeBullet.OriginatedRenderFrameId = currRenderFrame.Id
nextRenderFrameMeleeBullets = append(nextRenderFrameMeleeBullets, &newMeleeBullet)
thatPlayerInNextFrame.FramesToRecover = newMeleeBullet.RecoveryFrames
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATK1
if false == currPlayerDownsync.InAir {
thatPlayerInNextFrame.VelX = 0
}
Logger.Debug(fmt.Sprintf("roomId=%v, playerId=%v triggered a rising-edge of btnA at currRenderFrame.id=%v, delayedInputFrame.id=%v", pR.Id, playerId, currRenderFrame.Id, delayedInputFrame.InputFrameId))
} else if decodedInput.BtnALevel < prevBtnALevel {
Logger.Debug(fmt.Sprintf("roomId=%v, playerId=%v triggered a falling-edge of btnA at currRenderFrame.id=%v, delayedInputFrame.id=%v", pR.Id, playerId, currRenderFrame.Id, delayedInputFrame.InputFrameId))
} else {
// No bullet trigger, process movement inputs
// Note that by now "0 == thatPlayerInNextFrame.FramesToRecover", we should change "CharacterState" to "WALKING" or "IDLE" depending on player inputs
if 0 != decodedInput.Dx || 0 != decodedInput.Dy {
thatPlayerInNextFrame.DirX = decodedInput.Dx
thatPlayerInNextFrame.DirY = decodedInput.Dy
thatPlayerInNextFrame.VelX = decodedInput.Dx * currPlayerDownsync.Speed
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_WALKING
} else {
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1
thatPlayerInNextFrame.VelX = 0
}
}
}
} }
bulletPushbacks := make([]Vec2D, pR.Capacity) // Guaranteed determinism regardless of traversal order // 2. Process player movement
effPushbacks := make([]Vec2D, pR.Capacity) // Guaranteed determinism regardless of traversal order
// Reset playerCollider position from the "virtual grid position"
for _, player := range pR.PlayersArr { for _, player := range pR.PlayersArr {
playerId := player.Id playerId := player.Id
joinIndex := player.JoinIndex joinIndex := player.JoinIndex
bulletPushbacks[joinIndex-1].X, bulletPushbacks[joinIndex-1].Y = float64(0), float64(0)
effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y = float64(0), float64(0) effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y = float64(0), float64(0)
currPlayerDownsync := currRenderFrame.Players[playerId]
newVx, newVy := currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
playerCollider := collisionSysMap[collisionPlayerIndex] playerCollider := collisionSysMap[collisionPlayerIndex]
playerCollider.X, playerCollider.Y = VirtualGridToPolygonColliderAnchorPos(newVx, newVy, player.ColliderRadius, player.ColliderRadius, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, pR.VirtualGridToWorldRatio) currPlayerDownsync, thatPlayerInNextFrame := currRenderFrame.Players[playerId], nextRenderFramePlayers[playerId]
// Reset playerCollider position from the "virtual grid position"
newVx, newVy := currPlayerDownsync.VirtualGridX+currPlayerDownsync.VelX, currPlayerDownsync.VirtualGridY+currPlayerDownsync.VelY
colliderWidth, colliderHeight := player.ColliderRadius*2, player.ColliderRadius*4
playerCollider.X, playerCollider.Y = VirtualGridToPolygonColliderBLPos(newVx, newVy, colliderWidth*0.5, colliderHeight*0.5, pR.SnapIntoPlatformOverlap, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, pR.VirtualGridToWorldRatio)
// Update in the collision system
playerCollider.Update()
if currPlayerDownsync.InAir {
thatPlayerInNextFrame.VelX += pR.GravityX
thatPlayerInNextFrame.VelY += pR.GravityY
}
} }
// Check bullet-anything collisions first, because the pushbacks caused by bullets might later be reverted by player-barrier collision // 3. Add bullet colliders into collision system
bulletColliders := make(map[int32]*resolv.Object, 0) // Will all be removed at the end of `applyInputFrameDownsyncDynamicsOnSingleRenderFrame` due to the need for being rollback-compatible bulletColliders := make(map[int32]*resolv.Object, 0) // Will all be removed at the end of `applyInputFrameDownsyncDynamicsOnSingleRenderFrame` due to the need for being rollback-compatible
removedBulletsAtCurrFrame := make(map[int32]int32, 0) removedBulletsAtCurrFrame := make(map[int32]int32, 0)
for _, meleeBullet := range currRenderFrame.MeleeBullets { for _, meleeBullet := range currRenderFrame.MeleeBullets {
@ -1332,7 +1424,7 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
offenderWx, offenderWy := VirtualGridToWorldPos(offender.VirtualGridX, offender.VirtualGridY, pR.VirtualGridToWorldRatio) offenderWx, offenderWy := VirtualGridToWorldPos(offender.VirtualGridX, offender.VirtualGridY, pR.VirtualGridToWorldRatio)
bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy
newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "MeleeBullet") newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, 0, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "MeleeBullet")
newBulletCollider.Data = meleeBullet newBulletCollider.Data = meleeBullet
pR.Space.Add(newBulletCollider) pR.Space.Add(newBulletCollider)
collisionSysMap[collisionBulletIndex] = newBulletCollider collisionSysMap[collisionBulletIndex] = newBulletCollider
@ -1342,6 +1434,98 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
} }
} }
// 4. Invoke collision system stepping (no-op for backend collision lib)
// 5. Calc pushbacks for each player (after its movement) w/o bullets
for _, player := range pR.PlayersArr {
joinIndex := player.JoinIndex
playerId := player.Id
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
playerCollider := collisionSysMap[collisionPlayerIndex]
playerShape := playerCollider.Shape.(*resolv.ConvexPolygon)
hardPushbackNorms[joinIndex-1] = pR.calcHardPushbacksNorms(playerCollider, playerShape, pR.SnapIntoPlatformOverlap, &(effPushbacks[joinIndex-1]))
if 0 < len(hardPushbackNorms[joinIndex-1]) {
Logger.Debug(fmt.Sprintf("playerId=%d, joinIndex=%d got %d non-empty hardPushbacks at renderFrame.id=%d", playerId, joinIndex, len(hardPushbackNorms), currRenderFrame.Id))
}
currPlayerDownsync, thatPlayerInNextFrame := currRenderFrame.Players[playerId], nextRenderFramePlayers[playerId]
fallStopping := false
possiblyFallStoppedOnAnotherPlayer := false
collision := playerCollider.Check(0, 0)
if nil == collision {
continue
}
for _, obj := range collision.Objects {
isBarrier, isAnotherPlayer, isBullet := false, false, false
switch obj.Data.(type) {
case *Barrier:
isBarrier = true
case *Player:
isAnotherPlayer = true
case *MeleeBullet:
isBullet = true
}
if isBullet {
// ignore bullets for this step
continue
}
bShape := obj.Shape.(*resolv.ConvexPolygon)
overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, playerShape, bShape)
if !overlapped {
continue
}
normAlignmentWithGravity := (overlapResult.OverlapX*float64(0) + overlapResult.OverlapY*float64(-1.0))
landedOnGravityPushback := (pR.SnapIntoPlatformThreshold < normAlignmentWithGravity) // prevents false snapping on the lateral sides
if landedOnGravityPushback {
// kindly note that one player might land on top of another player, and snapping is also required in such case
pushbackX, pushbackY = (overlapResult.Overlap-pR.SnapIntoPlatformOverlap)*overlapResult.OverlapX, (overlapResult.Overlap-pR.SnapIntoPlatformOverlap)*overlapResult.OverlapY
thatPlayerInNextFrame.InAir = false
}
for _, hardPushbackNorm := range hardPushbackNorms[joinIndex-1] {
projectedMagnitude := pushbackX*hardPushbackNorm.X + pushbackY*hardPushbackNorm.Y
if isBarrier || (isAnotherPlayer && 0 > projectedMagnitude) {
pushbackX -= projectedMagnitude * hardPushbackNorm.X
pushbackY -= projectedMagnitude * hardPushbackNorm.Y
}
}
effPushbacks[joinIndex-1].X += pushbackX
effPushbacks[joinIndex-1].Y += pushbackY
if currPlayerDownsync.InAir && landedOnGravityPushback {
fallStopping = true
if isAnotherPlayer {
possiblyFallStoppedOnAnotherPlayer = true
}
if 1 == thatPlayerInNextFrame.JoinIndex {
Logger.Info(fmt.Sprintf("playerId=%d, joinIndex=%d fallStopping#1 at {renderFrame.id: %d, virtualX: %d, virtualY: %d, velX: %d, velY: %d} with effPushback={%.3f, %.3f}, overlapMag=%.4f, possiblyFallStoppedOnAnotherPlayer=%v", playerId, joinIndex, currRenderFrame.Id, currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY, currPlayerDownsync.VelX, currPlayerDownsync.VelY, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y, overlapResult.Overlap, possiblyFallStoppedOnAnotherPlayer))
}
}
if 1 == joinIndex && currPlayerDownsync.InAir && isBarrier && !landedOnGravityPushback {
Logger.Warn(fmt.Sprintf("playerId=%d, joinIndex=%d inAir & pushed back by barrier & not landed at {renderFrame.id: %d, virtualX: %d, virtualY: %d, velX: %d, velY: %d} with effPushback={%.3f, %.3f}, playerColliderPos={%.3f, %.3f}, barrierPos={%.3f, %.3f}, overlapMag=%.4f, len(hardPushbackNorms)=%d", playerId, joinIndex, currRenderFrame.Id, currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY, currPlayerDownsync.VelX, currPlayerDownsync.VelY, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y, playerCollider.X-pR.collisionSpaceOffsetX, playerCollider.Y-pR.collisionSpaceOffsetY, bShape.X-pR.collisionSpaceOffsetX, bShape.Y-pR.collisionSpaceOffsetY, overlapResult.Overlap, len(hardPushbackNorms)))
}
if 1 == joinIndex && currPlayerDownsync.InAir && isAnotherPlayer {
Logger.Warn(fmt.Sprintf("playerId=%d, joinIndex=%d inAir & pushed back by another player at {renderFrame.id: %d, virtualX: %d, virtualY: %d, velX: %d, velY: %d} with effPushback={%.3f, %.3f}, landedOnGravityPushback=%v, fallStopping=%v, playerColliderPos={%.3f, %.3f}, anotherPlayerColliderPos={%.3f, %.3f}, overlapMag=%.4f, len(hardPushbackNorms)=%d", playerId, joinIndex, currRenderFrame.Id, currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY, currPlayerDownsync.VelX, currPlayerDownsync.VelY, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y, landedOnGravityPushback, fallStopping, playerCollider.X-pR.collisionSpaceOffsetX, playerCollider.Y-pR.collisionSpaceOffsetY, bShape.X-pR.collisionSpaceOffsetX, bShape.Y-pR.collisionSpaceOffsetY, overlapResult.Overlap, len(hardPushbackNorms)))
}
}
if fallStopping {
thatPlayerInNextFrame.VelX = 0
thatPlayerInNextFrame.VelY = 0
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1
thatPlayerInNextFrame.FramesToRecover = 0
}
if currPlayerDownsync.InAir {
switch thatPlayerInNextFrame.CharacterState {
case ATK_CHARACTER_STATE_IDLE1:
case ATK_CHARACTER_STATE_WALKING:
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_IDLE1
case ATK_CHARACTER_STATE_ATK1:
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATK1
case ATK_CHARACTER_STATE_ATKED1:
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATKED1
default:
}
}
}
// 6. Check bullet-anything collisions
for _, bulletCollider := range bulletColliders { for _, bulletCollider := range bulletColliders {
shouldRemove := false shouldRemove := false
meleeBullet := bulletCollider.Data.(*MeleeBullet) meleeBullet := bulletCollider.Data.(*MeleeBullet)
@ -1355,15 +1539,32 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
case *Player: case *Player:
if meleeBullet.OffenderPlayerId != t.Id { if meleeBullet.OffenderPlayerId != t.Id {
if overlapped, _, _, _ := CalcPushbacks(0, 0, bulletShape, defenderShape); overlapped { if overlapped, _, _, _ := CalcPushbacks(0, 0, bulletShape, defenderShape); overlapped {
joinIndex := t.JoinIndex
xfac := float64(1.0) // By now, straight Punch offset doesn't respect "y-axis" xfac := float64(1.0) // By now, straight Punch offset doesn't respect "y-axis"
if 0 > offender.DirX { if 0 > offender.DirX {
xfac = float64(-1.0) xfac = float64(-1.0)
} }
bulletPushbacks[t.JoinIndex-1].X += xfac * meleeBullet.Pushback pushbackX, pushbackY := -xfac*meleeBullet.Pushback, float64(0)
nextRenderFramePlayers[t.Id].CharacterState = ATK_CHARACTER_STATE_ATKED1
for _, hardPushbackNorm := range hardPushbackNorms[joinIndex-1] {
projectedMagnitude := pushbackX*hardPushbackNorm.X + pushbackY*hardPushbackNorm.Y
if 0 > projectedMagnitude {
Logger.Info(fmt.Sprintf("defenderPlayerId=%d, joinIndex=%d reducing bullet pushback={%.3f, %.3f} by {%.3f, %.3f} where hardPushbackNorm={%.3f, %.3f}, projectedMagnitude=%.3f at renderFrame.id=%d", t.Id, joinIndex, pushbackX, pushbackY, projectedMagnitude*hardPushbackNorm.X, projectedMagnitude*hardPushbackNorm.Y, hardPushbackNorm.X, hardPushbackNorm.Y, projectedMagnitude, currRenderFrame.Id))
pushbackX -= projectedMagnitude * hardPushbackNorm.X
pushbackY -= projectedMagnitude * hardPushbackNorm.Y
}
}
effPushbacks[joinIndex-1].X += pushbackX
effPushbacks[joinIndex-1].Y += pushbackY
atkedPlayerInCurFrame, atkedPlayerInNextFrame := currRenderFrame.Players[t.Id], nextRenderFramePlayers[t.Id]
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATKED1
if atkedPlayerInCurFrame.InAir {
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATKED1
}
oldFramesToRecover := nextRenderFramePlayers[t.Id].FramesToRecover oldFramesToRecover := nextRenderFramePlayers[t.Id].FramesToRecover
if meleeBullet.HitStunFrames > oldFramesToRecover { if meleeBullet.HitStunFrames > oldFramesToRecover {
nextRenderFramePlayers[t.Id].FramesToRecover = meleeBullet.HitStunFrames atkedPlayerInNextFrame.FramesToRecover = meleeBullet.HitStunFrames
} }
Logger.Debug(fmt.Sprintf("roomId=%v, a meleeBullet collides w/ player at currRenderFrame.id=%v: b=%v, p=%v", pR.Id, currRenderFrame.Id, ConvexPolygonStr(bulletShape), ConvexPolygonStr(defenderShape))) Logger.Debug(fmt.Sprintf("roomId=%v, a meleeBullet collides w/ player at currRenderFrame.id=%v: b=%v, p=%v", pR.Id, currRenderFrame.Id, ConvexPolygonStr(bulletShape), ConvexPolygonStr(defenderShape)))
} }
@ -1379,6 +1580,7 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
} }
} }
// [WARNING] Remove bullets from collisionSys ANYWAY for the convenience of rollback
for _, meleeBullet := range currRenderFrame.MeleeBullets { for _, meleeBullet := range currRenderFrame.MeleeBullets {
collisionBulletIndex := COLLISION_BULLET_INDEX_PREFIX + meleeBullet.BattleLocalId collisionBulletIndex := COLLISION_BULLET_INDEX_PREFIX + meleeBullet.BattleLocalId
if bulletCollider, existent := collisionSysMap[collisionBulletIndex]; existent { if bulletCollider, existent := collisionSysMap[collisionBulletIndex]; existent {
@ -1388,124 +1590,51 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
if _, existent := removedBulletsAtCurrFrame[collisionBulletIndex]; existent { if _, existent := removedBulletsAtCurrFrame[collisionBulletIndex]; existent {
continue continue
} }
toRet.MeleeBullets = append(toRet.MeleeBullets, meleeBullet) nextRenderFrameMeleeBullets = append(nextRenderFrameMeleeBullets, meleeBullet)
} }
if nil != delayedInputFrame { // 7. Get players out of stuck barriers if there's any
var delayedInputFrameForPrevRenderFrame *InputFrameDownsync = nil for _, player := range pR.PlayersArr {
tmp := pR.InputsBuffer.GetByFrameId(pR.ConvertToInputFrameId(currRenderFrame.Id-1, pR.InputDelayFrames)) joinIndex := player.JoinIndex
if nil != tmp { playerId := player.Id
delayedInputFrameForPrevRenderFrame = tmp.(*InputFrameDownsync) collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
} playerCollider := collisionSysMap[collisionPlayerIndex]
inputList := delayedInputFrame.InputList playerShape := playerCollider.Shape.(*resolv.ConvexPolygon)
// Process player inputs // Update "virtual grid position"
for _, player := range pR.PlayersArr { currPlayerDownsync, thatPlayerInNextFrame := currRenderFrame.Players[playerId], nextRenderFramePlayers[playerId]
playerId := player.Id colliderWidth, colliderHeight := player.ColliderRadius*2, player.ColliderRadius*4
joinIndex := player.JoinIndex thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY = PolygonColliderBLToVirtualGridPos(playerCollider.X-effPushbacks[joinIndex-1].X, playerCollider.Y-effPushbacks[joinIndex-1].Y, colliderWidth*0.5, colliderHeight*0.5, pR.SnapIntoPlatformOverlap, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, pR.WorldToVirtualGridRatio)
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
playerCollider := collisionSysMap[collisionPlayerIndex] if 1 == thatPlayerInNextFrame.JoinIndex {
thatPlayerInNextFrame := nextRenderFramePlayers[playerId] if thatPlayerInNextFrame.InAir && (0 != thatPlayerInNextFrame.VelY) {
if 0 < thatPlayerInNextFrame.FramesToRecover { // Logger.Info(fmt.Sprintf("playerId=%d, joinIndex=%d inAir trajectory: {nextRenderFrame.id: %d, nextVirtualX: %d, nextVirtualY: %d, nextVelX: %d, nextVelY: %d}, with playerColliderPos={%.3f, %.3f}, effPushback={%.3f, %.3f}", playerId, joinIndex, currRenderFrame.Id+1, thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, playerShape.X-pR.collisionSpaceOffsetX, playerShape.Y-pR.collisionSpaceOffsetY, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y))
// No need to process inputs for this player, but there might be bullet pushbacks on this player
// Also note that in this case we keep "CharacterState" of this player from last render frame
playerCollider.X += bulletPushbacks[joinIndex-1].X
playerCollider.Y += bulletPushbacks[joinIndex-1].Y
// Update in the collision system
playerCollider.Update()
if 0 != bulletPushbacks[joinIndex-1].X || 0 != bulletPushbacks[joinIndex-1].Y {
Logger.Debug(fmt.Sprintf("roomId=%v, playerId=%v is pushed back by (%.2f, %.2f) by bullet impacts, now its framesToRecover is %d at currRenderFrame.id=%v", pR.Id, playerId, bulletPushbacks[joinIndex-1].X, bulletPushbacks[joinIndex-1].Y, thatPlayerInNextFrame.FramesToRecover, currRenderFrame.Id))
}
continue
} }
currPlayerDownsync := currRenderFrame.Players[playerId] if currPlayerDownsync.InAir && !thatPlayerInNextFrame.InAir {
decodedInput := pR.decodeInput(inputList[joinIndex-1]) Logger.Warn(fmt.Sprintf("playerId=%d, joinIndex=%d fallStopping#2 at {nextRenderFrame.id: %d, nextVirtualX: %d, nextVirtualY: %d, nextVelX: %d, nextVelY: %d}, with playerColliderPos={%.3f, %.3f}, effPushback={%.3f, %.3f}", playerId, joinIndex, currRenderFrame.Id+1, thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, playerShape.X-pR.collisionSpaceOffsetX, playerShape.Y-pR.collisionSpaceOffsetY, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y))
prevBtnALevel := int32(0)
if nil != delayedInputFrameForPrevRenderFrame {
prevDecodedInput := pR.decodeInput(delayedInputFrameForPrevRenderFrame.InputList[joinIndex-1])
prevBtnALevel = prevDecodedInput.BtnALevel
} }
if !currPlayerDownsync.InAir && thatPlayerInNextFrame.InAir {
if decodedInput.BtnALevel > prevBtnALevel { Logger.Warn(fmt.Sprintf("playerId=%d, joinIndex=%d took off at {nextRenderFrame.id: %d, nextVirtualX: %d, nextVirtualY: %d, nextVelX: %d, nextVelY: %d}, with playerColliderPos={%.3f, %.3f}, effPushback={%.3f, %.3f}", playerId, joinIndex, currRenderFrame.Id+1, thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, playerShape.X-pR.collisionSpaceOffsetX, playerShape.Y-pR.collisionSpaceOffsetY, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y))
punchSkillId := int32(1)
punchConfig := pR.MeleeSkillConfig[punchSkillId]
var newMeleeBullet MeleeBullet = *punchConfig
newMeleeBullet.BattleLocalId = pR.BulletBattleLocalIdCounter
pR.BulletBattleLocalIdCounter += 1
newMeleeBullet.OffenderJoinIndex = joinIndex
newMeleeBullet.OffenderPlayerId = playerId
newMeleeBullet.OriginatedRenderFrameId = currRenderFrame.Id
toRet.MeleeBullets = append(toRet.MeleeBullets, &newMeleeBullet)
thatPlayerInNextFrame.FramesToRecover = newMeleeBullet.RecoveryFrames
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATK1
Logger.Debug(fmt.Sprintf("roomId=%v, playerId=%v triggered a rising-edge of btnA at currRenderFrame.id=%v, delayedInputFrame.id=%v", pR.Id, playerId, currRenderFrame.Id, delayedInputFrame.InputFrameId))
} else if decodedInput.BtnALevel < prevBtnALevel {
Logger.Debug(fmt.Sprintf("roomId=%v, playerId=%v triggered a falling-edge of btnA at currRenderFrame.id=%v, delayedInputFrame.id=%v", pR.Id, playerId, currRenderFrame.Id, delayedInputFrame.InputFrameId))
} else {
// No bullet trigger, process movement inputs
// Note that by now "0 == thatPlayerInNextFrame.FramesToRecover", we should change "CharacterState" to "WALKING" or "IDLE" depending on player inputs
if 0 != decodedInput.Dx || 0 != decodedInput.Dy {
thatPlayerInNextFrame.DirX = decodedInput.Dx
thatPlayerInNextFrame.DirY = decodedInput.Dy
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_WALKING
} else {
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1
}
}
movementX, movementY := VirtualGridToWorldPos(decodedInput.Dx+decodedInput.Dx*currPlayerDownsync.Speed, decodedInput.Dy+decodedInput.Dy*currPlayerDownsync.Speed, pR.VirtualGridToWorldRatio)
playerCollider.X += movementX
playerCollider.Y += movementY
// Update in the collision system
playerCollider.Update()
}
// handle pushbacks upon collision after all movements treated as simultaneous
for _, player := range pR.PlayersArr {
joinIndex := player.JoinIndex
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
playerCollider := collisionSysMap[collisionPlayerIndex]
if collision := playerCollider.Check(0, 0); collision != nil {
playerShape := playerCollider.Shape.(*resolv.ConvexPolygon)
for _, obj := range collision.Objects {
barrierShape := obj.Shape.(*resolv.ConvexPolygon)
if overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, playerShape, barrierShape); overlapped {
Logger.Debug(fmt.Sprintf("Overlapped: a=%v, b=%v, pushbackX=%v, pushbackY=%v", ConvexPolygonStr(playerShape), ConvexPolygonStr(barrierShape), pushbackX, pushbackY))
effPushbacks[joinIndex-1].X += pushbackX
effPushbacks[joinIndex-1].Y += pushbackY
} else {
Logger.Debug(fmt.Sprintf("Collided BUT not overlapped: a=%v, b=%v, overlapResult=%v", ConvexPolygonStr(playerShape), ConvexPolygonStr(barrierShape), overlapResult))
}
}
} }
} }
for _, player := range pR.PlayersArr {
playerId := player.Id
joinIndex := player.JoinIndex
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
playerCollider := collisionSysMap[collisionPlayerIndex]
// Update "virtual grid position"
newVx, newVy := PolygonColliderAnchorToVirtualGridPos(playerCollider.X-effPushbacks[joinIndex-1].X, playerCollider.Y-effPushbacks[joinIndex-1].Y, player.ColliderRadius, player.ColliderRadius, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, pR.WorldToVirtualGridRatio)
thatPlayerInNextFrame := nextRenderFramePlayers[playerId]
thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY = newVx, newVy
}
Logger.Debug(fmt.Sprintf("After applyInputFrameDownsyncDynamicsOnSingleRenderFrame: currRenderFrame.Id=%v, inputList=%v, currRenderFrame.Players=%v, nextRenderFramePlayers=%v", currRenderFrame.Id, inputList, currRenderFrame.Players, nextRenderFramePlayers))
} }
return toRet return &RoomDownsyncFrame{
Id: currRenderFrame.Id + 1,
Players: nextRenderFramePlayers,
MeleeBullets: nextRenderFrameMeleeBullets,
CountdownNanos: (pR.BattleDurationNanos - int64(currRenderFrame.Id)*pR.RollbackEstimatedDtNanos),
}
} }
func (pR *Room) decodeInput(encodedInput uint64) *InputFrameDecoded { func (pR *Room) decodeInput(encodedInput uint64) *InputFrameDecoded {
encodedDirection := (encodedInput & uint64(15)) encodedDirection := (encodedInput & uint64(15))
btnALevel := int32((encodedInput >> 4) & 1) btnALevel := int32((encodedInput >> 4) & 1)
btnBLevel := int32((encodedInput >> 5) & 1)
return &InputFrameDecoded{ return &InputFrameDecoded{
Dx: DIRECTION_DECODER[encodedDirection][0], Dx: DIRECTION_DECODER[encodedDirection][0],
Dy: DIRECTION_DECODER[encodedDirection][1], Dy: DIRECTION_DECODER[encodedDirection][1],
BtnALevel: btnALevel, BtnALevel: btnALevel,
BtnBLevel: btnBLevel,
} }
} }
@ -1520,8 +1649,8 @@ func (pR *Room) refreshColliders(spaceW, spaceH int32) {
pR.Space = resolv.NewSpace(int(spaceW), int(spaceH), minStep, minStep) // allocate a new collision space everytime after a battle is settled pR.Space = resolv.NewSpace(int(spaceW), int(spaceH), minStep, minStep) // allocate a new collision space everytime after a battle is settled
for _, player := range pR.Players { for _, player := range pR.Players {
wx, wy := VirtualGridToWorldPos(player.VirtualGridX, player.VirtualGridY, pR.VirtualGridToWorldRatio) wx, wy := VirtualGridToWorldPos(player.VirtualGridX, player.VirtualGridY, pR.VirtualGridToWorldRatio)
colliderWidth, colliderHeight := player.ColliderRadius*2, player.ColliderRadius*3 colliderWidth, colliderHeight := player.ColliderRadius*2, player.ColliderRadius*4
playerCollider := GenerateRectCollider(wx, wy, colliderWidth, colliderHeight, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "Player") playerCollider := GenerateRectCollider(wx, wy, colliderWidth, colliderHeight, pR.SnapIntoPlatformOverlap, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "Player") // the coords of all barrier boundaries are multiples of tileWidth(i.e. 16), by adding snapping y-padding when "landedOnGravityPushback" all "playerCollider.Y" would be a multiple of 1.0
playerCollider.Data = player playerCollider.Data = player
pR.Space.Add(playerCollider) pR.Space.Add(playerCollider)
// Keep track of the collider in "pR.CollisionSysMap" // Keep track of the collider in "pR.CollisionSysMap"
@ -1534,6 +1663,7 @@ func (pR *Room) refreshColliders(spaceW, spaceH int32) {
for _, barrier := range pR.Barriers { for _, barrier := range pR.Barriers {
boundaryUnaligned := barrier.Boundary boundaryUnaligned := barrier.Boundary
barrierCollider := GenerateConvexPolygonCollider(boundaryUnaligned, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "Barrier") barrierCollider := GenerateConvexPolygonCollider(boundaryUnaligned, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "Barrier")
barrierCollider.Data = barrier
pR.Space.Add(barrierCollider) pR.Space.Add(barrierCollider)
} }
} }
@ -1572,19 +1702,29 @@ func (pR *Room) doBattleMainLoopPerTickBackendDynamicsWithProperLocking(prevRend
} }
if nil != inputsBufferSnapshot { if nil != inputsBufferSnapshot {
Logger.Warn(fmt.Sprintf("roomId=%v, room.RenderFrameId=%v, room.CurDynamicsRenderFrameId=%v, room.LastAllConfirmedInputFrameId=%v, unconfirmedMask=%v", pR.Id, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, pR.LastAllConfirmedInputFrameId, inputsBufferSnapshot.UnconfirmedMask)) // Logger.Warn(fmt.Sprintf("roomId=%v, room.RenderFrameId=%v, room.CurDynamicsRenderFrameId=%v, room.LastAllConfirmedInputFrameId=%v, unconfirmedMask=%v", pR.Id, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, pR.LastAllConfirmedInputFrameId, inputsBufferSnapshot.UnconfirmedMask))
pR.downsyncToAllPlayers(inputsBufferSnapshot) pR.downsyncToAllPlayers(inputsBufferSnapshot)
} }
} }
func (pR *Room) downsyncToAllPlayers(inputsBufferSnapshot *InputsBufferSnapshot) { func (pR *Room) downsyncToAllPlayers(inputsBufferSnapshot *InputsBufferSnapshot) {
/* /*
[WARNING] This function MUST BE called while "pR.InputsBufferLock" is LOCKED for preserving the order of generation of "inputsBufferSnapshot" -- see comments in "OnBattleCmdReceived" and [this issue](https://github.com/genxium/DelayNoMore/issues/12). [WARNING] This function MUST BE called while "pR.InputsBufferLock" is LOCKED to **preserve the order of generation of "inputsBufferSnapshot" for sending** -- see comments in "OnBattleCmdReceived" and [this issue](https://github.com/genxium/DelayNoMore/issues/12).
Actually if each player session were both intrinsically thread-safe & non-blocking for writing (like Java NIO), I could've just called "playerSession.WriteMessage" while holding "pR.InputsBufferLock" -- but the ws session provided by Gorilla library is neither thread-safe nor non-blocking for writing, which is fine because it creates a chance for the users to solve an interesting problem :) Actually if each player session were both intrinsically thread-safe & non-blocking for writing (like Java NIO), I could've just called "playerSession.WriteMessage" while holding "pR.InputsBufferLock" -- but the ws session provided by Gorilla library is neither thread-safe nor non-blocking for writing, which is fine because it creates a chance for the users to solve an interesting problem :)
*/
/* Moreover, we're downsyncing a same "inputsBufferSnapshot" for all players in the same battle and this is by design, i.e. not respecting "player.LastSentInputFrameId" because "new all-confirmed inputFrameDownsyncs" are the same for all players and ws is TCP-based (no loss of consecutive packets except for reconnection -- which is already handled by READDED_BATTLE_COLLIDER_ACKED)
Moreover, we're downsyncing a same "inputsBufferSnapshot" for all players in the same battle and this is by design, i.e. not respecting "player.LastSentInputFrameId" because "new all-confirmed inputFrameDownsyncs" are the same for all players and ws is TCP-based (no loss of consecutive packets except for reconnection -- which is already handled by READDED_BATTLE_COLLIDER_ACKED)
Lastly noting just for fun, if in "OnBattleCmdReceived" we need downsync to a single specific player (keeping **the order of generation of "inputsBufferSnapshot" preserved for sending** of course), in theory it's better to do it by the following order.
1. lock "InputsBuffer";
2. generate downsync msg;
3. lock "pR.PlayerDownsyncChanDict[playerId]";
4. put downsync msg to "pR.PlayerDownsyncChanDict[playerId]";
5. unlock "InputsBuffer";
6. now other threads are allowed to lock "inputsBuffer", and we can do "other things" on "pR.PlayerDownsyncChanDict[playerId]";
7. unlock "pR.PlayerDownsyncChanDict[playerId]".
The difference from our current approach is that the "pR.PlayerDownsyncChanDict[playerId]" in use is a Golang channel, i.e. when executing #4 it automatically executes #3 (before) & #7 (after) as well, thus we couldn't do #5 & #6 in between.
*/ */
for playerId, playerDownsyncChan := range pR.PlayerDownsyncChanDict { for playerId, playerDownsyncChan := range pR.PlayerDownsyncChanDict {
/* /*
@ -1592,8 +1732,20 @@ func (pR *Room) downsyncToAllPlayers(inputsBufferSnapshot *InputsBufferSnapshot)
The use of "downsyncLoop of each player" also waives the need of guarding each "pR.PlayerDownsyncSessionDict[playerId]" from multithread-access (e.g. by a "pR.PlayerDownsyncSessionMutexDict[playerId]"), i.e. Gorilla v1.2.0 "conn.WriteMessage" isn't thread-safe https://github.com/gorilla/websocket/blob/v1.2.0/conn.go#L585. The use of "downsyncLoop of each player" also waives the need of guarding each "pR.PlayerDownsyncSessionDict[playerId]" from multithread-access (e.g. by a "pR.PlayerDownsyncSessionMutexDict[playerId]"), i.e. Gorilla v1.2.0 "conn.WriteMessage" isn't thread-safe https://github.com/gorilla/websocket/blob/v1.2.0/conn.go#L585.
*/ */
playerDownsyncChan <- (*inputsBufferSnapshot) if player, existent := pR.Players[playerId]; existent {
Logger.Debug(fmt.Sprintf("Sent inputsBufferSnapshot(refRenderFrameId:%d, unconfirmedMask:%v) to for (roomId: %d, playerId:%d, playerDownsyncChan:%p)#1", inputsBufferSnapshot.RefRenderFrameId, inputsBufferSnapshot.UnconfirmedMask, pR.Id, playerId, playerDownsyncChan)) playerBattleState := atomic.LoadInt32(&(player.BattleState))
switch playerBattleState {
case PlayerBattleStateIns.DISCONNECTED:
case PlayerBattleStateIns.LOST:
case PlayerBattleStateIns.EXPELLED_DURING_GAME:
case PlayerBattleStateIns.EXPELLED_IN_DISMISSAL:
case PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK:
case PlayerBattleStateIns.READDED_PENDING_BATTLE_COLLIDER_ACK:
continue
}
playerDownsyncChan <- (*inputsBufferSnapshot)
// Logger.Info(fmt.Sprintf("Sent inputsBufferSnapshot(refRenderFrameId:%d, unconfirmedMask:%v) to for (roomId: %d, playerId:%d, playerDownsyncChan:%p)#1", inputsBufferSnapshot.RefRenderFrameId, inputsBufferSnapshot.UnconfirmedMask, pR.Id, playerId, playerDownsyncChan))
}
} }
} }
@ -1632,7 +1784,8 @@ func (pR *Room) downsyncToSinglePlayer(playerId int32, player *Player, refRender
2. reconnection 2. reconnection
*/ */
toSendInputFrameIdSt, toSendInputFrameIdEd := toSendInputFrameDownsyncsSnapshot[0].InputFrameId, toSendInputFrameDownsyncsSnapshot[len(toSendInputFrameDownsyncsSnapshot)-1].InputFrameId+1 //toSendInputFrameIdSt, toSendInputFrameIdEd := toSendInputFrameDownsyncsSnapshot[0].InputFrameId, toSendInputFrameDownsyncsSnapshot[len(toSendInputFrameDownsyncsSnapshot)-1].InputFrameId+1
_, toSendInputFrameIdEd := toSendInputFrameDownsyncsSnapshot[0].InputFrameId, toSendInputFrameDownsyncsSnapshot[len(toSendInputFrameDownsyncsSnapshot)-1].InputFrameId+1
if pR.BackendDynamicsEnabled && shouldResyncOverall { if pR.BackendDynamicsEnabled && shouldResyncOverall {
tmp := pR.RenderFrameBuffer.GetByFrameId(refRenderFrameId) tmp := pR.RenderFrameBuffer.GetByFrameId(refRenderFrameId)
if nil == tmp { if nil == tmp {
@ -1645,7 +1798,7 @@ func (pR *Room) downsyncToSinglePlayer(playerId int32, player *Player, refRender
} }
refRenderFrame.BackendUnconfirmedMask = unconfirmedMask refRenderFrame.BackendUnconfirmedMask = unconfirmedMask
pR.sendSafely(refRenderFrame, toSendInputFrameDownsyncsSnapshot, DOWNSYNC_MSG_ACT_FORCED_RESYNC, playerId, false) pR.sendSafely(refRenderFrame, toSendInputFrameDownsyncsSnapshot, DOWNSYNC_MSG_ACT_FORCED_RESYNC, playerId, false)
Logger.Warn(fmt.Sprintf("Sent refRenderFrameId=%v & inputFrameIds [%d, %d), for roomId=%v, playerId=%d, playerJoinIndex=%d, renderFrameId=%d, curDynamicsRenderFrameId=%d, playerLastSentInputFrameId=%d: InputsBuffer=%v", refRenderFrameId, toSendInputFrameIdSt, toSendInputFrameIdEd, pR.Id, playerId, player.JoinIndex, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, player.LastSentInputFrameId, pR.InputsBufferString(false))) // Logger.Warn(fmt.Sprintf("Sent refRenderFrameId=%v & inputFrameIds [%d, %d), for roomId=%v, playerId=%d, playerJoinIndex=%d, renderFrameId=%d, curDynamicsRenderFrameId=%d, playerLastSentInputFrameId=%d: InputsBuffer=%v", refRenderFrameId, toSendInputFrameIdSt, toSendInputFrameIdEd, pR.Id, playerId, player.JoinIndex, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, player.LastSentInputFrameId, pR.InputsBufferString(false)))
} else { } else {
pR.sendSafely(nil, toSendInputFrameDownsyncsSnapshot, DOWNSYNC_MSG_ACT_INPUT_BATCH, playerId, false) pR.sendSafely(nil, toSendInputFrameDownsyncsSnapshot, DOWNSYNC_MSG_ACT_INPUT_BATCH, playerId, false)
} }
@ -1687,3 +1840,29 @@ func (pR *Room) cloneInputsBuffer(stFrameId, edFrameId int32) []*InputFrameDowns
return cloned return cloned
} }
func (pR *Room) calcHardPushbacksNorms(playerCollider *resolv.Object, playerShape *resolv.ConvexPolygon, snapIntoPlatformOverlap float64, pEffPushback *Vec2D) []Vec2D {
ret := make([]Vec2D, 0, 10) // no one would simultaneously have more than 5 hardPushbacks
collision := playerCollider.Check(0, 0)
if nil == collision {
return ret
}
for _, obj := range collision.Objects {
switch obj.Data.(type) {
case *Barrier:
barrierShape := obj.Shape.(*resolv.ConvexPolygon)
overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, playerShape, barrierShape)
if !overlapped {
continue
}
// ALWAY snap into hardPushbacks!
// [OverlapX, OverlapY] is the unit vector that points into the platform
pushbackX, pushbackY = (overlapResult.Overlap-snapIntoPlatformOverlap)*overlapResult.OverlapX, (overlapResult.Overlap-snapIntoPlatformOverlap)*overlapResult.OverlapY
ret = append(ret, Vec2D{X: overlapResult.OverlapX, Y: overlapResult.OverlapY})
pEffPushback.X += pushbackX
pEffPushback.Y += pushbackY
default:
}
}
return ret
}

View File

@ -30,21 +30,24 @@ type PlayerDownsync struct {
VirtualGridX int32 `protobuf:"varint,2,opt,name=virtualGridX,proto3" json:"virtualGridX,omitempty"` VirtualGridX int32 `protobuf:"varint,2,opt,name=virtualGridX,proto3" json:"virtualGridX,omitempty"`
VirtualGridY int32 `protobuf:"varint,3,opt,name=virtualGridY,proto3" json:"virtualGridY,omitempty"` VirtualGridY int32 `protobuf:"varint,3,opt,name=virtualGridY,proto3" json:"virtualGridY,omitempty"`
DirX int32 `protobuf:"varint,4,opt,name=dirX,proto3" json:"dirX,omitempty"` DirX int32 `protobuf:"varint,4,opt,name=dirX,proto3" json:"dirX,omitempty"`
DirY int32 `protobuf:"varint,5,opt,name=dirY,proto3" json:"dirY,omitempty"` DirY int32 `protobuf:"varint,5,opt,name=dirY,proto3" json:"dirY,omitempty"` // "dirX" and "dirY" determines character facing
Speed int32 `protobuf:"varint,6,opt,name=speed,proto3" json:"speed,omitempty"` // in terms of virtual grid units VelX int32 `protobuf:"varint,6,opt,name=velX,proto3" json:"velX,omitempty"`
BattleState int32 `protobuf:"varint,7,opt,name=battleState,proto3" json:"battleState,omitempty"` VelY int32 `protobuf:"varint,7,opt,name=velY,proto3" json:"velY,omitempty"` // "velX" and "velY" is used to record the accumulated effect by accelerations (including gravity)
JoinIndex int32 `protobuf:"varint,8,opt,name=joinIndex,proto3" json:"joinIndex,omitempty"` Speed int32 `protobuf:"varint,8,opt,name=speed,proto3" json:"speed,omitempty"` // this is the instantaneous scalar attribute of a character, different from but will be accounted in "velX" and "velY"
ColliderRadius float64 `protobuf:"fixed64,9,opt,name=colliderRadius,proto3" json:"colliderRadius,omitempty"` BattleState int32 `protobuf:"varint,9,opt,name=battleState,proto3" json:"battleState,omitempty"`
Removed bool `protobuf:"varint,10,opt,name=removed,proto3" json:"removed,omitempty"` JoinIndex int32 `protobuf:"varint,10,opt,name=joinIndex,proto3" json:"joinIndex,omitempty"`
Score int32 `protobuf:"varint,11,opt,name=score,proto3" json:"score,omitempty"` ColliderRadius float64 `protobuf:"fixed64,11,opt,name=colliderRadius,proto3" json:"colliderRadius,omitempty"`
LastMoveGmtMillis int32 `protobuf:"varint,12,opt,name=lastMoveGmtMillis,proto3" json:"lastMoveGmtMillis,omitempty"` Removed bool `protobuf:"varint,12,opt,name=removed,proto3" json:"removed,omitempty"`
FramesToRecover int32 `protobuf:"varint,13,opt,name=framesToRecover,proto3" json:"framesToRecover,omitempty"` Score int32 `protobuf:"varint,13,opt,name=score,proto3" json:"score,omitempty"`
Hp int32 `protobuf:"varint,14,opt,name=hp,proto3" json:"hp,omitempty"` LastMoveGmtMillis int32 `protobuf:"varint,14,opt,name=lastMoveGmtMillis,proto3" json:"lastMoveGmtMillis,omitempty"`
MaxHp int32 `protobuf:"varint,15,opt,name=maxHp,proto3" json:"maxHp,omitempty"` FramesToRecover int32 `protobuf:"varint,15,opt,name=framesToRecover,proto3" json:"framesToRecover,omitempty"`
CharacterState int32 `protobuf:"varint,16,opt,name=characterState,proto3" json:"characterState,omitempty"` Hp int32 `protobuf:"varint,16,opt,name=hp,proto3" json:"hp,omitempty"`
Name string `protobuf:"bytes,17,opt,name=name,proto3" json:"name,omitempty"` MaxHp int32 `protobuf:"varint,17,opt,name=maxHp,proto3" json:"maxHp,omitempty"`
DisplayName string `protobuf:"bytes,18,opt,name=displayName,proto3" json:"displayName,omitempty"` CharacterState int32 `protobuf:"varint,18,opt,name=characterState,proto3" json:"characterState,omitempty"`
Avatar string `protobuf:"bytes,19,opt,name=avatar,proto3" json:"avatar,omitempty"` InAir bool `protobuf:"varint,19,opt,name=inAir,proto3" json:"inAir,omitempty"` // by design a standalone field only inferred by the collision result of "applyInputFrameDownsyncDynamicsOnSingleRenderFrame" instead of "characterState", because we need check the transition for "characterState" from this field, i.e. "inAir (prev -> curr)"
Name string `protobuf:"bytes,20,opt,name=name,proto3" json:"name,omitempty"`
DisplayName string `protobuf:"bytes,21,opt,name=displayName,proto3" json:"displayName,omitempty"`
Avatar string `protobuf:"bytes,22,opt,name=avatar,proto3" json:"avatar,omitempty"`
} }
func (x *PlayerDownsync) Reset() { func (x *PlayerDownsync) Reset() {
@ -114,6 +117,20 @@ func (x *PlayerDownsync) GetDirY() int32 {
return 0 return 0
} }
func (x *PlayerDownsync) GetVelX() int32 {
if x != nil {
return x.VelX
}
return 0
}
func (x *PlayerDownsync) GetVelY() int32 {
if x != nil {
return x.VelY
}
return 0
}
func (x *PlayerDownsync) GetSpeed() int32 { func (x *PlayerDownsync) GetSpeed() int32 {
if x != nil { if x != nil {
return x.Speed return x.Speed
@ -191,6 +208,13 @@ func (x *PlayerDownsync) GetCharacterState() int32 {
return 0 return 0
} }
func (x *PlayerDownsync) GetInAir() bool {
if x != nil {
return x.InAir
}
return false
}
func (x *PlayerDownsync) GetName() string { func (x *PlayerDownsync) GetName() string {
if x != nil { if x != nil {
return x.Name return x.Name
@ -220,6 +244,7 @@ type InputFrameDecoded struct {
Dx int32 `protobuf:"varint,1,opt,name=dx,proto3" json:"dx,omitempty"` Dx int32 `protobuf:"varint,1,opt,name=dx,proto3" json:"dx,omitempty"`
Dy int32 `protobuf:"varint,2,opt,name=dy,proto3" json:"dy,omitempty"` Dy int32 `protobuf:"varint,2,opt,name=dy,proto3" json:"dy,omitempty"`
BtnALevel int32 `protobuf:"varint,3,opt,name=btnALevel,proto3" json:"btnALevel,omitempty"` BtnALevel int32 `protobuf:"varint,3,opt,name=btnALevel,proto3" json:"btnALevel,omitempty"`
BtnBLevel int32 `protobuf:"varint,4,opt,name=btnBLevel,proto3" json:"btnBLevel,omitempty"`
} }
func (x *InputFrameDecoded) Reset() { func (x *InputFrameDecoded) Reset() {
@ -275,6 +300,13 @@ func (x *InputFrameDecoded) GetBtnALevel() int32 {
return 0 return 0
} }
func (x *InputFrameDecoded) GetBtnBLevel() int32 {
if x != nil {
return x.BtnBLevel
}
return 0
}
type InputFrameUpsync struct { type InputFrameUpsync struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -901,6 +933,11 @@ type BattleColliderInfo struct {
SpAtkLookupFrames int32 `protobuf:"varint,25,opt,name=spAtkLookupFrames,proto3" json:"spAtkLookupFrames,omitempty"` SpAtkLookupFrames int32 `protobuf:"varint,25,opt,name=spAtkLookupFrames,proto3" json:"spAtkLookupFrames,omitempty"`
RenderCacheSize int32 `protobuf:"varint,26,opt,name=renderCacheSize,proto3" json:"renderCacheSize,omitempty"` RenderCacheSize int32 `protobuf:"varint,26,opt,name=renderCacheSize,proto3" json:"renderCacheSize,omitempty"`
MeleeSkillConfig map[int32]*MeleeBullet `protobuf:"bytes,27,rep,name=meleeSkillConfig,proto3" json:"meleeSkillConfig,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // skillId -> skill MeleeSkillConfig map[int32]*MeleeBullet `protobuf:"bytes,27,rep,name=meleeSkillConfig,proto3" json:"meleeSkillConfig,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // skillId -> skill
SnapIntoPlatformOverlap float64 `protobuf:"fixed64,28,opt,name=snapIntoPlatformOverlap,proto3" json:"snapIntoPlatformOverlap,omitempty"`
SnapIntoPlatformThreshold float64 `protobuf:"fixed64,29,opt,name=snapIntoPlatformThreshold,proto3" json:"snapIntoPlatformThreshold,omitempty"`
JumpingInitVelY int32 `protobuf:"varint,30,opt,name=jumpingInitVelY,proto3" json:"jumpingInitVelY,omitempty"`
GravityX int32 `protobuf:"varint,31,opt,name=gravityX,proto3" json:"gravityX,omitempty"`
GravityY int32 `protobuf:"varint,32,opt,name=gravityY,proto3" json:"gravityY,omitempty"`
} }
func (x *BattleColliderInfo) Reset() { func (x *BattleColliderInfo) Reset() {
@ -1117,6 +1154,41 @@ func (x *BattleColliderInfo) GetMeleeSkillConfig() map[int32]*MeleeBullet {
return nil return nil
} }
func (x *BattleColliderInfo) GetSnapIntoPlatformOverlap() float64 {
if x != nil {
return x.SnapIntoPlatformOverlap
}
return 0
}
func (x *BattleColliderInfo) GetSnapIntoPlatformThreshold() float64 {
if x != nil {
return x.SnapIntoPlatformThreshold
}
return 0
}
func (x *BattleColliderInfo) GetJumpingInitVelY() int32 {
if x != nil {
return x.JumpingInitVelY
}
return 0
}
func (x *BattleColliderInfo) GetGravityX() int32 {
if x != nil {
return x.GravityX
}
return 0
}
func (x *BattleColliderInfo) GetGravityY() int32 {
if x != nil {
return x.GravityY
}
return 0
}
type RoomDownsyncFrame struct { type RoomDownsyncFrame struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -1202,7 +1274,7 @@ var file_room_downsync_frame_proto_rawDesc = []byte{
0x0a, 0x19, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x0a, 0x19, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x5f,
0x66, 0x72, 0x61, 0x6d, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x70, 0x72, 0x6f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x73, 0x1a, 0x0e, 0x67, 0x65, 0x6f, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x74, 0x6f, 0x73, 0x1a, 0x0e, 0x67, 0x65, 0x6f, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x22, 0xb2, 0x04, 0x0a, 0x0e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x44, 0x6f, 0x6f, 0x74, 0x6f, 0x22, 0xf0, 0x04, 0x0a, 0x0e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x44, 0x6f,
0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x22, 0x0a, 0x0c, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x22, 0x0a, 0x0c, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61,
0x6c, 0x47, 0x72, 0x69, 0x64, 0x58, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x76, 0x69, 0x6c, 0x47, 0x72, 0x69, 0x64, 0x58, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x76, 0x69,
@ -1211,279 +1283,299 @@ var file_room_downsync_frame_proto_rawDesc = []byte{
0x52, 0x0c, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x47, 0x72, 0x69, 0x64, 0x59, 0x12, 0x12, 0x52, 0x0c, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x47, 0x72, 0x69, 0x64, 0x59, 0x12, 0x12,
0x0a, 0x04, 0x64, 0x69, 0x72, 0x58, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x64, 0x69, 0x0a, 0x04, 0x64, 0x69, 0x72, 0x58, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x64, 0x69,
0x72, 0x58, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x69, 0x72, 0x59, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x72, 0x58, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x69, 0x72, 0x59, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05,
0x52, 0x04, 0x64, 0x69, 0x72, 0x59, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x70, 0x65, 0x65, 0x64, 0x18, 0x52, 0x04, 0x64, 0x69, 0x72, 0x59, 0x12, 0x12, 0x0a, 0x04, 0x76, 0x65, 0x6c, 0x58, 0x18, 0x06,
0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x73, 0x70, 0x65, 0x65, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x76, 0x65, 0x6c, 0x58, 0x12, 0x12, 0x0a, 0x04, 0x76, 0x65,
0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x6c, 0x59, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x76, 0x65, 0x6c, 0x59, 0x12, 0x14,
0x05, 0x52, 0x0b, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, 0x05, 0x73, 0x70, 0x65, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x73,
0x0a, 0x09, 0x6a, 0x6f, 0x69, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x70, 0x65, 0x65, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x53, 0x74,
0x05, 0x52, 0x09, 0x6a, 0x6f, 0x69, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x26, 0x0a, 0x0e, 0x61, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x61, 0x74, 0x74, 0x6c,
0x63, 0x6f, 0x6c, 0x6c, 0x69, 0x64, 0x65, 0x72, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x18, 0x09, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6a, 0x6f, 0x69, 0x6e, 0x49, 0x6e,
0x20, 0x01, 0x28, 0x01, 0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x69, 0x64, 0x65, 0x72, 0x52, 0x61, 0x64, 0x65, 0x78, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6a, 0x6f, 0x69, 0x6e, 0x49,
0x64, 0x69, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x18, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x69, 0x64, 0x65, 0x72,
0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x12, 0x14, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0e, 0x63, 0x6f,
0x0a, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x73, 0x6c, 0x6c, 0x69, 0x64, 0x65, 0x72, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07,
0x63, 0x6f, 0x72, 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x4d, 0x6f, 0x76, 0x65, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x72,
0x47, 0x6d, 0x74, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18,
0x11, 0x6c, 0x61, 0x73, 0x74, 0x4d, 0x6f, 0x76, 0x65, 0x47, 0x6d, 0x74, 0x4d, 0x69, 0x6c, 0x6c, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x2c, 0x0a, 0x11,
0x69, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x54, 0x6f, 0x52, 0x65, 0x6c, 0x61, 0x73, 0x74, 0x4d, 0x6f, 0x76, 0x65, 0x47, 0x6d, 0x74, 0x4d, 0x69, 0x6c, 0x6c, 0x69,
0x63, 0x6f, 0x76, 0x65, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x66, 0x72, 0x61, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x4d, 0x6f, 0x76,
0x6d, 0x65, 0x73, 0x54, 0x6f, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x65, 0x47, 0x6d, 0x74, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x66, 0x72,
0x68, 0x70, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x68, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6d, 0x65, 0x73, 0x54, 0x6f, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x18, 0x0f, 0x20,
0x6d, 0x61, 0x78, 0x48, 0x70, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6d, 0x61, 0x78, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x54, 0x6f, 0x52, 0x65, 0x63,
0x48, 0x70, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x68, 0x61, 0x72, 0x61, 0x63, 0x74, 0x65, 0x72, 0x53, 0x6f, 0x76, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x68, 0x70, 0x18, 0x10, 0x20, 0x01, 0x28, 0x05,
0x74, 0x61, 0x74, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x72, 0x52, 0x02, 0x68, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x61, 0x78, 0x48, 0x70, 0x18, 0x11, 0x20,
0x61, 0x63, 0x74, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6d, 0x61, 0x78, 0x48, 0x70, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x68,
0x6d, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x61, 0x72, 0x61, 0x63, 0x74, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x12, 0x20, 0x01,
0x0a, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x12, 0x20, 0x28, 0x05, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x72, 0x61, 0x63, 0x74, 0x65, 0x72, 0x53, 0x74, 0x61,
0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x41, 0x69, 0x72, 0x18, 0x13, 0x20, 0x01, 0x28,
0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x08, 0x52, 0x05, 0x69, 0x6e, 0x41, 0x69, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x22, 0x51, 0x0a, 0x11, 0x49, 0x6e, 0x70, 0x75, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b,
0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x12, 0x0e, 0x0a, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x15, 0x20, 0x01, 0x28,
0x02, 0x64, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x64, 0x78, 0x12, 0x0e, 0x0a, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16,
0x02, 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x64, 0x79, 0x12, 0x1c, 0x0a, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
0x09, 0x62, 0x74, 0x6e, 0x41, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x22, 0x6f, 0x0a, 0x11, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46,
0x52, 0x09, 0x62, 0x74, 0x6e, 0x41, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x50, 0x0a, 0x10, 0x49, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x64,
0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x12, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x64, 0x78, 0x12, 0x0e, 0x0a, 0x02, 0x64,
0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x64, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x62,
0x74, 0x6e, 0x41, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09,
0x62, 0x74, 0x6e, 0x41, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x74, 0x6e,
0x42, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x62, 0x74,
0x6e, 0x42, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x50, 0x0a, 0x10, 0x49, 0x6e, 0x70, 0x75, 0x74,
0x46, 0x72, 0x61, 0x6d, 0x65, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x12, 0x22, 0x0a, 0x0c, 0x69,
0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x05, 0x52, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12,
0x18, 0x0a, 0x07, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04,
0x52, 0x07, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x22, 0x7c, 0x0a, 0x12, 0x49, 0x6e, 0x70,
0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x12,
0x22, 0x0a, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18, 0x22, 0x0a, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d,
0x65, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x18, 0x02, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4c, 0x69, 0x73, 0x74,
0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x22, 0x7c, 0x0a, 0x18, 0x02, 0x20, 0x03, 0x28, 0x04, 0x52, 0x09, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4c, 0x69, 0x73,
0x12, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4c, 0x69,
0x79, 0x6e, 0x63, 0x12, 0x22, 0x0a, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72,
0x65, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x6d, 0x65, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x3b, 0x0a, 0x0f, 0x48, 0x65, 0x61, 0x72, 0x74,
0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x62, 0x65, 0x61, 0x74, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x12, 0x28, 0x0a, 0x0f, 0x63, 0x6c,
0x4c, 0x69, 0x73, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x04, 0x52, 0x09, 0x69, 0x6e, 0x70, 0x75, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20,
0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73,
0x65, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x63, 0x6f, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xb8, 0x02, 0x0a, 0x05, 0x57, 0x73, 0x52, 0x65, 0x71, 0x12, 0x14,
0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x3b, 0x0a, 0x0f, 0x48, 0x0a, 0x05, 0x6d, 0x73, 0x67, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6d,
0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x12, 0x28, 0x73, 0x67, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x64,
0x0a, 0x0f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x64,
0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x61,
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xb8, 0x02, 0x0a, 0x05, 0x57, 0x73, 0x52, 0x63, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6a, 0x6f, 0x69, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18,
0x65, 0x71, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x73, 0x67, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6a, 0x6f, 0x69, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78,
0x05, 0x52, 0x05, 0x6d, 0x73, 0x67, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49,
0x65, 0x72, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x79, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x46,
0x65, 0x72, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x12, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67,
0x05, 0x52, 0x03, 0x61, 0x63, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6a, 0x6f, 0x69, 0x6e, 0x49, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18, 0x06, 0x20, 0x01,
0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6a, 0x6f, 0x69, 0x6e, 0x49, 0x28, 0x05, 0x52, 0x12, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46,
0x6e, 0x64, 0x65, 0x78, 0x12, 0x24, 0x0a, 0x0d, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x46, 0x72, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x4e, 0x0a, 0x15, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46,
0x61, 0x6d, 0x65, 0x49, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x61, 0x63, 0x6b, 0x72, 0x61, 0x6d, 0x65, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x42, 0x61, 0x74, 0x63, 0x68, 0x18,
0x69, 0x6e, 0x67, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x12, 0x61, 0x63, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x49,
0x6b, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x52,
0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x15, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x55, 0x70, 0x73, 0x79, 0x6e,
0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x4e, 0x0a, 0x15, 0x69, 0x6e, 0x63, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x27, 0x0a, 0x02, 0x68, 0x62, 0x18, 0x08, 0x20, 0x01,
0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x42, 0x61, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x48, 0x65, 0x61, 0x72,
0x74, 0x63, 0x68, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x74, 0x62, 0x65, 0x61, 0x74, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x52, 0x02, 0x68, 0x62, 0x22,
0x6f, 0x73, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x55, 0x70, 0x73, 0x89, 0x02, 0x0a, 0x06, 0x57, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65,
0x79, 0x6e, 0x63, 0x52, 0x15, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x55, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x72, 0x65, 0x74, 0x12, 0x20, 0x0a, 0x0b,
0x70, 0x73, 0x79, 0x6e, 0x63, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x27, 0x0a, 0x02, 0x68, 0x62, 0x65, 0x63, 0x68, 0x6f, 0x65, 0x64, 0x4d, 0x73, 0x67, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x05, 0x52, 0x0b, 0x65, 0x63, 0x68, 0x6f, 0x65, 0x64, 0x4d, 0x73, 0x67, 0x49, 0x64, 0x12, 0x10,
0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x52, 0x0a, 0x03, 0x61, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x61, 0x63, 0x74,
0x02, 0x68, 0x62, 0x22, 0x89, 0x02, 0x0a, 0x06, 0x57, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x10, 0x12, 0x2b, 0x0a, 0x03, 0x72, 0x64, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e,
0x0a, 0x03, 0x72, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x72, 0x65, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73,
0x12, 0x20, 0x0a, 0x0b, 0x65, 0x63, 0x68, 0x6f, 0x65, 0x64, 0x4d, 0x73, 0x67, 0x49, 0x64, 0x18, 0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x03, 0x72, 0x64, 0x66, 0x12, 0x54, 0x0a,
0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x65, 0x63, 0x68, 0x6f, 0x65, 0x64, 0x4d, 0x73, 0x67, 0x17, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x73,
0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x79, 0x6e, 0x63, 0x42, 0x61, 0x74, 0x63, 0x68, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a,
0x03, 0x61, 0x63, 0x74, 0x12, 0x2b, 0x0a, 0x03, 0x72, 0x64, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61,
0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x44, 0x6d, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x52, 0x17, 0x69, 0x6e, 0x70, 0x75,
0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x03, 0x72, 0x64, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x42, 0x61,
0x66, 0x12, 0x54, 0x0a, 0x17, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x74, 0x63, 0x68, 0x12, 0x36, 0x0a, 0x08, 0x62, 0x63, 0x69, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x18,
0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x42, 0x61, 0x74, 0x63, 0x68, 0x18, 0x05, 0x20, 0x03, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x42,
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x49, 0x6e, 0x70, 0x75,
0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x52, 0x17,
0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79,
0x6e, 0x63, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x36, 0x0a, 0x08, 0x62, 0x63, 0x69, 0x46, 0x72,
0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x73, 0x2e, 0x42, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x69, 0x64, 0x65,
0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x62, 0x63, 0x69, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x22,
0xc6, 0x01, 0x0a, 0x14, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72,
0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x2a, 0x0a, 0x10, 0x72, 0x65, 0x66, 0x52,
0x65, 0x6e, 0x64, 0x65, 0x72, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x05, 0x52, 0x10, 0x72, 0x65, 0x66, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x46, 0x72, 0x61,
0x6d, 0x65, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x0f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72,
0x6d, 0x65, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x75,
0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x12, 0x58,
0x0a, 0x19, 0x74, 0x6f, 0x53, 0x65, 0x6e, 0x64, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61,
0x6d, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74,
0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x52, 0x19, 0x74,
0x6f, 0x53, 0x65, 0x6e, 0x64, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44,
0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x73, 0x22, 0xe5, 0x05, 0x0a, 0x0b, 0x4d, 0x65, 0x6c,
0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x62, 0x61, 0x74, 0x74,
0x6c, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52,
0x0d, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x24,
0x0a, 0x0d, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18,
0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x46, 0x72,
0x61, 0x6d, 0x65, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x46, 0x72,
0x61, 0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x61, 0x63, 0x74, 0x69,
0x76, 0x65, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x63, 0x6f,
0x76, 0x65, 0x72, 0x79, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05,
0x52, 0x0e, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73,
0x12, 0x34, 0x0a, 0x15, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x46, 0x72, 0x61, 0x6d,
0x65, 0x73, 0x4f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52,
0x15, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x4f,
0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x30, 0x0a, 0x13, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65,
0x72, 0x79, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x4f, 0x6e, 0x48, 0x69, 0x74, 0x18, 0x06, 0x20,
0x01, 0x28, 0x05, 0x52, 0x13, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x46, 0x72, 0x61,
0x6d, 0x65, 0x73, 0x4f, 0x6e, 0x48, 0x69, 0x74, 0x12, 0x35, 0x0a, 0x0b, 0x6d, 0x6f, 0x76, 0x65,
0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e,
0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x56, 0x65, 0x63,
0x32, 0x44, 0x52, 0x0b, 0x6d, 0x6f, 0x76, 0x65, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x12,
0x22, 0x0a, 0x0c, 0x68, 0x69, 0x74, 0x62, 0x6f, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18,
0x08, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x68, 0x69, 0x74, 0x62, 0x6f, 0x78, 0x4f, 0x66, 0x66,
0x73, 0x65, 0x74, 0x12, 0x33, 0x0a, 0x0a, 0x68, 0x69, 0x74, 0x62, 0x6f, 0x78, 0x53, 0x69, 0x7a,
0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x56, 0x65, 0x63, 0x32, 0x44, 0x52, 0x0a, 0x68, 0x69,
0x74, 0x62, 0x6f, 0x78, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x38, 0x0a, 0x17, 0x6f, 0x72, 0x69, 0x67,
0x69, 0x6e, 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x46, 0x72, 0x61, 0x6d,
0x65, 0x49, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x17, 0x6f, 0x72, 0x69, 0x67, 0x69,
0x6e, 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x46, 0x72, 0x61, 0x6d, 0x65,
0x49, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x68, 0x69, 0x74, 0x53, 0x74, 0x75, 0x6e, 0x46, 0x72, 0x61,
0x6d, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x68, 0x69, 0x74, 0x53, 0x74,
0x75, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x62, 0x6c, 0x6f, 0x63,
0x6b, 0x53, 0x74, 0x75, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28,
0x05, 0x52, 0x0f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x74, 0x75, 0x6e, 0x46, 0x72, 0x61, 0x6d,
0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x75, 0x73, 0x68, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x0d,
0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x70, 0x75, 0x73, 0x68, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x2e,
0x0a, 0x12, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72,
0x54, 0x79, 0x70, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x72, 0x65, 0x6c, 0x65,
0x61, 0x73, 0x65, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16,
0x0a, 0x06, 0x64, 0x61, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06,
0x64, 0x61, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x6f, 0x66, 0x66, 0x65, 0x6e, 0x64,
0x65, 0x72, 0x4a, 0x6f, 0x69, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x10, 0x20, 0x01, 0x28,
0x05, 0x52, 0x11, 0x6f, 0x66, 0x66, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x4a, 0x6f, 0x69, 0x6e, 0x49,
0x6e, 0x64, 0x65, 0x78, 0x12, 0x2a, 0x0a, 0x10, 0x6f, 0x66, 0x66, 0x65, 0x6e, 0x64, 0x65, 0x72,
0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10,
0x6f, 0x66, 0x66, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x64,
0x22, 0x98, 0x0d, 0x0a, 0x12, 0x42, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x69,
0x64, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, 0x61, 0x67, 0x65,
0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x67,
0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x5f, 0x0a, 0x11, 0x73, 0x74, 0x72, 0x54, 0x6f, 0x56, 0x65,
0x63, 0x32, 0x44, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x31, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x42, 0x61, 0x74, 0x74, 0x6c, 0x65,
0x43, 0x6f, 0x6c, 0x6c, 0x69, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x53, 0x74, 0x72,
0x54, 0x6f, 0x56, 0x65, 0x63, 0x32, 0x44, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x45, 0x6e,
0x74, 0x72, 0x79, 0x52, 0x11, 0x73, 0x74, 0x72, 0x54, 0x6f, 0x56, 0x65, 0x63, 0x32, 0x44, 0x4c,
0x69, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x12, 0x6b, 0x0a, 0x15, 0x73, 0x74, 0x72, 0x54, 0x6f, 0x50,
0x6f, 0x6c, 0x79, 0x67, 0x6f, 0x6e, 0x32, 0x44, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x18,
0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x42,
0x61, 0x74, 0x74, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x69, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x69, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x66,
0x6f, 0x2e, 0x53, 0x74, 0x72, 0x54, 0x6f, 0x50, 0x6f, 0x6c, 0x79, 0x67, 0x6f, 0x6e, 0x32, 0x44, 0x6f, 0x52, 0x08, 0x62, 0x63, 0x69, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x22, 0xc6, 0x01, 0x0a, 0x14,
0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x15, 0x73, 0x74, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x53, 0x6e, 0x61, 0x70,
0x73, 0x68, 0x6f, 0x74, 0x12, 0x2a, 0x0a, 0x10, 0x72, 0x65, 0x66, 0x52, 0x65, 0x6e, 0x64, 0x65,
0x72, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10,
0x72, 0x65, 0x66, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64,
0x12, 0x28, 0x0a, 0x0f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4d,
0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x75, 0x6e, 0x63, 0x6f, 0x6e,
0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x12, 0x58, 0x0a, 0x19, 0x74, 0x6f,
0x53, 0x65, 0x6e, 0x64, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x6f,
0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d,
0x65, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x52, 0x19, 0x74, 0x6f, 0x53, 0x65, 0x6e,
0x64, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x73,
0x79, 0x6e, 0x63, 0x73, 0x22, 0xe5, 0x05, 0x0a, 0x0b, 0x4d, 0x65, 0x6c, 0x65, 0x65, 0x42, 0x75,
0x6c, 0x6c, 0x65, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x4c, 0x6f,
0x63, 0x61, 0x6c, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x62, 0x61, 0x74,
0x74, 0x6c, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x73, 0x74,
0x61, 0x72, 0x74, 0x75, 0x70, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
0x05, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73,
0x12, 0x22, 0x0a, 0x0c, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73,
0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x46, 0x72,
0x61, 0x6d, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79,
0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72, 0x65,
0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x15,
0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x4f, 0x6e,
0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, 0x72, 0x65, 0x63,
0x6f, 0x76, 0x65, 0x72, 0x79, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x4f, 0x6e, 0x42, 0x6c, 0x6f,
0x63, 0x6b, 0x12, 0x30, 0x0a, 0x13, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x46, 0x72,
0x61, 0x6d, 0x65, 0x73, 0x4f, 0x6e, 0x48, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52,
0x13, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x4f,
0x6e, 0x48, 0x69, 0x74, 0x12, 0x35, 0x0a, 0x0b, 0x6d, 0x6f, 0x76, 0x65, 0x66, 0x6f, 0x72, 0x77,
0x61, 0x72, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x68, 0x61, 0x72,
0x65, 0x64, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x56, 0x65, 0x63, 0x32, 0x44, 0x52, 0x0b,
0x6d, 0x6f, 0x76, 0x65, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x12, 0x22, 0x0a, 0x0c, 0x68,
0x69, 0x74, 0x62, 0x6f, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28,
0x01, 0x52, 0x0c, 0x68, 0x69, 0x74, 0x62, 0x6f, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12,
0x33, 0x0a, 0x0a, 0x68, 0x69, 0x74, 0x62, 0x6f, 0x78, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x09, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x73, 0x2e, 0x56, 0x65, 0x63, 0x32, 0x44, 0x52, 0x0a, 0x68, 0x69, 0x74, 0x62, 0x6f, 0x78,
0x53, 0x69, 0x7a, 0x65, 0x12, 0x38, 0x0a, 0x17, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74,
0x65, 0x64, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18,
0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x17, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x65,
0x64, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x24,
0x0a, 0x0d, 0x68, 0x69, 0x74, 0x53, 0x74, 0x75, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18,
0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x68, 0x69, 0x74, 0x53, 0x74, 0x75, 0x6e, 0x46, 0x72,
0x61, 0x6d, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x74, 0x75,
0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x62,
0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x74, 0x75, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1a,
0x0a, 0x08, 0x70, 0x75, 0x73, 0x68, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x01,
0x52, 0x08, 0x70, 0x75, 0x73, 0x68, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x2e, 0x0a, 0x12, 0x72, 0x65,
0x6c, 0x65, 0x61, 0x73, 0x65, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65,
0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x54,
0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x61,
0x6d, 0x61, 0x67, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x64, 0x61, 0x6d, 0x61,
0x67, 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x6f, 0x66, 0x66, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x4a, 0x6f,
0x69, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x6f,
0x66, 0x66, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x4a, 0x6f, 0x69, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78,
0x12, 0x2a, 0x0a, 0x10, 0x6f, 0x66, 0x66, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x50, 0x6c, 0x61, 0x79,
0x65, 0x72, 0x49, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x6f, 0x66, 0x66, 0x65,
0x6e, 0x64, 0x65, 0x72, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x64, 0x22, 0xf2, 0x0e, 0x0a,
0x12, 0x42, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x69, 0x64, 0x65, 0x72, 0x49,
0x6e, 0x66, 0x6f, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d,
0x65, 0x12, 0x5f, 0x0a, 0x11, 0x73, 0x74, 0x72, 0x54, 0x6f, 0x56, 0x65, 0x63, 0x32, 0x44, 0x4c,
0x69, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x42, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x6c,
0x69, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x53, 0x74, 0x72, 0x54, 0x6f, 0x56, 0x65,
0x63, 0x32, 0x44, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52,
0x11, 0x73, 0x74, 0x72, 0x54, 0x6f, 0x56, 0x65, 0x63, 0x32, 0x44, 0x4c, 0x69, 0x73, 0x74, 0x4d,
0x61, 0x70, 0x12, 0x6b, 0x0a, 0x15, 0x73, 0x74, 0x72, 0x54, 0x6f, 0x50, 0x6f, 0x6c, 0x79, 0x67,
0x6f, 0x6e, 0x32, 0x44, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x35, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x42, 0x61, 0x74, 0x74, 0x6c,
0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x69, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x53, 0x74,
0x72, 0x54, 0x6f, 0x50, 0x6f, 0x6c, 0x79, 0x67, 0x6f, 0x6e, 0x32, 0x44, 0x4c, 0x69, 0x73, 0x74, 0x72, 0x54, 0x6f, 0x50, 0x6f, 0x6c, 0x79, 0x67, 0x6f, 0x6e, 0x32, 0x44, 0x4c, 0x69, 0x73, 0x74,
0x4d, 0x61, 0x70, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x67, 0x65, 0x44, 0x69, 0x73, 0x63, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x15, 0x73, 0x74, 0x72, 0x54, 0x6f, 0x50,
0x72, 0x65, 0x74, 0x65, 0x57, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x6f, 0x6c, 0x79, 0x67, 0x6f, 0x6e, 0x32, 0x44, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x12,
0x67, 0x65, 0x44, 0x69, 0x73, 0x63, 0x72, 0x65, 0x74, 0x65, 0x57, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x26, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x67, 0x65, 0x44, 0x69, 0x73, 0x63, 0x72, 0x65, 0x74, 0x65,
0x74, 0x61, 0x67, 0x65, 0x44, 0x69, 0x73, 0x63, 0x72, 0x65, 0x74, 0x65, 0x48, 0x18, 0x05, 0x20, 0x57, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x67, 0x65, 0x44, 0x69,
0x01, 0x28, 0x05, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x67, 0x65, 0x44, 0x69, 0x73, 0x63, 0x72, 0x65, 0x73, 0x63, 0x72, 0x65, 0x74, 0x65, 0x57, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x67, 0x65,
0x74, 0x65, 0x48, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x67, 0x65, 0x54, 0x69, 0x6c, 0x65, 0x44, 0x69, 0x73, 0x63, 0x72, 0x65, 0x74, 0x65, 0x48, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52,
0x57, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x67, 0x65, 0x54, 0x69, 0x0e, 0x73, 0x74, 0x61, 0x67, 0x65, 0x44, 0x69, 0x73, 0x63, 0x72, 0x65, 0x74, 0x65, 0x48, 0x12,
0x6c, 0x65, 0x57, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x67, 0x65, 0x54, 0x69, 0x6c, 0x65, 0x1e, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x67, 0x65, 0x54, 0x69, 0x6c, 0x65, 0x57, 0x18, 0x06, 0x20,
0x48, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x67, 0x65, 0x54, 0x69, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x67, 0x65, 0x54, 0x69, 0x6c, 0x65, 0x57, 0x12,
0x6c, 0x65, 0x48, 0x12, 0x26, 0x0a, 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x54, 0x1e, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x67, 0x65, 0x54, 0x69, 0x6c, 0x65, 0x48, 0x18, 0x07, 0x20,
0x6f, 0x50, 0x69, 0x6e, 0x67, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x69, 0x6e, 0x74, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x67, 0x65, 0x54, 0x69, 0x6c, 0x65, 0x48, 0x12,
0x65, 0x72, 0x76, 0x61, 0x6c, 0x54, 0x6f, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x34, 0x0a, 0x15, 0x77, 0x26, 0x0a, 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x54, 0x6f, 0x50, 0x69, 0x6e,
0x69, 0x6c, 0x6c, 0x4b, 0x69, 0x63, 0x6b, 0x49, 0x66, 0x49, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x67, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61,
0x65, 0x46, 0x6f, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, 0x77, 0x69, 0x6c, 0x6c, 0x6c, 0x54, 0x6f, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x34, 0x0a, 0x15, 0x77, 0x69, 0x6c, 0x6c, 0x4b,
0x4b, 0x69, 0x63, 0x6b, 0x49, 0x66, 0x49, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x46, 0x6f, 0x69, 0x63, 0x6b, 0x49, 0x66, 0x49, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x46, 0x6f, 0x72,
0x72, 0x12, 0x20, 0x0a, 0x0b, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, 0x77, 0x69, 0x6c, 0x6c, 0x4b, 0x69, 0x63, 0x6b,
0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x6f, 0x6f, 0x49, 0x66, 0x49, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x46, 0x6f, 0x72, 0x12, 0x20, 0x0a,
0x6d, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x14, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x44, 0x75, 0x72, 0x0b, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x18, 0x0a, 0x20, 0x01,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12,
0x05, 0x52, 0x14, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x32, 0x0a, 0x14, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x30, 0x0a, 0x13, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x62,
0x65, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x0d, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x72, 0x61,
0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x44, 0x75, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x30, 0x0a, 0x13, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x44, 0x75, 0x72,
0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03,
0x76, 0x65, 0x72, 0x46, 0x70, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x65, 0x52, 0x13, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x72, 0x76, 0x65, 0x72, 0x46, 0x70, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x46,
0x44, 0x65, 0x6c, 0x61, 0x79, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x70, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
0x05, 0x52, 0x10, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x46, 0x72, 0x61, 0x46, 0x70, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x44, 0x65, 0x6c, 0x61,
0x6d, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x79, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x69,
0x65, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12,
0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x46, 0x72, 0x61,
0x26, 0x0a, 0x0e, 0x6e, 0x73, 0x74, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x6d, 0x65, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x69, 0x6e, 0x70, 0x75, 0x74,
0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x6e, 0x73, 0x74, 0x44, 0x65, 0x6c, 0x61, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x6e,
0x79, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x46, 0x0a, 0x1e, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x74, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x11, 0x20,
0x46, 0x72, 0x61, 0x6d, 0x65, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x6e, 0x73, 0x74, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x46, 0x72, 0x61,
0x54, 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x05, 0x52, 0x6d, 0x65, 0x73, 0x12, 0x46, 0x0a, 0x1e, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d,
0x1e, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x65, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x54, 0x6f, 0x6c, 0x65,
0x63, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x54, 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x05, 0x52, 0x1e, 0x69, 0x6e, 0x70,
0x48, 0x0a, 0x1f, 0x6d, 0x61, 0x78, 0x43, 0x68, 0x61, 0x73, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x6e, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x44, 0x65, 0x6c,
0x64, 0x65, 0x72, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x50, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x61, 0x79, 0x54, 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x48, 0x0a, 0x1f, 0x6d,
0x74, 0x65, 0x18, 0x13, 0x20, 0x01, 0x28, 0x05, 0x52, 0x1f, 0x6d, 0x61, 0x78, 0x43, 0x68, 0x61, 0x61, 0x78, 0x43, 0x68, 0x61, 0x73, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x46,
0x73, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x50, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x13,
0x50, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x70, 0x6c, 0x61, 0x20, 0x01, 0x28, 0x05, 0x52, 0x1f, 0x6d, 0x61, 0x78, 0x43, 0x68, 0x61, 0x73, 0x69, 0x6e, 0x67,
0x79, 0x65, 0x72, 0x42, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x14, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x50, 0x65, 0x72, 0x55,
0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x42, 0x61, 0x74, 0x74, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x42,
0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3c, 0x0a, 0x19, 0x72, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x05,
0x61, 0x63, 0x6b, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x64, 0x44, 0x74, 0x4d, 0x69, 0x52, 0x11, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x42, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x53, 0x74,
0x6c, 0x6c, 0x69, 0x73, 0x18, 0x15, 0x20, 0x01, 0x28, 0x01, 0x52, 0x19, 0x72, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x3c, 0x0a, 0x19, 0x72, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x45,
0x62, 0x61, 0x63, 0x6b, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x64, 0x44, 0x74, 0x4d, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x64, 0x44, 0x74, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x73,
0x69, 0x6c, 0x6c, 0x69, 0x73, 0x12, 0x3a, 0x0a, 0x18, 0x72, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x18, 0x15, 0x20, 0x01, 0x28, 0x01, 0x52, 0x19, 0x72, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b,
0x6b, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x64, 0x44, 0x74, 0x4e, 0x61, 0x6e, 0x6f, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x64, 0x44, 0x74, 0x4d, 0x69, 0x6c, 0x6c, 0x69,
0x73, 0x18, 0x16, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x72, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x73, 0x12, 0x3a, 0x0a, 0x18, 0x72, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x45, 0x73, 0x74,
0x6b, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x64, 0x44, 0x74, 0x4e, 0x61, 0x6e, 0x6f, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x64, 0x44, 0x74, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x16, 0x20,
0x73, 0x12, 0x38, 0x0a, 0x17, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x54, 0x6f, 0x56, 0x69, 0x72, 0x74, 0x01, 0x28, 0x03, 0x52, 0x18, 0x72, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x45, 0x73, 0x74,
0x75, 0x61, 0x6c, 0x47, 0x72, 0x69, 0x64, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x18, 0x17, 0x20, 0x01, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x64, 0x44, 0x74, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x12, 0x38, 0x0a,
0x28, 0x01, 0x52, 0x17, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x54, 0x6f, 0x56, 0x69, 0x72, 0x74, 0x75, 0x17, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x54, 0x6f, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x47,
0x61, 0x6c, 0x47, 0x72, 0x69, 0x64, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x12, 0x38, 0x0a, 0x17, 0x76, 0x72, 0x69, 0x64, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x18, 0x17, 0x20, 0x01, 0x28, 0x01, 0x52, 0x17,
0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x47, 0x72, 0x69, 0x64, 0x54, 0x6f, 0x57, 0x6f, 0x72, 0x6c, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x54, 0x6f, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x47, 0x72,
0x64, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x18, 0x18, 0x20, 0x01, 0x28, 0x01, 0x52, 0x17, 0x76, 0x69, 0x69, 0x64, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x12, 0x38, 0x0a, 0x17, 0x76, 0x69, 0x72, 0x74, 0x75,
0x72, 0x74, 0x75, 0x61, 0x6c, 0x47, 0x72, 0x69, 0x64, 0x54, 0x6f, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x61, 0x6c, 0x47, 0x72, 0x69, 0x64, 0x54, 0x6f, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x52, 0x61, 0x74,
0x52, 0x61, 0x74, 0x69, 0x6f, 0x12, 0x2c, 0x0a, 0x11, 0x73, 0x70, 0x41, 0x74, 0x6b, 0x4c, 0x6f, 0x69, 0x6f, 0x18, 0x18, 0x20, 0x01, 0x28, 0x01, 0x52, 0x17, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61,
0x6f, 0x6b, 0x75, 0x70, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x19, 0x20, 0x01, 0x28, 0x05, 0x6c, 0x47, 0x72, 0x69, 0x64, 0x54, 0x6f, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x52, 0x61, 0x74, 0x69,
0x52, 0x11, 0x73, 0x70, 0x41, 0x74, 0x6b, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x46, 0x72, 0x61, 0x6f, 0x12, 0x2c, 0x0a, 0x11, 0x73, 0x70, 0x41, 0x74, 0x6b, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70,
0x6d, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x43, 0x61, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x19, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x73, 0x70,
0x68, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x72, 0x65, 0x41, 0x74, 0x6b, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12,
0x6e, 0x64, 0x65, 0x72, 0x43, 0x61, 0x63, 0x68, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x5c, 0x0a, 0x28, 0x0a, 0x0f, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x43, 0x61, 0x63, 0x68, 0x65, 0x53, 0x69,
0x10, 0x6d, 0x65, 0x6c, 0x65, 0x65, 0x53, 0x6b, 0x69, 0x6c, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x7a, 0x65, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72,
0x67, 0x18, 0x1b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x43, 0x61, 0x63, 0x68, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x5c, 0x0a, 0x10, 0x6d, 0x65, 0x6c,
0x2e, 0x42, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x69, 0x64, 0x65, 0x72, 0x49, 0x65, 0x65, 0x53, 0x6b, 0x69, 0x6c, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x1b, 0x20,
0x6e, 0x66, 0x6f, 0x2e, 0x4d, 0x65, 0x6c, 0x65, 0x65, 0x53, 0x6b, 0x69, 0x6c, 0x6c, 0x43, 0x6f, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x42, 0x61, 0x74,
0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x10, 0x6d, 0x65, 0x6c, 0x65, 0x65, 0x74, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x69, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e,
0x53, 0x6b, 0x69, 0x6c, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x5d, 0x0a, 0x16, 0x53, 0x4d, 0x65, 0x6c, 0x65, 0x65, 0x53, 0x6b, 0x69, 0x6c, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
0x74, 0x72, 0x54, 0x6f, 0x56, 0x65, 0x63, 0x32, 0x44, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x10, 0x6d, 0x65, 0x6c, 0x65, 0x65, 0x53, 0x6b, 0x69, 0x6c,
0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x38, 0x0a, 0x17, 0x73, 0x6e, 0x61, 0x70, 0x49,
0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x6e, 0x74, 0x6f, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x4f, 0x76, 0x65, 0x72, 0x6c,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x70, 0x61, 0x70, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x01, 0x52, 0x17, 0x73, 0x6e, 0x61, 0x70, 0x49, 0x6e,
0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x56, 0x65, 0x63, 0x32, 0x44, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x74, 0x6f, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x4f, 0x76, 0x65, 0x72, 0x6c, 0x61,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x65, 0x0a, 0x1a, 0x53, 0x74, 0x70, 0x12, 0x3c, 0x0a, 0x19, 0x73, 0x6e, 0x61, 0x70, 0x49, 0x6e, 0x74, 0x6f, 0x50, 0x6c, 0x61,
0x72, 0x54, 0x6f, 0x50, 0x6f, 0x6c, 0x79, 0x67, 0x6f, 0x6e, 0x32, 0x44, 0x4c, 0x69, 0x73, 0x74, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x1d,
0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x20, 0x01, 0x28, 0x01, 0x52, 0x19, 0x73, 0x6e, 0x61, 0x70, 0x49, 0x6e, 0x74, 0x6f, 0x50, 0x6c,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x05, 0x76, 0x61, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12,
0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x28, 0x0a, 0x0f, 0x6a, 0x75, 0x6d, 0x70, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x69, 0x74, 0x56, 0x65,
0x65, 0x64, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x50, 0x6f, 0x6c, 0x79, 0x67, 0x6f, 0x6e, 0x6c, 0x59, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x6a, 0x75, 0x6d, 0x70, 0x69, 0x6e,
0x32, 0x44, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x67, 0x49, 0x6e, 0x69, 0x74, 0x56, 0x65, 0x6c, 0x59, 0x12, 0x1a, 0x0a, 0x08, 0x67, 0x72, 0x61,
0x01, 0x1a, 0x58, 0x0a, 0x15, 0x4d, 0x65, 0x6c, 0x65, 0x65, 0x53, 0x6b, 0x69, 0x6c, 0x6c, 0x43, 0x76, 0x69, 0x74, 0x79, 0x58, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x67, 0x72, 0x61,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x76, 0x69, 0x74, 0x79, 0x58, 0x12, 0x1a, 0x0a, 0x08, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x79,
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x05, 0x59, 0x18, 0x20, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x79,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x59, 0x1a, 0x5d, 0x0a, 0x16, 0x53, 0x74, 0x72, 0x54, 0x6f, 0x56, 0x65, 0x63, 0x32, 0x44, 0x4c,
0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x4d, 0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xd2, 0x02, 0x0a, 0x11, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a,
0x52, 0x6f, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x73,
0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x68, 0x61, 0x72, 0x65, 0x64, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x56, 0x65, 0x63, 0x32,
0x64, 0x12, 0x40, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x44, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01,
0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x1a, 0x65, 0x0a, 0x1a, 0x53, 0x74, 0x72, 0x54, 0x6f, 0x50, 0x6f, 0x6c, 0x79, 0x67, 0x6f, 0x6e,
0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x2e, 0x50, 0x6c, 0x32, 0x44, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10,
0x61, 0x79, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x70, 0x6c, 0x61, 0x79, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
0x65, 0x72, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x31, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x63, 0x6f, 0x75, 0x1b, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x50,
0x6e, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x12, 0x37, 0x0a, 0x0c, 0x6d, 0x6f, 0x6c, 0x79, 0x67, 0x6f, 0x6e, 0x32, 0x44, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61,
0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x58, 0x0a, 0x15, 0x4d, 0x65, 0x6c, 0x65, 0x65,
0x53, 0x6b, 0x69, 0x6c, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79,
0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b,
0x65, 0x79, 0x12, 0x29, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x4d, 0x65, 0x6c, 0x65, 0x65, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x4d, 0x65, 0x6c, 0x65, 0x65,
0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x0c, 0x6d, 0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,
0x6c, 0x65, 0x74, 0x73, 0x12, 0x36, 0x0a, 0x16, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x55, 0x01, 0x22, 0xd2, 0x02, 0x0a, 0x11, 0x52, 0x6f, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79,
0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x18, 0x05, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
0x20, 0x01, 0x28, 0x04, 0x52, 0x16, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x40, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x79, 0x65,
0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x1a, 0x52, 0x0a, 0x0c, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x72,
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x61, 0x6d, 0x65, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79,
0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x52, 0x07, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x6f, 0x75,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x44, 0x6f, 0x77, 0x6e, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
0x6e, 0x73, 0x79, 0x6e, 0x63, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x03, 0x52, 0x0e, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x4e, 0x61, 0x6e, 0x6f,
0x42, 0x13, 0x5a, 0x11, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x73, 0x72, 0x76, 0x2f, 0x70, 0x73, 0x12, 0x37, 0x0a, 0x0c, 0x6d, 0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74,
0x72, 0x6f, 0x74, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
0x2e, 0x4d, 0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x0c, 0x6d, 0x65,
0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x73, 0x12, 0x36, 0x0a, 0x16, 0x62, 0x61,
0x63, 0x6b, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64,
0x4d, 0x61, 0x73, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x16, 0x62, 0x61, 0x63, 0x6b,
0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4d, 0x61,
0x73, 0x6b, 0x1a, 0x52, 0x0a, 0x0c, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74,
0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52,
0x03, 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x50, 0x6c, 0x61,
0x79, 0x65, 0x72, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x52, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x13, 0x5a, 0x11, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65,
0x5f, 0x73, 0x72, 0x76, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
} }
var ( var (

View File

@ -265,9 +265,14 @@ func Serve(c *gin.Context) {
WorldToVirtualGridRatio: pRoom.WorldToVirtualGridRatio, WorldToVirtualGridRatio: pRoom.WorldToVirtualGridRatio,
VirtualGridToWorldRatio: pRoom.VirtualGridToWorldRatio, VirtualGridToWorldRatio: pRoom.VirtualGridToWorldRatio,
SpAtkLookupFrames: pRoom.SpAtkLookupFrames, SpAtkLookupFrames: pRoom.SpAtkLookupFrames,
RenderCacheSize: pRoom.RenderCacheSize, RenderCacheSize: pRoom.RenderCacheSize,
MeleeSkillConfig: pRoom.MeleeSkillConfig, MeleeSkillConfig: pRoom.MeleeSkillConfig,
SnapIntoPlatformOverlap: pRoom.SnapIntoPlatformOverlap,
SnapIntoPlatformThreshold: pRoom.SnapIntoPlatformThreshold,
JumpingInitVelY: pRoom.JumpingInitVelY,
GravityX: pRoom.GravityX,
GravityY: pRoom.GravityY,
} }
resp := &pb.WsResp{ resp := &pb.WsResp{

BIN
charts/jump_sync_spedup.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 MiB

View File

@ -34,15 +34,18 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi
spaceOffsetX := float64(spaceW) * 0.5 spaceOffsetX := float64(spaceW) * 0.5
spaceOffsetY := float64(spaceH) * 0.5 spaceOffsetY := float64(spaceH) * 0.5
virtualGridToWorldRatio := 0.1 worldToVirtualGridRatio := float64(1000)
playerDefaultSpeed := 20 virtualGridToWorldRatio := float64(1) / worldToVirtualGridRatio
playerDefaultSpeed := 1 * worldToVirtualGridRatio
minStep := (int(float64(playerDefaultSpeed)*virtualGridToWorldRatio) << 2) minStep := (int(float64(playerDefaultSpeed)*virtualGridToWorldRatio) << 2)
playerColliderRadius := float64(24) playerColliderRadius := float64(12)
playerColliders := make([]*resolv.Object, len(playerPosList.Eles)) playerColliders := make([]*resolv.Object, len(playerPosList.Eles))
snapIntoPlatformOverlap := float64(0.1)
space := resolv.NewSpace(int(spaceW), int(spaceH), minStep, minStep) space := resolv.NewSpace(int(spaceW), int(spaceH), minStep, minStep)
for i, playerPos := range playerPosList.Eles { for i, playerPos := range playerPosList.Eles {
playerCollider := GenerateRectCollider(playerPos.X, playerPos.Y, playerColliderRadius*2, playerColliderRadius*2, spaceOffsetX, spaceOffsetY, "Player") // [WARNING] Deliberately not using a circle because "resolv v0.5.1" doesn't yet align circle center with space cell center, regardless of the "specified within-object offset" colliderWidth, colliderHeight := playerColliderRadius*2, playerColliderRadius*4
Logger.Info(fmt.Sprintf("Player Collider#%d: player world pos =(%.2f, %.2f), shape=%v", i, playerPos.X, playerPos.Y, ConvexPolygonStr(playerCollider.Shape.(*resolv.ConvexPolygon)))) playerCollider := GenerateRectCollider(playerPos.X, playerPos.Y, colliderWidth, colliderHeight, snapIntoPlatformOverlap, spaceOffsetX, spaceOffsetY, "Player") // [WARNING] Deliberately not using a circle because "resolv v0.5.1" doesn't yet align circle center with space cell center, regardless of the "specified within-object offset"
Logger.Info(fmt.Sprintf("Player Collider#%d: player world pos=(%.2f, %.2f), shape=%v", i, playerPos.X, playerPos.Y, ConvexPolygonStr(playerCollider.Shape.(*resolv.ConvexPolygon))))
playerColliders[i] = playerCollider playerColliders[i] = playerCollider
space.Add(playerCollider) space.Add(playerCollider)
} }
@ -50,7 +53,7 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi
barrierLocalId := 0 barrierLocalId := 0
for _, barrierUnaligned := range barrierList.Eles { for _, barrierUnaligned := range barrierList.Eles {
barrierCollider := GenerateConvexPolygonCollider(barrierUnaligned, spaceOffsetX, spaceOffsetY, "Barrier") barrierCollider := GenerateConvexPolygonCollider(barrierUnaligned, spaceOffsetX, spaceOffsetY, "Barrier")
Logger.Info(fmt.Sprintf("Added barrier: shape=%v", ConvexPolygonStr(barrierCollider.Shape.(*resolv.ConvexPolygon)))) Logger.Debug(fmt.Sprintf("Added barrier: shape=%v", ConvexPolygonStr(barrierCollider.Shape.(*resolv.ConvexPolygon))))
space.Add(barrierCollider) space.Add(barrierCollider)
barrierLocalId++ barrierLocalId++
} }
@ -59,24 +62,26 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi
moveToCollide := false moveToCollide := false
if moveToCollide { if moveToCollide {
newVx, newVy := int32(-2959), int32(-2261)
effPushback := Vec2D{X: float64(0), Y: float64(0)} effPushback := Vec2D{X: float64(0), Y: float64(0)}
toTestPlayerCollider := playerColliders[0] toTestPlayerCollider := playerColliders[0]
toTestPlayerCollider.X, toTestPlayerCollider.Y = VirtualGridToPolygonColliderAnchorPos(newVx, newVy, playerColliderRadius, playerColliderRadius, spaceOffsetX, spaceOffsetY, virtualGridToWorldRatio) newVx, newVy := int32(27999), int32(-420270)
colliderWidth, colliderHeight := playerColliderRadius*2, playerColliderRadius*4
toTestPlayerCollider.X, toTestPlayerCollider.Y = VirtualGridToPolygonColliderTLPos(newVx, newVy, colliderWidth, colliderHeight, spaceOffsetX, spaceOffsetY, virtualGridToWorldRatio)
Logger.Info(fmt.Sprintf("Checking collision for virtual (%d, %d), now playerShape=%v", newVx, newVy, ConvexPolygonStr(toTestPlayerCollider.Shape.(*resolv.ConvexPolygon)))) Logger.Info(fmt.Sprintf("Checking collision for playerShape=%v", ConvexPolygonStr(toTestPlayerCollider.Shape.(*resolv.ConvexPolygon))))
toTestPlayerCollider.Update() toTestPlayerCollider.Update()
if collision := toTestPlayerCollider.Check(0, 0); collision != nil { if collision := toTestPlayerCollider.Check(0, 0); collision != nil {
playerShape := toTestPlayerCollider.Shape.(*resolv.ConvexPolygon) playerShape := toTestPlayerCollider.Shape.(*resolv.ConvexPolygon)
for _, obj := range collision.Objects { for _, obj := range collision.Objects {
barrierShape := obj.Shape.(*resolv.ConvexPolygon) bShape := obj.Shape.(*resolv.ConvexPolygon)
if overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, playerShape, barrierShape); overlapped { Logger.Warn(fmt.Sprintf("Checking potential: a=%v, b=%v", ConvexPolygonStr(playerShape), ConvexPolygonStr(bShape)))
Logger.Warn(fmt.Sprintf("Overlapped: a=%v, b=%v, pushbackX=%v, pushbackY=%v", ConvexPolygonStr(playerShape), ConvexPolygonStr(barrierShape), pushbackX, pushbackY)) if overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, playerShape, bShape); overlapped {
Logger.Warn(fmt.Sprintf("Overlapped: a=%v, b=%v, pushbackX=%v, pushbackY=%v", ConvexPolygonStr(playerShape), ConvexPolygonStr(bShape), pushbackX, pushbackY))
effPushback.X += pushbackX effPushback.X += pushbackX
effPushback.Y += pushbackY effPushback.Y += pushbackY
} else { } else {
Logger.Warn(fmt.Sprintf("Collided BUT not overlapped: a=%v, b=%v, overlapResult=%v", ConvexPolygonStr(playerShape), ConvexPolygonStr(barrierShape), overlapResult)) Logger.Warn(fmt.Sprintf("Collided BUT not overlapped: a=%v, b=%v, overlapResult=%v", ConvexPolygonStr(playerShape), ConvexPolygonStr(bShape), overlapResult))
} }
} }
toTestPlayerCollider.X -= effPushback.X toTestPlayerCollider.X -= effPushback.X
@ -115,7 +120,7 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi
offenderWx, offenderWy := playerPosList.Eles[0].X, playerPosList.Eles[0].Y offenderWx, offenderWy := playerPosList.Eles[0].X, playerPosList.Eles[0].Y
bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy
newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, spaceOffsetX, spaceOffsetY, "MeleeBullet") newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, 0, spaceOffsetX, spaceOffsetY, "MeleeBullet")
space.Add(newBulletCollider) space.Add(newBulletCollider)
bulletShape := newBulletCollider.Shape.(*resolv.ConvexPolygon) bulletShape := newBulletCollider.Shape.(*resolv.ConvexPolygon)
Logger.Warn(fmt.Sprintf("bullet ->: Added bullet collider to space: a=%v", ConvexPolygonStr(bulletShape))) Logger.Warn(fmt.Sprintf("bullet ->: Added bullet collider to space: a=%v", ConvexPolygonStr(bulletShape)))
@ -138,7 +143,7 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi
offenderWx, offenderWy := playerPosList.Eles[1].X, playerPosList.Eles[1].Y offenderWx, offenderWy := playerPosList.Eles[1].X, playerPosList.Eles[1].Y
bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy
newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, spaceOffsetX, spaceOffsetY, "MeleeBullet") newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, 0, spaceOffsetX, spaceOffsetY, "MeleeBullet")
space.Add(newBulletCollider) space.Add(newBulletCollider)
bulletShape := newBulletCollider.Shape.(*resolv.ConvexPolygon) bulletShape := newBulletCollider.Shape.(*resolv.ConvexPolygon)
Logger.Warn(fmt.Sprintf("bullet <-: Added bullet collider to space: a=%v", ConvexPolygonStr(bulletShape))) Logger.Warn(fmt.Sprintf("bullet <-: Added bullet collider to space: a=%v", ConvexPolygonStr(bulletShape)))
@ -177,7 +182,7 @@ func (world *WorldColliderDisplay) Draw(screen *ebiten.Image) {
} }
} }
world.Game.DebugDraw(screen, world.Space) //world.Game.DebugDraw(screen, world.Space)
if world.Game.ShowHelpText { if world.Game.ShowHelpText {

View File

@ -10,33 +10,33 @@ func NormVec2D(dx, dy float64) Vec2D {
} }
func AlignPolygon2DToBoundingBox(input *Polygon2D) *Polygon2D { func AlignPolygon2DToBoundingBox(input *Polygon2D) *Polygon2D {
// Transform again to put "anchor" at the top-left point of the bounding box for "resolv" // Transform again to put "anchor" at the "bottom-left point (w.r.t. world space)" of the bounding box for "resolv"
boundingBoxTL := &Vec2D{ boundingBoxBL := &Vec2D{
X: math.MaxFloat64, X: math.MaxFloat64,
Y: math.MaxFloat64, Y: math.MaxFloat64,
} }
for _, p := range input.Points { for _, p := range input.Points {
if p.X < boundingBoxTL.X { if p.X < boundingBoxBL.X {
boundingBoxTL.X = p.X boundingBoxBL.X = p.X
} }
if p.Y < boundingBoxTL.Y { if p.Y < boundingBoxBL.Y {
boundingBoxTL.Y = p.Y boundingBoxBL.Y = p.Y
} }
} }
// Now "input.Anchor" should move to "input.Anchor+boundingBoxTL", thus "boundingBoxTL" is also the value of the negative diff for all "input.Points" // Now "input.Anchor" should move to "input.Anchor+boundingBoxBL", thus "boundingBoxBL" is also the value of the negative diff for all "input.Points"
output := &Polygon2D{ output := &Polygon2D{
Anchor: &Vec2D{ Anchor: &Vec2D{
X: input.Anchor.X + boundingBoxTL.X, X: input.Anchor.X + boundingBoxBL.X,
Y: input.Anchor.Y + boundingBoxTL.Y, Y: input.Anchor.Y + boundingBoxBL.Y,
}, },
Points: make([]*Vec2D, len(input.Points)), Points: make([]*Vec2D, len(input.Points)),
} }
for i, p := range input.Points { for i, p := range input.Points {
output.Points[i] = &Vec2D{ output.Points[i] = &Vec2D{
X: p.X - boundingBoxTL.X, X: p.X - boundingBoxBL.X,
Y: p.Y - boundingBoxTL.Y, Y: p.Y - boundingBoxBL.Y,
} }
} }

View File

@ -18,13 +18,13 @@ func ConvexPolygonStr(body *resolv.ConvexPolygon) string {
return fmt.Sprintf("{\n%s\n}", strings.Join(s, ",\n")) return fmt.Sprintf("{\n%s\n}", strings.Join(s, ",\n"))
} }
func GenerateRectCollider(origX, origY, w, h, spaceOffsetX, spaceOffsetY float64, tag string) *resolv.Object { func GenerateRectCollider(wx, wy, w, h, bottomPadding, spaceOffsetX, spaceOffsetY float64, tag string) *resolv.Object {
cx, cy := WorldToPolygonColliderAnchorPos(origX, origY, w*0.5, h*0.5, spaceOffsetX, spaceOffsetY) blX, blY := WorldToPolygonColliderBLPos(wx, wy, w*0.5, h*0.5, bottomPadding, spaceOffsetX, spaceOffsetY)
return GenerateRectColliderInCollisionSpace(cx, cy, w, h, tag) return generateRectColliderInCollisionSpace(blX, blY, w, h+bottomPadding, tag)
} }
func GenerateRectColliderInCollisionSpace(cx, cy, w, h float64, tag string) *resolv.Object { func generateRectColliderInCollisionSpace(blX, blY, w, h float64, tag string) *resolv.Object {
collider := resolv.NewObject(cx, cy, w, h, tag) collider := resolv.NewObject(blX, blY, w, h, tag) // Unlike its frontend counter part, the position of a "resolv.Object" must be specified by "bottom-left point" because "w" and "h" must be positive, see "resolv.Object.BoundsToSpace" for details
shape := resolv.NewRectangle(0, 0, w, h) shape := resolv.NewRectangle(0, 0, w, h)
collider.SetShape(shape) collider.SetShape(shape)
return collider return collider
@ -66,6 +66,7 @@ func CalcPushbacks(oldDx, oldDy float64, playerShape, barrierShape *resolv.Conve
playerShape.SetPosition(origX, origY) playerShape.SetPosition(origX, origY)
}() }()
playerShape.SetPosition(origX+oldDx, origY+oldDy) playerShape.SetPosition(origX+oldDx, origY+oldDy)
overlapResult := &SatResult{ overlapResult := &SatResult{
Overlap: 0, Overlap: 0,
OverlapX: 0, OverlapX: 0,
@ -74,7 +75,7 @@ func CalcPushbacks(oldDx, oldDy float64, playerShape, barrierShape *resolv.Conve
BContainedInA: true, BContainedInA: true,
Axis: vector.Vector{0, 0}, Axis: vector.Vector{0, 0},
} }
if overlapped := IsPolygonPairOverlapped(playerShape, barrierShape, overlapResult); overlapped { if overlapped := isPolygonPairOverlapped(playerShape, barrierShape, overlapResult); overlapped {
pushbackX, pushbackY := overlapResult.Overlap*overlapResult.OverlapX, overlapResult.Overlap*overlapResult.OverlapY pushbackX, pushbackY := overlapResult.Overlap*overlapResult.OverlapX, overlapResult.Overlap*overlapResult.OverlapY
return true, pushbackX, pushbackY, overlapResult return true, pushbackX, pushbackY, overlapResult
} else { } else {
@ -91,16 +92,17 @@ type SatResult struct {
Axis vector.Vector Axis vector.Vector
} }
func IsPolygonPairOverlapped(a, b *resolv.ConvexPolygon, result *SatResult) bool { func isPolygonPairOverlapped(a, b *resolv.ConvexPolygon, result *SatResult) bool {
aCnt, bCnt := len(a.Points), len(b.Points) aCnt, bCnt := len(a.Points), len(b.Points)
// Single point case // Single point case
if 1 == aCnt && 1 == bCnt { if 1 == aCnt && 1 == bCnt {
if nil != result { if nil != result {
result.Overlap = 0 result.Overlap = 0
} }
return a.Points[0].X() == b.Points[0].X() && a.Points[0].Y() == b.Points[0].Y() return a.Points[0][0] == b.Points[0][0] && a.Points[0][1] == b.Points[0][1]
} }
//Logger.Info(fmt.Sprintf("Checking collision between a=%v, b=%v", ConvexPolygonStr(a), ConvexPolygonStr(b)))
if 1 < aCnt { if 1 < aCnt {
for _, axis := range a.SATAxes() { for _, axis := range a.SATAxes() {
if isPolygonPairSeparatedByDir(a, b, axis.Unit(), result) { if isPolygonPairSeparatedByDir(a, b, axis.Unit(), result) {
@ -116,6 +118,7 @@ func IsPolygonPairOverlapped(a, b *resolv.ConvexPolygon, result *SatResult) bool
} }
} }
} }
//Logger.Info(fmt.Sprintf("a=%v and b=%v are overlapped", ConvexPolygonStr(a), ConvexPolygonStr(b)))
return true return true
} }
@ -137,9 +140,10 @@ func isPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e vector.Vector, re
e = (-2.98, 1.49).Unit() e = (-2.98, 1.49).Unit()
*/ */
//Logger.Info(fmt.Sprintf("Checking separation between a=%v, b=%v along axis e={%.3f, %.3f}#1", ConvexPolygonStr(a), ConvexPolygonStr(b), e[0], e[1]))
var aStart, aEnd, bStart, bEnd float64 = math.MaxFloat64, -math.MaxFloat64, math.MaxFloat64, -math.MaxFloat64 var aStart, aEnd, bStart, bEnd float64 = math.MaxFloat64, -math.MaxFloat64, math.MaxFloat64, -math.MaxFloat64
for _, p := range a.Points { for _, p := range a.Points {
dot := (p.X()+a.X)*e.X() + (p.Y()+a.Y)*e.Y() dot := (p[0]+a.X)*e[0] + (p[1]+a.Y)*e[1]
if aStart > dot { if aStart > dot {
aStart = dot aStart = dot
@ -151,7 +155,7 @@ func isPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e vector.Vector, re
} }
for _, p := range b.Points { for _, p := range b.Points {
dot := (p.X()+b.X)*e.X() + (p.Y()+b.Y)*e.Y() dot := (p[0]+b.X)*e[0] + (p[1]+b.Y)*e[1]
if bStart > dot { if bStart > dot {
bStart = dot bStart = dot
@ -168,7 +172,6 @@ func isPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e vector.Vector, re
} }
if nil != result { if nil != result {
result.Axis = e
overlap := float64(0) overlap := float64(0)
if aStart < bStart { if aStart < bStart {
@ -209,16 +212,19 @@ func isPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e vector.Vector, re
absoluteOverlap = -overlap absoluteOverlap = -overlap
} }
if 0 == currentOverlap || currentOverlap > absoluteOverlap { if (0 == result.Axis[0] && 0 == result.Axis[1]) || currentOverlap > absoluteOverlap {
var sign float64 = 1 var sign float64 = 1
if overlap < 0 { if overlap < 0 {
sign = -1 sign = -1
} }
result.Overlap = absoluteOverlap result.Overlap = absoluteOverlap
result.OverlapX = e.X() * sign result.OverlapX = e[0] * sign
result.OverlapY = e.Y() * sign result.OverlapY = e[1] * sign
} }
result.Axis = e
//Logger.Info(fmt.Sprintf("Checking separation between a=%v, b=%v along axis e={%.3f, %.3f}#2: aStart=%.3f, aEnd=%.3f, bStart=%.3f, bEnd=%.3f, overlap=%.3f, currentOverlap=%.3f, absoluteOverlap=%.3f, result=%v", ConvexPolygonStr(a), ConvexPolygonStr(b), e[0], e[1], aStart, aEnd, bStart, bEnd, overlap, currentOverlap, absoluteOverlap, result))
} }
// the specified unit vector "e" doesn't separate "a" and "b", overlap result is generated // the specified unit vector "e" doesn't separate "a" and "b", overlap result is generated
@ -240,20 +246,38 @@ func VirtualGridToWorldPos(vx, vy int32, virtualGridToWorldRatio float64) (float
return wx, wy return wx, wy
} }
func WorldToPolygonColliderAnchorPos(wx, wy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (float64, float64) { func WorldToPolygonColliderBLPos(wx, wy, halfBoundingW, halfBoundingH, bottomPadding, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (float64, float64) {
return wx - halfBoundingW + collisionSpaceOffsetX, wy - halfBoundingH + collisionSpaceOffsetY return wx - halfBoundingW + collisionSpaceOffsetX, wy - halfBoundingH - bottomPadding + collisionSpaceOffsetY
} }
func PolygonColliderAnchorToWorldPos(cx, cy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (float64, float64) { func WorldToPolygonColliderTLPos(wx, wy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (float64, float64) {
return cx + halfBoundingW - collisionSpaceOffsetX, cy + halfBoundingH - collisionSpaceOffsetY return wx - halfBoundingW + collisionSpaceOffsetX, wy + halfBoundingH + collisionSpaceOffsetY
} }
func PolygonColliderAnchorToVirtualGridPos(cx, cy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64, worldToVirtualGridRatio float64) (int32, int32) { func PolygonColliderBLToWorldPos(cx, cy, halfBoundingW, halfBoundingH, bottomPadding, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (float64, float64) {
wx, wy := PolygonColliderAnchorToWorldPos(cx, cy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY) return cx + halfBoundingW - collisionSpaceOffsetX, cy + halfBoundingH + bottomPadding - collisionSpaceOffsetY
}
func PolygonColliderTLToWorldPos(cx, cy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (float64, float64) {
return cx + halfBoundingW - collisionSpaceOffsetX, cy - halfBoundingH - collisionSpaceOffsetY
}
func PolygonColliderBLToVirtualGridPos(cx, cy, halfBoundingW, halfBoundingH, bottomPadding, collisionSpaceOffsetX, collisionSpaceOffsetY float64, worldToVirtualGridRatio float64) (int32, int32) {
wx, wy := PolygonColliderBLToWorldPos(cx, cy, halfBoundingW, halfBoundingH, bottomPadding, collisionSpaceOffsetX, collisionSpaceOffsetY)
return WorldToVirtualGridPos(wx, wy, worldToVirtualGridRatio) return WorldToVirtualGridPos(wx, wy, worldToVirtualGridRatio)
} }
func VirtualGridToPolygonColliderAnchorPos(vx, vy int32, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64, virtualGridToWorldRatio float64) (float64, float64) { func PolygonColliderTLToVirtualGridPos(cx, cy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64, worldToVirtualGridRatio float64) (int32, int32) {
wx, wy := VirtualGridToWorldPos(vx, vy, virtualGridToWorldRatio) wx, wy := PolygonColliderTLToWorldPos(cx, cy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY)
return WorldToPolygonColliderAnchorPos(wx, wy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY) return WorldToVirtualGridPos(wx, wy, worldToVirtualGridRatio)
}
func VirtualGridToPolygonColliderBLPos(vx, vy int32, halfBoundingW, halfBoundingH, bottomPadding, collisionSpaceOffsetX, collisionSpaceOffsetY float64, virtualGridToWorldRatio float64) (float64, float64) {
wx, wy := VirtualGridToWorldPos(vx, vy, virtualGridToWorldRatio)
return WorldToPolygonColliderBLPos(wx, wy, halfBoundingW, halfBoundingH, bottomPadding, collisionSpaceOffsetX, collisionSpaceOffsetY)
}
func VirtualGridToPolygonColliderTLPos(vx, vy int32, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64, virtualGridToWorldRatio float64) (float64, float64) {
wx, wy := VirtualGridToWorldPos(vx, vy, virtualGridToWorldRatio)
return WorldToPolygonColliderTLPos(wx, wy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY)
} }

View File

@ -7,6 +7,7 @@ import (
"encoding/base64" "encoding/base64"
"encoding/xml" "encoding/xml"
"errors" "errors"
"fmt"
"go.uber.org/zap" "go.uber.org/zap"
"io/ioutil" "io/ioutil"
"math" "math"
@ -43,13 +44,13 @@ type TmxOrTsxPolyline struct {
type TmxOrTsxObject struct { type TmxOrTsxObject struct {
Id int `xml:"id,attr"` Id int `xml:"id,attr"`
Gid *int `xml:"gid,attr"` Gid int `xml:"gid,attr"`
X float64 `xml:"x,attr"` X float64 `xml:"x,attr"`
Y float64 `xml:"y,attr"` Y float64 `xml:"y,attr"`
Properties *TmxOrTsxProperties `xml:"properties"` Properties *TmxOrTsxProperties `xml:"properties"`
Polyline *TmxOrTsxPolyline `xml:"polyline"` Polyline *TmxOrTsxPolyline `xml:"polyline"`
Width *float64 `xml:"width,attr"` Width float64 `xml:"width,attr"`
Height *float64 `xml:"height,attr"` Height float64 `xml:"height,attr"`
} }
type TmxOrTsxObjectGroup struct { type TmxOrTsxObjectGroup struct {
@ -376,13 +377,37 @@ func ParseTmxLayersAndGroups(pTmxMapIns *TmxMap, gidBoundariesMap map[int]StrToP
} }
for _, singleObjInTmxFile := range objGroup.Objects { for _, singleObjInTmxFile := range objGroup.Objects {
if nil == singleObjInTmxFile.Polyline {
continue
}
if nil == singleObjInTmxFile.Properties.Property || "boundary_type" != singleObjInTmxFile.Properties.Property[0].Name || "barrier" != singleObjInTmxFile.Properties.Property[0].Value { if nil == singleObjInTmxFile.Properties.Property || "boundary_type" != singleObjInTmxFile.Properties.Property[0].Name || "barrier" != singleObjInTmxFile.Properties.Property[0].Value {
continue continue
} }
if nil == singleObjInTmxFile.Polyline {
pts := make([]*Vec2D, 4)
s := make([]string, 0)
pts[0] = &Vec2D{
X: float64(0),
Y: float64(0),
}
pts[1] = &Vec2D{
X: float64(0),
Y: singleObjInTmxFile.Height,
}
pts[2] = &Vec2D{
X: singleObjInTmxFile.Width,
Y: singleObjInTmxFile.Height,
}
pts[3] = &Vec2D{
X: singleObjInTmxFile.Width,
Y: float64(0),
}
for _, pt := range pts {
s = append(s, fmt.Sprintf("%v,%v", pt.X, pt.Y))
}
singleObjInTmxFile.Polyline = &TmxOrTsxPolyline{
Points: strings.Join(s, " "),
}
}
thePolygon2DInWorld, err := tmxPolylineToPolygon2D(pTmxMapIns, singleObjInTmxFile, singleObjInTmxFile.Polyline) thePolygon2DInWorld, err := tmxPolylineToPolygon2D(pTmxMapIns, singleObjInTmxFile, singleObjInTmxFile.Polyline)
if nil != err { if nil != err {
panic(err) panic(err)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"width":64,"imagePath":"SoldierFireGhost_tex.png","SubTexture":[{"frameWidth":12,"y":42,"frameHeight":11,"width":11,"frameX":-1,"height":11,"name":"biu","frameY":0,"x":16},{"width":5,"y":42,"height":7,"name":"rightArm","x":29},{"frameWidth":15,"y":27,"frameHeight":16,"width":13,"frameX":-1,"height":16,"name":"yinmoqe00","frameY":0,"x":1},{"width":17,"y":1,"height":21,"name":"body","x":28},{"width":5,"y":42,"height":7,"name":"rightShoulder","x":36},{"width":10,"y":45,"height":9,"name":"rightFrontArm","x":1},{"width":7,"y":56,"height":7,"name":"rightHand","x":1},{"width":6,"y":55,"height":6,"name":"leftArm","x":32},{"width":7,"y":55,"height":6,"name":"leftShoulder","x":23},{"width":10,"y":27,"height":11,"name":"leftFrontArm","x":16},{"width":17,"y":24,"height":16,"name":"head","x":28},{"width":25,"y":1,"height":24,"name":"head2","x":1},{"width":8,"y":55,"height":7,"name":"leftHand","x":13},{"frameWidth":4,"y":51,"frameHeight":4,"width":2,"frameX":-1,"height":2,"name":"huomiao01","frameY":-1,"x":29}],"height":64,"name":"SoldierFireGhost"} {"imagePath":"SoldierFireGhost_tex.png","width":64,"height":64,"name":"SoldierFireGhost","SubTexture":[{"frameY":0,"y":42,"frameWidth":12,"frameHeight":11,"width":11,"height":11,"name":"biu","frameX":-1,"x":16},{"width":5,"y":42,"height":7,"name":"rightArm","x":29},{"frameY":0,"y":27,"frameWidth":15,"frameHeight":16,"width":13,"height":16,"name":"yinmoqe00","frameX":-1,"x":1},{"width":17,"y":1,"height":21,"name":"body","x":28},{"width":5,"y":42,"height":7,"name":"rightShoulder","x":36},{"width":10,"y":45,"height":9,"name":"rightFrontArm","x":1},{"width":7,"y":56,"height":7,"name":"rightHand","x":1},{"width":6,"y":55,"height":6,"name":"leftArm","x":32},{"width":7,"y":55,"height":6,"name":"leftShoulder","x":23},{"width":10,"y":27,"height":11,"name":"leftFrontArm","x":16},{"width":25,"y":1,"height":24,"name":"head2","x":1},{"width":17,"y":24,"height":16,"name":"head","x":28},{"width":8,"y":55,"height":7,"name":"leftHand","x":13},{"frameY":-1,"y":51,"frameWidth":4,"frameHeight":4,"width":2,"height":2,"name":"huomiao01","frameX":-1,"x":29}]}

View File

@ -1,7 +1,7 @@
{ {
"ver": "1.0.0", "ver": "1.0.0",
"uuid": "4a9187d5-a9ad-4464-a03c-d2f3cc277051", "uuid": "4a9187d5-a9ad-4464-a03c-d2f3cc277051",
"atlasJson": "{\"width\":64,\"imagePath\":\"SoldierFireGhost_tex.png\",\"SubTexture\":[{\"frameWidth\":12,\"y\":42,\"frameHeight\":11,\"width\":11,\"frameX\":-1,\"height\":11,\"name\":\"biu\",\"frameY\":0,\"x\":16},{\"width\":5,\"y\":42,\"height\":7,\"name\":\"rightArm\",\"x\":29},{\"frameWidth\":15,\"y\":27,\"frameHeight\":16,\"width\":13,\"frameX\":-1,\"height\":16,\"name\":\"yinmoqe00\",\"frameY\":0,\"x\":1},{\"width\":17,\"y\":1,\"height\":21,\"name\":\"body\",\"x\":28},{\"width\":5,\"y\":42,\"height\":7,\"name\":\"rightShoulder\",\"x\":36},{\"width\":10,\"y\":45,\"height\":9,\"name\":\"rightFrontArm\",\"x\":1},{\"width\":7,\"y\":56,\"height\":7,\"name\":\"rightHand\",\"x\":1},{\"width\":6,\"y\":55,\"height\":6,\"name\":\"leftArm\",\"x\":32},{\"width\":7,\"y\":55,\"height\":6,\"name\":\"leftShoulder\",\"x\":23},{\"width\":10,\"y\":27,\"height\":11,\"name\":\"leftFrontArm\",\"x\":16},{\"width\":17,\"y\":24,\"height\":16,\"name\":\"head\",\"x\":28},{\"width\":25,\"y\":1,\"height\":24,\"name\":\"head2\",\"x\":1},{\"width\":8,\"y\":55,\"height\":7,\"name\":\"leftHand\",\"x\":13},{\"frameWidth\":4,\"y\":51,\"frameHeight\":4,\"width\":2,\"frameX\":-1,\"height\":2,\"name\":\"huomiao01\",\"frameY\":-1,\"x\":29}],\"height\":64,\"name\":\"SoldierFireGhost\"}", "atlasJson": "{\"imagePath\":\"SoldierFireGhost_tex.png\",\"width\":64,\"height\":64,\"name\":\"SoldierFireGhost\",\"SubTexture\":[{\"frameY\":0,\"y\":42,\"frameWidth\":12,\"frameHeight\":11,\"width\":11,\"height\":11,\"name\":\"biu\",\"frameX\":-1,\"x\":16},{\"width\":5,\"y\":42,\"height\":7,\"name\":\"rightArm\",\"x\":29},{\"frameY\":0,\"y\":27,\"frameWidth\":15,\"frameHeight\":16,\"width\":13,\"height\":16,\"name\":\"yinmoqe00\",\"frameX\":-1,\"x\":1},{\"width\":17,\"y\":1,\"height\":21,\"name\":\"body\",\"x\":28},{\"width\":5,\"y\":42,\"height\":7,\"name\":\"rightShoulder\",\"x\":36},{\"width\":10,\"y\":45,\"height\":9,\"name\":\"rightFrontArm\",\"x\":1},{\"width\":7,\"y\":56,\"height\":7,\"name\":\"rightHand\",\"x\":1},{\"width\":6,\"y\":55,\"height\":6,\"name\":\"leftArm\",\"x\":32},{\"width\":7,\"y\":55,\"height\":6,\"name\":\"leftShoulder\",\"x\":23},{\"width\":10,\"y\":27,\"height\":11,\"name\":\"leftFrontArm\",\"x\":16},{\"width\":25,\"y\":1,\"height\":24,\"name\":\"head2\",\"x\":1},{\"width\":17,\"y\":24,\"height\":16,\"name\":\"head\",\"x\":28},{\"width\":8,\"y\":55,\"height\":7,\"name\":\"leftHand\",\"x\":13},{\"frameY\":-1,\"y\":51,\"frameWidth\":4,\"frameHeight\":4,\"width\":2,\"height\":2,\"name\":\"huomiao01\",\"frameX\":-1,\"x\":29}]}",
"texture": "700d963b-2192-4219-a066-8be5b3db7453", "texture": "700d963b-2192-4219-a066-8be5b3db7453",
"subMetas": {} "subMetas": {}
} }

View File

@ -0,0 +1,205 @@
{
"__type__": "cc.AnimationClip",
"_name": "InAirIdle1",
"_objFlags": 0,
"_native": "",
"_duration": 0.5166666666666667,
"sample": 60,
"speed": 1,
"wrapMode": 2,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "dd02916e-9ac8-4fe7-a944-d6082eb9007a"
}
},
{
"frame": 0.016666666666666666,
"value": {
"__uuid__": "1906b14b-f3a2-4dc9-9e0d-99e3b334e67b"
}
},
{
"frame": 0.03333333333333333,
"value": {
"__uuid__": "1b4f284c-be67-403b-9f5d-59aa641d3c92"
}
},
{
"frame": 0.05,
"value": {
"__uuid__": "6feb197e-2013-48fd-bbbb-3d2809cb1d63"
}
},
{
"frame": 0.06666666666666667,
"value": {
"__uuid__": "2db4e807-a5d2-4c09-b033-a0ae97e5d0bf"
}
},
{
"frame": 0.08333333333333333,
"value": {
"__uuid__": "73958a6b-31a7-4bb8-babd-1aefba55a793"
}
},
{
"frame": 0.1,
"value": {
"__uuid__": "9545ca77-8002-4ad1-a91e-1e343cdf0e0b"
}
},
{
"frame": 0.11666666666666667,
"value": {
"__uuid__": "581e4c15-9de3-4b72-a91a-2e82ac6b092c"
}
},
{
"frame": 0.13333333333333333,
"value": {
"__uuid__": "4af94082-f36b-4b9e-9077-bd458fd0b188"
}
},
{
"frame": 0.15,
"value": {
"__uuid__": "5463290d-7b25-4625-8be5-1a16dbe3bd83"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "e507775a-1009-47a8-b1a8-8ade0104e4c2"
}
},
{
"frame": 0.18333333333333332,
"value": {
"__uuid__": "6583a9e1-92fb-4db2-9437-9d2b26bc5920"
}
},
{
"frame": 0.2,
"value": {
"__uuid__": "b07e2da2-d1f2-4ec8-acdf-92706d0be9e0"
}
},
{
"frame": 0.21666666666666667,
"value": {
"__uuid__": "dd60bf4f-6b5f-4385-9e46-1e49e6a7cfbe"
}
},
{
"frame": 0.23333333333333334,
"value": {
"__uuid__": "46811e43-c874-41d5-8799-6fafc904cd5a"
}
},
{
"frame": 0.25,
"value": {
"__uuid__": "0a370e7e-e25f-4faf-9a76-73b246338a4d"
}
},
{
"frame": 0.26666666666666666,
"value": {
"__uuid__": "1f003135-e929-4a05-9029-76e7cb8c76ef"
}
},
{
"frame": 0.2833333333333333,
"value": {
"__uuid__": "b78015ba-98e4-4ccd-9852-a7ec053a0ba4"
}
},
{
"frame": 0.3,
"value": {
"__uuid__": "0589abef-dc0d-4d33-a084-25b273ca1368"
}
},
{
"frame": 0.31666666666666665,
"value": {
"__uuid__": "20542755-bbfa-43cd-b593-e73d121b5ed6"
}
},
{
"frame": 0.3333333333333333,
"value": {
"__uuid__": "5d5ac5af-3da7-4b22-9183-5885736e2ca7"
}
},
{
"frame": 0.35,
"value": {
"__uuid__": "47bbe2a2-a983-4aca-8a3e-6cd6df5eee03"
}
},
{
"frame": 0.36666666666666664,
"value": {
"__uuid__": "6d3fb572-d6af-4b7e-9350-9ff482569127"
}
},
{
"frame": 0.38333333333333336,
"value": {
"__uuid__": "573cfba0-4534-4886-849a-61f4f2cbd349"
}
},
{
"frame": 0.4,
"value": {
"__uuid__": "0b4ebd8a-6316-4802-aa24-49ab08e6a75b"
}
},
{
"frame": 0.4166666666666667,
"value": {
"__uuid__": "5b7fff7b-5818-4be8-b65d-c212d15e6e71"
}
},
{
"frame": 0.43333333333333335,
"value": {
"__uuid__": "f964ec77-c016-44fa-8f55-3e59ae30283d"
}
},
{
"frame": 0.45,
"value": {
"__uuid__": "679a79eb-85c6-4445-8517-36465c57c6da"
}
},
{
"frame": 0.4666666666666667,
"value": {
"__uuid__": "8764bb9b-4b08-4bc5-b9e5-93af39321c70"
}
},
{
"frame": 0.48333333333333334,
"value": {
"__uuid__": "f291785e-2685-4ba0-b38b-9607972ce6f0"
}
},
{
"frame": 0.5,
"value": {
"__uuid__": "a4e84eb9-b866-4cdc-8925-a5d29c65aea5"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "43dbf141-be76-48c3-bdef-29233ccbe30d",
"subMetas": {}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 470 KiB

After

Width:  |  Height:  |  Size: 238 KiB

View File

@ -1,18 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<map version="1.2" tiledversion="1.2.3" orientation="orthogonal" renderorder="right-down" width="128" height="128" tilewidth="16" tileheight="16" infinite="0" nextlayerid="3" nextobjectid="39"> <map version="1.2" tiledversion="1.2.3" orientation="orthogonal" renderorder="right-down" width="128" height="128" tilewidth="16" tileheight="16" infinite="0" nextlayerid="3" nextobjectid="87">
<tileset firstgid="1" source="tiles0.tsx"/> <tileset firstgid="1" source="tiles0.tsx"/>
<tileset firstgid="65" source="tiles1.tsx"/> <tileset firstgid="65" source="tiles1.tsx"/>
<tileset firstgid="129" source="tiles2.tsx"/> <tileset firstgid="129" source="tiles2.tsx"/>
<layer id="2" name="Ground" width="128" height="128"> <layer id="2" name="Ground" width="128" height="128">
<data encoding="base64" compression="zlib"> <data encoding="base64" compression="zlib">
eJzt2q1u22AYhuEo1UhBtYFWKh/bmRRMRYNFPZPxwcKi7jznSInkWk7teG5ex88FLhQD570d5/PP1WazuQIAAAAAAAAAAAAAAAAAgAmu96r3g/N5bjn0fw5U3aGy/3bv0P+p8XDEdoX0f9//GP3Xp93/YaD/9QJa6f95/fvov26773671zeb7jmhupX+5+2fQH/9qztU9v8yQXUz/fXXX3/9a/ovgf7666+//vrrf1n9/4zc7tue/uvq/9Z4PfLZ7vnz38ZP/cv7T53p4Z7i7/+kv/7667/bl/sO/dff/77T3+9ff/3r+38dMGf/Nv1r+w917+v/0vh1okN/67/l9B/b/nFj/X8pxvYf236r/0UZ6t/X+Efj5kj7bv+uxxPpX9e/r/1NS7f7Z/S/a3zXfzH9Pzrvt/vPTf/l9p+yRjyV/ufvP+YY6M5yzr5D5uy/XUCLJfafa8YfmdJe//X0r6Z/9jGgv/7661/doqp/9fyrpV//Vc+/mv7Z9M+mfzb9s+mfTf9s+mfTP5v+2fTPpn82/bPpn03/bPpn0z+b/tn0z6Z/Nv2z6Z/N+9/Z9M+mfzb//9n0z6Z/Nv2z6Z9N/2z6Z9M/m/7Z0vtT3wEAAAAAAAAAAAAAAAA4v39IY4NC eJzt3DFq3EAUgOFlTRoXJiEkkNQpAr5EyuDKVdKl8k1yh9wg94wMK1DEaqXVjvRGel/xgXFh7PnfSBqx+O5wONwBAAAAAAAAAAAAAAAAAMAM9yfRvwfreelo+78kFN0hsv/xpO3/q/E04LhD+v/ff4j++9Pt/zTS/76CVvov1/8c/fft9W//cHJubfrXhOhW+q/bPwP99Y/uENn//QzRzfSP7b+nGdBf/+gOW+xfE/1z9rf/c/cvMQP6b5f9r7/+efuXmAH9y69t+07x942W3vv666//Nvp/OtE/T/9u7+7X+uuvf1z/tyNK9u/SP7b/WPdz/f80flyp7e/5r57+U9s/Hzz/b8XU/lPbH/XflLH+5xo/Nh4G2vf79z1fSf+4/ufaP3T0uy/R/2Pji/7V9L903e/2L03/evvPeUa8lv7r958yA/21LNl3TMn+xwpa1Ni/1BpfMqe9/vvpf82MLPGz9d/GDCxF/9wzoL/+mftHr3+07Oe/6PWPpn9u+uemf27656Z/bvrnpn9u+uemf27656Z/bvrnpn9u+uemf276z7eHzwnpX0//dyPeDNB/H/3t/7r77/Fzovrnpv+6hu7tkf2PFbSouf/Pk/71vv1+9B7Wf9n+l+75+m/XlP5DZ65XY+e1yOv6Nf2jO9Tav+389Qa3ns/1j93/tyrxjkb/5fqXfp821L/WGcjef63936rtPZH+6/ZvZyDq/73oP96/5PP8pfPDEP3r2f+3zsGc/mveJ/SfZu4cfJ7Z/2/ju/7V9J+r6f9tbv/WknOQvT/xHQAAAAAAAAAAAAAAAID1/QPUuKyX
</data> </data>
</layer> </layer>
<objectgroup id="1" name="PlayerStartingPos"> <objectgroup id="1" name="PlayerStartingPos">
<object id="135" x="1090" y="833.667"> <object id="135" x="1010" y="1432">
<point/> <point/>
</object> </object>
<object id="137" x="1215" y="830.5"> <object id="137" x="950" y="1432">
<point/> <point/>
</object> </object>
</objectgroup> </objectgroup>
@ -20,149 +20,112 @@
<properties> <properties>
<property name="type" value="barrier_and_shelter"/> <property name="type" value="barrier_and_shelter"/>
</properties> </properties>
<object id="8" x="648.242" y="480.606"> <object id="54" x="656" y="1504" width="80" height="16">
<properties> <properties>
<property name="boundary_type" value="barrier"/> <property name="boundary_type" value="barrier"/>
</properties> </properties>
<polyline points="0,0 0,18.6667 1041.33,21.3333 1041.33,-1.33333"/>
</object> </object>
<object id="9" x="650.667" y="1604.67"> <object id="55" x="735" y="1552" width="113.5" height="15.5">
<properties> <properties>
<property name="boundary_type" value="barrier"/> <property name="boundary_type" value="barrier"/>
</properties> </properties>
<polyline points="0,0 0,18.6667 1041.33,21.3333 1041.33,-1.33333"/>
</object> </object>
<object id="10" x="634.485" y="505.455"> <object id="57" x="768" y="1472" width="32" height="16">
<properties> <properties>
<property name="boundary_type" value="barrier"/> <property name="boundary_type" value="barrier"/>
</properties> </properties>
<polyline points="0,0 4,1110 24,1110 24,-8"/>
</object> </object>
<object id="11" x="1677.64" y="501.333"> <object id="58" x="1040" y="1536" width="80" height="16">
<properties> <properties>
<property name="boundary_type" value="barrier"/> <property name="boundary_type" value="barrier"/>
</properties> </properties>
<polyline points="0,0 4,1110 24,1110 24,-8"/>
</object> </object>
<object id="14" x="688.667" y="464"> <object id="59" x="1040" y="1568" width="224" height="48">
<properties> <properties>
<property name="boundary_type" value="barrier"/> <property name="boundary_type" value="barrier"/>
</properties> </properties>
<polyline points="0,0 -0.666667,78 33.3333,78 32,-0.666667"/>
</object> </object>
<object id="15" x="833.333" y="495.333"> <object id="60" x="1216" y="1344" width="224" height="16">
<properties> <properties>
<property name="boundary_type" value="barrier"/> <property name="boundary_type" value="barrier"/>
</properties> </properties>
<polyline points="0,0 -112,1.33333 -111.333,44.6667 -1.33333,44.6667"/>
</object> </object>
<object id="17" x="832" y="574"> <object id="62" x="1040" y="1552" width="208" height="16">
<properties> <properties>
<property name="boundary_type" value="barrier"/> <property name="boundary_type" value="barrier"/>
</properties> </properties>
<polyline points="0,0 -67.3333,0 -67.3333,-76.6667 0.666667,-76"/>
</object> </object>
<object id="18" x="865.333" y="606.667"> <object id="63" x="1040" y="1504" width="48" height="16">
<properties> <properties>
<property name="boundary_type" value="barrier"/> <property name="boundary_type" value="barrier"/>
</properties> </properties>
<polyline points="0,0 -210,-0.666667 -210,143.333 0,142.667"/>
</object> </object>
<object id="19" x="754.667" y="1055.33"> <object id="64" x="1040" y="1520" width="64" height="16">
<properties> <properties>
<property name="boundary_type" value="barrier"/> <property name="boundary_type" value="barrier"/>
</properties> </properties>
<polyline points="-2,1.33333 -100,0.666667 -97.3333,-454 -9.33333,-451.333"/>
</object> </object>
<object id="20" x="769.333" y="747.333"> <object id="65" x="1040" y="1488" width="32" height="16">
<properties> <properties>
<property name="boundary_type" value="barrier"/> <property name="boundary_type" value="barrier"/>
</properties> </properties>
<polyline points="0,0 -115.333,0.666667 -114.667,160.667 -2,162"/>
</object> </object>
<object id="21" x="768" y="960.667"> <object id="66" x="1040" y="1472" width="16" height="16">
<properties> <properties>
<property name="boundary_type" value="barrier"/> <property name="boundary_type" value="barrier"/>
</properties> </properties>
<polyline points="0,0 -18,0.666667 -18.6667,95.3333 0,95.3333"/>
</object> </object>
<object id="23" x="786" y="1058.67"> <object id="67" x="784" y="1456" width="256" height="16">
<properties> <properties>
<property name="boundary_type" value="barrier"/> <property name="boundary_type" value="barrier"/>
</properties> </properties>
<polyline points="0,0 0,-52.6667 -20,-52 -19.3333,-1.33333"/>
</object> </object>
<object id="24" x="1118.67" y="749.333"> <object id="73" x="783.75" y="1567.5" width="96.25" height="15.5">
<properties> <properties>
<property name="boundary_type" value="barrier"/> <property name="boundary_type" value="barrier"/>
</properties> </properties>
<polyline points="0,0 -0.666667,-94 -254,-93.3333 -256.667,1.33333"/>
</object> </object>
<object id="25" x="1168" y="975.333"> <object id="74" x="815.75" y="1583.75" width="96.25" height="15.5">
<properties> <properties>
<property name="boundary_type" value="barrier"/> <property name="boundary_type" value="barrier"/>
</properties> </properties>
<polyline points="0,0 0,16.6667 224.667,17.3333 225.333,-2"/>
</object> </object>
<object id="28" x="1394.67" y="958"> <object id="79" x="640" y="1616" width="1056" height="16">
<properties> <properties>
<property name="boundary_type" value="barrier"/> <property name="boundary_type" value="barrier"/>
</properties> </properties>
<polyline points="0,0 -210.667,1.33333 -210.667,17.3333 -2,18"/>
</object> </object>
<object id="29" x="1119" y="654.5"> <object id="80" x="1296" y="1600">
<properties> <properties>
<property name="boundary_type" value="barrier"/> <property name="boundary_type" value="barrier"/>
</properties> </properties>
<polyline points="0,0 0,63.5 272.5,65 273,-1"/> <polyline points="0,0 -32.5,0 -32.25,-16.5 -16.5,-16.5"/>
</object> </object>
<object id="30" x="1136.5" y="717.5"> <object id="82" x="1328" y="1616">
<properties> <properties>
<property name="boundary_type" value="barrier"/> <property name="boundary_type" value="barrier"/>
</properties> </properties>
<polyline points="0,0 -0.5,17 255,17.5 255,1.5"/> <polyline points="0,0 -64.5,0 -64.0038,-15.75 -16.4734,-15.75"/>
</object> </object>
<object id="31" x="1152" y="735.667"> <object id="83" x="640" y="480" width="1056" height="16">
<properties> <properties>
<property name="boundary_type" value="barrier"/> <property name="boundary_type" value="barrier"/>
</properties> </properties>
<polyline points="0,0 0,15 80.3333,15.3333 80.3333,-2"/>
</object> </object>
<object id="32" x="1280.67" y="734.667"> <object id="84" x="640" y="480" width="16" height="1152">
<properties> <properties>
<property name="boundary_type" value="barrier"/> <property name="boundary_type" value="barrier"/>
</properties> </properties>
<polyline points="0,0 -0.666667,64.3333 48,65 48,0"/>
</object> </object>
<object id="34" x="1329" y="783"> <object id="85" x="1680" y="480" width="16" height="1152">
<properties> <properties>
<property name="boundary_type" value="barrier"/> <property name="boundary_type" value="barrier"/>
</properties> </properties>
<polyline points="0,0 63.3333,0.333333 63,-48.3333 -0.666667,-48.3333"/>
</object> </object>
<object id="35" x="1296.67" y="799"> <object id="86" x="1104" y="1408" width="96" height="16">
<properties> <properties>
<property name="boundary_type" value="barrier"/> <property name="boundary_type" value="barrier"/>
</properties> </properties>
<polyline points="0,0 -0.666667,31.3333 31.3333,31.6667 31.3333,-0.333333"/>
</object>
<object id="36" x="1280" y="848">
<properties>
<property name="boundary_type" value="barrier"/>
</properties>
<polyline points="0,0 0.333333,62 112,63 111.667,-1.33333"/>
</object>
<object id="37" x="1392.33" y="911.333">
<properties>
<property name="boundary_type" value="barrier"/>
</properties>
<polyline points="0,0 -81.3333,-0.666667 -81,45 -0.666667,46.3333"/>
</object>
<object id="38" x="1344.33" y="800.667">
<properties>
<property name="boundary_type" value="barrier"/>
</properties>
<polyline points="0,0 0,46.3333 47,46.3333 47,-1"/>
</object> </object>
</objectgroup> </objectgroup>
</map> </map>

View File

@ -9,28 +9,32 @@ message PlayerDownsync {
int32 virtualGridX = 2; int32 virtualGridX = 2;
int32 virtualGridY = 3; int32 virtualGridY = 3;
int32 dirX = 4; int32 dirX = 4;
int32 dirY = 5; int32 dirY = 5; // "dirX" and "dirY" determines character facing
int32 speed = 6; // in terms of virtual grid units int32 velX = 6;
int32 battleState = 7; int32 velY = 7; // "velX" and "velY" is used to record the accumulated effect by accelerations (including gravity)
int32 joinIndex = 8; int32 speed = 8; // this is the instantaneous scalar attribute of a character, different from but will be accounted in "velX" and "velY"
double colliderRadius = 9; int32 battleState = 9;
bool removed = 10; int32 joinIndex = 10;
int32 score = 11; double colliderRadius = 11;
int32 lastMoveGmtMillis = 12; bool removed = 12;
int32 framesToRecover = 13; int32 score = 13;
int32 hp = 14; int32 lastMoveGmtMillis = 14;
int32 maxHp = 15; int32 framesToRecover = 15;
int32 characterState = 16; int32 hp = 16;
int32 maxHp = 17;
int32 characterState = 18;
bool inAir = 19; // by design a standalone field only inferred by the collision result of "applyInputFrameDownsyncDynamicsOnSingleRenderFrame" instead of "characterState", because we need check the transition for "characterState" from this field, i.e. "inAir (prev -> curr)"
string name = 17; string name = 20;
string displayName = 18; string displayName = 21;
string avatar = 19; string avatar = 22;
} }
message InputFrameDecoded { message InputFrameDecoded {
int32 dx = 1; int32 dx = 1;
int32 dy = 2; int32 dy = 2;
int32 btnALevel = 3; int32 btnALevel = 3;
int32 btnBLevel = 4;
} }
message InputFrameUpsync { message InputFrameUpsync {
@ -133,6 +137,12 @@ message BattleColliderInfo {
int32 renderCacheSize = 26; int32 renderCacheSize = 26;
map<int32, MeleeBullet> meleeSkillConfig = 27; // skillId -> skill map<int32, MeleeBullet> meleeSkillConfig = 27; // skillId -> skill
double snapIntoPlatformOverlap = 28;
double snapIntoPlatformThreshold = 29;
int32 jumpingInitVelY = 30;
int32 gravityX = 31;
int32 gravityY = 32;
} }
message RoomDownsyncFrame { message RoomDownsyncFrame {

View File

@ -33,14 +33,14 @@
"_active": true, "_active": true,
"_components": [ "_components": [
{ {
"__id__": 20 "__id__": 23
}, },
{ {
"__id__": 21 "__id__": 24
} }
], ],
"_prefab": { "_prefab": {
"__id__": 22 "__id__": 25
}, },
"_opacity": 255, "_opacity": 255,
"_color": { "_color": {
@ -65,7 +65,7 @@
"ctor": "Float64Array", "ctor": "Float64Array",
"array": [ "array": [
0, 0,
3, 0,
0, 0,
0, 0,
0, 0,
@ -97,7 +97,7 @@
"__id__": 1 "__id__": 1
}, },
"_children": [], "_children": [],
"_active": true, "_active": false,
"_components": [ "_components": [
{ {
"__id__": 3 "__id__": 3
@ -116,8 +116,8 @@
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 46.68, "width": 28.01,
"height": 27.72 "height": 15.12
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@ -128,8 +128,8 @@
"__type__": "TypedArray", "__type__": "TypedArray",
"ctor": "Float64Array", "ctor": "Float64Array",
"array": [ "array": [
-5, 0,
50, 0,
0, 0,
0, 0,
0, 0,
@ -169,8 +169,8 @@
"_useOriginalSize": false, "_useOriginalSize": false,
"_string": "(0, 0)", "_string": "(0, 0)",
"_N$string": "(0, 0)", "_N$string": "(0, 0)",
"_fontSize": 20, "_fontSize": 12,
"_lineHeight": 22, "_lineHeight": 12,
"_enableWrapText": true, "_enableWrapText": true,
"_N$file": null, "_N$file": null,
"_isSystemFontUsed": true, "_isSystemFontUsed": true,
@ -277,7 +277,7 @@
"__uuid__": "472df5d3-35e7-4184-9e6c-7f41bee65ee3" "__uuid__": "472df5d3-35e7-4184-9e6c-7f41bee65ee3"
}, },
"_texture": null, "_texture": null,
"_stopped": false, "_stopped": true,
"playOnLoad": true, "playOnLoad": true,
"autoRemoveOnFinish": false, "autoRemoveOnFinish": false,
"totalParticles": 200, "totalParticles": 200,
@ -395,8 +395,8 @@
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 76, "width": 24,
"height": 84 "height": 24
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@ -408,7 +408,7 @@
"ctor": "Float64Array", "ctor": "Float64Array",
"array": [ "array": [
3, 3,
182, 60,
0, 0,
0, 0,
0, 0,
@ -451,7 +451,7 @@
"__uuid__": "a2170e4c-df31-41ef-be73-f4f605e75821" "__uuid__": "a2170e4c-df31-41ef-be73-f4f605e75821"
}, },
"_type": 0, "_type": 0,
"_sizeMode": 1, "_sizeMode": 0,
"_fillType": 0, "_fillType": 0,
"_fillCenter": { "_fillCenter": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@ -490,12 +490,15 @@
}, },
{ {
"__id__": 15 "__id__": 15
},
{
"__id__": 18
} }
], ],
"_active": true, "_active": true,
"_components": [], "_components": [],
"_prefab": { "_prefab": {
"__id__": 19 "__id__": 22
}, },
"_opacity": 255, "_opacity": 255,
"_color": { "_color": {
@ -584,7 +587,7 @@
"ctor": "Float64Array", "ctor": "Float64Array",
"array": [ "array": [
0, 0,
0, -24,
0, 0,
0, 0,
0, 0,
@ -671,13 +674,128 @@
"_components": [ "_components": [
{ {
"__id__": 16 "__id__": 16
},
{
"__id__": 17
} }
], ],
"_prefab": { "_prefab": {
"__id__": 18 "__id__": 17
},
"_opacity": 255,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_contentSize": {
"__type__": "cc.Size",
"width": 0,
"height": 0
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_trs": {
"__type__": "TypedArray",
"ctor": "Float64Array",
"array": [
0,
-24,
0,
0,
0,
0,
1,
1,
1,
1
]
},
"_eulerAngles": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_skewX": 0,
"_skewY": 0,
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": ""
},
{
"__type__": "dragonBones.ArmatureDisplay",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 15
},
"_enabled": true,
"_materials": [
{
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
}
],
"_armatureName": "SoldierFireGhost",
"_animationName": "Idle1",
"_preCacheMode": 0,
"_cacheMode": 0,
"playTimes": -1,
"premultipliedAlpha": false,
"_armatureKey": "36230012-8df3-4e85-afad-76ec47d0e4d7#4a9187d5-a9ad-4464-a03c-d2f3cc277051",
"_accTime": 0,
"_playCount": 0,
"_frameCache": null,
"_curFrame": null,
"_playing": false,
"_armatureCache": null,
"_N$dragonAsset": {
"__uuid__": "36230012-8df3-4e85-afad-76ec47d0e4d7"
},
"_N$dragonAtlasAsset": {
"__uuid__": "4a9187d5-a9ad-4464-a03c-d2f3cc277051"
},
"_N$_defaultArmatureIndex": 0,
"_N$_animationIndex": 8,
"_N$_defaultCacheMode": 0,
"_N$timeScale": 1,
"_N$debugBones": false,
"_N$enableBatch": false,
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__uuid__": "59bff7a2-23e1-4d69-bce7-afb37eae196a"
},
"fileId": "3b2LJFABVL7ozO2U81FC4U",
"sync": false
},
{
"__type__": "cc.Node",
"_name": "SoldierFireGhostFrameAnim",
"_objFlags": 0,
"_parent": {
"__id__": 11
},
"_children": [],
"_active": false,
"_components": [
{
"__id__": 19
},
{
"__id__": 20
}
],
"_prefab": {
"__id__": 21
}, },
"_opacity": 255, "_opacity": 255,
"_color": { "_color": {
@ -731,7 +849,7 @@
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 15 "__id__": 18
}, },
"_enabled": true, "_enabled": true,
"_defaultClip": null, "_defaultClip": null,
@ -745,6 +863,15 @@
{ {
"__uuid__": "c738236a-0702-45f8-aa38-99457b051997" "__uuid__": "c738236a-0702-45f8-aa38-99457b051997"
}, },
{
"__uuid__": "c69bcceb-d7d1-4e33-9623-e2a374a0a6b6"
},
{
"__uuid__": "43dbf141-be76-48c3-bdef-29233ccbe30d"
},
{
"__uuid__": "c738236a-0702-45f8-aa38-99457b051997"
},
{ {
"__uuid__": "c69bcceb-d7d1-4e33-9623-e2a374a0a6b6" "__uuid__": "c69bcceb-d7d1-4e33-9623-e2a374a0a6b6"
} }
@ -757,7 +884,7 @@
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 15 "__id__": 18
}, },
"_enabled": true, "_enabled": true,
"_materials": [ "_materials": [

View File

@ -1,151 +0,0 @@
[
{
"__type__": "cc.Prefab",
"_name": "",
"_objFlags": 0,
"_native": "",
"data": {
"__id__": 1
},
"optimizationPolicy": 0,
"asyncLoadAssets": false,
"readonly": false
},
{
"__type__": "cc.Node",
"_name": "SoldierFireGhost",
"_objFlags": 0,
"_parent": null,
"_children": [],
"_active": true,
"_components": [
{
"__id__": 2
},
{
"__id__": 3
}
],
"_prefab": {
"__id__": 4
},
"_opacity": 255,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_contentSize": {
"__type__": "cc.Size",
"width": 1425,
"height": 1024
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_trs": {
"__type__": "TypedArray",
"ctor": "Float64Array",
"array": [
0,
0,
0,
0,
0,
0,
1,
1,
1,
1
]
},
"_eulerAngles": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_skewX": 0,
"_skewY": 0,
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": ""
},
{
"__type__": "cc.Animation",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 1
},
"_enabled": true,
"_defaultClip": null,
"_clips": [
{
"__uuid__": "252b321f-81f4-485c-85bd-ea44d298cb76"
},
null,
null,
null,
{
"__uuid__": "c738236a-0702-45f8-aa38-99457b051997"
},
{
"__uuid__": "f51bb583-0010-48f3-a6a1-451a78ac2d65"
},
{
"__uuid__": "c69bcceb-d7d1-4e33-9623-e2a374a0a6b6"
}
],
"playOnLoad": false,
"_id": ""
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 1
},
"_enabled": true,
"_materials": [
{
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
}
],
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
"_spriteFrame": null,
"_type": 0,
"_sizeMode": 1,
"_fillType": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_atlas": {
"__uuid__": "145769c8-a259-42bc-8cce-6e035f493c70"
},
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "44k8wsxglPsJSrMCX58Yve",
"sync": false
}
]

View File

@ -1,8 +0,0 @@
{
"ver": "1.2.5",
"uuid": "e3fc2487-17d1-4ff9-ae1f-38e974509077",
"optimizationPolicy": "AUTO",
"asyncLoadAssets": false,
"readonly": false,
"subMetas": {}
}

View File

@ -440,7 +440,7 @@
"array": [ "array": [
0, 0,
0, 0,
210.54381524704266, 216.19964242526865,
0, 0,
0, 0,
0, 0,

View File

@ -172,8 +172,8 @@
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 3200, "width": 2048,
"height": 3200 "height": 2048
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@ -191,8 +191,8 @@
0, 0,
0, 0,
1, 1,
2, 1.5,
2, 1.5,
1 1
] ]
}, },
@ -454,7 +454,7 @@
"array": [ "array": [
0, 0,
0, 0,
216.50635094610968, 216.67520680312998,
0, 0,
0, 0,
0, 0,

View File

@ -5,8 +5,33 @@ window.ATK_CHARACTER_STATE = {
Walking: [1, "Walking"], Walking: [1, "Walking"],
Atk1: [2, "Atk1"], Atk1: [2, "Atk1"],
Atked1: [3, "Atked1"], Atked1: [3, "Atked1"],
InAirIdle1: [4, "InAirIdle1"],
InAirAtk1: [5, "Atk1"],
InAirAtked1: [6, "Atked1"],
}; };
window.toInAirConjugate = function(foo) {
switch (foo) {
case window.ATK_CHARACTER_STATE.Idle1[0]:
case window.ATK_CHARACTER_STATE.Walking[0]:
return window.ATK_CHARACTER_STATE.InAirIdle1[0];
case window.ATK_CHARACTER_STATE.Atk1[0]:
return window.ATK_CHARACTER_STATE.InAirAtk1[0];
case window.ATK_CHARACTER_STATE.Atked1[0]:
return window.ATK_CHARACTER_STATE.InAirAtked1[0];
case window.ATK_CHARACTER_STATE.InAirIdle1[0]:
return window.ATK_CHARACTER_STATE.Idle1[0];
case window.ATK_CHARACTER_STATE.InAirAtk1[0]:
return window.ATK_CHARACTER_STATE.Atk1[0];
case window.ATK_CHARACTER_STATE.InAirAtked1[0]:
return window.ATK_CHARACTER_STATE.Atked1[0];
default:
console.warn(`Invalid characterState ${foo} received, no in air conjugate is available!`);
return null;
}
}
window.ATK_CHARACTER_STATE_ARR = []; window.ATK_CHARACTER_STATE_ARR = [];
for (let k in window.ATK_CHARACTER_STATE) { for (let k in window.ATK_CHARACTER_STATE) {
window.ATK_CHARACTER_STATE_ARR.push(window.ATK_CHARACTER_STATE[k]); window.ATK_CHARACTER_STATE_ARR.push(window.ATK_CHARACTER_STATE[k]);
@ -15,6 +40,12 @@ for (let k in window.ATK_CHARACTER_STATE) {
window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET = new Set(); window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET = new Set();
window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.add(window.ATK_CHARACTER_STATE.Idle1[0]); window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.add(window.ATK_CHARACTER_STATE.Idle1[0]);
window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.add(window.ATK_CHARACTER_STATE.Walking[0]); window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.add(window.ATK_CHARACTER_STATE.Walking[0]);
window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.add(window.ATK_CHARACTER_STATE.InAirIdle1[0]);
window.ATK_CHARACTER_STATE_IN_AIR_SET = new Set();
window.ATK_CHARACTER_STATE_IN_AIR_SET.add(window.ATK_CHARACTER_STATE.InAirIdle1[0]);
window.ATK_CHARACTER_STATE_IN_AIR_SET.add(window.ATK_CHARACTER_STATE.InAirAtk1[0]);
window.ATK_CHARACTER_STATE_IN_AIR_SET.add(window.ATK_CHARACTER_STATE.InAirAtked1[0]);
/* /*
Kindly note that the use of dragonBones anim is an informed choice for the feasibility of "gotoAndPlayByFrame", which is a required feature by "Map.rollbackAndChase". You might find that "cc.Animation" -- the traditional frame anim -- can also suffice this requirement, yet if we want to develop 3D frontend in the future, working with skeletal anim will make a smoother transition. Kindly note that the use of dragonBones anim is an informed choice for the feasibility of "gotoAndPlayByFrame", which is a required feature by "Map.rollbackAndChase". You might find that "cc.Animation" -- the traditional frame anim -- can also suffice this requirement, yet if we want to develop 3D frontend in the future, working with skeletal anim will make a smoother transition.
@ -37,6 +68,7 @@ cc.Class({
this.hp = 100; this.hp = 100;
this.maxHp = 100; this.maxHp = 100;
this.framesToRecover = 0; this.framesToRecover = 0;
this.inAir = true;
}, },
setSpecies(speciesName) { setSpecies(speciesName) {
@ -49,7 +81,6 @@ cc.Class({
this.animComp = this.effAnimNode.getComponent(dragonBones.ArmatureDisplay); this.animComp = this.effAnimNode.getComponent(dragonBones.ArmatureDisplay);
if (!this.animComp) { if (!this.animComp) {
this.animComp = this.effAnimNode.getComponent(cc.Animation); this.animComp = this.effAnimNode.getComponent(cc.Animation);
this.effAnimNode.anchorY = 0.0; // Otherwise the frame anim will show with an incorrect y-offset even if the collider boundaries are all correct!
} }
this.effAnimNode.active = true; this.effAnimNode.active = true;
}, },
@ -96,7 +127,7 @@ cc.Class({
_interruptPlayingAnimAndPlayNewAnimDragonBones(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl, playingAnimName) { _interruptPlayingAnimAndPlayNewAnimDragonBones(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl, playingAnimName) {
if (window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) { if (window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) {
// No "framesToRecover" // No "framesToRecover"
//console.warn(`#DragonBones JoinIndex=${rdfPlayer.joinIndex}, ${playingAnimName} -> ${newAnimName}`); // console.warn(`#DragonBones JoinIndex=${rdfPlayer.joinIndex}, ${playingAnimName} -> ${newAnimName}`);
underlyingAnimationCtrl.gotoAndPlayByFrame(newAnimName, 0, -1); underlyingAnimationCtrl.gotoAndPlayByFrame(newAnimName, 0, -1);
} else { } else {
const animationData = underlyingAnimationCtrl._animations[newAnimName]; const animationData = underlyingAnimationCtrl._animations[newAnimName];
@ -112,7 +143,7 @@ cc.Class({
_interruptPlayingAnimAndPlayNewAnimFrameAnim(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, playingAnimName) { _interruptPlayingAnimAndPlayNewAnimFrameAnim(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, playingAnimName) {
if (window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) { if (window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) {
// No "framesToRecover" // No "framesToRecover"
//console.warn(`#DragonBones JoinIndex=${rdfPlayer.joinIndex}, ${playingAnimName} -> ${newAnimName}`); //console.warn(`#FrameAnim JoinIndex=${rdfPlayer.joinIndex}, ${playingAnimName} -> ${newAnimName}`);
this.animComp.play(newAnimName, 0); this.animComp.play(newAnimName, 0);
return; return;
} }

View File

@ -402,6 +402,7 @@ cc.Class({
console.log(`Received parsedBattleColliderInfo via ws`); console.log(`Received parsedBattleColliderInfo via ws`);
// TODO: Upon reconnection, the backend might have already been sending down data that'd trigger "onRoomDownsyncFrame & onInputFrameDownsyncBatch", but frontend could reject those data due to "battleState != PlayerBattleState.ACTIVE". // TODO: Upon reconnection, the backend might have already been sending down data that'd trigger "onRoomDownsyncFrame & onInputFrameDownsyncBatch", but frontend could reject those data due to "battleState != PlayerBattleState.ACTIVE".
Object.assign(self, parsedBattleColliderInfo); Object.assign(self, parsedBattleColliderInfo);
self.gravityX = parsedBattleColliderInfo.gravityX; // to avoid integer default value 0 accidentally becoming null in "Object.assign(...)"
self.tooFastDtIntervalMillis = 0.5 * self.rollbackEstimatedDtMillis; self.tooFastDtIntervalMillis = 0.5 * self.rollbackEstimatedDtMillis;
const tiledMapIns = self.node.getComponent(cc.TiledMap); const tiledMapIns = self.node.getComponent(cc.TiledMap);
@ -426,6 +427,16 @@ cc.Class({
mapNode.removeAllChildren(); mapNode.removeAllChildren();
self._resetCurrentMatch(); self._resetCurrentMatch();
if (self.showCriticalCoordinateLabels) {
const drawer = new cc.Node();
drawer.setPosition(cc.v2(0, 0))
safelyAddChild(self.node, drawer);
setLocalZOrder(drawer, 999);
const g = drawer.addComponent(cc.Graphics);
g.lineWidth = 2;
self.g = g;
}
tiledMapIns.tmxAsset = tmxAsset; tiledMapIns.tmxAsset = tmxAsset;
const newMapSize = tiledMapIns.getMapSize(); const newMapSize = tiledMapIns.getMapSize();
const newTileSize = tiledMapIns.getTileSize(); const newTileSize = tiledMapIns.getTileSize();
@ -442,14 +453,19 @@ cc.Class({
} }
let barrierIdCounter = 0; let barrierIdCounter = 0;
const boundaryObjs = tileCollisionManager.extractBoundaryObjects(self.node); const refBoundaryObjs = tileCollisionManager.extractBoundaryObjects(self.node).barriers;
for (let boundaryObj of boundaryObjs.barriers) { const boundaryObjs = parsedBattleColliderInfo.strToPolygon2DListMap;
const x0 = boundaryObj.anchor.x, for (let k = 0; k < boundaryObjs["Barrier"].eles.length; k++) {
y0 = boundaryObj.anchor.y; let boundaryObj = boundaryObjs["Barrier"].eles[k];
const refBoundaryObj = refBoundaryObjs[k];
const newBarrierCollider = self.collisionSys.createPolygon(x0, y0, Array.from(boundaryObj, p => { // boundaryObj = refBoundaryObj;
const [x0, y0] = [boundaryObj.anchor.x, boundaryObj.anchor.y];
const newBarrierCollider = self.collisionSys.createPolygon(x0, y0, Array.from(boundaryObj.points, p => {
return [p.x, p.y]; return [p.x, p.y];
})); }));
newBarrierCollider.data = {
hardPushback: true
};
if (self.showCriticalCoordinateLabels) { if (self.showCriticalCoordinateLabels) {
for (let i = 0; i < boundaryObj.length; ++i) { for (let i = 0; i < boundaryObj.length; ++i) {
@ -762,18 +778,19 @@ cc.Class({
if (1 == joinIndex) { if (1 == joinIndex) {
playerScriptIns.setSpecies("SoldierWaterGhost"); playerScriptIns.setSpecies("SoldierWaterGhost");
} else if (2 == joinIndex) { } else if (2 == joinIndex) {
playerScriptIns.setSpecies("SoldierFireGhost"); playerScriptIns.setSpecies("SoldierFireGhostFrameAnim");
} }
const [wx, wy] = self.virtualGridToWorldPos(vx, vy); const [wx, wy] = self.virtualGridToWorldPos(vx, vy);
newPlayerNode.setPosition(wx, wy); newPlayerNode.setPosition(wx, wy);
playerScriptIns.mapNode = self.node; playerScriptIns.mapNode = self.node;
const colliderWidth = playerDownsyncInfo.colliderRadius * 2, const colliderWidth = playerDownsyncInfo.colliderRadius * 2,
colliderHeight = playerDownsyncInfo.colliderRadius * 3; colliderHeight = playerDownsyncInfo.colliderRadius * 4;
const [x0, y0] = self.virtualGridToPolygonColliderAnchorPos(vx, vy, colliderWidth, colliderHeight), const cpos = self.virtualGridToPolygonColliderTLPos(vx, vy, colliderWidth * 0.5, colliderHeight * 0.5); // the top-left corner is kept having integer coords
pts = [[0, 0], [colliderWidth, 0], [colliderWidth, colliderHeight], [0, colliderHeight]]; const pts = [[0, 0], [colliderWidth, 0], [colliderWidth, -colliderHeight - self.snapIntoPlatformOverlap], [0, -colliderHeight - self.snapIntoPlatformOverlap]];
const newPlayerCollider = self.collisionSys.createPolygon(x0, y0, pts); // [WARNING] The animNode "anchor & offset" are tuned to fit in this collider by "ControlledCharacter prefab & AttackingCharacter.js"!
const newPlayerCollider = self.collisionSys.createPolygon(cpos[0], cpos[1], pts);
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
newPlayerCollider.data = playerDownsyncInfo; newPlayerCollider.data = playerDownsyncInfo;
self.collisionSysMap.set(collisionPlayerIndex, newPlayerCollider); self.collisionSysMap.set(collisionPlayerIndex, newPlayerCollider);
@ -833,6 +850,7 @@ cc.Class({
*/ */
// [WARNING] Don't try to get "prevRdf(i.e. renderFrameId == latest-1)" by "self.recentRenderCache.getByFrameId(...)" here, as the cache might have been updated by asynchronous "onRoomDownsyncFrame(...)" calls! // [WARNING] Don't try to get "prevRdf(i.e. renderFrameId == latest-1)" by "self.recentRenderCache.getByFrameId(...)" here, as the cache might have been updated by asynchronous "onRoomDownsyncFrame(...)" calls!
self.applyRoomDownsyncFrameDynamics(rdf, prevRdf); self.applyRoomDownsyncFrameDynamics(rdf, prevRdf);
self.showDebugBoundaries(rdf);
++self.renderFrameId; // [WARNING] It's important to increment the renderFrameId AFTER all the operations above!!! ++self.renderFrameId; // [WARNING] It's important to increment the renderFrameId AFTER all the operations above!!!
self.lastRenderFrameIdTriggeredAt = performance.now(); self.lastRenderFrameIdTriggeredAt = performance.now();
let t3 = performance.now(); let t3 = performance.now();
@ -961,13 +979,13 @@ cc.Class({
applyRoomDownsyncFrameDynamics(rdf, prevRdf) { applyRoomDownsyncFrameDynamics(rdf, prevRdf) {
const self = this; const self = this;
for (let [playerId, playerRichInfo] of self.playerRichInfoDict.entries()) { for (let [playerId, playerRichInfo] of self.playerRichInfoDict.entries()) {
const immediatePlayerInfo = rdf.players[playerId]; const currPlayerDownsync = rdf.players[playerId];
const prevRdfPlayer = (null == prevRdf ? null : prevRdf.players[playerId]); const prevRdfPlayer = (null == prevRdf ? null : prevRdf.players[playerId]);
const [wx, wy] = self.virtualGridToWorldPos(immediatePlayerInfo.virtualGridX, immediatePlayerInfo.virtualGridY); const [wx, wy] = self.virtualGridToWorldPos(currPlayerDownsync.virtualGridX, currPlayerDownsync.virtualGridY);
//const justJiggling = (self.jigglingEps1D >= Math.abs(wx - playerRichInfo.node.x) && self.jigglingEps1D >= Math.abs(wy - playerRichInfo.node.y)); //const justJiggling = (self.jigglingEps1D >= Math.abs(wx - playerRichInfo.node.x) && self.jigglingEps1D >= Math.abs(wy - playerRichInfo.node.y));
playerRichInfo.node.setPosition(wx, wy); playerRichInfo.node.setPosition(wx, wy);
playerRichInfo.scriptIns.updateSpeed(immediatePlayerInfo.speed); playerRichInfo.scriptIns.updateSpeed(currPlayerDownsync.speed);
playerRichInfo.scriptIns.updateCharacterAnim(immediatePlayerInfo, prevRdfPlayer, false); playerRichInfo.scriptIns.updateCharacterAnim(currPlayerDownsync, prevRdfPlayer, false);
} }
// Update countdown // Update countdown
@ -977,6 +995,73 @@ cc.Class({
} }
}, },
showDebugBoundaries(rdf) {
const self = this;
if (self.showCriticalCoordinateLabels) {
let g = self.g;
g.clear();
for (let k in self.collisionSys._bvh._bodies) {
const body = self.collisionSys._bvh._bodies[k];
if (!body._polygon) continue;
if (null != body.data && null != body.data.joinIndex) {
// character
if (1 == body.data.joinIndex) {
g.strokeColor = cc.Color.BLUE;
} else {
g.strokeColor = cc.Color.RED;
}
} else {
// barrier
g.strokeColor = cc.Color.WHITE;
}
g.moveTo(body.x, body.y);
const cnt = body._coords.length;
for (let j = 0; j < cnt; j += 2) {
const x = body._coords[j],
y = body._coords[j + 1];
g.lineTo(x, y);
}
g.lineTo(body.x, body.y);
g.stroke();
}
// For convenience of recovery upon reconnection, active bullets are always created & immediately removed from "collisionSys" within "applyInputFrameDownsyncDynamicsOnSingleRenderFrame"
for (let k in rdf.meleeBullets) {
const meleeBullet = rdf.meleeBullets[k];
if (
meleeBullet.originatedRenderFrameId + meleeBullet.startupFrames <= rdf.id
&&
meleeBullet.originatedRenderFrameId + meleeBullet.startupFrames + meleeBullet.activeFrames > rdf.id
) {
const offender = rdf.players[meleeBullet.offenderPlayerId];
if (1 == offender.joinIndex) {
g.strokeColor = cc.Color.BLUE;
} else {
g.strokeColor = cc.Color.RED;
}
let xfac = 1; // By now, straight Punch offset doesn't respect "y-axis"
if (0 > offender.dirX) {
xfac = -1;
}
const [offenderWx, offenderWy] = self.virtualGridToWorldPos(offender.virtualGridX, offender.virtualGridY);
const bulletWx = offenderWx + xfac * meleeBullet.hitboxOffset;
const bulletWy = offenderWy;
const bulletCpos = self.worldToPolygonColliderTLPos(bulletWx, bulletWy, meleeBullet.hitboxSize.x * 0.5, meleeBullet.hitboxSize.y * 0.5);
const pts = [[0, 0], [meleeBullet.hitboxSize.x, 0], [meleeBullet.hitboxSize.x, -meleeBullet.hitboxSize.y], [0, -meleeBullet.hitboxSize.y]];
g.moveTo(bulletCpos[0], bulletCpos[1]);
for (let j = 0; j < pts.length; j += 1) {
g.lineTo(pts[j][0] + bulletCpos[0], pts[j][1] + bulletCpos[1]);
}
g.lineTo(bulletCpos[0], bulletCpos[1]);
g.stroke();
}
}
}
},
getCachedInputFrameDownsyncWithPrediction(inputFrameId) { getCachedInputFrameDownsyncWithPrediction(inputFrameId) {
const self = this; const self = this;
const inputFrameDownsync = self.recentInputCache.getByFrameId(inputFrameId); const inputFrameDownsync = self.recentInputCache.getByFrameId(inputFrameId);
@ -1003,7 +1088,10 @@ cc.Class({
virtualGridY: currPlayerDownsync.virtualGridY, virtualGridY: currPlayerDownsync.virtualGridY,
dirX: currPlayerDownsync.dirX, dirX: currPlayerDownsync.dirX,
dirY: currPlayerDownsync.dirY, dirY: currPlayerDownsync.dirY,
velX: currPlayerDownsync.velX,
velY: currPlayerDownsync.velY,
characterState: currPlayerDownsync.characterState, characterState: currPlayerDownsync.characterState,
inAir: true,
speed: currPlayerDownsync.speed, speed: currPlayerDownsync.speed,
battleState: currPlayerDownsync.battleState, battleState: currPlayerDownsync.battleState,
score: currPlayerDownsync.score, score: currPlayerDownsync.score,
@ -1016,27 +1104,104 @@ cc.Class({
} }
const nextRenderFrameMeleeBullets = []; const nextRenderFrameMeleeBullets = [];
const effPushbacks = new Array(self.playerRichInfoArr.length);
const hardPushbackNorms = new Array(self.playerRichInfoArr.length);
const bulletPushbacks = new Array(self.playerRichInfoArr.length); // Guaranteed determinism regardless of traversal order // 1. Process player inputs
const effPushbacks = new Array(self.playerRichInfoArr.length); // Guaranteed determinism regardless of traversal order /*
[WARNING] Player input alone WOULD NOT take "characterState" into any "ATK_CHARACTER_STATE_IN_AIR_SET", only after the calculation of "effPushbacks" do we know exactly whether or not a player is "inAir", the finalize the transition of "thatPlayerInNextFrame.characterState".
*/
if (null != delayedInputFrame) {
const delayedInputFrameForPrevRenderFrame = self.getCachedInputFrameDownsyncWithPrediction(self._convertToInputFrameId(currRenderFrame.id - 1, self.inputDelayFrames));
const inputList = delayedInputFrame.inputList;
for (let j in self.playerRichInfoArr) {
const joinIndex = parseInt(j) + 1;
const playerRichInfo = self.playerRichInfoArr[j];
const playerId = playerRichInfo.id;
const currPlayerDownsync = currRenderFrame.players[playerId];
const thatPlayerInNextFrame = nextRenderFramePlayers[playerId];
if (0 < thatPlayerInNextFrame.framesToRecover) {
// No need to process inputs for this player, but there might be bullet pushbacks on this player
continue;
}
// Reset playerCollider position from the "virtual grid position" const decodedInput = self.ctrl.decodeInput(inputList[joinIndex - 1]);
const prevDecodedInput = (null == delayedInputFrameForPrevRenderFrame ? null : self.ctrl.decodeInput(delayedInputFrameForPrevRenderFrame.inputList[joinIndex - 1]));
const prevBtnALevel = (null == prevDecodedInput ? 0 : prevDecodedInput.btnALevel);
const prevBtnBLevel = (null == prevDecodedInput ? 0 : prevDecodedInput.btnBLevel);
if (1 == decodedInput.btnBLevel && 0 == prevBtnBLevel) {
const characStateAlreadyInAir = window.ATK_CHARACTER_STATE_IN_AIR_SET.has(thatPlayerInNextFrame.characterState);
const characStateIsInterruptWaivable = window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(thatPlayerInNextFrame.characterState);
if (
!characStateAlreadyInAir
&&
characStateIsInterruptWaivable
) {
thatPlayerInNextFrame.velY = self.jumpingInitVelY;
if (1 == joinIndex) {
console.log(`playerId=${playerId}, joinIndex=${joinIndex} jumped at {renderFrame.id: ${currRenderFrame.id}, virtualX: ${currPlayerDownsync.virtualGridX}, virtualY: ${currPlayerDownsync.virtualGridY}, nextVelX: ${thatPlayerInNextFrame.velX}, nextVelY: ${thatPlayerInNextFrame.velY}}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
}
}
}
if (1 == decodedInput.btnALevel && 0 == prevBtnALevel) {
const punchSkillId = 1;
const punch = window.pb.protos.MeleeBullet.create(self.meleeSkillConfig[punchSkillId]);
thatPlayerInNextFrame.framesToRecover = punch.recoveryFrames;
punch.battleLocalId = self.bulletBattleLocalIdCounter++;
punch.offenderJoinIndex = joinIndex;
punch.offenderPlayerId = playerId;
punch.originatedRenderFrameId = currRenderFrame.id;
nextRenderFrameMeleeBullets.push(punch);
// console.log(`playerId=${playerId}, joinIndex=${joinIndex} triggered a rising-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atk1[0];
if (false == currPlayerDownsync.inAir) {
thatPlayerInNextFrame.velX = 0; // prohibits simultaneous movement with Atk1 on the ground
}
} else if (0 == decodedInput.btnALevel && 1 == prevBtnALevel) {
// console.log(`playerId=${playerId} triggered a falling-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
} else {
// No bullet trigger, process joystick movement inputs.
if (0 != decodedInput.dx || 0 != decodedInput.dy) {
// Update directions and thus would eventually update moving animation accordingly
thatPlayerInNextFrame.dirX = decodedInput.dx;
thatPlayerInNextFrame.dirY = decodedInput.dy;
thatPlayerInNextFrame.velX = decodedInput.dx * currPlayerDownsync.speed;
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Walking[0];
} else {
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Idle1[0];
thatPlayerInNextFrame.velX = 0;
}
}
}
}
// 2. Process player movement
for (let j in self.playerRichInfoArr) { for (let j in self.playerRichInfoArr) {
const joinIndex = parseInt(j) + 1; const joinIndex = parseInt(j) + 1;
bulletPushbacks[joinIndex - 1] = [0.0, 0.0];
effPushbacks[joinIndex - 1] = [0.0, 0.0]; effPushbacks[joinIndex - 1] = [0.0, 0.0];
const playerRichInfo = self.playerRichInfoArr[j]; const playerRichInfo = self.playerRichInfoArr[j];
const playerId = playerRichInfo.id; const playerId = playerRichInfo.id;
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
const playerCollider = collisionSysMap.get(collisionPlayerIndex); const playerCollider = collisionSysMap.get(collisionPlayerIndex);
const currPlayerDownsync = currRenderFrame.players[playerId]; const currPlayerDownsync = currRenderFrame.players[playerId];
const thatPlayerInNextFrame = nextRenderFramePlayers[playerId];
// Reset playerCollider position from the "virtual grid position"
const [newVx, newVy] = [currPlayerDownsync.virtualGridX + currPlayerDownsync.velX, currPlayerDownsync.virtualGridY + currPlayerDownsync.velY];
const colliderWidth = self.playerRichInfoArr[joinIndex - 1].colliderRadius * 2,
colliderHeight = self.playerRichInfoArr[joinIndex - 1].colliderRadius * 4;
const newCpos = self.virtualGridToPolygonColliderTLPos(newVx, newVy, colliderWidth * 0.5, colliderHeight * 0.5);
playerCollider.x = newCpos[0];
playerCollider.y = newCpos[1];
const newVx = currPlayerDownsync.virtualGridX; if (currPlayerDownsync.inAir) {
const newVy = currPlayerDownsync.virtualGridY; thatPlayerInNextFrame.velX += self.gravityX;
[playerCollider.x, playerCollider.y] = self.virtualGridToPolygonColliderAnchorPos(newVx, newVy, self.playerRichInfoArr[joinIndex - 1].colliderRadius, self.playerRichInfoArr[joinIndex - 1].colliderRadius); thatPlayerInNextFrame.velY += self.gravityY;
}
} }
// Check bullet-anything collisions first, because the pushbacks caused by bullets might later be reverted by player-barrier collision // 3. Add bullet colliders into collision system
const bulletColliders = new Map(); // Will all be removed at the end of `applyInputFrameDownsyncDynamicsOnSingleRenderFrame` due to the need for being rollback-compatible const bulletColliders = new Map(); // Will all be removed at the end of `applyInputFrameDownsyncDynamicsOnSingleRenderFrame` due to the need for being rollback-compatible
const removedBulletsAtCurrFrame = new Set(); const removedBulletsAtCurrFrame = new Set();
for (let k in currRenderFrame.meleeBullets) { for (let k in currRenderFrame.meleeBullets) {
@ -1058,38 +1223,131 @@ cc.Class({
const [offenderWx, offenderWy] = self.virtualGridToWorldPos(offender.virtualGridX, offender.virtualGridY); const [offenderWx, offenderWy] = self.virtualGridToWorldPos(offender.virtualGridX, offender.virtualGridY);
const bulletWx = offenderWx + xfac * meleeBullet.hitboxOffset; const bulletWx = offenderWx + xfac * meleeBullet.hitboxOffset;
const bulletWy = offenderWy; const bulletWy = offenderWy;
const [bulletCx, bulletCy] = self.worldToPolygonColliderAnchorPos(bulletWx, bulletWy, meleeBullet.hitboxSize.x * 0.5, meleeBullet.hitboxSize.y * 0.5), const bulletCpos = self.worldToPolygonColliderTLPos(bulletWx, bulletWy, meleeBullet.hitboxSize.x * 0.5, meleeBullet.hitboxSize.y * 0.5),
pts = [[0, 0], [meleeBullet.hitboxSize.x, 0], [meleeBullet.hitboxSize.x, meleeBullet.hitboxSize.y], [0, meleeBullet.hitboxSize.y]]; pts = [[0, 0], [meleeBullet.hitboxSize.x, 0], [meleeBullet.hitboxSize.x, -meleeBullet.hitboxSize.y], [0, -meleeBullet.hitboxSize.y]];
const newBulletCollider = collisionSys.createPolygon(bulletCx, bulletCy, pts); const newBulletCollider = collisionSys.createPolygon(bulletCpos[0], bulletCpos[1], pts);
newBulletCollider.data = meleeBullet; newBulletCollider.data = meleeBullet;
collisionSysMap.set(collisionBulletIndex, newBulletCollider); collisionSysMap.set(collisionBulletIndex, newBulletCollider);
bulletColliders.set(collisionBulletIndex, newBulletCollider); bulletColliders.set(collisionBulletIndex, newBulletCollider);
// console.log(`A meleeBullet is added to collisionSys at currRenderFrame.id=${currRenderFrame.id} as start-up frames ended and active frame is not yet ended: ${JSON.stringify(meleeBullet)}`);
} }
} }
// 4. Invoke collision system stepping
collisionSys.update(); collisionSys.update();
const result1 = collisionSys.createResult(); // Can I reuse a "self.collisionSysResult" object throughout the whole battle? const result = collisionSys.createResult(); // Can I reuse a "self.collisionSysResult" object throughout the whole battle?
// 5. Calc pushbacks for each player (after its movement) w/o bullets
for (let j in self.playerRichInfoArr) {
const joinIndex = parseInt(j) + 1;
const playerRichInfo = self.playerRichInfoArr[j];
const playerId = playerRichInfo.id;
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
const potentials = playerCollider.potentials();
hardPushbackNorms[joinIndex - 1] = self.calcHardPushbacksNorms(playerCollider, potentials, result, self.snapIntoPlatformOverlap, effPushbacks[joinIndex - 1]);
const currPlayerDownsync = currRenderFrame.players[playerId];
const thatPlayerInNextFrame = nextRenderFramePlayers[playerId];
let fallStopping = false;
let possiblyFallStoppedOnAnotherPlayer = false;
for (const potential of potentials) {
let [isBarrier, isAnotherPlayer, isBullet] = [true == potential.data.hardPushback, null != potential.data.joinIndex, null != potential.data.offenderJoinIndex];
// ignore bullets for this step
if (isBullet) continue;
// Test if the player collides with the wall/another player
if (!playerCollider.collides(potential, result)) continue;
const normAlignmentWithGravity = (result.overlap_x * 0 + result.overlap_y * (-1.0));
const landedOnGravityPushback = (self.snapIntoPlatformThreshold < normAlignmentWithGravity); // prevents false snapping on the lateral sides
let pushback = [result.overlap * result.overlap_x, result.overlap * result.overlap_y];
if (landedOnGravityPushback) {
// kindly note that one player might land on top of another player, and snapping is also required in such case
pushback = [(result.overlap - self.snapIntoPlatformOverlap) * result.overlap_x, (result.overlap - self.snapIntoPlatformOverlap) * result.overlap_y];
thatPlayerInNextFrame.inAir = false;
}
for (let hardPushbackNorm of hardPushbackNorms[joinIndex - 1]) {
// remove pushback component on the directions of "hardPushbackNorms[joinIndex-1]" (by now those hardPushbacks are already accounted in "effPushbacks[joinIndex-1]")
const projectedMagnitude = pushback[0] * hardPushbackNorm[0] + pushback[1] * hardPushbackNorm[1];
if (isBarrier
||
(isAnotherPlayer && 0 > projectedMagnitude)
) {
// [WARNING] Pushing by another player is different from pushing by barrier!
// Otherwise the player couldn't be pushed by another player to opposite dir of a side wall
pushback[0] -= projectedMagnitude * hardPushbackNorm[0];
pushback[1] -= projectedMagnitude * hardPushbackNorm[1];
}
}
effPushbacks[joinIndex - 1][0] += pushback[0];
effPushbacks[joinIndex - 1][1] += pushback[1];
if (currPlayerDownsync.inAir && landedOnGravityPushback) {
fallStopping = true;
if (isAnotherPlayer) {
possiblyFallStoppedOnAnotherPlayer = true;
}
if (1 == thatPlayerInNextFrame.joinIndex) {
console.log(`playerId=${playerId}, joinIndex=${currPlayerDownsync.joinIndex} fallStopping#1 at {renderFrame.id: ${currRenderFrame.id}, virtualX: ${currPlayerDownsync.virtualGridX}, virtualY: ${currPlayerDownsync.virtualGridY}, velX: ${currPlayerDownsync.velX}, velY: ${currPlayerDownsync.velY}} with effPushback={${effPushbacks[joinIndex - 1][0].toFixed(3)}, ${effPushbacks[joinIndex - 1][1].toFixed(3)}}, overlayMag=${result.overlap.toFixed(4)}, possiblyFallStoppedOnAnotherPlayer=${possiblyFallStoppedOnAnotherPlayer}`);
}
}
if (1 == joinIndex && currPlayerDownsync.inAir && isBarrier && !landedOnGravityPushback) {
console.warn(`playerId=${playerId}, joinIndex=${currPlayerDownsync.joinIndex} inAir & pushed back by barrier & not landed at {renderFrame.id: ${currRenderFrame.id}, virtualX: ${currPlayerDownsync.virtualGridX}, virtualY: ${currPlayerDownsync.virtualGridY}, velX: ${currPlayerDownsync.velX}, velY: ${currPlayerDownsync.velY}} with effPushback={${effPushbacks[joinIndex - 1][0].toFixed(3)}, ${effPushbacks[joinIndex - 1][1].toFixed(3)}}, playerColliderPos={${playerCollider.x.toFixed(3)}, ${playerCollider.y.toFixed(3)}}, barrierPos={${potential.x.toFixed(3)}, ${potential.y.toFixed(3)}}, overlayMag=${result.overlap.toFixed(4)}, len(hardPushbackNorms)=${hardPushbackNorms.length}`);
}
if (1 == joinIndex && currPlayerDownsync.inAir && isAnotherPlayer) {
console.warn(`playerId=${playerId}, joinIndex=${currPlayerDownsync.joinIndex} inAir and pushed back by another player at {renderFrame.id: ${currRenderFrame.id}, virtualX: ${currPlayerDownsync.virtualGridX}, virtualY: ${currPlayerDownsync.virtualGridY}, velX: ${currPlayerDownsync.velX}, velY: ${currPlayerDownsync.velY}} with effPushback={${effPushbacks[joinIndex - 1][0].toFixed(3)}, ${effPushbacks[joinIndex - 1][1].toFixed(3)}}, landedOnGravityPushback=${landedOnGravityPushback}, fallStopping=${fallStopping}, playerColliderPos={${playerCollider.x.toFixed(3)}, ${playerCollider.y.toFixed(3)}}, anotherPlayerColliderPos={${potential.x.toFixed(3)}, ${potential.y.toFixed(3)}}, overlayMag=${result.overlap.toFixed(4)}, len(hardPushbackNorms)=${hardPushbackNorms.length}`);
}
}
if (fallStopping) {
thatPlayerInNextFrame.velX = 0;
thatPlayerInNextFrame.velY = 0;
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Idle1[0];
thatPlayerInNextFrame.framesToRecover = 0;
}
if (currPlayerDownsync.inAir) {
thatPlayerInNextFrame.characterState = window.toInAirConjugate(thatPlayerInNextFrame.characterState);
}
}
// 6. Check bullet-anything collisions
bulletColliders.forEach((bulletCollider, collisionBulletIndex) => { bulletColliders.forEach((bulletCollider, collisionBulletIndex) => {
const potentials = bulletCollider.potentials(); const potentials = bulletCollider.potentials();
const offender = currRenderFrame.players[bulletCollider.data.offenderPlayerId]; const offender = currRenderFrame.players[bulletCollider.data.offenderPlayerId];
let shouldRemove = false; let shouldRemove = false;
for (const potential of potentials) { for (const potential of potentials) {
if (null != potential.data && potential.data.joinIndex == bulletCollider.data.offenderJoinIndex) continue; if (null != potential.data && potential.data.joinIndex == bulletCollider.data.offenderJoinIndex) continue;
if (!bulletCollider.collides(potential, result1)) continue; if (!bulletCollider.collides(potential, result)) continue;
if (null != potential.data && null !== potential.data.joinIndex) { if (null != potential.data && null != potential.data.joinIndex) {
const playerId = potential.data.id;
const joinIndex = potential.data.joinIndex; const joinIndex = potential.data.joinIndex;
let xfac = 1; let xfac = 1;
if (0 > offender.dirX) { if (0 > offender.dirX) {
xfac = -1; xfac = -1;
} }
bulletPushbacks[joinIndex - 1][0] += xfac * bulletCollider.data.pushback; // Only for straight punch, there's no y-pushback // Only for straight punch, there's no y-pushback
bulletPushbacks[joinIndex - 1][1] += 0; let bulletPushback = [-xfac * bulletCollider.data.pushback, 0];
const thatAckedPlayerInNextFrame = nextRenderFramePlayers[potential.data.id]; // console.log(`playerId=${playerId}, joinIndex=${joinIndex} is supposed to be pushed back by meleeBullet for bulletPushback=${JSON.stringify(bulletPushback)} at renderFrame.id=${currRenderFrame.id}`);
thatAckedPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atked1[0]; for (let hardPushbackNorm of hardPushbackNorms[joinIndex - 1]) {
const oldFramesToRecover = thatAckedPlayerInNextFrame.framesToRecover; const projectedMagnitude = bulletPushback[0] * hardPushbackNorm[0] + bulletPushback[1] * hardPushbackNorm[1];
thatAckedPlayerInNextFrame.framesToRecover = (oldFramesToRecover > bulletCollider.data.hitStunFrames ? oldFramesToRecover : bulletCollider.data.hitStunFrames); // In case the hit player is already stun, we extend it if (0 > projectedMagnitude) {
// Otherwise when smashing into a wall the atked player would be pushed into the wall first and only got back in the next renderFrame, not what I want here
bulletPushback[0] -= (projectedMagnitude * hardPushbackNorm[0]);
bulletPushback[1] -= (projectedMagnitude * hardPushbackNorm[1]);
// console.log(`playerId=${playerId}, joinIndex=${joinIndex} reducing bulletPushback=${JSON.stringify(bulletPushback)} by ${JSON.stringify([projectedMagnitude * hardPushbackNorm[0], projectedMagnitude * hardPushbackNorm[1]])} where hardPushbackNorm=${JSON.stringify(hardPushbackNorm)}, projectedMagnitude=${projectedMagnitude} at renderFrame.id=${currRenderFrame.id}`);
}
}
// console.log(`playerId=${playerId}, joinIndex=${joinIndex} is actually pushed back by meleeBullet for bulletPushback=${JSON.stringify(bulletPushback)} at renderFrame.id=${currRenderFrame.id}`);
effPushbacks[joinIndex - 1][0] += bulletPushback[0];
effPushbacks[joinIndex - 1][1] += bulletPushback[1];
const [atkedPlayerInCurFrame, atkedPlayerInNextFrame] = [currRenderFrame.players[potential.data.id], nextRenderFramePlayers[potential.data.id]];
atkedPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atked1[0];
if (atkedPlayerInCurFrame.inAir) {
atkedPlayerInNextFrame.characterState = window.toInAirConjugate(atkedPlayerInNextFrame.characterState);
}
const oldFramesToRecover = atkedPlayerInNextFrame.framesToRecover;
atkedPlayerInNextFrame.framesToRecover = (oldFramesToRecover > bulletCollider.data.hitStunFrames ? oldFramesToRecover : bulletCollider.data.hitStunFrames); // In case the hit player is already stun, we extend it
} }
shouldRemove = true; shouldRemove = true;
} }
@ -1111,95 +1369,32 @@ cc.Class({
nextRenderFrameMeleeBullets.push(meleeBullet); nextRenderFrameMeleeBullets.push(meleeBullet);
} }
// Process player inputs // 7. Get players out of stuck barriers if there's any
if (null != delayedInputFrame) { for (let j in self.playerRichInfoArr) {
const delayedInputFrameForPrevRenderFrame = self.getCachedInputFrameDownsyncWithPrediction(self._convertToInputFrameId(currRenderFrame.id - 1, self.inputDelayFrames)); const joinIndex = parseInt(j) + 1;
const inputList = delayedInputFrame.inputList; const playerId = self.playerRichInfoArr[j].id;
for (let j in self.playerRichInfoArr) { const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
const joinIndex = parseInt(j) + 1; const playerCollider = collisionSysMap.get(collisionPlayerIndex);
effPushbacks[joinIndex - 1] = [0.0, 0.0]; // Update "virtual grid position"
const playerRichInfo = self.playerRichInfoArr[j]; const currPlayerDownsync = currRenderFrame.players[playerId];
const playerId = playerRichInfo.id; const thatPlayerInNextFrame = nextRenderFramePlayers[playerId];
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; const colliderWidth = self.playerRichInfoArr[joinIndex - 1].colliderRadius * 2,
const playerCollider = collisionSysMap.get(collisionPlayerIndex); colliderHeight = self.playerRichInfoArr[joinIndex - 1].colliderRadius * 4;
const currPlayerDownsync = currRenderFrame.players[playerId]; const newVpos = self.polygonColliderTLToVirtualGridPos(playerCollider.x - effPushbacks[joinIndex - 1][0], playerCollider.y - effPushbacks[joinIndex - 1][1], colliderWidth * 0.5, colliderHeight * 0.5);
const thatPlayerInNextFrame = nextRenderFramePlayers[playerId]; thatPlayerInNextFrame.virtualGridX = newVpos[0];
if (0 < thatPlayerInNextFrame.framesToRecover) { thatPlayerInNextFrame.virtualGridY = newVpos[1];
// No need to process inputs for this player, but there might be bullet pushbacks on this player
playerCollider.x += bulletPushbacks[joinIndex - 1][0]; if (1 == thatPlayerInNextFrame.joinIndex) {
playerCollider.y += bulletPushbacks[joinIndex - 1][1]; if (thatPlayerInNextFrame.inAir && 0 != thatPlayerInNextFrame.velY) {
if (0 != bulletPushbacks[joinIndex - 1][0] || 0 != bulletPushbacks[joinIndex - 1][1]) { // console.log(`playerId=${playerId}, joinIndex=${thatPlayerInNextFrame.joinIndex} inAir trajectory: {nextRenderFrame.id: ${currRenderFrame.id + 1}, nextVirtualX: ${thatPlayerInNextFrame.virtualGridX}, nextVirtualY: ${thatPlayerInNextFrame.virtualGridY}, nextVelX: ${thatPlayerInNextFrame.velX}, nextVelY: ${thatPlayerInNextFrame.velY}}, with playerColliderPos={${playerCollider.x.toFixed(3)}, ${playerCollider.y.toFixed(3)}}, effPushback={${effPushbacks[joinIndex - 1][0].toFixed(3)}, ${effPushbacks[joinIndex - 1][1].toFixed(3)}}`);
console.log(`playerId=${playerId}, joinIndex=${joinIndex} is pushbacked back by ${bulletPushbacks[joinIndex - 1]} by bullet impacts, now its framesToRecover is ${thatPlayerInNextFrame.framesToRecover}`);
}
continue;
} }
if (currPlayerDownsync.inAir && !thatPlayerInNextFrame.inAir) {
const decodedInput = self.ctrl.decodeInput(inputList[joinIndex - 1]); console.warn(`playerId=${playerId}, joinIndex=${thatPlayerInNextFrame.joinIndex} fallStopping#2 at {nextRenderFrame.id: ${currRenderFrame.id + 1}, nextVirtualX: ${thatPlayerInNextFrame.virtualGridX}, nextVirtualY: ${thatPlayerInNextFrame.virtualGridY}, nextVelX: ${thatPlayerInNextFrame.velX}, nextVelY: ${thatPlayerInNextFrame.velY}}, with playerColliderPos={${playerCollider.x.toFixed(3)}, ${playerCollider.y.toFixed(3)}}, effPushback={${effPushbacks[joinIndex - 1][0].toFixed(3)}, ${effPushbacks[joinIndex - 1][1].toFixed(3)}}`);
}
const prevDecodedInput = (null == delayedInputFrameForPrevRenderFrame ? null : self.ctrl.decodeInput(delayedInputFrameForPrevRenderFrame.inputList[joinIndex - 1])); if (!currPlayerDownsync.inAir && thatPlayerInNextFrame.inAir) {
const prevBtnALevel = (null == prevDecodedInput ? 0 : prevDecodedInput.btnALevel); console.warn(`playerId=${playerId}, joinIndex=${thatPlayerInNextFrame.joinIndex} took off at {nextRenderFrame.id: ${currRenderFrame.id + 1}, nextVirtualX: ${thatPlayerInNextFrame.virtualGridX}, nextVirtualY: ${thatPlayerInNextFrame.virtualGridY}, nextVelX: ${thatPlayerInNextFrame.velX}, nextVelY: ${thatPlayerInNextFrame.velY}}, with playerColliderPos={${playerCollider.x.toFixed(3)}, ${playerCollider.y.toFixed(3)}}, effPushback={${effPushbacks[joinIndex - 1][0].toFixed(3)}, ${effPushbacks[joinIndex - 1][1].toFixed(3)}}`);
if (1 == decodedInput.btnALevel && 0 == prevBtnALevel) {
// console.log(`playerId=${playerId} triggered a rising-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
if (self.bulletTriggerEnabled) {
const punchSkillId = 1;
const punch = window.pb.protos.MeleeBullet.create(self.meleeSkillConfig[punchSkillId]);
thatPlayerInNextFrame.framesToRecover = punch.recoveryFrames;
punch.battleLocalId = self.bulletBattleLocalIdCounter++;
punch.offenderJoinIndex = joinIndex;
punch.offenderPlayerId = playerId;
punch.originatedRenderFrameId = currRenderFrame.id;
nextRenderFrameMeleeBullets.push(punch);
// console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}: ${self._stringifyRecentInputCache(true)}`);
// console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atk1[0];
}
} else if (0 == decodedInput.btnALevel && 1 == prevBtnALevel) {
// console.log(`playerId=${playerId} triggered a falling-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
} else {
// No bullet trigger, process movement inputs
if (0 != decodedInput.dx || 0 != decodedInput.dy) {
// Update directions and thus would eventually update moving animation accordingly
thatPlayerInNextFrame.dirX = decodedInput.dx;
thatPlayerInNextFrame.dirY = decodedInput.dy;
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Walking[0];
} else {
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Idle1[0];
}
const [movementX, movementY] = self.virtualGridToWorldPos(decodedInput.dx + currPlayerDownsync.speed * decodedInput.dx, decodedInput.dy + currPlayerDownsync.speed * decodedInput.dy);
playerCollider.x += movementX;
playerCollider.y += movementY;
} }
} }
collisionSys.update(); // by now all "bulletCollider"s are removed
const result2 = collisionSys.createResult(); // Can I reuse a "self.collisionSysResult" object throughout the whole battle?
for (let j in self.playerRichInfoArr) {
const joinIndex = parseInt(j) + 1;
const playerId = self.playerRichInfoArr[j].id;
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
const potentials = playerCollider.potentials();
for (const potential of potentials) {
// Test if the player collides with the wall
if (!playerCollider.collides(potential, result2)) continue;
// Push the player out of the wall
effPushbacks[joinIndex - 1][0] += result2.overlap * result2.overlap_x;
effPushbacks[joinIndex - 1][1] += result2.overlap * result2.overlap_y;
}
}
for (let j in self.playerRichInfoArr) {
const joinIndex = parseInt(j) + 1;
const playerId = self.playerRichInfoArr[j].id;
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
const thatPlayerInNextFrame = nextRenderFramePlayers[playerId];
[thatPlayerInNextFrame.virtualGridX, thatPlayerInNextFrame.virtualGridY] = self.polygonColliderAnchorToVirtualGridPos(playerCollider.x - effPushbacks[joinIndex - 1][0], playerCollider.y - effPushbacks[joinIndex - 1][1], self.playerRichInfoArr[j].colliderRadius, self.playerRichInfoArr[j].colliderRadius);
}
} }
return window.pb.protos.RoomDownsyncFrame.create({ return window.pb.protos.RoomDownsyncFrame.create({
@ -1214,11 +1409,10 @@ cc.Class({
This function eventually calculates a "RoomDownsyncFrame" where "RoomDownsyncFrame.id == renderFrameIdEd" if not interruptted. This function eventually calculates a "RoomDownsyncFrame" where "RoomDownsyncFrame.id == renderFrameIdEd" if not interruptted.
*/ */
const self = this; const self = this;
let i = renderFrameIdSt, let prevLatestRdf = null,
prevLatestRdf = null,
latestRdf = null; latestRdf = null;
do { for (let i = renderFrameIdSt; i < renderFrameIdEd; i++) {
latestRdf = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing", this function can be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"! latestRdf = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing", this function can be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"!
if (null == latestRdf) { if (null == latestRdf) {
console.warn(`Couldn't find renderFrame for i=${i} to rollback, self.renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, might've been interruptted by onRoomDownsyncFrame`); console.warn(`Couldn't find renderFrame for i=${i} to rollback, self.renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, might've been interruptted by onRoomDownsyncFrame`);
@ -1250,8 +1444,7 @@ cc.Class({
self.chaserRenderFrameId = latestRdf.id; self.chaserRenderFrameId = latestRdf.id;
} }
self.recentRenderCache.setByFrameId(latestRdf, latestRdf.id); self.recentRenderCache.setByFrameId(latestRdf, latestRdf.id);
++i; }
} while (i < renderFrameIdEd);
return [prevLatestRdf, latestRdf]; return [prevLatestRdf, latestRdf];
}, },
@ -1325,23 +1518,41 @@ cc.Class({
return [wx, wy]; return [wx, wy];
}, },
worldToPolygonColliderAnchorPos(wx, wy, halfBoundingW, halfBoundingH) { worldToPolygonColliderTLPos(wx, wy, halfBoundingW, halfBoundingH) {
return [wx - halfBoundingW, wy - halfBoundingH]; return [wx - halfBoundingW, wy + halfBoundingH];
}, },
polygonColliderAnchorToWorldPos(cx, cy, halfBoundingW, halfBoundingH) { polygonColliderTLToWorldPos(cx, cy, halfBoundingW, halfBoundingH) {
return [cx + halfBoundingW, cy + halfBoundingH]; return [cx + halfBoundingW, cy - halfBoundingH];
}, },
polygonColliderAnchorToVirtualGridPos(cx, cy, halfBoundingW, halfBoundingH) { polygonColliderTLToVirtualGridPos(cx, cy, halfBoundingW, halfBoundingH) {
const self = this; const self = this;
const [wx, wy] = self.polygonColliderAnchorToWorldPos(cx, cy, halfBoundingW, halfBoundingH); const [wx, wy] = self.polygonColliderTLToWorldPos(cx, cy, halfBoundingW, halfBoundingH);
return self.worldToVirtualGridPos(wx, wy) return self.worldToVirtualGridPos(wx, wy)
}, },
virtualGridToPolygonColliderAnchorPos(vx, vy, halfBoundingW, halfBoundingH) { virtualGridToPolygonColliderTLPos(vx, vy, halfBoundingW, halfBoundingH) {
const self = this; const self = this;
const [wx, wy] = self.virtualGridToWorldPos(vx, vy); const [wx, wy] = self.virtualGridToWorldPos(vx, vy);
return self.worldToPolygonColliderAnchorPos(wx, wy, halfBoundingW, halfBoundingH) return self.worldToPolygonColliderTLPos(wx, wy, halfBoundingW, halfBoundingH)
},
calcHardPushbacksNorms(collider, potentials, result, snapIntoPlatformOverlap, effPushback) {
const self = this;
let ret = [];
for (const potential of potentials) {
if (null == potential.data || !(true == potential.data.hardPushback)) continue;
if (!collider.collides(potential, result)) continue;
// ALWAY snap into hardPushbacks!
// [overlay_x, overlap_y] is the unit vector that points into the platform
const [pushbackX, pushbackY] = [(result.overlap - snapIntoPlatformOverlap) * result.overlap_x, (result.overlap - snapIntoPlatformOverlap) * result.overlap_y];
ret.push([result.overlap_x, result.overlap_y]);
effPushback[0] += pushbackX;
effPushback[1] += pushbackY;
}
return ret;
}, },
}); });

View File

@ -13,6 +13,7 @@ cc.Class({
onLoad() { onLoad() {
const self = this; const self = this;
window.mapIns = self; window.mapIns = self;
self.showCriticalCoordinateLabels = true;
cc.director.getCollisionManager().enabled = false; cc.director.getCollisionManager().enabled = false;
@ -34,9 +35,11 @@ cc.Class({
self.inputFrameUpsyncDelayTolerance = 2; self.inputFrameUpsyncDelayTolerance = 2;
self.renderCacheSize = 1024; self.renderCacheSize = 1024;
self.serverFps = 60;
self.rollbackEstimatedDt = 0.016667; self.rollbackEstimatedDt = 0.016667;
self.rollbackEstimatedDtMillis = 16.667; self.rollbackEstimatedDtMillis = 16.667;
self.rollbackEstimatedDtNanos = 16666666; self.rollbackEstimatedDtNanos = 16666666;
self.tooFastDtIntervalMillis = 0.5 * self.rollbackEstimatedDtMillis;
self.worldToVirtualGridRatio = 1000; self.worldToVirtualGridRatio = 1000;
self.virtualGridToWorldRatio = 1.0 / self.worldToVirtualGridRatio; self.virtualGridToWorldRatio = 1.0 / self.worldToVirtualGridRatio;
@ -67,6 +70,16 @@ cc.Class({
} }
}; };
/*
[WARNING] As when a character is standing on a barrier, if not carefully curated there MIGHT BE a bouncing sequence of "[(inAir -> dropIntoBarrier ->), (notInAir -> pushedOutOfBarrier ->)], [(inAir -> ..."
Moreover, "snapIntoPlatformOverlap" should be small enough such that the walking "velX" or jumping initial "velY" can escape from it by 1 renderFrame (when jumping is triggered, the character is waived from snappig for 1 renderFrame).
*/
self.snapIntoPlatformOverlap = 0.1;
self.snapIntoPlatformThreshold = 0.5; // a platform must be "horizontal enough" for a character to "stand on"
self.jumpingInitVelY = 7 * self.worldToVirtualGridRatio; // unit: (virtual grid length/renderFrame)
[self.gravityX, self.gravityY] = [0, -0.5*self.worldToVirtualGridRatio]; // unit: (virtual grid length/renderFrame^2)
const tiledMapIns = self.node.getComponent(cc.TiledMap); const tiledMapIns = self.node.getComponent(cc.TiledMap);
const fullPathOfTmxFile = cc.js.formatStr("map/%s/map", "dungeon"); const fullPathOfTmxFile = cc.js.formatStr("map/%s/map", "dungeon");
@ -80,6 +93,17 @@ cc.Class({
mapNode.removeAllChildren(); mapNode.removeAllChildren();
self._resetCurrentMatch(); self._resetCurrentMatch();
if (self.showCriticalCoordinateLabels) {
const drawer = new cc.Node();
drawer.setPosition(cc.v2(0, 0))
safelyAddChild(self.node, drawer);
setLocalZOrder(drawer, 999);
const g = drawer.addComponent(cc.Graphics);
g.lineWidth = 2;
self.g = g;
}
tiledMapIns.tmxAsset = tmxAsset; tiledMapIns.tmxAsset = tmxAsset;
const newMapSize = tiledMapIns.getMapSize(); const newMapSize = tiledMapIns.getMapSize();
const newTileSize = tiledMapIns.getTileSize(); const newTileSize = tiledMapIns.getTileSize();
@ -95,8 +119,11 @@ cc.Class({
const newBarrier = self.collisionSys.createPolygon(x0, y0, Array.from(boundaryObj, p => { const newBarrier = self.collisionSys.createPolygon(x0, y0, Array.from(boundaryObj, p => {
return [p.x, p.y]; return [p.x, p.y];
})); }));
newBarrier.data = {
hardPushback: true
};
if (self.showCriticalCoordinateLabels) { if (false && self.showCriticalCoordinateLabels) {
for (let i = 0; i < boundaryObj.length; ++i) { for (let i = 0; i < boundaryObj.length; ++i) {
const barrierVertLabelNode = new cc.Node(); const barrierVertLabelNode = new cc.Node();
switch (i % 4) { switch (i % 4) {
@ -136,30 +163,36 @@ cc.Class({
const startRdf = window.pb.protos.RoomDownsyncFrame.create({ const startRdf = window.pb.protos.RoomDownsyncFrame.create({
id: window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START, id: window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START,
players: { players: {
10: { 10: window.pb.protos.PlayerDownsync.create({
id: 10, id: 10,
joinIndex: 1, joinIndex: 1,
virtualGridX: 0, virtualGridX: self.worldToVirtualGridPos(boundaryObjs.playerStartingPositions[0].x, boundaryObjs.playerStartingPositions[0].y)[0],
virtualGridY: 0, virtualGridY: self.worldToVirtualGridPos(boundaryObjs.playerStartingPositions[0].x, boundaryObjs.playerStartingPositions[0].y)[1],
speed: 1 * self.worldToVirtualGridRatio, speed: 1 * self.worldToVirtualGridRatio,
colliderRadius: 12, colliderRadius: 12,
characterState: window.ATK_CHARACTER_STATE.Idle1[0], characterState: window.ATK_CHARACTER_STATE.InAirIdle1[0],
framesToRecover: 0, framesToRecover: 0,
dirX: 0, dirX: 0,
dirY: 0, dirY: 0,
}, velX: 0,
11: { velY: 0,
inAir: true,
}),
11: window.pb.protos.PlayerDownsync.create({
id: 11, id: 11,
joinIndex: 2, joinIndex: 2,
virtualGridX: 80 * self.worldToVirtualGridRatio, virtualGridX: self.worldToVirtualGridPos(boundaryObjs.playerStartingPositions[1].x, boundaryObjs.playerStartingPositions[1].y)[0],
virtualGridY: 0 * self.worldToVirtualGridRatio, virtualGridY: self.worldToVirtualGridPos(boundaryObjs.playerStartingPositions[1].x, boundaryObjs.playerStartingPositions[1].y)[1],
speed: 1 * self.worldToVirtualGridRatio, speed: 1 * self.worldToVirtualGridRatio,
colliderRadius: 12, colliderRadius: 12,
characterState: window.ATK_CHARACTER_STATE.Idle1[0], characterState: window.ATK_CHARACTER_STATE.InAirIdle1[0],
framesToRecover: 0, framesToRecover: 0,
dirX: 0, dirX: 0,
dirY: 0, dirY: 0,
}, velX: 0,
velY: 0,
inAir: true,
}),
} }
}); });
self.selfPlayerInfo = { self.selfPlayerInfo = {
@ -177,7 +210,8 @@ cc.Class({
const self = this; const self = this;
if (ALL_BATTLE_STATES.IN_BATTLE == self.battleState) { if (ALL_BATTLE_STATES.IN_BATTLE == self.battleState) {
const elapsedMillisSinceLastFrameIdTriggered = performance.now() - self.lastRenderFrameIdTriggeredAt; const elapsedMillisSinceLastFrameIdTriggered = performance.now() - self.lastRenderFrameIdTriggeredAt;
if (elapsedMillisSinceLastFrameIdTriggered < (self.rollbackEstimatedDtMillis)) { if (elapsedMillisSinceLastFrameIdTriggered < self.tooFastDtIntervalMillis) {
// [WARNING] We should avoid a frontend ticking too fast to prevent cheating, as well as ticking too slow to cause a "resync avalanche" that impacts user experience!
// console.debug("Avoiding too fast frame@renderFrameId=", self.renderFrameId, ": elapsedMillisSinceLastFrameIdTriggered=", elapsedMillisSinceLastFrameIdTriggered); // console.debug("Avoiding too fast frame@renderFrameId=", self.renderFrameId, ": elapsedMillisSinceLastFrameIdTriggered=", elapsedMillisSinceLastFrameIdTriggered);
return; return;
} }
@ -194,11 +228,12 @@ cc.Class({
const [prevRdf, rdf] = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.collisionSys, self.collisionSysMap, false); const [prevRdf, rdf] = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.collisionSys, self.collisionSysMap, false);
self.applyRoomDownsyncFrameDynamics(rdf, prevRdf); self.applyRoomDownsyncFrameDynamics(rdf, prevRdf);
self.showDebugBoundaries(rdf);
++self.renderFrameId;
self.lastRenderFrameIdTriggeredAt = performance.now();
let t3 = performance.now(); let t3 = performance.now();
} catch (err) { } catch (err) {
console.error("Error during Map.update", err); console.error("Error during Map.update", err);
} finally {
++self.renderFrameId; // [WARNING] It's important to increment the renderFrameId AFTER all the operations above!!!
} }
} }
}, },

View File

@ -108,6 +108,7 @@ TileCollisionManager.prototype.continuousMapNodePosToContinuousObjLayerOffset =
window.battleEntityTypeNameToGlobalGid = {}; window.battleEntityTypeNameToGlobalGid = {};
TileCollisionManager.prototype.extractBoundaryObjects = function(withTiledMapNode) { TileCollisionManager.prototype.extractBoundaryObjects = function(withTiledMapNode) {
let toRet = { let toRet = {
playerStartingPositions: [],
barriers: [], barriers: [],
}; };
const tiledMapIns = withTiledMapNode.getComponent(cc.TiledMap); // This is a magic name. const tiledMapIns = withTiledMapNode.getComponent(cc.TiledMap); // This is a magic name.
@ -115,6 +116,18 @@ TileCollisionManager.prototype.extractBoundaryObjects = function(withTiledMapNod
const allObjectGroups = tiledMapIns.getObjectGroups(); const allObjectGroups = tiledMapIns.getObjectGroups();
for (let i = 0; i < allObjectGroups.length; ++i) { for (let i = 0; i < allObjectGroups.length; ++i) {
var objectGroup = allObjectGroups[i]; var objectGroup = allObjectGroups[i];
if ("PlayerStartingPos" == objectGroup.getGroupName()) {
var allObjects = objectGroup.getObjects();
for (let j = 0; j < allObjects.length; ++j) {
const cccMaskedX = allObjects[j].x,
cccMaskedY = allObjects[j].y;
const origX = cccMaskedX,
origY = withTiledMapNode.getContentSize().height - cccMaskedY; // FIXME: I don't know why CocosCreator did this, it's stupid and MIGHT NOT WORK IN ISOMETRIC orientation!
let wpos = this.continuousObjLayerOffsetToContinuousMapNodePos(withTiledMapNode, cc.v2(origX, origY));
toRet.playerStartingPositions.push(wpos);
}
continue;
}
if ("barrier_and_shelter" != objectGroup.getProperty("type")) continue; if ("barrier_and_shelter" != objectGroup.getProperty("type")) continue;
var allObjects = objectGroup.getObjects(); var allObjects = objectGroup.getObjects();
for (let j = 0; j < allObjects.length; ++j) { for (let j = 0; j < allObjects.length; ++j) {
@ -123,21 +136,33 @@ TileCollisionManager.prototype.extractBoundaryObjects = function(withTiledMapNod
if (0 < gid) { if (0 < gid) {
continue; continue;
} }
const polylinePoints = object.polylinePoints;
if (null == polylinePoints) {
continue
}
const boundaryType = object.boundary_type; const boundaryType = object.boundary_type;
let toPushBarriers = []; let toPushBarrier = [];
toPushBarriers.boundaryType = boundaryType; toPushBarrier.boundaryType = boundaryType;
switch (boundaryType) { switch (boundaryType) {
case "barrier": case "barrier":
let polylinePoints = object.polylinePoints;
if (null == polylinePoints) {
polylinePoints = [{
x: 0,
y: 0
}, {
x: object.width,
y: 0
}, {
x: object.width,
y: -object.height
}, {
x: 0,
y: -object.height
}];
}
for (let k = 0; k < polylinePoints.length; ++k) { for (let k = 0; k < polylinePoints.length; ++k) {
/* Since CocosCreatorv2.1.3, the Y-coord of object polylines is inverted compared to that of the tmx file. */ /* Since CocosCreatorv2.1.3, the Y-coord of object polylines is inverted compared to that of the tmx file. */
toPushBarriers.push(this.continuousObjLayerVecToContinuousMapNodeVec(withTiledMapNode, cc.v2(polylinePoints[k].x, -polylinePoints[k].y))); toPushBarrier.push(this.continuousObjLayerVecToContinuousMapNodeVec(withTiledMapNode, cc.v2(polylinePoints[k].x, -polylinePoints[k].y)));
} }
toPushBarriers.anchor = this.continuousObjLayerOffsetToContinuousMapNodePos(withTiledMapNode, object.offset); // DON'T use "(object.x, object.y)" which are wrong/meaningless! toPushBarrier.anchor = this.continuousObjLayerOffsetToContinuousMapNodePos(withTiledMapNode, object.offset); // DON'T use "(object.x, object.y)" which are wrong/meaningless!
toRet.barriers.push(toPushBarriers); toRet.barriers.push(toPushBarrier);
break; break;
default: default:
break; break;

View File

@ -99,6 +99,7 @@ cc.Class({
this.cachedBtnLeftLevel = 0; this.cachedBtnLeftLevel = 0;
this.cachedBtnRightLevel = 0; this.cachedBtnRightLevel = 0;
this.cachedBtnALevel = 0; this.cachedBtnALevel = 0;
this.cachedBtnBLevel = 0;
this.canvasNode = this.mapNode.parent; this.canvasNode = this.mapNode.parent;
this.mainCameraNode = this.canvasNode.getChildByName("Main Camera"); // Cannot drag and assign the `mainCameraNode` from CocosCreator EDITOR directly, otherwise it'll cause an infinite loading time, till v2.1.0. this.mainCameraNode = this.canvasNode.getChildByName("Main Camera"); // Cannot drag and assign the `mainCameraNode` from CocosCreator EDITOR directly, otherwise it'll cause an infinite loading time, till v2.1.0.
@ -168,6 +169,9 @@ cc.Class({
case cc.macro.KEY.h: case cc.macro.KEY.h:
self.cachedBtnALevel = 1; self.cachedBtnALevel = 1;
break; break;
case cc.macro.KEY.j:
self.cachedBtnBLevel = 1;
break;
default: default:
break; break;
} }
@ -190,6 +194,9 @@ cc.Class({
case cc.macro.KEY.h: case cc.macro.KEY.h:
self.cachedBtnALevel = 0; self.cachedBtnALevel = 0;
break; break;
case cc.macro.KEY.j:
self.cachedBtnBLevel = 0;
break;
default: default:
break; break;
} }
@ -400,7 +407,8 @@ cc.Class({
getEncodedInput() { getEncodedInput() {
const discretizedDir = this.discretizeDirection(this.stickhead.x, this.stickhead.y, this.joyStickEps).encodedIdx; // There're only 9 dirs, thus using only the lower 4-bits const discretizedDir = this.discretizeDirection(this.stickhead.x, this.stickhead.y, this.joyStickEps).encodedIdx; // There're only 9 dirs, thus using only the lower 4-bits
const btnALevel = (this.cachedBtnALevel << 4); const btnALevel = (this.cachedBtnALevel << 4);
return (btnALevel + discretizedDir); const btnBLevel = (this.cachedBtnBLevel << 5);
return (btnBLevel + btnALevel + discretizedDir);
}, },
decodeInput(encodedInput) { decodeInput(encodedInput) {
@ -410,10 +418,12 @@ cc.Class({
console.error("Unexpected encodedDirection = ", encodedDirection); console.error("Unexpected encodedDirection = ", encodedDirection);
} }
const btnALevel = ((encodedInput >> 4) & 1); const btnALevel = ((encodedInput >> 4) & 1);
const btnBLevel = ((encodedInput >> 5) & 1);
return window.pb.protos.InputFrameDecoded.create({ return window.pb.protos.InputFrameDecoded.create({
dx: mappedDirection[0], dx: mappedDirection[0],
dy: mappedDirection[1], dy: mappedDirection[1],
btnALevel: btnALevel, btnALevel: btnALevel,
btnBLevel: btnBLevel,
}); });
}, },
}); });

View File

@ -1196,6 +1196,8 @@ $root.protos = (function() {
* @property {number|null} [virtualGridY] PlayerDownsync virtualGridY * @property {number|null} [virtualGridY] PlayerDownsync virtualGridY
* @property {number|null} [dirX] PlayerDownsync dirX * @property {number|null} [dirX] PlayerDownsync dirX
* @property {number|null} [dirY] PlayerDownsync dirY * @property {number|null} [dirY] PlayerDownsync dirY
* @property {number|null} [velX] PlayerDownsync velX
* @property {number|null} [velY] PlayerDownsync velY
* @property {number|null} [speed] PlayerDownsync speed * @property {number|null} [speed] PlayerDownsync speed
* @property {number|null} [battleState] PlayerDownsync battleState * @property {number|null} [battleState] PlayerDownsync battleState
* @property {number|null} [joinIndex] PlayerDownsync joinIndex * @property {number|null} [joinIndex] PlayerDownsync joinIndex
@ -1207,6 +1209,7 @@ $root.protos = (function() {
* @property {number|null} [hp] PlayerDownsync hp * @property {number|null} [hp] PlayerDownsync hp
* @property {number|null} [maxHp] PlayerDownsync maxHp * @property {number|null} [maxHp] PlayerDownsync maxHp
* @property {number|null} [characterState] PlayerDownsync characterState * @property {number|null} [characterState] PlayerDownsync characterState
* @property {boolean|null} [inAir] PlayerDownsync inAir
* @property {string|null} [name] PlayerDownsync name * @property {string|null} [name] PlayerDownsync name
* @property {string|null} [displayName] PlayerDownsync displayName * @property {string|null} [displayName] PlayerDownsync displayName
* @property {string|null} [avatar] PlayerDownsync avatar * @property {string|null} [avatar] PlayerDownsync avatar
@ -1267,6 +1270,22 @@ $root.protos = (function() {
*/ */
PlayerDownsync.prototype.dirY = 0; PlayerDownsync.prototype.dirY = 0;
/**
* PlayerDownsync velX.
* @member {number} velX
* @memberof protos.PlayerDownsync
* @instance
*/
PlayerDownsync.prototype.velX = 0;
/**
* PlayerDownsync velY.
* @member {number} velY
* @memberof protos.PlayerDownsync
* @instance
*/
PlayerDownsync.prototype.velY = 0;
/** /**
* PlayerDownsync speed. * PlayerDownsync speed.
* @member {number} speed * @member {number} speed
@ -1355,6 +1374,14 @@ $root.protos = (function() {
*/ */
PlayerDownsync.prototype.characterState = 0; PlayerDownsync.prototype.characterState = 0;
/**
* PlayerDownsync inAir.
* @member {boolean} inAir
* @memberof protos.PlayerDownsync
* @instance
*/
PlayerDownsync.prototype.inAir = false;
/** /**
* PlayerDownsync name. * PlayerDownsync name.
* @member {string} name * @member {string} name
@ -1413,34 +1440,40 @@ $root.protos = (function() {
writer.uint32(/* id 4, wireType 0 =*/32).int32(message.dirX); writer.uint32(/* id 4, wireType 0 =*/32).int32(message.dirX);
if (message.dirY != null && Object.hasOwnProperty.call(message, "dirY")) if (message.dirY != null && Object.hasOwnProperty.call(message, "dirY"))
writer.uint32(/* id 5, wireType 0 =*/40).int32(message.dirY); writer.uint32(/* id 5, wireType 0 =*/40).int32(message.dirY);
if (message.velX != null && Object.hasOwnProperty.call(message, "velX"))
writer.uint32(/* id 6, wireType 0 =*/48).int32(message.velX);
if (message.velY != null && Object.hasOwnProperty.call(message, "velY"))
writer.uint32(/* id 7, wireType 0 =*/56).int32(message.velY);
if (message.speed != null && Object.hasOwnProperty.call(message, "speed")) if (message.speed != null && Object.hasOwnProperty.call(message, "speed"))
writer.uint32(/* id 6, wireType 0 =*/48).int32(message.speed); writer.uint32(/* id 8, wireType 0 =*/64).int32(message.speed);
if (message.battleState != null && Object.hasOwnProperty.call(message, "battleState")) if (message.battleState != null && Object.hasOwnProperty.call(message, "battleState"))
writer.uint32(/* id 7, wireType 0 =*/56).int32(message.battleState); writer.uint32(/* id 9, wireType 0 =*/72).int32(message.battleState);
if (message.joinIndex != null && Object.hasOwnProperty.call(message, "joinIndex")) if (message.joinIndex != null && Object.hasOwnProperty.call(message, "joinIndex"))
writer.uint32(/* id 8, wireType 0 =*/64).int32(message.joinIndex); writer.uint32(/* id 10, wireType 0 =*/80).int32(message.joinIndex);
if (message.colliderRadius != null && Object.hasOwnProperty.call(message, "colliderRadius")) if (message.colliderRadius != null && Object.hasOwnProperty.call(message, "colliderRadius"))
writer.uint32(/* id 9, wireType 1 =*/73).double(message.colliderRadius); writer.uint32(/* id 11, wireType 1 =*/89).double(message.colliderRadius);
if (message.removed != null && Object.hasOwnProperty.call(message, "removed")) if (message.removed != null && Object.hasOwnProperty.call(message, "removed"))
writer.uint32(/* id 10, wireType 0 =*/80).bool(message.removed); writer.uint32(/* id 12, wireType 0 =*/96).bool(message.removed);
if (message.score != null && Object.hasOwnProperty.call(message, "score")) if (message.score != null && Object.hasOwnProperty.call(message, "score"))
writer.uint32(/* id 11, wireType 0 =*/88).int32(message.score); writer.uint32(/* id 13, wireType 0 =*/104).int32(message.score);
if (message.lastMoveGmtMillis != null && Object.hasOwnProperty.call(message, "lastMoveGmtMillis")) if (message.lastMoveGmtMillis != null && Object.hasOwnProperty.call(message, "lastMoveGmtMillis"))
writer.uint32(/* id 12, wireType 0 =*/96).int32(message.lastMoveGmtMillis); writer.uint32(/* id 14, wireType 0 =*/112).int32(message.lastMoveGmtMillis);
if (message.framesToRecover != null && Object.hasOwnProperty.call(message, "framesToRecover")) if (message.framesToRecover != null && Object.hasOwnProperty.call(message, "framesToRecover"))
writer.uint32(/* id 13, wireType 0 =*/104).int32(message.framesToRecover); writer.uint32(/* id 15, wireType 0 =*/120).int32(message.framesToRecover);
if (message.hp != null && Object.hasOwnProperty.call(message, "hp")) if (message.hp != null && Object.hasOwnProperty.call(message, "hp"))
writer.uint32(/* id 14, wireType 0 =*/112).int32(message.hp); writer.uint32(/* id 16, wireType 0 =*/128).int32(message.hp);
if (message.maxHp != null && Object.hasOwnProperty.call(message, "maxHp")) if (message.maxHp != null && Object.hasOwnProperty.call(message, "maxHp"))
writer.uint32(/* id 15, wireType 0 =*/120).int32(message.maxHp); writer.uint32(/* id 17, wireType 0 =*/136).int32(message.maxHp);
if (message.characterState != null && Object.hasOwnProperty.call(message, "characterState")) if (message.characterState != null && Object.hasOwnProperty.call(message, "characterState"))
writer.uint32(/* id 16, wireType 0 =*/128).int32(message.characterState); writer.uint32(/* id 18, wireType 0 =*/144).int32(message.characterState);
if (message.inAir != null && Object.hasOwnProperty.call(message, "inAir"))
writer.uint32(/* id 19, wireType 0 =*/152).bool(message.inAir);
if (message.name != null && Object.hasOwnProperty.call(message, "name")) if (message.name != null && Object.hasOwnProperty.call(message, "name"))
writer.uint32(/* id 17, wireType 2 =*/138).string(message.name); writer.uint32(/* id 20, wireType 2 =*/162).string(message.name);
if (message.displayName != null && Object.hasOwnProperty.call(message, "displayName")) if (message.displayName != null && Object.hasOwnProperty.call(message, "displayName"))
writer.uint32(/* id 18, wireType 2 =*/146).string(message.displayName); writer.uint32(/* id 21, wireType 2 =*/170).string(message.displayName);
if (message.avatar != null && Object.hasOwnProperty.call(message, "avatar")) if (message.avatar != null && Object.hasOwnProperty.call(message, "avatar"))
writer.uint32(/* id 19, wireType 2 =*/154).string(message.avatar); writer.uint32(/* id 22, wireType 2 =*/178).string(message.avatar);
return writer; return writer;
}; };
@ -1496,58 +1529,70 @@ $root.protos = (function() {
break; break;
} }
case 6: { case 6: {
message.speed = reader.int32(); message.velX = reader.int32();
break; break;
} }
case 7: { case 7: {
message.battleState = reader.int32(); message.velY = reader.int32();
break; break;
} }
case 8: { case 8: {
message.joinIndex = reader.int32(); message.speed = reader.int32();
break; break;
} }
case 9: { case 9: {
message.colliderRadius = reader.double(); message.battleState = reader.int32();
break; break;
} }
case 10: { case 10: {
message.removed = reader.bool(); message.joinIndex = reader.int32();
break; break;
} }
case 11: { case 11: {
message.score = reader.int32(); message.colliderRadius = reader.double();
break; break;
} }
case 12: { case 12: {
message.lastMoveGmtMillis = reader.int32(); message.removed = reader.bool();
break; break;
} }
case 13: { case 13: {
message.framesToRecover = reader.int32(); message.score = reader.int32();
break; break;
} }
case 14: { case 14: {
message.hp = reader.int32(); message.lastMoveGmtMillis = reader.int32();
break; break;
} }
case 15: { case 15: {
message.maxHp = reader.int32(); message.framesToRecover = reader.int32();
break; break;
} }
case 16: { case 16: {
message.characterState = reader.int32(); message.hp = reader.int32();
break; break;
} }
case 17: { case 17: {
message.name = reader.string(); message.maxHp = reader.int32();
break; break;
} }
case 18: { case 18: {
message.displayName = reader.string(); message.characterState = reader.int32();
break; break;
} }
case 19: { case 19: {
message.inAir = reader.bool();
break;
}
case 20: {
message.name = reader.string();
break;
}
case 21: {
message.displayName = reader.string();
break;
}
case 22: {
message.avatar = reader.string(); message.avatar = reader.string();
break; break;
} }
@ -1601,6 +1646,12 @@ $root.protos = (function() {
if (message.dirY != null && message.hasOwnProperty("dirY")) if (message.dirY != null && message.hasOwnProperty("dirY"))
if (!$util.isInteger(message.dirY)) if (!$util.isInteger(message.dirY))
return "dirY: integer expected"; return "dirY: integer expected";
if (message.velX != null && message.hasOwnProperty("velX"))
if (!$util.isInteger(message.velX))
return "velX: integer expected";
if (message.velY != null && message.hasOwnProperty("velY"))
if (!$util.isInteger(message.velY))
return "velY: integer expected";
if (message.speed != null && message.hasOwnProperty("speed")) if (message.speed != null && message.hasOwnProperty("speed"))
if (!$util.isInteger(message.speed)) if (!$util.isInteger(message.speed))
return "speed: integer expected"; return "speed: integer expected";
@ -1634,6 +1685,9 @@ $root.protos = (function() {
if (message.characterState != null && message.hasOwnProperty("characterState")) if (message.characterState != null && message.hasOwnProperty("characterState"))
if (!$util.isInteger(message.characterState)) if (!$util.isInteger(message.characterState))
return "characterState: integer expected"; return "characterState: integer expected";
if (message.inAir != null && message.hasOwnProperty("inAir"))
if (typeof message.inAir !== "boolean")
return "inAir: boolean expected";
if (message.name != null && message.hasOwnProperty("name")) if (message.name != null && message.hasOwnProperty("name"))
if (!$util.isString(message.name)) if (!$util.isString(message.name))
return "name: string expected"; return "name: string expected";
@ -1668,6 +1722,10 @@ $root.protos = (function() {
message.dirX = object.dirX | 0; message.dirX = object.dirX | 0;
if (object.dirY != null) if (object.dirY != null)
message.dirY = object.dirY | 0; message.dirY = object.dirY | 0;
if (object.velX != null)
message.velX = object.velX | 0;
if (object.velY != null)
message.velY = object.velY | 0;
if (object.speed != null) if (object.speed != null)
message.speed = object.speed | 0; message.speed = object.speed | 0;
if (object.battleState != null) if (object.battleState != null)
@ -1690,6 +1748,8 @@ $root.protos = (function() {
message.maxHp = object.maxHp | 0; message.maxHp = object.maxHp | 0;
if (object.characterState != null) if (object.characterState != null)
message.characterState = object.characterState | 0; message.characterState = object.characterState | 0;
if (object.inAir != null)
message.inAir = Boolean(object.inAir);
if (object.name != null) if (object.name != null)
message.name = String(object.name); message.name = String(object.name);
if (object.displayName != null) if (object.displayName != null)
@ -1718,6 +1778,8 @@ $root.protos = (function() {
object.virtualGridY = 0; object.virtualGridY = 0;
object.dirX = 0; object.dirX = 0;
object.dirY = 0; object.dirY = 0;
object.velX = 0;
object.velY = 0;
object.speed = 0; object.speed = 0;
object.battleState = 0; object.battleState = 0;
object.joinIndex = 0; object.joinIndex = 0;
@ -1729,6 +1791,7 @@ $root.protos = (function() {
object.hp = 0; object.hp = 0;
object.maxHp = 0; object.maxHp = 0;
object.characterState = 0; object.characterState = 0;
object.inAir = false;
object.name = ""; object.name = "";
object.displayName = ""; object.displayName = "";
object.avatar = ""; object.avatar = "";
@ -1743,6 +1806,10 @@ $root.protos = (function() {
object.dirX = message.dirX; object.dirX = message.dirX;
if (message.dirY != null && message.hasOwnProperty("dirY")) if (message.dirY != null && message.hasOwnProperty("dirY"))
object.dirY = message.dirY; object.dirY = message.dirY;
if (message.velX != null && message.hasOwnProperty("velX"))
object.velX = message.velX;
if (message.velY != null && message.hasOwnProperty("velY"))
object.velY = message.velY;
if (message.speed != null && message.hasOwnProperty("speed")) if (message.speed != null && message.hasOwnProperty("speed"))
object.speed = message.speed; object.speed = message.speed;
if (message.battleState != null && message.hasOwnProperty("battleState")) if (message.battleState != null && message.hasOwnProperty("battleState"))
@ -1765,6 +1832,8 @@ $root.protos = (function() {
object.maxHp = message.maxHp; object.maxHp = message.maxHp;
if (message.characterState != null && message.hasOwnProperty("characterState")) if (message.characterState != null && message.hasOwnProperty("characterState"))
object.characterState = message.characterState; object.characterState = message.characterState;
if (message.inAir != null && message.hasOwnProperty("inAir"))
object.inAir = message.inAir;
if (message.name != null && message.hasOwnProperty("name")) if (message.name != null && message.hasOwnProperty("name"))
object.name = message.name; object.name = message.name;
if (message.displayName != null && message.hasOwnProperty("displayName")) if (message.displayName != null && message.hasOwnProperty("displayName"))
@ -1812,6 +1881,7 @@ $root.protos = (function() {
* @property {number|null} [dx] InputFrameDecoded dx * @property {number|null} [dx] InputFrameDecoded dx
* @property {number|null} [dy] InputFrameDecoded dy * @property {number|null} [dy] InputFrameDecoded dy
* @property {number|null} [btnALevel] InputFrameDecoded btnALevel * @property {number|null} [btnALevel] InputFrameDecoded btnALevel
* @property {number|null} [btnBLevel] InputFrameDecoded btnBLevel
*/ */
/** /**
@ -1853,6 +1923,14 @@ $root.protos = (function() {
*/ */
InputFrameDecoded.prototype.btnALevel = 0; InputFrameDecoded.prototype.btnALevel = 0;
/**
* InputFrameDecoded btnBLevel.
* @member {number} btnBLevel
* @memberof protos.InputFrameDecoded
* @instance
*/
InputFrameDecoded.prototype.btnBLevel = 0;
/** /**
* Creates a new InputFrameDecoded instance using the specified properties. * Creates a new InputFrameDecoded instance using the specified properties.
* @function create * @function create
@ -1883,6 +1961,8 @@ $root.protos = (function() {
writer.uint32(/* id 2, wireType 0 =*/16).int32(message.dy); writer.uint32(/* id 2, wireType 0 =*/16).int32(message.dy);
if (message.btnALevel != null && Object.hasOwnProperty.call(message, "btnALevel")) if (message.btnALevel != null && Object.hasOwnProperty.call(message, "btnALevel"))
writer.uint32(/* id 3, wireType 0 =*/24).int32(message.btnALevel); writer.uint32(/* id 3, wireType 0 =*/24).int32(message.btnALevel);
if (message.btnBLevel != null && Object.hasOwnProperty.call(message, "btnBLevel"))
writer.uint32(/* id 4, wireType 0 =*/32).int32(message.btnBLevel);
return writer; return writer;
}; };
@ -1929,6 +2009,10 @@ $root.protos = (function() {
message.btnALevel = reader.int32(); message.btnALevel = reader.int32();
break; break;
} }
case 4: {
message.btnBLevel = reader.int32();
break;
}
default: default:
reader.skipType(tag & 7); reader.skipType(tag & 7);
break; break;
@ -1973,6 +2057,9 @@ $root.protos = (function() {
if (message.btnALevel != null && message.hasOwnProperty("btnALevel")) if (message.btnALevel != null && message.hasOwnProperty("btnALevel"))
if (!$util.isInteger(message.btnALevel)) if (!$util.isInteger(message.btnALevel))
return "btnALevel: integer expected"; return "btnALevel: integer expected";
if (message.btnBLevel != null && message.hasOwnProperty("btnBLevel"))
if (!$util.isInteger(message.btnBLevel))
return "btnBLevel: integer expected";
return null; return null;
}; };
@ -1994,6 +2081,8 @@ $root.protos = (function() {
message.dy = object.dy | 0; message.dy = object.dy | 0;
if (object.btnALevel != null) if (object.btnALevel != null)
message.btnALevel = object.btnALevel | 0; message.btnALevel = object.btnALevel | 0;
if (object.btnBLevel != null)
message.btnBLevel = object.btnBLevel | 0;
return message; return message;
}; };
@ -2014,6 +2103,7 @@ $root.protos = (function() {
object.dx = 0; object.dx = 0;
object.dy = 0; object.dy = 0;
object.btnALevel = 0; object.btnALevel = 0;
object.btnBLevel = 0;
} }
if (message.dx != null && message.hasOwnProperty("dx")) if (message.dx != null && message.hasOwnProperty("dx"))
object.dx = message.dx; object.dx = message.dx;
@ -2021,6 +2111,8 @@ $root.protos = (function() {
object.dy = message.dy; object.dy = message.dy;
if (message.btnALevel != null && message.hasOwnProperty("btnALevel")) if (message.btnALevel != null && message.hasOwnProperty("btnALevel"))
object.btnALevel = message.btnALevel; object.btnALevel = message.btnALevel;
if (message.btnBLevel != null && message.hasOwnProperty("btnBLevel"))
object.btnBLevel = message.btnBLevel;
return object; return object;
}; };
@ -4453,6 +4545,11 @@ $root.protos = (function() {
* @property {number|null} [spAtkLookupFrames] BattleColliderInfo spAtkLookupFrames * @property {number|null} [spAtkLookupFrames] BattleColliderInfo spAtkLookupFrames
* @property {number|null} [renderCacheSize] BattleColliderInfo renderCacheSize * @property {number|null} [renderCacheSize] BattleColliderInfo renderCacheSize
* @property {Object.<string,protos.MeleeBullet>|null} [meleeSkillConfig] BattleColliderInfo meleeSkillConfig * @property {Object.<string,protos.MeleeBullet>|null} [meleeSkillConfig] BattleColliderInfo meleeSkillConfig
* @property {number|null} [snapIntoPlatformOverlap] BattleColliderInfo snapIntoPlatformOverlap
* @property {number|null} [snapIntoPlatformThreshold] BattleColliderInfo snapIntoPlatformThreshold
* @property {number|null} [jumpingInitVelY] BattleColliderInfo jumpingInitVelY
* @property {number|null} [gravityX] BattleColliderInfo gravityX
* @property {number|null} [gravityY] BattleColliderInfo gravityY
*/ */
/** /**
@ -4681,6 +4778,46 @@ $root.protos = (function() {
*/ */
BattleColliderInfo.prototype.meleeSkillConfig = $util.emptyObject; BattleColliderInfo.prototype.meleeSkillConfig = $util.emptyObject;
/**
* BattleColliderInfo snapIntoPlatformOverlap.
* @member {number} snapIntoPlatformOverlap
* @memberof protos.BattleColliderInfo
* @instance
*/
BattleColliderInfo.prototype.snapIntoPlatformOverlap = 0;
/**
* BattleColliderInfo snapIntoPlatformThreshold.
* @member {number} snapIntoPlatformThreshold
* @memberof protos.BattleColliderInfo
* @instance
*/
BattleColliderInfo.prototype.snapIntoPlatformThreshold = 0;
/**
* BattleColliderInfo jumpingInitVelY.
* @member {number} jumpingInitVelY
* @memberof protos.BattleColliderInfo
* @instance
*/
BattleColliderInfo.prototype.jumpingInitVelY = 0;
/**
* BattleColliderInfo gravityX.
* @member {number} gravityX
* @memberof protos.BattleColliderInfo
* @instance
*/
BattleColliderInfo.prototype.gravityX = 0;
/**
* BattleColliderInfo gravityY.
* @member {number} gravityY
* @memberof protos.BattleColliderInfo
* @instance
*/
BattleColliderInfo.prototype.gravityY = 0;
/** /**
* Creates a new BattleColliderInfo instance using the specified properties. * Creates a new BattleColliderInfo instance using the specified properties.
* @function create * @function create
@ -4766,6 +4903,16 @@ $root.protos = (function() {
writer.uint32(/* id 27, wireType 2 =*/218).fork().uint32(/* id 1, wireType 0 =*/8).int32(keys[i]); writer.uint32(/* id 27, wireType 2 =*/218).fork().uint32(/* id 1, wireType 0 =*/8).int32(keys[i]);
$root.protos.MeleeBullet.encode(message.meleeSkillConfig[keys[i]], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim().ldelim(); $root.protos.MeleeBullet.encode(message.meleeSkillConfig[keys[i]], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim().ldelim();
} }
if (message.snapIntoPlatformOverlap != null && Object.hasOwnProperty.call(message, "snapIntoPlatformOverlap"))
writer.uint32(/* id 28, wireType 1 =*/225).double(message.snapIntoPlatformOverlap);
if (message.snapIntoPlatformThreshold != null && Object.hasOwnProperty.call(message, "snapIntoPlatformThreshold"))
writer.uint32(/* id 29, wireType 1 =*/233).double(message.snapIntoPlatformThreshold);
if (message.jumpingInitVelY != null && Object.hasOwnProperty.call(message, "jumpingInitVelY"))
writer.uint32(/* id 30, wireType 0 =*/240).int32(message.jumpingInitVelY);
if (message.gravityX != null && Object.hasOwnProperty.call(message, "gravityX"))
writer.uint32(/* id 31, wireType 0 =*/248).int32(message.gravityX);
if (message.gravityY != null && Object.hasOwnProperty.call(message, "gravityY"))
writer.uint32(/* id 32, wireType 0 =*/256).int32(message.gravityY);
return writer; return writer;
}; };
@ -4961,6 +5108,26 @@ $root.protos = (function() {
message.meleeSkillConfig[key] = value; message.meleeSkillConfig[key] = value;
break; break;
} }
case 28: {
message.snapIntoPlatformOverlap = reader.double();
break;
}
case 29: {
message.snapIntoPlatformThreshold = reader.double();
break;
}
case 30: {
message.jumpingInitVelY = reader.int32();
break;
}
case 31: {
message.gravityX = reader.int32();
break;
}
case 32: {
message.gravityY = reader.int32();
break;
}
default: default:
reader.skipType(tag & 7); reader.skipType(tag & 7);
break; break;
@ -5099,6 +5266,21 @@ $root.protos = (function() {
} }
} }
} }
if (message.snapIntoPlatformOverlap != null && message.hasOwnProperty("snapIntoPlatformOverlap"))
if (typeof message.snapIntoPlatformOverlap !== "number")
return "snapIntoPlatformOverlap: number expected";
if (message.snapIntoPlatformThreshold != null && message.hasOwnProperty("snapIntoPlatformThreshold"))
if (typeof message.snapIntoPlatformThreshold !== "number")
return "snapIntoPlatformThreshold: number expected";
if (message.jumpingInitVelY != null && message.hasOwnProperty("jumpingInitVelY"))
if (!$util.isInteger(message.jumpingInitVelY))
return "jumpingInitVelY: integer expected";
if (message.gravityX != null && message.hasOwnProperty("gravityX"))
if (!$util.isInteger(message.gravityX))
return "gravityX: integer expected";
if (message.gravityY != null && message.hasOwnProperty("gravityY"))
if (!$util.isInteger(message.gravityY))
return "gravityY: integer expected";
return null; return null;
}; };
@ -5204,6 +5386,16 @@ $root.protos = (function() {
message.meleeSkillConfig[keys[i]] = $root.protos.MeleeBullet.fromObject(object.meleeSkillConfig[keys[i]]); message.meleeSkillConfig[keys[i]] = $root.protos.MeleeBullet.fromObject(object.meleeSkillConfig[keys[i]]);
} }
} }
if (object.snapIntoPlatformOverlap != null)
message.snapIntoPlatformOverlap = Number(object.snapIntoPlatformOverlap);
if (object.snapIntoPlatformThreshold != null)
message.snapIntoPlatformThreshold = Number(object.snapIntoPlatformThreshold);
if (object.jumpingInitVelY != null)
message.jumpingInitVelY = object.jumpingInitVelY | 0;
if (object.gravityX != null)
message.gravityX = object.gravityX | 0;
if (object.gravityY != null)
message.gravityY = object.gravityY | 0;
return message; return message;
}; };
@ -5257,6 +5449,11 @@ $root.protos = (function() {
object.virtualGridToWorldRatio = 0; object.virtualGridToWorldRatio = 0;
object.spAtkLookupFrames = 0; object.spAtkLookupFrames = 0;
object.renderCacheSize = 0; object.renderCacheSize = 0;
object.snapIntoPlatformOverlap = 0;
object.snapIntoPlatformThreshold = 0;
object.jumpingInitVelY = 0;
object.gravityX = 0;
object.gravityY = 0;
} }
if (message.stageName != null && message.hasOwnProperty("stageName")) if (message.stageName != null && message.hasOwnProperty("stageName"))
object.stageName = message.stageName; object.stageName = message.stageName;
@ -5326,6 +5523,16 @@ $root.protos = (function() {
for (var j = 0; j < keys2.length; ++j) for (var j = 0; j < keys2.length; ++j)
object.meleeSkillConfig[keys2[j]] = $root.protos.MeleeBullet.toObject(message.meleeSkillConfig[keys2[j]], options); object.meleeSkillConfig[keys2[j]] = $root.protos.MeleeBullet.toObject(message.meleeSkillConfig[keys2[j]], options);
} }
if (message.snapIntoPlatformOverlap != null && message.hasOwnProperty("snapIntoPlatformOverlap"))
object.snapIntoPlatformOverlap = options.json && !isFinite(message.snapIntoPlatformOverlap) ? String(message.snapIntoPlatformOverlap) : message.snapIntoPlatformOverlap;
if (message.snapIntoPlatformThreshold != null && message.hasOwnProperty("snapIntoPlatformThreshold"))
object.snapIntoPlatformThreshold = options.json && !isFinite(message.snapIntoPlatformThreshold) ? String(message.snapIntoPlatformThreshold) : message.snapIntoPlatformThreshold;
if (message.jumpingInitVelY != null && message.hasOwnProperty("jumpingInitVelY"))
object.jumpingInitVelY = message.jumpingInitVelY;
if (message.gravityX != null && message.hasOwnProperty("gravityX"))
object.gravityX = message.gravityX;
if (message.gravityY != null && message.hasOwnProperty("gravityY"))
object.gravityY = message.gravityY;
return object; return object;
}; };

View File

@ -11,24 +11,28 @@ function polygonStr(body) {
return JSON.stringify(coords); return JSON.stringify(coords);
} }
const playerCollider = collisionSys.createPolygon(1269.665, 1353.335, [[0, 0], [64, 0], [64, 64], [0, 64]]); const playerCollider1 = collisionSys.createPolygon(944.000, 676.000, [[0, 0], [24, 0], [24, 48], [0, 48]]);
playerCollider1.data = {isPlayer: true};
const playerCollider2 = collisionSys.createPolygon(958.000, 724.000, [[0, 0], [24, 0], [24, 48], [0, 48]]);
playerCollider2.data = {isPlayer: true};
const barrierCollider1 = collisionSys.createPolygon(1277.7159000000001, 1570.5575, [[642.5696, 319.159], [0, 319.15680000000003], [5.7286, 0], [643.7451, 0.9014999999999986]]); const barrierCollider1 = collisionSys.createPolygon(1277.7159000000001, 1570.5575, [[642.5696, 319.159], [0, 319.15680000000003], [5.7286, 0], [643.7451, 0.9014999999999986]]);
const barrierCollider2 = collisionSys.createPolygon(1289.039, 1318.0805, [[628.626, 54.254500000000064], [0, 56.03250000000003], [0.42449999999999477, 1.1229999999999905], [625.9715000000001, 0]]); const barrierCollider2 = collisionSys.createPolygon(1289.039, 1318.0805, [[628.626, 54.254500000000064], [0, 56.03250000000003], [0.42449999999999477, 1.1229999999999905], [625.9715000000001, 0]]);
const barrierCollider3 = collisionSys.createPolygon(1207, 1310, [[69, 581], [0, 579], [8, 3], [79, 0]]); const barrierCollider3 = collisionSys.createPolygon(1207, 1310, [[69, 581], [0, 579], [8, 3], [79, 0]]);
playerCollider.x += -2.98;
playerCollider.y += -50.0;
collisionSys.update(); collisionSys.update();
const effPushback = [0.0, 0.0]; const effPushback = [0.0, 0.0];
const result = collisionSys.createResult(); const result = collisionSys.createResult();
const potentials = playerCollider.potentials(); // Check collision for player1
const potentials = playerCollider1.potentials();
for (const barrier of potentials) { for (const potential of potentials) {
if (!playerCollider.collides(barrier, result)) continue; if (null == potential.data || true != potential.data.isPlayer) continue;
console.log(`Collided player potential of a=${polygonStr(playerCollider1)}: b=${polygonStr(potential)}`);
if (!playerCollider1.collides(potential, result)) continue;
console.log(`Collided player of a=${polygonStr(playerCollider1)}: b=${polygonStr(potential)}`);
const pushbackX = result.overlap * result.overlap_x; const pushbackX = result.overlap * result.overlap_x;
const pushbackY = result.overlap * result.overlap_y; const pushbackY = result.overlap * result.overlap_y;
console.log(`Overlapped: a=${polygonStr(result.a)}, b=${polygonStr(result.b)}, pushbackX=${pushbackX}, pushbackY=${pushbackY}`); console.log(`Overlapped: a=${polygonStr(result.a)}, b=${polygonStr(result.b)}, pushbackX=${pushbackX}, pushbackY=${pushbackY}`);