2022-09-20 15:50:01 +00:00
package models
import (
2022-11-09 06:20:26 +00:00
. "battle_srv/common"
"battle_srv/common/utils"
. "battle_srv/protos"
2022-10-14 08:08:22 +00:00
. "dnmshared"
2022-11-10 13:28:46 +00:00
. "dnmshared/sharedprotos"
2022-10-31 00:50:40 +00:00
"encoding/xml"
2022-09-20 15:50:01 +00:00
"fmt"
"github.com/golang/protobuf/proto"
"github.com/gorilla/websocket"
2022-09-29 04:21:04 +00:00
"github.com/solarlune/resolv"
2022-09-20 15:50:01 +00:00
"go.uber.org/zap"
2022-10-31 00:50:40 +00:00
"io/ioutil"
2022-09-20 15:50:01 +00:00
"math/rand"
2022-10-31 00:50:40 +00:00
"os"
"path/filepath"
2022-09-24 04:01:50 +00:00
"strings"
2022-09-20 15:50:01 +00:00
"sync"
"sync/atomic"
"time"
)
const (
UPSYNC_MSG_ACT_HB_PING = int32 ( 1 )
UPSYNC_MSG_ACT_PLAYER_CMD = int32 ( 2 )
UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK = int32 ( 3 )
2022-10-02 16:22:05 +00:00
DOWNSYNC_MSG_ACT_HB_REQ = int32 ( 1 )
DOWNSYNC_MSG_ACT_INPUT_BATCH = int32 ( 2 )
DOWNSYNC_MSG_ACT_BATTLE_STOPPED = int32 ( 3 )
DOWNSYNC_MSG_ACT_FORCED_RESYNC = int32 ( 4 )
2022-10-01 15:54:48 +00:00
DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START = int32 ( - 1 )
DOWNSYNC_MSG_ACT_BATTLE_START = int32 ( 0 )
2022-11-30 13:51:06 +00:00
DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED = int32 ( - 98 )
2022-09-20 15:50:01 +00:00
)
const (
MAGIC_JOIN_INDEX_DEFAULT = 0
MAGIC_JOIN_INDEX_INVALID = - 1
)
const (
COLLISION_CATEGORY_CONTROLLED_PLAYER = ( 1 << 1 )
2022-09-26 15:09:18 +00:00
COLLISION_CATEGORY_BARRIER = ( 1 << 2 )
COLLISION_MASK_FOR_CONTROLLED_PLAYER = ( COLLISION_CATEGORY_BARRIER )
COLLISION_MASK_FOR_BARRIER = ( COLLISION_CATEGORY_CONTROLLED_PLAYER )
2022-09-29 04:21:04 +00:00
COLLISION_PLAYER_INDEX_PREFIX = ( 1 << 17 )
COLLISION_BARRIER_INDEX_PREFIX = ( 1 << 16 )
2022-11-23 14:11:28 +00:00
COLLISION_BULLET_INDEX_PREFIX = ( 1 << 15 )
2022-09-20 15:50:01 +00:00
)
2022-10-04 03:24:47 +00:00
const (
MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED = - 1
MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED = - 2
)
2022-11-24 09:48:07 +00:00
const (
ATK_CHARACTER_STATE_IDLE1 = 0
ATK_CHARACTER_STATE_WALKING = 1
ATK_CHARACTER_STATE_ATK1 = 2
ATK_CHARACTER_STATE_ATKED1 = 3
)
2022-11-25 03:20:05 +00:00
const (
2022-11-27 13:33:34 +00:00
DEFAULT_PLAYER_RADIUS = float64 ( 12 )
2022-11-25 03:20:05 +00:00
)
2022-11-10 10:18:00 +00:00
// These directions are chosen such that when speed is changed to "(speedX+delta, speedY+delta)" for any of them, the direction is unchanged.
2022-09-20 15:50:01 +00:00
var DIRECTION_DECODER = [ ] [ ] int32 {
{ 0 , 0 } ,
2022-11-10 10:18:00 +00:00
{ 0 , + 2 } ,
{ 0 , - 2 } ,
2022-09-20 15:50:01 +00:00
{ + 2 , 0 } ,
{ - 2 , 0 } ,
2022-11-10 10:18:00 +00:00
{ + 1 , + 1 } ,
{ - 1 , - 1 } ,
{ + 1 , - 1 } ,
{ - 1 , + 1 } ,
2022-09-26 15:09:18 +00:00
}
2022-09-20 15:50:01 +00:00
type RoomBattleState struct {
IDLE int32
WAITING int32
PREPARE int32
IN_BATTLE int32
STOPPING_BATTLE_FOR_SETTLEMENT int32
IN_SETTLEMENT int32
IN_DISMISSAL int32
}
type BattleStartCbType func ( )
type SignalToCloseConnCbType func ( customRetCode int , customRetMsg string )
// A single instance containing only "named constant integers" to be shared by all threads.
var RoomBattleStateIns RoomBattleState
func InitRoomBattleStateIns ( ) {
RoomBattleStateIns = RoomBattleState {
IDLE : 0 ,
WAITING : - 1 ,
PREPARE : 10000000 ,
IN_BATTLE : 10000001 ,
STOPPING_BATTLE_FOR_SETTLEMENT : 10000002 ,
IN_SETTLEMENT : 10000003 ,
IN_DISMISSAL : 10000004 ,
}
}
func calRoomScore ( inRoomPlayerCount int32 , roomPlayerCnt int , currentRoomBattleState int32 ) float32 {
x := float32 ( inRoomPlayerCount ) / float32 ( roomPlayerCnt )
d := ( x - 0.5 )
d2 := d * d
return - 7.8125 * d2 + 5.0 - float32 ( currentRoomBattleState )
}
type Room struct {
2022-11-09 04:19:29 +00:00
Id int32
Capacity int
collisionSpaceOffsetX float64
collisionSpaceOffsetY float64
Players map [ int32 ] * Player
PlayersArr [ ] * Player // ordered by joinIndex
2022-11-24 13:56:34 +00:00
Space * resolv . Space
2022-11-09 04:19:29 +00:00
CollisionSysMap map [ int32 ] * resolv . Object
2022-09-20 15:50:01 +00:00
/ * *
* The following ` PlayerDownsyncSessionDict ` is NOT individually put
* under ` type Player struct ` for a reason .
*
* Upon each connection establishment , a new instance ` player Player ` is created for the given ` playerId ` .
* To be specific , if
* - that ` playerId == 42 ` accidentally reconnects in just several milliseconds after a passive disconnection , e . g . due to bad wireless signal strength , and
* - that ` type Player struct ` contains a ` DownsyncSession ` field
*
* , then we might have to
* - clean up ` previousPlayerInstance.DownsyncSession `
* - initialize ` currentPlayerInstance.DownsyncSession `
*
* to avoid chaotic flaws .
*
* Moreover , during the invocation of ` PlayerSignalToCloseDict ` , the ` Player ` instance is supposed to be deallocated ( though not synchronously ) .
* /
PlayerDownsyncSessionDict map [ int32 ] * websocket . Conn
PlayerSignalToCloseDict map [ int32 ] SignalToCloseConnCbType
Score float32
State int32
Index int
2022-09-29 04:21:04 +00:00
RenderFrameId int32
2022-11-29 04:49:49 +00:00
CurDynamicsRenderFrameId int32 // [WARNING] The dynamics of backend is ALWAYS MOVING FORWARD BY ALL-CONFIRMED INPUTFRAMES (either by upsync or forced), i.e. no rollback; Moreover when "true == BackendDynamicsEnabled" we always have "Room.CurDynamicsRenderFrameId >= Room.RenderFrameId" because each "all-confirmed inputFrame" is applied on "all applicable renderFrames" in one-go hence often sees a future "renderFrame" earlier
2022-09-20 15:50:01 +00:00
EffectivePlayerCount int32
DismissalWaitGroup sync . WaitGroup
Barriers map [ int32 ] * Barrier
2022-10-03 03:42:19 +00:00
InputsBuffer * RingBuffer // Indices are STRICTLY consecutive
2022-11-30 08:53:48 +00:00
InputsBufferLock sync . Mutex
2022-12-04 15:36:55 +00:00
RenderFrameBuffer * RingBuffer // Indices are STRICTLY consecutive
2022-09-20 15:50:01 +00:00
LastAllConfirmedInputFrameId int32
LastAllConfirmedInputFrameIdWithChange int32
LastAllConfirmedInputList [ ] uint64
JoinIndexBooleanArr [ ] bool
2022-11-20 16:23:01 +00:00
2022-11-30 16:30:35 +00:00
BackendDynamicsEnabled bool
LastRenderFrameIdTriggeredAt int64
PlayerDefaultSpeed int32
2022-11-20 16:23:01 +00:00
2022-12-01 03:35:56 +00:00
BulletBattleLocalIdCounter int32
dilutedRollbackEstimatedDtNanos int64
2022-11-30 08:53:48 +00:00
BattleColliderInfo // Compositing to send centralized magic numbers
2022-09-20 15:50:01 +00:00
}
func ( pR * Room ) updateScore ( ) {
pR . Score = calRoomScore ( pR . EffectivePlayerCount , pR . Capacity , pR . State )
}
func ( pR * Room ) AddPlayerIfPossible ( pPlayerFromDbInit * Player , session * websocket . Conn , signalToCloseConnOfThisPlayer SignalToCloseConnCbType ) bool {
playerId := pPlayerFromDbInit . Id
// TODO: Any thread-safety concern for accessing "pR" here?
if RoomBattleStateIns . IDLE != pR . State && RoomBattleStateIns . WAITING != pR . State {
Logger . Warn ( "AddPlayerIfPossible error, roomState:" , zap . Any ( "playerId" , playerId ) , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "roomState" , pR . State ) , zap . Any ( "roomEffectivePlayerCount" , pR . EffectivePlayerCount ) )
return false
}
if _ , existent := pR . Players [ playerId ] ; existent {
Logger . Warn ( "AddPlayerIfPossible error, existing in the room.PlayersDict:" , zap . Any ( "playerId" , playerId ) , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "roomState" , pR . State ) , zap . Any ( "roomEffectivePlayerCount" , pR . EffectivePlayerCount ) )
return false
}
defer pR . onPlayerAdded ( playerId )
2022-10-04 03:24:47 +00:00
pPlayerFromDbInit . AckingFrameId = - 1
2022-09-20 15:50:01 +00:00
pPlayerFromDbInit . AckingInputFrameId = - 1
2022-10-04 03:24:47 +00:00
pPlayerFromDbInit . LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED
2022-09-20 15:50:01 +00:00
pPlayerFromDbInit . BattleState = PlayerBattleStateIns . ADDED_PENDING_BATTLE_COLLIDER_ACK
2022-11-25 03:20:05 +00:00
pPlayerFromDbInit . Speed = pR . PlayerDefaultSpeed // Hardcoded
pPlayerFromDbInit . ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded
2022-09-20 15:50:01 +00:00
pR . Players [ playerId ] = pPlayerFromDbInit
pR . PlayerDownsyncSessionDict [ playerId ] = session
pR . PlayerSignalToCloseDict [ playerId ] = signalToCloseConnOfThisPlayer
return true
}
func ( pR * Room ) ReAddPlayerIfPossible ( pTmpPlayerInstance * Player , session * websocket . Conn , signalToCloseConnOfThisPlayer SignalToCloseConnCbType ) bool {
playerId := pTmpPlayerInstance . Id
// TODO: Any thread-safety concern for accessing "pR" and "pEffectiveInRoomPlayerInstance" here?
if RoomBattleStateIns . PREPARE != pR . State && RoomBattleStateIns . WAITING != pR . State && RoomBattleStateIns . IN_BATTLE != pR . State && RoomBattleStateIns . IN_SETTLEMENT != pR . State && RoomBattleStateIns . IN_DISMISSAL != pR . State {
Logger . Warn ( "ReAddPlayerIfPossible error due to roomState:" , zap . Any ( "playerId" , playerId ) , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "roomState" , pR . State ) , zap . Any ( "roomEffectivePlayerCount" , pR . EffectivePlayerCount ) )
return false
}
if _ , existent := pR . Players [ playerId ] ; ! existent {
Logger . Warn ( "ReAddPlayerIfPossible error due to player nonexistent for room:" , zap . Any ( "playerId" , playerId ) , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "roomState" , pR . State ) , zap . Any ( "roomEffectivePlayerCount" , pR . EffectivePlayerCount ) )
return false
}
/ *
* WARNING : The "pTmpPlayerInstance *Player" used here is a temporarily constructed
* instance from "<proj-root>/battle_srv/ws/serve.go" , which is NOT the same as "pR.Players[pTmpPlayerInstance.Id]" .
* -- YFLu
* /
defer pR . onPlayerReAdded ( playerId )
pEffectiveInRoomPlayerInstance := pR . Players [ playerId ]
2022-10-04 03:24:47 +00:00
pEffectiveInRoomPlayerInstance . AckingFrameId = - 1
2022-09-20 15:50:01 +00:00
pEffectiveInRoomPlayerInstance . AckingInputFrameId = - 1
2022-10-04 03:24:47 +00:00
pEffectiveInRoomPlayerInstance . LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED
2022-09-20 15:50:01 +00:00
pEffectiveInRoomPlayerInstance . BattleState = PlayerBattleStateIns . READDED_PENDING_BATTLE_COLLIDER_ACK
2022-11-25 03:20:05 +00:00
pEffectiveInRoomPlayerInstance . Speed = pR . PlayerDefaultSpeed // Hardcoded
pEffectiveInRoomPlayerInstance . ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded
pR . PlayerDownsyncSessionDict [ playerId ] = session
pR . PlayerSignalToCloseDict [ playerId ] = signalToCloseConnOfThisPlayer
2022-09-20 15:50:01 +00:00
2022-10-04 03:24:47 +00:00
Logger . Warn ( "ReAddPlayerIfPossible finished." , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "playerId" , playerId ) , zap . Any ( "joinIndex" , pEffectiveInRoomPlayerInstance . JoinIndex ) , zap . Any ( "playerBattleState" , pEffectiveInRoomPlayerInstance . BattleState ) , zap . Any ( "roomState" , pR . State ) , zap . Any ( "roomEffectivePlayerCount" , pR . EffectivePlayerCount ) , zap . Any ( "AckingFrameId" , pEffectiveInRoomPlayerInstance . AckingFrameId ) , zap . Any ( "AckingInputFrameId" , pEffectiveInRoomPlayerInstance . AckingInputFrameId ) , zap . Any ( "LastSentInputFrameId" , pEffectiveInRoomPlayerInstance . LastSentInputFrameId ) )
2022-09-20 15:50:01 +00:00
return true
}
func ( pR * Room ) ChooseStage ( ) error {
/ *
* We use the verb "refresh" here to imply that upon invocation of this function , all colliders will be recovered if they were destroyed in the previous battle .
*
* -- YFLu , 2019 - 0 9 - 04
* /
pwd , err := os . Getwd ( )
2022-10-14 08:08:22 +00:00
if nil != err {
panic ( err )
}
2022-09-20 15:50:01 +00:00
rand . Seed ( time . Now ( ) . Unix ( ) )
2022-11-16 14:11:56 +00:00
stageNameList := [ ] string { "dungeon" /*"dungeon", "simple", "richsoil" */ }
2022-09-20 15:50:01 +00:00
chosenStageIndex := rand . Int ( ) % len ( stageNameList ) // Hardcoded temporarily. -- YFLu
pR . StageName = stageNameList [ chosenStageIndex ]
relativePathForAllStages := "../frontend/assets/resources/map"
relativePathForChosenStage := fmt . Sprintf ( "%s/%s" , relativePathForAllStages , pR . StageName )
pTmxMapIns := & TmxMap { }
absDirPathContainingDirectlyTmxFile := filepath . Join ( pwd , relativePathForChosenStage )
absTmxFilePath := fmt . Sprintf ( "%s/map.tmx" , absDirPathContainingDirectlyTmxFile )
if ! filepath . IsAbs ( absTmxFilePath ) {
panic ( "Tmx filepath must be absolute!" )
}
byteArr , err := ioutil . ReadFile ( absTmxFilePath )
if nil != err {
panic ( err )
}
err = xml . Unmarshal ( byteArr , pTmxMapIns )
if nil != err {
panic ( err )
}
2022-11-22 09:12:51 +00:00
// Obtain the content of `gidBoundariesMap`.
gidBoundariesMap := make ( map [ int ] StrToPolygon2DListMap , 0 )
2022-09-20 15:50:01 +00:00
for _ , tileset := range pTmxMapIns . Tilesets {
relativeTsxFilePath := fmt . Sprintf ( "%s/%s" , filepath . Join ( pwd , relativePathForChosenStage ) , tileset . Source ) // Note that "TmxTileset.Source" can be a string of "relative path".
absTsxFilePath , err := filepath . Abs ( relativeTsxFilePath )
if nil != err {
panic ( err )
}
if ! filepath . IsAbs ( absTsxFilePath ) {
panic ( "Filepath must be absolute!" )
}
byteArrOfTsxFile , err := ioutil . ReadFile ( absTsxFilePath )
if nil != err {
panic ( err )
}
2022-11-22 09:12:51 +00:00
DeserializeTsxToColliderDict ( pTmxMapIns , byteArrOfTsxFile , int ( tileset . FirstGid ) , gidBoundariesMap )
2022-09-20 15:50:01 +00:00
}
2022-11-22 09:12:51 +00:00
stageDiscreteW , stageDiscreteH , stageTileW , stageTileH , strToVec2DListMap , strToPolygon2DListMap , err := ParseTmxLayersAndGroups ( pTmxMapIns , gidBoundariesMap )
2022-09-20 15:50:01 +00:00
if nil != err {
panic ( err )
}
pR . StageDiscreteW = stageDiscreteW
pR . StageDiscreteH = stageDiscreteH
pR . StageTileW = stageTileW
pR . StageTileH = stageTileH
2022-11-20 16:23:01 +00:00
pR . StrToVec2DListMap = strToVec2DListMap
pR . StrToPolygon2DListMap = strToPolygon2DListMap
2022-09-20 15:50:01 +00:00
2022-11-20 16:23:01 +00:00
barrierPolygon2DList := * ( strToPolygon2DListMap [ "Barrier" ] )
2022-09-20 15:50:01 +00:00
2022-09-26 15:09:18 +00:00
var barrierLocalIdInBattle int32 = 0
2022-11-09 06:20:26 +00:00
for _ , polygon2DUnaligned := range barrierPolygon2DList . Eles {
2022-10-22 05:38:10 +00:00
polygon2D := AlignPolygon2DToBoundingBox ( polygon2DUnaligned )
2022-09-20 15:50:01 +00:00
/ *
// For debug-printing only.
2022-09-26 15:09:18 +00:00
Logger . Info ( "ChooseStage printing polygon2D for barrierPolygon2DList" , zap . Any ( "barrierLocalIdInBattle" , barrierLocalIdInBattle ) , zap . Any ( "polygon2D.Anchor" , polygon2D . Anchor ) , zap . Any ( "polygon2D.Points" , polygon2D . Points ) )
2022-09-20 15:50:01 +00:00
* /
2022-09-26 15:09:18 +00:00
pR . Barriers [ barrierLocalIdInBattle ] = & Barrier {
2022-09-29 04:21:04 +00:00
Boundary : polygon2D ,
2022-09-20 15:50:01 +00:00
}
2022-09-26 15:09:18 +00:00
barrierLocalIdInBattle ++
2022-09-20 15:50:01 +00:00
}
return nil
}
2022-09-29 04:21:04 +00:00
func ( pR * Room ) ConvertToInputFrameId ( renderFrameId int32 , inputDelayFrames int32 ) int32 {
2022-10-01 12:45:38 +00:00
// Specifically when "renderFrameId < inputDelayFrames", the result is 0.
2022-09-29 04:21:04 +00:00
return ( ( renderFrameId - inputDelayFrames ) >> pR . InputScaleFrames )
}
2022-10-02 16:22:05 +00:00
func ( pR * Room ) ConvertToGeneratingRenderFrameId ( inputFrameId int32 ) int32 {
return ( inputFrameId << pR . InputScaleFrames )
}
2022-09-29 04:21:04 +00:00
func ( pR * Room ) ConvertToFirstUsedRenderFrameId ( inputFrameId int32 , inputDelayFrames int32 ) int32 {
return ( ( inputFrameId << pR . InputScaleFrames ) + inputDelayFrames )
}
func ( pR * Room ) ConvertToLastUsedRenderFrameId ( inputFrameId int32 , inputDelayFrames int32 ) int32 {
2022-10-01 12:45:38 +00:00
return ( ( inputFrameId << pR . InputScaleFrames ) + inputDelayFrames + ( 1 << pR . InputScaleFrames ) - 1 )
2022-09-20 15:50:01 +00:00
}
2022-10-02 16:22:05 +00:00
func ( pR * Room ) RenderFrameBufferString ( ) string {
2022-12-04 15:36:55 +00:00
return fmt . Sprintf ( "{renderFrameId: %d, stRenderFrameId: %d, edRenderFrameId: %d, curDynamicsRenderFrameId: %d}" , pR . RenderFrameId , pR . RenderFrameBuffer . StFrameId , pR . RenderFrameBuffer . EdFrameId , pR . CurDynamicsRenderFrameId )
2022-10-02 16:22:05 +00:00
}
2022-10-03 03:42:19 +00:00
func ( pR * Room ) InputsBufferString ( allDetails bool ) string {
2022-10-01 12:45:38 +00:00
if allDetails {
2022-10-02 03:33:40 +00:00
// Appending of the array of strings can be very SLOW due to on-demand heap allocation! Use this printing with caution.
s := make ( [ ] string , 0 )
2022-10-03 03:42:19 +00:00
s = append ( s , fmt . Sprintf ( "{renderFrameId: %v, stInputFrameId: %v, edInputFrameId: %v, lastAllConfirmedInputFrameIdWithChange: %v, lastAllConfirmedInputFrameId: %v}" , pR . RenderFrameId , pR . InputsBuffer . StFrameId , pR . InputsBuffer . EdFrameId , pR . LastAllConfirmedInputFrameIdWithChange , pR . LastAllConfirmedInputFrameId ) )
2022-12-04 15:36:55 +00:00
for playerId , player := range pR . PlayersArr {
2022-10-01 12:45:38 +00:00
s = append ( s , fmt . Sprintf ( "{playerId: %v, ackingFrameId: %v, ackingInputFrameId: %v, lastSentInputFrameId: %v}" , playerId , player . AckingFrameId , player . AckingInputFrameId , player . LastSentInputFrameId ) )
}
2022-10-03 03:42:19 +00:00
for i := pR . InputsBuffer . StFrameId ; i < pR . InputsBuffer . EdFrameId ; i ++ {
tmp := pR . InputsBuffer . GetByFrameId ( i )
2022-10-01 12:45:38 +00:00
if nil == tmp {
break
}
2022-11-09 06:20:26 +00:00
f := tmp . ( * InputFrameDownsync )
2022-11-24 09:48:07 +00:00
//s = append(s, fmt.Sprintf("{inputFrameId: %v, inputList: %v, &inputList: %p, confirmedList: %v}", f.InputFrameId, f.InputList, &(f.InputList), f.ConfirmedList))
2022-10-01 12:45:38 +00:00
s = append ( s , fmt . Sprintf ( "{inputFrameId: %v, inputList: %v, confirmedList: %v}" , f . InputFrameId , f . InputList , f . ConfirmedList ) )
2022-09-20 15:50:01 +00:00
}
2022-10-02 16:22:05 +00:00
return strings . Join ( s , "; " )
2022-10-02 03:33:40 +00:00
} else {
2022-10-03 03:42:19 +00:00
return fmt . Sprintf ( "{renderFrameId: %d, stInputFrameId: %d, edInputFrameId: %d, lastAllConfirmedInputFrameIdWithChange: %d, lastAllConfirmedInputFrameId: %d}" , pR . RenderFrameId , pR . InputsBuffer . StFrameId , pR . InputsBuffer . EdFrameId , pR . LastAllConfirmedInputFrameIdWithChange , pR . LastAllConfirmedInputFrameId )
2022-10-02 03:33:40 +00:00
}
2022-09-20 15:50:01 +00:00
}
func ( pR * Room ) StartBattle ( ) {
if RoomBattleStateIns . WAITING != pR . State {
2022-11-30 13:51:06 +00:00
Logger . Debug ( "[StartBattle] Battle not started due to not being WAITING!" , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "roomState" , pR . State ) )
2022-09-20 15:50:01 +00:00
return
}
2022-09-29 04:21:04 +00:00
pR . RenderFrameId = 0
2022-10-01 12:45:38 +00:00
// Initialize the "collisionSys" as well as "RenderFrameBuffer"
2022-09-29 04:21:04 +00:00
pR . CurDynamicsRenderFrameId = 0
2022-11-09 06:20:26 +00:00
kickoffFrame := & RoomDownsyncFrame {
2022-10-01 12:45:38 +00:00
Id : pR . RenderFrameId ,
2022-11-21 09:27:32 +00:00
Players : toPbPlayers ( pR . Players , false ) ,
2022-10-01 12:45:38 +00:00
CountdownNanos : pR . BattleDurationNanos ,
}
pR . RenderFrameBuffer . Put ( kickoffFrame )
2022-09-20 15:50:01 +00:00
2022-09-29 04:21:04 +00:00
// Refresh "Colliders"
2022-11-08 13:38:23 +00:00
spaceW := pR . StageDiscreteW * pR . StageTileW
spaceH := pR . StageDiscreteH * pR . StageTileH
2022-11-12 14:53:35 +00:00
pR . collisionSpaceOffsetX , pR . collisionSpaceOffsetY = float64 ( spaceW ) * 0.5 , float64 ( spaceH ) * 0.5
2022-11-09 04:19:29 +00:00
pR . refreshColliders ( spaceW , spaceH )
2022-09-20 15:50:01 +00:00
/ * *
2022-11-30 13:51:06 +00:00
* Will be triggered from a goroutine which executes the critical ` Room.AddPlayerIfPossible ` , thus the ` battleMainLoop ` should be detached .
* All of the consecutive stages , e . g . settlement , dismissal , should share the same goroutine with ` battleMainLoop ` .
*
* As "defer" is only applicable to function scope , the use of "pR.InputsBufferLock" within "battleMainLoop" is embedded into each subroutine call .
* /
2022-09-20 15:50:01 +00:00
battleMainLoop := func ( ) {
defer func ( ) {
2022-09-24 04:01:50 +00:00
if r := recover ( ) ; r != nil {
Logger . Error ( "battleMainLoop, recovery spot#1, recovered from: " , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "panic" , r ) )
}
2022-11-30 16:30:35 +00:00
pR . StopBattleForSettlement ( )
Logger . Info ( fmt . Sprintf ( "The `battleMainLoop` for roomId=%v is stopped@renderFrameId=%v, with battleDurationFrames=%v:\n%v" , pR . Id , pR . RenderFrameId , pR . BattleDurationFrames , pR . InputsBufferString ( false ) ) ) // This takes sometime to print
2022-09-20 15:50:01 +00:00
pR . onBattleStoppedForSettlement ( )
} ( )
2022-10-10 04:17:23 +00:00
pR . LastRenderFrameIdTriggeredAt = utils . UnixtimeNano ( )
2022-09-20 15:50:01 +00:00
Logger . Info ( "The `battleMainLoop` is started for:" , zap . Any ( "roomId" , pR . Id ) )
for {
2022-11-29 16:04:52 +00:00
stCalculation := utils . UnixtimeNano ( )
2022-10-10 04:17:23 +00:00
elapsedNanosSinceLastFrameIdTriggered := stCalculation - pR . LastRenderFrameIdTriggeredAt
2022-11-30 16:30:35 +00:00
if elapsedNanosSinceLastFrameIdTriggered < pR . RollbackEstimatedDtNanos {
Logger . Debug ( fmt . Sprintf ( "renderFrameId=%v@roomId=%v: Is backend running too fast? elapsedNanosSinceLastFrameIdTriggered=%v" , pR . RenderFrameId , pR . Id , elapsedNanosSinceLastFrameIdTriggered ) )
2022-10-10 04:17:23 +00:00
}
if pR . RenderFrameId > pR . BattleDurationFrames {
2022-10-14 03:49:04 +00:00
return
2022-09-20 15:50:01 +00:00
}
if swapped := atomic . CompareAndSwapInt32 ( & pR . State , RoomBattleStateIns . IN_BATTLE , RoomBattleStateIns . IN_BATTLE ) ; ! swapped {
return
}
2022-11-30 08:53:48 +00:00
if 0 == pR . RenderFrameId {
2022-12-04 15:36:55 +00:00
for _ , player := range pR . PlayersArr {
playerId := player . Id
2022-11-30 13:51:06 +00:00
thatPlayerBattleState := atomic . LoadInt32 ( & ( player . BattleState ) ) // Might be changed in "OnPlayerDisconnected/OnPlayerLost" from other threads
2022-11-30 08:53:48 +00:00
// [WARNING] DON'T try to send any message to an inactive player!
2022-11-30 13:51:06 +00:00
switch thatPlayerBattleState {
2022-11-30 08:53:48 +00:00
case PlayerBattleStateIns . DISCONNECTED :
case PlayerBattleStateIns . LOST :
2022-12-01 04:17:30 +00:00
case PlayerBattleStateIns . EXPELLED_DURING_GAME :
case PlayerBattleStateIns . EXPELLED_IN_DISMISSAL :
2022-11-30 08:53:48 +00:00
continue
}
kickoffFrame := pR . RenderFrameBuffer . GetByFrameId ( 0 ) . ( * RoomDownsyncFrame )
pR . sendSafely ( kickoffFrame , nil , DOWNSYNC_MSG_ACT_BATTLE_START , playerId )
}
Logger . Info ( fmt . Sprintf ( "In `battleMainLoop` for roomId=%v sent out kickoffFrame" , pR . Id ) )
2022-09-20 15:50:01 +00:00
}
2022-10-01 12:45:38 +00:00
2022-11-30 08:53:48 +00:00
dynamicsDuration := int64 ( 0 )
2022-11-30 13:51:06 +00:00
2022-11-30 08:53:48 +00:00
// Prefab and buffer backend inputFrameDownsync
2022-11-30 16:30:35 +00:00
if pR . BackendDynamicsEnabled {
2022-12-04 15:36:55 +00:00
unconfirmedMask , inputsBufferSnapshot := pR . doBattleMainLoopPerTickBackendDynamicsWithProperLocking ( & dynamicsDuration )
2022-11-29 16:04:52 +00:00
if 0 < unconfirmedMask {
// Otherwise no need to downsync immediately
2022-12-04 15:36:55 +00:00
Logger . Debug ( fmt . Sprintf ( "roomId=%v, room.RenderFrameId=%v, room.CurDynamicsRenderFrameId=%v, room.LastAllConfirmedInputFrameId=%v, unconfirmedMask=%v" , pR . Id , pR . RenderFrameId , pR . CurDynamicsRenderFrameId , pR . LastAllConfirmedInputFrameId , unconfirmedMask ) )
pR . downsyncToAllPlayers ( pR . LastAllConfirmedInputFrameId , unconfirmedMask , inputsBufferSnapshot )
2022-10-01 12:45:38 +00:00
}
}
2022-09-29 04:21:04 +00:00
pR . RenderFrameId ++
2022-11-29 04:49:49 +00:00
pR . LastRenderFrameIdTriggeredAt = utils . UnixtimeNano ( )
elapsedInCalculation := ( pR . LastRenderFrameIdTriggeredAt - stCalculation )
2022-11-30 16:30:35 +00:00
if elapsedInCalculation > pR . RollbackEstimatedDtNanos {
Logger . Warn ( fmt . Sprintf ( "SLOW FRAME! Elapsed time statistics: roomId=%v, room.RenderFrameId=%v, elapsedInCalculation=%v ns, dynamicsDuration=%v ns, RollbackEstimatedDtNanos=%v" , pR . Id , pR . RenderFrameId , elapsedInCalculation , dynamicsDuration , pR . RollbackEstimatedDtNanos ) )
2022-10-01 12:45:38 +00:00
}
2022-12-01 03:35:56 +00:00
time . Sleep ( time . Duration ( pR . dilutedRollbackEstimatedDtNanos - elapsedInCalculation ) )
2022-09-20 15:50:01 +00:00
}
}
pR . onBattlePrepare ( func ( ) {
pR . onBattleStarted ( ) // NOTE: Deliberately not using `defer`.
go battleMainLoop ( )
} )
}
2022-10-03 03:42:19 +00:00
func ( pR * Room ) toDiscreteInputsBufferIndex ( inputFrameId int32 , joinIndex int32 ) int32 {
return ( inputFrameId << 2 ) + joinIndex // allowing joinIndex upto 15
}
2022-11-09 06:20:26 +00:00
func ( pR * Room ) OnBattleCmdReceived ( pReq * WsReq ) {
2022-11-30 08:53:48 +00:00
// [WARNING] This function "OnBattleCmdReceived" could be called by different ws sessions and thus from different threads!
2022-12-04 15:36:55 +00:00
// TODO: Put a rate limiter on this function!
2022-09-20 15:50:01 +00:00
if swapped := atomic . CompareAndSwapInt32 ( & pR . State , RoomBattleStateIns . IN_BATTLE , RoomBattleStateIns . IN_BATTLE ) ; ! swapped {
return
}
playerId := pReq . PlayerId
2022-11-30 08:53:48 +00:00
var player * Player = nil
var existent bool = false
2022-09-20 15:50:01 +00:00
inputFrameUpsyncBatch := pReq . InputFrameUpsyncBatch
ackingFrameId := pReq . AckingFrameId
ackingInputFrameId := pReq . AckingInputFrameId
2022-11-30 08:53:48 +00:00
if player , existent = pR . Players [ playerId ] ; ! existent {
2022-10-01 12:45:38 +00:00
Logger . Warn ( fmt . Sprintf ( "upcmd player doesn't exist: roomId=%v, playerId=%v" , pR . Id , playerId ) )
2022-09-29 04:21:04 +00:00
return
}
2022-09-20 15:50:01 +00:00
2022-11-30 08:53:48 +00:00
atomic . StoreInt32 ( & ( player . AckingFrameId ) , ackingFrameId )
atomic . StoreInt32 ( & ( player . AckingInputFrameId ) , ackingInputFrameId )
2022-09-20 15:50:01 +00:00
2022-12-04 15:36:55 +00:00
newAllConfirmedCount := pR . markConfirmationIfApplicable ( inputFrameUpsyncBatch , playerId , player )
// [WARNING] By now "pR.InputsBufferLock" is unlocked
2022-11-30 08:53:48 +00:00
if 0 < newAllConfirmedCount {
// Downsync new all-confirmed inputFrames asap
2022-12-04 15:36:55 +00:00
pR . downsyncToAllPlayers ( pR . LastAllConfirmedInputFrameId , uint64 ( 0 ) , pR . createInputsBufferSnapshot ( ) )
2022-09-20 15:50:01 +00:00
}
}
2022-11-09 06:20:26 +00:00
func ( pR * Room ) onInputFrameDownsyncAllConfirmed ( inputFrameDownsync * InputFrameDownsync , playerId int32 ) {
2022-12-04 15:36:55 +00:00
// [WARNING] This function MUST BE called while "pR.InputsBufferLock" is locked!
2022-10-01 12:45:38 +00:00
inputFrameId := inputFrameDownsync . InputFrameId
if - 1 == pR . LastAllConfirmedInputFrameIdWithChange || false == pR . equalInputLists ( inputFrameDownsync . InputList , pR . LastAllConfirmedInputList ) {
2022-10-02 16:22:05 +00:00
if - 1 == playerId {
2022-10-10 13:58:29 +00:00
Logger . Debug ( fmt . Sprintf ( "Key inputFrame change: roomId=%v, newInputFrameId=%v, lastInputFrameId=%v, newInputList=%v, lastInputList=%v, InputsBuffer=%v" , pR . Id , inputFrameId , pR . LastAllConfirmedInputFrameId , inputFrameDownsync . InputList , pR . LastAllConfirmedInputList , pR . InputsBufferString ( false ) ) )
2022-10-02 16:22:05 +00:00
} else {
2022-10-10 13:58:29 +00:00
Logger . Debug ( fmt . Sprintf ( "Key inputFrame change: roomId=%v, playerId=%v, newInputFrameId=%v, lastInputFrameId=%v, newInputList=%v, lastInputList=%v, InputsBuffer=%v" , pR . Id , playerId , inputFrameId , pR . LastAllConfirmedInputFrameId , inputFrameDownsync . InputList , pR . LastAllConfirmedInputList , pR . InputsBufferString ( false ) ) )
2022-10-02 16:22:05 +00:00
}
2022-12-04 15:36:55 +00:00
pR . LastAllConfirmedInputFrameIdWithChange = inputFrameId
2022-09-29 04:21:04 +00:00
}
2022-12-04 15:36:55 +00:00
pR . LastAllConfirmedInputFrameId = inputFrameId
2022-09-29 04:21:04 +00:00
for i , v := range inputFrameDownsync . InputList {
// To avoid potential misuse of pointers
pR . LastAllConfirmedInputList [ i ] = v
}
2022-10-02 16:22:05 +00:00
if - 1 == playerId {
2022-10-03 03:42:19 +00:00
Logger . Debug ( fmt . Sprintf ( "inputFrame lifecycle#2[forced-allconfirmed]: roomId=%v, InputsBuffer=%v" , pR . Id , pR . InputsBufferString ( false ) ) )
2022-10-02 16:22:05 +00:00
} else {
2022-10-10 13:58:29 +00:00
Logger . Debug ( fmt . Sprintf ( "inputFrame lifecycle#2[allconfirmed]: roomId=%v, playerId=%v, InputsBuffer=%v" , pR . Id , playerId , pR . InputsBufferString ( false ) ) )
2022-09-29 04:21:04 +00:00
}
}
2022-09-20 15:50:01 +00:00
func ( pR * Room ) equalInputLists ( lhs [ ] uint64 , rhs [ ] uint64 ) bool {
if len ( lhs ) != len ( rhs ) {
return false
}
for i , _ := range lhs {
if lhs [ i ] != rhs [ i ] {
return false
}
}
return true
}
func ( pR * Room ) StopBattleForSettlement ( ) {
if RoomBattleStateIns . IN_BATTLE != pR . State {
return
}
pR . State = RoomBattleStateIns . STOPPING_BATTLE_FOR_SETTLEMENT
Logger . Info ( "Stopping the `battleMainLoop` for:" , zap . Any ( "roomId" , pR . Id ) )
2022-09-29 04:21:04 +00:00
pR . RenderFrameId ++
2022-09-20 15:50:01 +00:00
for playerId , _ := range pR . Players {
2022-11-09 06:20:26 +00:00
assembledFrame := RoomDownsyncFrame {
2022-09-29 04:21:04 +00:00
Id : pR . RenderFrameId ,
2022-11-21 09:27:32 +00:00
Players : toPbPlayers ( pR . Players , false ) ,
2022-09-20 15:50:01 +00:00
CountdownNanos : - 1 , // TODO: Replace this magic constant!
}
2022-10-02 16:22:05 +00:00
pR . sendSafely ( & assembledFrame , nil , DOWNSYNC_MSG_ACT_BATTLE_STOPPED , playerId )
2022-09-20 15:50:01 +00:00
}
// Note that `pR.onBattleStoppedForSettlement` will be called by `battleMainLoop`.
}
func ( pR * Room ) onBattleStarted ( ) {
if RoomBattleStateIns . PREPARE != pR . State {
return
}
pR . State = RoomBattleStateIns . IN_BATTLE
pR . updateScore ( )
}
func ( pR * Room ) onBattlePrepare ( cb BattleStartCbType ) {
if RoomBattleStateIns . WAITING != pR . State {
Logger . Warn ( "[onBattlePrepare] Battle not started after all players' battle state checked!" , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "roomState" , pR . State ) )
return
}
pR . State = RoomBattleStateIns . PREPARE
Logger . Info ( "Battle state transitted to RoomBattleStateIns.PREPARE for:" , zap . Any ( "roomId" , pR . Id ) )
2022-11-09 06:20:26 +00:00
battleReadyToStartFrame := & RoomDownsyncFrame {
2022-10-01 15:54:48 +00:00
Id : DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START ,
2022-11-21 09:27:32 +00:00
Players : toPbPlayers ( pR . Players , true ) ,
2022-09-24 04:01:50 +00:00
CountdownNanos : pR . BattleDurationNanos ,
2022-09-20 15:50:01 +00:00
}
2022-10-02 03:33:40 +00:00
Logger . Info ( "Sending out frame for RoomBattleState.PREPARE:" , zap . Any ( "battleReadyToStartFrame" , battleReadyToStartFrame ) )
2022-09-20 15:50:01 +00:00
for _ , player := range pR . Players {
2022-10-01 15:54:48 +00:00
pR . sendSafely ( battleReadyToStartFrame , nil , DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START , player . Id )
2022-09-20 15:50:01 +00:00
}
battlePreparationNanos := int64 ( 6000000000 )
preparationLoop := func ( ) {
defer func ( ) {
Logger . Info ( "The `preparationLoop` is stopped for:" , zap . Any ( "roomId" , pR . Id ) )
cb ( )
} ( )
preparationLoopStartedNanos := utils . UnixtimeNano ( )
totalElapsedNanos := int64 ( 0 )
for {
if totalElapsedNanos > battlePreparationNanos {
break
}
now := utils . UnixtimeNano ( )
totalElapsedNanos = ( now - preparationLoopStartedNanos )
time . Sleep ( time . Duration ( battlePreparationNanos - totalElapsedNanos ) )
}
}
go preparationLoop ( )
}
func ( pR * Room ) onBattleStoppedForSettlement ( ) {
if RoomBattleStateIns . STOPPING_BATTLE_FOR_SETTLEMENT != pR . State {
return
}
defer func ( ) {
pR . onSettlementCompleted ( )
} ( )
pR . State = RoomBattleStateIns . IN_SETTLEMENT
Logger . Info ( "The room is in settlement:" , zap . Any ( "roomId" , pR . Id ) )
// TODO: Some settlement labor.
}
func ( pR * Room ) onSettlementCompleted ( ) {
pR . Dismiss ( )
}
func ( pR * Room ) Dismiss ( ) {
if RoomBattleStateIns . IN_SETTLEMENT != pR . State {
return
}
pR . State = RoomBattleStateIns . IN_DISMISSAL
if 0 < len ( pR . Players ) {
Logger . Info ( "The room is in dismissal:" , zap . Any ( "roomId" , pR . Id ) )
for playerId , _ := range pR . Players {
Logger . Info ( "Adding 1 to pR.DismissalWaitGroup:" , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "playerId" , playerId ) )
pR . DismissalWaitGroup . Add ( 1 )
pR . expelPlayerForDismissal ( playerId )
pR . DismissalWaitGroup . Done ( )
Logger . Info ( "Decremented 1 to pR.DismissalWaitGroup:" , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "playerId" , playerId ) )
}
pR . DismissalWaitGroup . Wait ( )
}
2022-10-02 03:33:40 +00:00
pR . OnDismissed ( )
2022-09-20 15:50:01 +00:00
}
2022-10-02 03:33:40 +00:00
func ( pR * Room ) OnDismissed ( ) {
2022-09-20 15:50:01 +00:00
// Always instantiates new HeapRAM blocks and let the old blocks die out due to not being retained by any root reference.
2022-11-24 09:48:07 +00:00
pR . BulletBattleLocalIdCounter = 0
2022-11-13 04:52:17 +00:00
pR . WorldToVirtualGridRatio = float64 ( 1000 )
2022-11-09 04:19:29 +00:00
pR . VirtualGridToWorldRatio = float64 ( 1.0 ) / pR . WorldToVirtualGridRatio // this is a one-off computation, should avoid division in iterations
2022-11-20 16:23:01 +00:00
pR . SpAtkLookupFrames = 5
2022-11-27 13:33:34 +00:00
pR . PlayerDefaultSpeed = int32 ( float64 ( 1 ) * pR . WorldToVirtualGridRatio ) // in virtual grids per frame
2022-09-20 15:50:01 +00:00
pR . Players = make ( map [ int32 ] * Player )
2022-09-29 04:21:04 +00:00
pR . PlayersArr = make ( [ ] * Player , pR . Capacity )
pR . CollisionSysMap = make ( map [ int32 ] * resolv . Object )
2022-09-20 15:50:01 +00:00
pR . PlayerDownsyncSessionDict = make ( map [ int32 ] * websocket . Conn )
pR . PlayerSignalToCloseDict = make ( map [ int32 ] SignalToCloseConnCbType )
2022-10-02 03:33:40 +00:00
pR . JoinIndexBooleanArr = make ( [ ] bool , pR . Capacity )
pR . Barriers = make ( map [ int32 ] * Barrier )
2022-11-24 11:45:48 +00:00
pR . RenderCacheSize = 1024
2022-11-23 04:30:30 +00:00
pR . RenderFrameBuffer = NewRingBuffer ( pR . RenderCacheSize )
2022-11-30 16:30:35 +00:00
pR . InputsBuffer = NewRingBuffer ( ( pR . RenderCacheSize >> 1 ) + 1 )
2022-09-20 15:50:01 +00:00
pR . LastAllConfirmedInputFrameId = - 1
pR . LastAllConfirmedInputFrameIdWithChange = - 1
pR . LastAllConfirmedInputList = make ( [ ] uint64 , pR . Capacity )
2022-10-02 03:33:40 +00:00
pR . RenderFrameId = 0
pR . CurDynamicsRenderFrameId = 0
pR . InputDelayFrames = 8
2022-11-29 13:32:18 +00:00
pR . NstDelayFrames = 4
2022-10-02 03:33:40 +00:00
pR . InputScaleFrames = uint32 ( 2 )
pR . ServerFps = 60
2022-10-14 03:49:04 +00:00
pR . RollbackEstimatedDtMillis = 16.667 // Use fixed-and-low-precision to mitigate the inconsistent floating-point-number issue between Golang and JavaScript
2022-11-30 16:30:35 +00:00
pR . RollbackEstimatedDtNanos = 16666666 // A little smaller than the actual per frame time, just for logging FAST FRAME
2022-12-04 15:36:55 +00:00
dilutedServerFps := float64 ( 59.5 )
pR . dilutedRollbackEstimatedDtNanos = pR . RollbackEstimatedDtNanos * int64 ( float64 ( pR . ServerFps ) / dilutedServerFps )
2022-10-10 04:17:23 +00:00
pR . BattleDurationFrames = 30 * pR . ServerFps
pR . BattleDurationNanos = int64 ( pR . BattleDurationFrames ) * ( pR . RollbackEstimatedDtNanos + 1 )
2022-10-02 03:33:40 +00:00
pR . InputFrameUpsyncDelayTolerance = 2
2022-11-29 13:32:18 +00:00
pR . MaxChasingRenderFramesPerUpdate = 8
2022-09-20 15:50:01 +00:00
2022-11-30 13:51:06 +00:00
pR . BackendDynamicsEnabled = true // [WARNING] When "false", recovery upon reconnection wouldn't work!
2022-11-24 09:48:07 +00:00
punchSkillId := int32 ( 1 )
2022-11-24 11:45:48 +00:00
pR . MeleeSkillConfig = make ( map [ int32 ] * MeleeBullet , 0 )
pR . MeleeSkillConfig [ punchSkillId ] = & MeleeBullet {
// for offender
2022-11-27 11:38:26 +00:00
StartupFrames : int32 ( 10 ) ,
2022-11-25 00:21:03 +00:00
ActiveFrames : int32 ( 3 ) ,
2022-11-27 13:33:34 +00:00
RecoveryFrames : int32 ( 34 ) ,
2022-11-27 11:38:26 +00:00
RecoveryFramesOnBlock : int32 ( 34 ) ,
RecoveryFramesOnHit : int32 ( 34 ) ,
2022-11-24 11:45:48 +00:00
Moveforward : & Vec2D {
X : 0 ,
Y : 0 ,
} ,
2022-11-27 11:38:26 +00:00
HitboxOffset : float64 ( 12.0 ) , // should be about the radius of the PlayerCollider
2022-11-24 11:45:48 +00:00
HitboxSize : & Vec2D {
2022-11-27 11:38:26 +00:00
X : float64 ( 23.0 ) ,
2022-11-27 13:33:34 +00:00
Y : float64 ( 32.0 ) ,
2022-11-24 11:45:48 +00:00
} ,
// for defender
HitStunFrames : int32 ( 18 ) ,
BlockStunFrames : int32 ( 9 ) ,
2022-11-27 11:38:26 +00:00
Pushback : float64 ( 8.0 ) ,
2022-11-24 11:45:48 +00:00
ReleaseTriggerType : int32 ( 1 ) , // 1: rising-edge, 2: falling-edge
Damage : int32 ( 5 ) ,
2022-11-24 09:48:07 +00:00
}
2022-10-25 02:42:36 +00:00
2022-09-20 15:50:01 +00:00
pR . ChooseStage ( )
pR . EffectivePlayerCount = 0
// [WARNING] It's deliberately ordered such that "pR.State = RoomBattleStateIns.IDLE" is put AFTER all the refreshing operations above.
pR . State = RoomBattleStateIns . IDLE
pR . updateScore ( )
Logger . Info ( "The room is completely dismissed:" , zap . Any ( "roomId" , pR . Id ) )
}
func ( pR * Room ) expelPlayerDuringGame ( playerId int32 ) {
2022-12-01 04:17:30 +00:00
if signalToCloseConnOfThisPlayer , existent := pR . PlayerSignalToCloseDict [ playerId ] ; existent {
signalToCloseConnOfThisPlayer ( Constants . RetCode . UnknownError , "" ) // TODO: Specify an error code
}
pR . onPlayerExpelledDuringGame ( playerId )
2022-09-20 15:50:01 +00:00
}
func ( pR * Room ) expelPlayerForDismissal ( playerId int32 ) {
2022-12-01 04:17:30 +00:00
if signalToCloseConnOfThisPlayer , existent := pR . PlayerSignalToCloseDict [ playerId ] ; existent {
signalToCloseConnOfThisPlayer ( Constants . RetCode . UnknownError , "" ) // TODO: Specify an error code
}
2022-09-20 15:50:01 +00:00
pR . onPlayerExpelledForDismissal ( playerId )
}
func ( pR * Room ) onPlayerExpelledDuringGame ( playerId int32 ) {
pR . onPlayerLost ( playerId )
}
func ( pR * Room ) onPlayerExpelledForDismissal ( playerId int32 ) {
pR . onPlayerLost ( playerId )
Logger . Info ( "onPlayerExpelledForDismissal:" , zap . Any ( "playerId" , playerId ) , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "nowRoomBattleState" , pR . State ) , zap . Any ( "nowRoomEffectivePlayerCount" , pR . EffectivePlayerCount ) )
}
func ( pR * Room ) OnPlayerDisconnected ( playerId int32 ) {
defer func ( ) {
if r := recover ( ) ; r != nil {
Logger . Error ( "Room OnPlayerDisconnected, recovery spot#1, recovered from: " , zap . Any ( "playerId" , playerId ) , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "panic" , r ) )
}
} ( )
2022-11-29 13:32:18 +00:00
if player , existent := pR . Players [ playerId ] ; existent {
2022-11-30 13:51:06 +00:00
thatPlayerBattleState := atomic . LoadInt32 ( & ( player . BattleState ) )
switch thatPlayerBattleState {
2022-09-20 15:50:01 +00:00
case PlayerBattleStateIns . DISCONNECTED :
case PlayerBattleStateIns . LOST :
case PlayerBattleStateIns . EXPELLED_DURING_GAME :
case PlayerBattleStateIns . EXPELLED_IN_DISMISSAL :
Logger . Info ( "Room OnPlayerDisconnected[early return #1]:" , zap . Any ( "playerId" , playerId ) , zap . Any ( "playerBattleState" , pR . Players [ playerId ] . BattleState ) , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "nowRoomBattleState" , pR . State ) , zap . Any ( "nowRoomEffectivePlayerCount" , pR . EffectivePlayerCount ) )
return
}
} else {
// Not even the "pR.Players[playerId]" exists.
Logger . Info ( "Room OnPlayerDisconnected[early return #2]:" , zap . Any ( "playerId" , playerId ) , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "nowRoomBattleState" , pR . State ) , zap . Any ( "nowRoomEffectivePlayerCount" , pR . EffectivePlayerCount ) )
return
}
2022-11-29 13:32:18 +00:00
currRoomBattleState := atomic . LoadInt32 ( & ( pR . State ) )
switch currRoomBattleState {
2022-09-20 15:50:01 +00:00
case RoomBattleStateIns . WAITING :
pR . onPlayerLost ( playerId )
delete ( pR . Players , playerId ) // Note that this statement MUST be put AFTER `pR.onPlayerLost(...)` to avoid nil pointer exception.
if 0 == pR . EffectivePlayerCount {
2022-11-29 13:32:18 +00:00
atomic . StoreInt32 ( & ( pR . State ) , RoomBattleStateIns . IDLE )
2022-09-20 15:50:01 +00:00
}
pR . updateScore ( )
Logger . Info ( "Player disconnected while room is at RoomBattleStateIns.WAITING:" , zap . Any ( "playerId" , playerId ) , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "nowRoomBattleState" , pR . State ) , zap . Any ( "nowRoomEffectivePlayerCount" , pR . EffectivePlayerCount ) )
default :
2022-11-29 13:32:18 +00:00
atomic . StoreInt32 ( & ( pR . Players [ playerId ] . BattleState ) , PlayerBattleStateIns . DISCONNECTED )
2022-09-20 15:50:01 +00:00
pR . clearPlayerNetworkSession ( playerId ) // Still need clear the network session pointers, because "OnPlayerDisconnected" is only triggered from "signalToCloseConnOfThisPlayer" in "ws/serve.go", when the same player reconnects the network session pointers will be re-assigned
2022-11-30 16:30:35 +00:00
Logger . Warn ( "OnPlayerDisconnected finished:" , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "playerId" , playerId ) , zap . Any ( "playerBattleState" , pR . Players [ playerId ] . BattleState ) , zap . Any ( "nowRoomBattleState" , pR . State ) , zap . Any ( "nowRoomEffectivePlayerCount" , pR . EffectivePlayerCount ) )
2022-09-20 15:50:01 +00:00
}
}
func ( pR * Room ) onPlayerLost ( playerId int32 ) {
defer func ( ) {
if r := recover ( ) ; r != nil {
Logger . Error ( "Room OnPlayerLost, recovery spot, recovered from: " , zap . Any ( "playerId" , playerId ) , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "panic" , r ) )
}
} ( )
if player , existent := pR . Players [ playerId ] ; existent {
2022-11-29 13:32:18 +00:00
atomic . StoreInt32 ( & ( player . BattleState ) , PlayerBattleStateIns . LOST )
2022-09-20 15:50:01 +00:00
pR . clearPlayerNetworkSession ( playerId )
pR . EffectivePlayerCount --
indiceInJoinIndexBooleanArr := int ( player . JoinIndex - 1 )
if ( 0 <= indiceInJoinIndexBooleanArr ) && ( indiceInJoinIndexBooleanArr < len ( pR . JoinIndexBooleanArr ) ) {
pR . JoinIndexBooleanArr [ indiceInJoinIndexBooleanArr ] = false
} else {
2022-11-30 16:30:35 +00:00
Logger . Warn ( "Room OnPlayerLost, pR.JoinIndexBooleanArr is out of range: " , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "playerId" , playerId ) , zap . Any ( "indiceInJoinIndexBooleanArr" , indiceInJoinIndexBooleanArr ) , zap . Any ( "len(pR.JoinIndexBooleanArr)" , len ( pR . JoinIndexBooleanArr ) ) )
2022-09-20 15:50:01 +00:00
}
player . JoinIndex = MAGIC_JOIN_INDEX_INVALID
2022-11-30 16:30:35 +00:00
Logger . Warn ( "OnPlayerLost: " , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "playerId" , playerId ) , zap . Any ( "resulted pR.JoinIndexBooleanArr" , pR . JoinIndexBooleanArr ) )
2022-09-20 15:50:01 +00:00
}
}
func ( pR * Room ) clearPlayerNetworkSession ( playerId int32 ) {
if _ , y := pR . PlayerDownsyncSessionDict [ playerId ] ; y {
2022-11-30 16:30:35 +00:00
Logger . Debug ( "clearPlayerNetworkSession:" , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "playerId" , playerId ) )
2022-09-20 15:50:01 +00:00
delete ( pR . PlayerDownsyncSessionDict , playerId )
delete ( pR . PlayerSignalToCloseDict , playerId )
}
}
func ( pR * Room ) onPlayerAdded ( playerId int32 ) {
pR . EffectivePlayerCount ++
if 1 == pR . EffectivePlayerCount {
pR . State = RoomBattleStateIns . WAITING
}
for index , value := range pR . JoinIndexBooleanArr {
if false == value {
pR . Players [ playerId ] . JoinIndex = int32 ( index ) + 1
pR . JoinIndexBooleanArr [ index ] = true
// Lazily assign the initial position of "Player" for "RoomDownsyncFrame".
2022-11-20 16:23:01 +00:00
playerPosList := * ( pR . StrToVec2DListMap [ "PlayerStartingPos" ] )
2022-11-09 06:20:26 +00:00
if index > len ( playerPosList . Eles ) {
2022-09-20 15:50:01 +00:00
panic ( fmt . Sprintf ( "onPlayerAdded error, index >= len(playerPosList), roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v" , pR . Id , playerId , pR . State , pR . EffectivePlayerCount ) )
}
2022-11-09 06:20:26 +00:00
playerPos := playerPosList . Eles [ index ]
2022-09-20 15:50:01 +00:00
if nil == playerPos {
panic ( fmt . Sprintf ( "onPlayerAdded error, nil == playerPos, roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v" , pR . Id , playerId , pR . State , pR . EffectivePlayerCount ) )
}
2022-11-12 12:34:38 +00:00
pR . Players [ playerId ] . VirtualGridX , pR . Players [ playerId ] . VirtualGridY = WorldToVirtualGridPos ( playerPos . X , playerPos . Y , pR . WorldToVirtualGridRatio )
2022-11-25 03:20:05 +00:00
// Hardcoded initial character orientation/facing
if 0 == ( pR . Players [ playerId ] . JoinIndex % 2 ) {
pR . Players [ playerId ] . DirX = - 2
pR . Players [ playerId ] . DirY = 0
} else {
pR . Players [ playerId ] . DirX = + 2
pR . Players [ playerId ] . DirY = 0
}
2022-09-20 15:50:01 +00:00
break
}
}
pR . updateScore ( )
2022-11-30 13:51:06 +00:00
Logger . Info ( "onPlayerAdded:" , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "playerId" , playerId ) , zap . Any ( "playerBattleState" , pR . Players [ playerId ] . BattleState ) , zap . Any ( "joinIndex" , pR . Players [ playerId ] . JoinIndex ) , zap . Any ( "EffectivePlayerCount" , pR . EffectivePlayerCount ) , zap . Any ( "resulted pR.JoinIndexBooleanArr" , pR . JoinIndexBooleanArr ) , zap . Any ( "RoomBattleState" , pR . State ) )
2022-09-20 15:50:01 +00:00
}
func ( pR * Room ) onPlayerReAdded ( playerId int32 ) {
/ *
* [ WARNING ]
*
* If a player quits at "RoomBattleState.WAITING" , then his / her re - joining will always invoke ` AddPlayerIfPossible(...) ` . Therefore , this
* function will only be invoked for players who quit the battle at ">RoomBattleState.WAITING" and re - join at "RoomBattleState.IN_BATTLE" , during which the ` pR.JoinIndexBooleanArr ` doesn ' t change .
* /
Logger . Info ( "Room got `onPlayerReAdded` invoked," , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "playerId" , playerId ) , zap . Any ( "resulted pR.JoinIndexBooleanArr" , pR . JoinIndexBooleanArr ) )
pR . updateScore ( )
}
func ( pR * Room ) OnPlayerBattleColliderAcked ( playerId int32 ) bool {
2022-10-04 03:24:47 +00:00
targetPlayer , existing := pR . Players [ playerId ]
if false == existing {
2022-09-20 15:50:01 +00:00
return false
}
2022-11-30 16:30:35 +00:00
Logger . Debug ( fmt . Sprintf ( "OnPlayerBattleColliderAcked-before: roomId=%v, roomState=%v, targetPlayerId=%v, targetPlayerBattleState=%v, capacity=%v, EffectivePlayerCount=%v" , pR . Id , pR . State , targetPlayer . Id , targetPlayer . BattleState , pR . Capacity , pR . EffectivePlayerCount ) )
2022-11-30 13:51:06 +00:00
switch targetPlayer . BattleState {
case PlayerBattleStateIns . ADDED_PENDING_BATTLE_COLLIDER_ACK :
playerAckedFrame := & RoomDownsyncFrame {
Id : pR . RenderFrameId ,
Players : toPbPlayers ( pR . Players , true ) ,
2022-11-26 16:00:39 +00:00
}
2022-11-30 13:51:06 +00:00
// Broadcast normally added player info to all players in the same room
for thatPlayerId , thatPlayer := range pR . Players {
/ *
[ WARNING ]
This ` playerAckedFrame ` is the first ever "RoomDownsyncFrame" for every "PersistentSessionClient on the frontend" , and it goes right after each "BattleColliderInfo" .
By making use of the sequential nature of each ws session , all later "RoomDownsyncFrame" s generated after ` pRoom.StartBattle() ` will be put behind this ` playerAckedFrame ` .
This function is triggered by an upsync message via WebSocket , thus downsync sending is also available by now .
* /
thatPlayerBattleState := atomic . LoadInt32 ( & ( thatPlayer . BattleState ) )
2022-12-01 03:35:56 +00:00
Logger . Debug ( fmt . Sprintf ( "OnPlayerBattleColliderAcked-middle: roomId=%v, roomState=%v, targetPlayerId=%v, targetPlayerBattleState=%v, thatPlayerId=%v, thatPlayerBattleState=%v" , pR . Id , pR . State , targetPlayer . Id , targetPlayer . BattleState , thatPlayer . Id , thatPlayerBattleState ) )
2022-11-30 13:51:06 +00:00
if thatPlayerId == targetPlayer . Id || ( PlayerBattleStateIns . ADDED_PENDING_BATTLE_COLLIDER_ACK == thatPlayerBattleState || PlayerBattleStateIns . ACTIVE == thatPlayerBattleState ) {
2022-11-30 16:30:35 +00:00
Logger . Debug ( fmt . Sprintf ( "OnPlayerBattleColliderAcked-sending DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED: roomId=%v, roomState=%v, targetPlayerId=%v, targetPlayerBattleState=%v, capacity=%v, EffectivePlayerCount=%v" , pR . Id , pR . State , targetPlayer . Id , targetPlayer . BattleState , pR . Capacity , pR . EffectivePlayerCount ) )
2022-11-30 13:51:06 +00:00
pR . sendSafely ( playerAckedFrame , nil , DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED , thatPlayer . Id )
2022-10-02 03:33:40 +00:00
}
}
2022-11-30 13:51:06 +00:00
case PlayerBattleStateIns . READDED_PENDING_BATTLE_COLLIDER_ACK :
// only send resync info to the targetPlayer
// i.e. implies that "targetPlayer.LastSentInputFrameId == MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED"
2022-12-04 15:36:55 +00:00
pR . downsyncToSinglePlayer ( playerId , targetPlayer , pR . LastAllConfirmedInputFrameId , uint64 ( 0 ) , pR . createInputsBufferSnapshot ( ) )
2022-11-30 13:51:06 +00:00
default :
2022-09-20 15:50:01 +00:00
}
2022-10-01 15:54:48 +00:00
targetPlayer . BattleState = PlayerBattleStateIns . ACTIVE
2022-11-30 16:30:35 +00:00
Logger . Debug ( fmt . Sprintf ( "OnPlayerBattleColliderAcked-post-downsync: roomId=%v, roomState=%v, targetPlayerId=%v, targetPlayerBattleState=%v, capacity=%v, EffectivePlayerCount=%v" , pR . Id , pR . State , targetPlayer . Id , targetPlayer . BattleState , pR . Capacity , pR . EffectivePlayerCount ) )
2022-09-20 15:50:01 +00:00
2022-10-01 15:54:48 +00:00
if pR . Capacity == int ( pR . EffectivePlayerCount ) {
2022-09-20 15:50:01 +00:00
allAcked := true
for _ , p := range pR . Players {
if PlayerBattleStateIns . ACTIVE != p . BattleState {
2022-11-30 13:51:06 +00:00
Logger . Warn ( "unexpectedly got an inactive player" , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "playerId" , p . Id ) , zap . Any ( "battleState" , p . BattleState ) )
2022-09-20 15:50:01 +00:00
allAcked = false
break
}
}
if true == allAcked {
pR . StartBattle ( ) // WON'T run if the battle state is not in WAITING.
}
}
pR . updateScore ( )
return true
}
2022-11-30 13:51:06 +00:00
func ( pR * Room ) sendSafely ( roomDownsyncFrame * RoomDownsyncFrame , toSendInputFrameDownsyncs [ ] * InputFrameDownsync , act int32 , playerId int32 ) {
2022-09-20 15:50:01 +00:00
defer func ( ) {
if r := recover ( ) ; r != nil {
2022-11-30 16:30:35 +00:00
Logger . Error ( "sendSafely, recovered from: " , zap . Any ( "roomId" , pR . Id ) , zap . Any ( "playerId" , playerId ) , zap . Any ( "panic" , r ) )
2022-09-20 15:50:01 +00:00
}
} ( )
2022-11-30 16:30:35 +00:00
if playerDownsyncSession , existent := pR . PlayerDownsyncSessionDict [ playerId ] ; existent {
pResp := & WsResp {
Ret : int32 ( Constants . RetCode . Ok ) ,
Act : act ,
Rdf : roomDownsyncFrame ,
InputFrameDownsyncBatch : toSendInputFrameDownsyncs ,
}
2022-09-20 15:50:01 +00:00
2022-11-30 16:30:35 +00:00
theBytes , marshalErr := proto . Marshal ( pResp )
if nil != marshalErr {
panic ( fmt . Sprintf ( "Error marshaling downsync message: roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v" , pR . Id , playerId , pR . State , pR . EffectivePlayerCount ) )
}
2022-09-20 15:50:01 +00:00
2022-11-30 16:30:35 +00:00
if err := playerDownsyncSession . WriteMessage ( websocket . BinaryMessage , theBytes ) ; nil != err {
panic ( fmt . Sprintf ( "Error sending downsync message: roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v, err=%v" , pR . Id , playerId , pR . State , pR . EffectivePlayerCount , err ) )
}
2022-09-20 15:50:01 +00:00
}
}
2022-09-29 04:21:04 +00:00
func ( pR * Room ) shouldPrefabInputFrameDownsync ( renderFrameId int32 ) bool {
return ( ( renderFrameId & ( ( 1 << pR . InputScaleFrames ) - 1 ) ) == 0 )
2022-09-20 15:50:01 +00:00
}
2022-12-04 15:36:55 +00:00
func ( pR * Room ) prefabInputFrameDownsync ( inputFrameId int32 ) * InputFrameDownsync {
2022-09-29 04:21:04 +00:00
/ *
Kindly note that on backend the prefab is much simpler than its frontend counterpart , because frontend will upsync its latest command immediately if there ' s any change w . r . t . its own prev cmd , thus if no upsync received from a frontend ,
- EITHER it ' s due to local lag and bad network ,
- OR there ' s no change w . r . t . to its prev cmd .
* /
2022-11-30 13:51:06 +00:00
2022-11-09 06:20:26 +00:00
var currInputFrameDownsync * InputFrameDownsync = nil
2022-09-26 15:09:18 +00:00
2022-10-03 03:42:19 +00:00
if 0 == inputFrameId && 0 == pR . InputsBuffer . Cnt {
2022-11-09 06:20:26 +00:00
currInputFrameDownsync = & InputFrameDownsync {
2022-09-29 04:21:04 +00:00
InputFrameId : 0 ,
InputList : make ( [ ] uint64 , pR . Capacity ) ,
ConfirmedList : uint64 ( 0 ) ,
}
} else {
2022-11-20 10:53:33 +00:00
tmp := pR . InputsBuffer . GetByFrameId ( inputFrameId - 1 ) // There's no need for the backend to find the "lastAllConfirmed inputs" for prefabbing, either "BackendDynamicsEnabled" is true or false
2022-09-29 04:21:04 +00:00
if nil == tmp {
2022-10-03 03:42:19 +00:00
panic ( fmt . Sprintf ( "Error prefabbing inputFrameDownsync: roomId=%v, InputsBuffer=%v" , pR . Id , pR . InputsBufferString ( false ) ) )
2022-09-29 04:21:04 +00:00
}
2022-11-09 06:20:26 +00:00
prevInputFrameDownsync := tmp . ( * InputFrameDownsync )
2022-11-24 09:48:07 +00:00
currInputList := make ( [ ] uint64 , pR . Capacity ) // Would be a clone of the values
for i , _ := range currInputList {
2022-11-24 11:45:48 +00:00
currInputList [ i ] = ( prevInputFrameDownsync . InputList [ i ] & uint64 ( 15 ) ) // Don't predict attack input!
2022-11-24 09:48:07 +00:00
}
2022-11-09 06:20:26 +00:00
currInputFrameDownsync = & InputFrameDownsync {
2022-09-29 04:21:04 +00:00
InputFrameId : inputFrameId ,
InputList : currInputList ,
ConfirmedList : uint64 ( 0 ) ,
2022-09-26 15:09:18 +00:00
}
2022-10-04 03:24:47 +00:00
}
2022-10-03 03:42:19 +00:00
pR . InputsBuffer . Put ( currInputFrameDownsync )
2022-09-29 04:21:04 +00:00
return currInputFrameDownsync
2022-09-26 15:09:18 +00:00
}
2022-12-04 15:36:55 +00:00
func ( pR * Room ) markConfirmationIfApplicable ( inputFrameUpsyncBatch [ ] * InputFrameUpsync , playerId int32 , player * Player ) int {
Logger . Debug ( fmt . Sprintf ( "markConfirmationIfApplicable-InputsBufferLock about to lock: roomId=%v, fromPlayerId=%v" , pR . Id , playerId ) )
pR . InputsBufferLock . Lock ( )
Logger . Debug ( fmt . Sprintf ( "markConfirmationIfApplicable-InputsBufferLock locked: roomId=%v, fromPlayerId=%v" , pR . Id , playerId ) )
defer func ( ) {
pR . InputsBufferLock . Unlock ( )
Logger . Debug ( fmt . Sprintf ( "markConfirmationIfApplicable-InputsBufferLock unlocked: roomId=%v, fromPlayerId=%v" , pR . Id , playerId ) )
} ( )
for _ , inputFrameUpsync := range inputFrameUpsyncBatch {
clientInputFrameId := inputFrameUpsync . InputFrameId
if clientInputFrameId < pR . InputsBuffer . StFrameId {
// The updates to "pR.InputsBuffer.StFrameId" is monotonically increasing, thus if "clientInputFrameId < pR.InputsBuffer.StFrameId" at any moment of time, it is obsolete in the future.
Logger . Debug ( fmt . Sprintf ( "Omitting obsolete inputFrameUpsync#1: roomId=%v, playerId=%v, clientInputFrameId=%v, lastAllConfirmedInputFrameId=%v, InputsBuffer=%v" , pR . Id , playerId , clientInputFrameId , pR . LastAllConfirmedInputFrameId , pR . InputsBufferString ( false ) ) )
continue
}
if clientInputFrameId < pR . LastAllConfirmedInputFrameId {
Logger . Info ( fmt . Sprintf ( "Omitting obsolete inputFrameUpsync#2: roomId=%v, playerId=%v, clientInputFrameId=%v, lastAllConfirmedInputFrameId=%v, InputsBuffer=%v" , pR . Id , playerId , clientInputFrameId , pR . LastAllConfirmedInputFrameId , pR . InputsBufferString ( false ) ) )
continue
}
if clientInputFrameId > pR . InputsBuffer . EdFrameId {
Logger . Warn ( fmt . Sprintf ( "Dropping too advanced inputFrameUpsync: roomId=%v, playerId=%v, clientInputFrameId=%v, lastAllConfirmedInputFrameId=%v, InputsBuffer=%v; is this player cheating?" , pR . Id , playerId , clientInputFrameId , pR . LastAllConfirmedInputFrameId , pR . InputsBufferString ( false ) ) )
continue
}
var targetInputFrameDownsync * InputFrameDownsync = nil
if clientInputFrameId == pR . InputsBuffer . EdFrameId {
targetInputFrameDownsync = pR . prefabInputFrameDownsync ( clientInputFrameId )
Logger . Debug ( fmt . Sprintf ( "OnBattleCmdReceived-Prefabbed new inputFrameDownsync from inputFrameUpsync: roomId=%v, playerId=%v, clientInputFrameId=%v, InputsBuffer=%v" , pR . Id , playerId , clientInputFrameId , pR . InputsBufferString ( false ) ) )
} else {
targetInputFrameDownsync = pR . InputsBuffer . GetByFrameId ( clientInputFrameId ) . ( * InputFrameDownsync )
Logger . Debug ( fmt . Sprintf ( "OnBattleCmdReceived-stuffing inputFrameDownsync from inputFrameUpsync: roomId=%v, playerId=%v, clientInputFrameId=%v, InputsBuffer=%v" , pR . Id , playerId , clientInputFrameId , pR . InputsBufferString ( false ) ) )
}
targetInputFrameDownsync . InputList [ player . JoinIndex - 1 ] = inputFrameUpsync . Encoded
targetInputFrameDownsync . ConfirmedList |= uint64 ( 1 << uint32 ( player . JoinIndex - 1 ) )
}
2022-11-30 08:53:48 +00:00
newAllConfirmedCount := 0
2022-10-25 15:36:55 +00:00
inputFrameId1 := pR . LastAllConfirmedInputFrameId + 1
totPlayerCnt := uint32 ( pR . Capacity )
allConfirmedMask := uint64 ( ( 1 << totPlayerCnt ) - 1 )
2022-11-30 08:53:48 +00:00
2022-11-20 16:23:01 +00:00
for inputFrameId := inputFrameId1 ; inputFrameId < pR . InputsBuffer . EdFrameId ; inputFrameId ++ {
2022-10-25 15:36:55 +00:00
tmp := pR . InputsBuffer . GetByFrameId ( inputFrameId )
if nil == tmp {
2022-11-30 08:53:48 +00:00
panic ( fmt . Sprintf ( "inputFrameId=%v doesn't exist for roomId=%v! InputsBuffer=%v" , inputFrameId , pR . Id , pR . InputsBufferString ( false ) ) )
2022-10-25 15:36:55 +00:00
}
2022-11-30 16:30:35 +00:00
shouldBreakConfirmation := false
2022-11-09 06:20:26 +00:00
inputFrameDownsync := tmp . ( * InputFrameDownsync )
2022-11-30 16:30:35 +00:00
if allConfirmedMask != inputFrameDownsync . ConfirmedList {
2022-12-04 15:36:55 +00:00
for _ , player := range pR . PlayersArr {
2022-11-30 16:30:35 +00:00
thatPlayerBattleState := atomic . LoadInt32 ( & ( player . BattleState ) )
thatPlayerJoinMask := uint64 ( 1 << uint32 ( player . JoinIndex - 1 ) )
if 0 == ( inputFrameDownsync . ConfirmedList & thatPlayerJoinMask ) {
if thatPlayerBattleState == PlayerBattleStateIns . ACTIVE {
shouldBreakConfirmation = true
break
} else {
Logger . Debug ( fmt . Sprintf ( "markConfirmationIfApplicable for roomId=%v, skipping UNCONFIRMED BUT INACTIVE player(id:%v, joinIndex:%v) while checking inputFrameId=[%v, %v): InputsBuffer=%v" , pR . Id , player . Id , player . JoinIndex , inputFrameId1 , pR . InputsBuffer . EdFrameId , pR . InputsBufferString ( false ) ) )
}
}
}
}
if shouldBreakConfirmation {
break
} else {
2022-11-30 08:53:48 +00:00
newAllConfirmedCount += 1
2022-10-25 15:36:55 +00:00
pR . onInputFrameDownsyncAllConfirmed ( inputFrameDownsync , - 1 )
}
}
2022-11-30 08:53:48 +00:00
2022-11-30 13:51:06 +00:00
Logger . Debug ( fmt . Sprintf ( "markConfirmationIfApplicable checking inputFrameId=[%v, %v) for roomId=%v, newAllConfirmedCount=%d: InputsBuffer=%v" , inputFrameId1 , pR . InputsBuffer . EdFrameId , pR . Id , newAllConfirmedCount , pR . InputsBufferString ( false ) ) )
2022-11-30 08:53:48 +00:00
return newAllConfirmedCount
2022-10-25 02:42:36 +00:00
}
2022-09-29 04:21:04 +00:00
func ( pR * Room ) forceConfirmationIfApplicable ( ) uint64 {
2022-12-04 15:36:55 +00:00
// [WARNING] This function MUST BE called while "pR.InputsBufferLock" is locked!
2022-10-01 12:45:38 +00:00
// Force confirmation of non-all-confirmed inputFrame EXACTLY ONE AT A TIME, returns the non-confirmed mask of players, e.g. in a 4-player-battle returning 1001 means that players with JoinIndex=1 and JoinIndex=4 are non-confirmed for inputFrameId2
renderFrameId1 := ( pR . RenderFrameId - pR . NstDelayFrames ) // the renderFrameId which should've been rendered on frontend
if 0 > renderFrameId1 || ! pR . shouldPrefabInputFrameDownsync ( renderFrameId1 ) {
/ *
The backend "shouldPrefabInputFrameDownsync" shares the same rule as frontend "shouldGenerateInputFrameUpsync" .
It ' s also important that "forceConfirmationIfApplicable" is NOT EXECUTED for every renderFrame , such that when a player is forced to resync , it has some time , i . e . ( 1 << InputScaleFrames ) renderFrames , to upsync again .
* /
return 0
}
inputFrameId2 := pR . ConvertToInputFrameId ( renderFrameId1 , 0 ) // The inputFrame to force confirmation (if necessary)
2022-10-02 16:22:05 +00:00
if inputFrameId2 < pR . LastAllConfirmedInputFrameId {
// No need to force confirmation, the inputFrames already arrived
2022-10-02 03:33:40 +00:00
Logger . Debug ( fmt . Sprintf ( "inputFrameId2=%v is already all-confirmed for roomId=%v[type#1], no need to force confirmation of it" , inputFrameId2 , pR . Id ) )
2022-10-02 16:22:05 +00:00
return 0
}
2022-11-30 13:51:06 +00:00
2022-10-03 03:42:19 +00:00
tmp := pR . InputsBuffer . GetByFrameId ( inputFrameId2 )
2022-10-01 12:45:38 +00:00
if nil == tmp {
2022-10-03 03:42:19 +00:00
panic ( fmt . Sprintf ( "inputFrameId2=%v doesn't exist for roomId=%v, this is abnormal because the server should prefab inputFrameDownsync in a most advanced pace, check the prefab logic! InputsBuffer=%v" , inputFrameId2 , pR . Id , pR . InputsBufferString ( false ) ) )
2022-10-01 12:45:38 +00:00
}
totPlayerCnt := uint32 ( pR . Capacity )
allConfirmedMask := uint64 ( ( 1 << totPlayerCnt ) - 1 )
2022-09-29 04:21:04 +00:00
2022-10-01 12:45:38 +00:00
// Force confirmation of "inputFrame2"
2022-11-11 12:10:43 +00:00
inputFrame2 := tmp . ( * InputFrameDownsync )
2022-10-03 15:54:38 +00:00
oldConfirmedList := inputFrame2 . ConfirmedList
unconfirmedMask := ( oldConfirmedList ^ allConfirmedMask )
inputFrame2 . ConfirmedList = allConfirmedMask
2022-10-01 12:45:38 +00:00
pR . onInputFrameDownsyncAllConfirmed ( inputFrame2 , - 1 )
2022-09-29 04:21:04 +00:00
2022-10-01 12:45:38 +00:00
return unconfirmedMask
2022-09-20 15:50:01 +00:00
}
2022-11-08 13:38:23 +00:00
func ( pR * Room ) applyInputFrameDownsyncDynamics ( fromRenderFrameId int32 , toRenderFrameId int32 , spaceOffsetX , spaceOffsetY float64 ) {
2022-09-29 04:21:04 +00:00
if fromRenderFrameId >= toRenderFrameId {
return
2022-09-20 15:50:01 +00:00
}
2022-10-01 12:45:38 +00:00
Logger . Debug ( fmt . Sprintf ( "Applying inputFrame dynamics: roomId=%v, room.RenderFrameId=%v, fromRenderFrameId=%v, toRenderFrameId=%v" , pR . Id , pR . RenderFrameId , fromRenderFrameId , toRenderFrameId ) )
totPlayerCnt := uint32 ( pR . Capacity )
allConfirmedMask := uint64 ( ( 1 << totPlayerCnt ) - 1 )
2022-09-29 04:21:04 +00:00
2022-11-30 13:51:06 +00:00
Logger . Debug ( fmt . Sprintf ( "applyInputFrameDownsyncDynamics-InputsBufferLock about to lock: roomId=%v" , pR . Id ) )
pR . InputsBufferLock . Lock ( )
Logger . Debug ( fmt . Sprintf ( "applyInputFrameDownsyncDynamics-InputsBufferLock locked: roomId=%v" , pR . Id ) )
defer func ( ) {
pR . InputsBufferLock . Unlock ( )
Logger . Debug ( fmt . Sprintf ( "applyInputFrameDownsyncDynamics-InputsBufferLock unlocked: roomId=%v" , pR . Id ) )
} ( )
2022-09-29 04:21:04 +00:00
for collisionSysRenderFrameId := fromRenderFrameId ; collisionSysRenderFrameId < toRenderFrameId ; collisionSysRenderFrameId ++ {
2022-11-10 13:28:46 +00:00
currRenderFrameTmp := pR . RenderFrameBuffer . GetByFrameId ( collisionSysRenderFrameId )
if nil == currRenderFrameTmp {
panic ( fmt . Sprintf ( "collisionSysRenderFrameId=%v doesn't exist for roomId=%v, this is abnormal because it's to be used for applying dynamics to [fromRenderFrameId:%v, toRenderFrameId:%v)! RenderFrameBuffer=%v" , collisionSysRenderFrameId , pR . Id , fromRenderFrameId , toRenderFrameId , pR . RenderFrameBufferString ( ) ) )
}
currRenderFrame := currRenderFrameTmp . ( * RoomDownsyncFrame )
2022-09-29 04:21:04 +00:00
delayedInputFrameId := pR . ConvertToInputFrameId ( collisionSysRenderFrameId , pR . InputDelayFrames )
2022-11-10 13:28:46 +00:00
var delayedInputFrame * InputFrameDownsync = nil
2022-09-29 04:21:04 +00:00
if 0 <= delayedInputFrameId {
2022-10-02 16:22:05 +00:00
if delayedInputFrameId > pR . LastAllConfirmedInputFrameId {
2022-10-03 03:42:19 +00:00
panic ( fmt . Sprintf ( "delayedInputFrameId=%v is not yet all-confirmed for roomId=%v, this is abnormal because it's to be used for applying dynamics to [fromRenderFrameId:%v, toRenderFrameId:%v) @ collisionSysRenderFrameId=%v! InputsBuffer=%v" , delayedInputFrameId , pR . Id , fromRenderFrameId , toRenderFrameId , collisionSysRenderFrameId , pR . InputsBufferString ( false ) ) )
2022-10-02 16:22:05 +00:00
}
2022-10-03 03:42:19 +00:00
tmp := pR . InputsBuffer . GetByFrameId ( delayedInputFrameId )
2022-10-01 12:45:38 +00:00
if nil == tmp {
2022-10-03 03:42:19 +00:00
panic ( fmt . Sprintf ( "delayedInputFrameId=%v doesn't exist for roomId=%v, this is abnormal because it's to be used for applying dynamics to [fromRenderFrameId:%v, toRenderFrameId:%v) @ collisionSysRenderFrameId=%v! InputsBuffer=%v" , delayedInputFrameId , pR . Id , fromRenderFrameId , toRenderFrameId , collisionSysRenderFrameId , pR . InputsBufferString ( false ) ) )
2022-10-01 12:45:38 +00:00
}
2022-11-10 16:01:53 +00:00
delayedInputFrame = tmp . ( * InputFrameDownsync )
2022-10-02 16:22:05 +00:00
// [WARNING] It's possible that by now "allConfirmedMask != delayedInputFrame.ConfirmedList && delayedInputFrameId <= pR.LastAllConfirmedInputFrameId", we trust "pR.LastAllConfirmedInputFrameId" as the TOP AUTHORITY.
2022-12-04 15:36:55 +00:00
delayedInputFrame . ConfirmedList = allConfirmedMask
2022-11-10 13:28:46 +00:00
}
2022-11-11 05:27:48 +00:00
2022-11-10 13:28:46 +00:00
nextRenderFrame := pR . applyInputFrameDownsyncDynamicsOnSingleRenderFrame ( delayedInputFrame , currRenderFrame , pR . CollisionSysMap )
pR . RenderFrameBuffer . Put ( nextRenderFrame )
pR . CurDynamicsRenderFrameId ++
}
}
2022-10-03 03:42:19 +00:00
2022-11-10 13:28:46 +00:00
// TODO: Write unit-test for this function to compare with its frontend counter part
func ( pR * Room ) applyInputFrameDownsyncDynamicsOnSingleRenderFrame ( delayedInputFrame * InputFrameDownsync , currRenderFrame * RoomDownsyncFrame , collisionSysMap map [ int32 ] * resolv . Object ) * RoomDownsyncFrame {
2022-11-23 07:04:32 +00:00
// TODO: Derive "nextRenderFramePlayers[*].CharacterState" as the frontend counter-part!
2022-11-10 13:28:46 +00:00
nextRenderFramePlayers := make ( map [ int32 ] * PlayerDownsync , pR . Capacity )
// Make a copy first
for playerId , currPlayerDownsync := range currRenderFrame . Players {
nextRenderFramePlayers [ playerId ] = & PlayerDownsync {
2022-11-23 14:11:28 +00:00
Id : playerId ,
VirtualGridX : currPlayerDownsync . VirtualGridX ,
VirtualGridY : currPlayerDownsync . VirtualGridY ,
DirX : currPlayerDownsync . DirX ,
DirY : currPlayerDownsync . DirY ,
CharacterState : currPlayerDownsync . CharacterState ,
Speed : currPlayerDownsync . Speed ,
BattleState : currPlayerDownsync . BattleState ,
Score : currPlayerDownsync . Score ,
Removed : currPlayerDownsync . Removed ,
JoinIndex : currPlayerDownsync . JoinIndex ,
FramesToRecover : currPlayerDownsync . FramesToRecover - 1 ,
Hp : currPlayerDownsync . Hp ,
MaxHp : currPlayerDownsync . MaxHp ,
}
if nextRenderFramePlayers [ playerId ] . FramesToRecover < 0 {
nextRenderFramePlayers [ playerId ] . FramesToRecover = 0
2022-11-10 13:28:46 +00:00
}
}
2022-11-10 10:18:00 +00:00
2022-11-10 13:28:46 +00:00
toRet := & RoomDownsyncFrame {
Id : currRenderFrame . Id + 1 ,
Players : nextRenderFramePlayers ,
CountdownNanos : ( pR . BattleDurationNanos - int64 ( currRenderFrame . Id ) * pR . RollbackEstimatedDtNanos ) ,
2022-11-23 14:11:28 +00:00
MeleeBullets : make ( [ ] * MeleeBullet , 0 ) , // Is there any better way to reduce malloc/free impact, e.g. smart prediction for fixed memory allocation?
}
bulletPushbacks := make ( [ ] Vec2D , pR . Capacity ) // Guaranteed determinism regardless of traversal order
effPushbacks := make ( [ ] Vec2D , pR . Capacity ) // Guaranteed determinism regardless of traversal order
// Reset playerCollider position from the "virtual grid position"
2022-12-04 15:36:55 +00:00
for _ , player := range pR . PlayersArr {
playerId := player . Id
2022-11-23 14:11:28 +00:00
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 )
currPlayerDownsync := currRenderFrame . Players [ playerId ]
newVx , newVy := currPlayerDownsync . VirtualGridX , currPlayerDownsync . VirtualGridY
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
playerCollider := collisionSysMap [ collisionPlayerIndex ]
playerCollider . X , playerCollider . Y = VirtualGridToPolygonColliderAnchorPos ( newVx , newVy , player . ColliderRadius , player . ColliderRadius , pR . collisionSpaceOffsetX , pR . collisionSpaceOffsetY , pR . VirtualGridToWorldRatio )
}
// Check bullet-anything collisions first, because the pushbacks caused by bullets might later be reverted by player-barrier collision
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 )
for _ , meleeBullet := range currRenderFrame . MeleeBullets {
if ( meleeBullet . OriginatedRenderFrameId + meleeBullet . StartupFrames <= currRenderFrame . Id ) && ( meleeBullet . OriginatedRenderFrameId + meleeBullet . StartupFrames + meleeBullet . ActiveFrames > currRenderFrame . Id ) {
collisionBulletIndex := COLLISION_BULLET_INDEX_PREFIX + meleeBullet . BattleLocalId
collisionOffenderIndex := COLLISION_PLAYER_INDEX_PREFIX + meleeBullet . OffenderJoinIndex
offenderCollider := collisionSysMap [ collisionOffenderIndex ]
offender := currRenderFrame . Players [ meleeBullet . OffenderPlayerId ]
2022-11-25 00:21:03 +00:00
xfac := float64 ( 1.0 ) // By now, straight Punch offset doesn't respect "y-axis"
2022-11-23 14:11:28 +00:00
if 0 > offender . DirX {
xfac = float64 ( - 1.0 )
}
2022-11-25 00:21:03 +00:00
offenderWx , offenderWy := VirtualGridToWorldPos ( offender . VirtualGridX , offender . VirtualGridY , pR . VirtualGridToWorldRatio )
bulletWx , bulletWy := offenderWx + xfac * meleeBullet . HitboxOffset , offenderWy
2022-11-23 14:11:28 +00:00
2022-11-25 00:21:03 +00:00
newBulletCollider := GenerateRectCollider ( bulletWx , bulletWy , meleeBullet . HitboxSize . X , meleeBullet . HitboxSize . Y , pR . collisionSpaceOffsetX , pR . collisionSpaceOffsetY , "MeleeBullet" )
2022-11-24 09:48:07 +00:00
newBulletCollider . Data = meleeBullet
2022-11-24 13:56:34 +00:00
pR . Space . Add ( newBulletCollider )
2022-11-23 14:11:28 +00:00
collisionSysMap [ collisionBulletIndex ] = newBulletCollider
bulletColliders [ collisionBulletIndex ] = newBulletCollider
2022-11-24 13:56:34 +00:00
2022-11-25 00:21:03 +00:00
Logger . Debug ( fmt . Sprintf ( "roomId=%v, a meleeBullet is added to collisionSys at currRenderFrame.id=%v as start-up frames ended and active frame is not yet ended: %v, from offenderCollider=%v, xfac=%v" , pR . Id , currRenderFrame . Id , ConvexPolygonStr ( newBulletCollider . Shape . ( * resolv . ConvexPolygon ) ) , ConvexPolygonStr ( offenderCollider . Shape . ( * resolv . ConvexPolygon ) ) , xfac ) )
2022-11-23 14:11:28 +00:00
}
}
2022-11-24 09:48:07 +00:00
for _ , bulletCollider := range bulletColliders {
shouldRemove := false
meleeBullet := bulletCollider . Data . ( * MeleeBullet )
collisionBulletIndex := COLLISION_BULLET_INDEX_PREFIX + meleeBullet . BattleLocalId
2022-11-24 13:56:34 +00:00
bulletShape := bulletCollider . Shape . ( * resolv . ConvexPolygon )
2022-11-24 09:48:07 +00:00
if collision := bulletCollider . Check ( 0 , 0 ) ; collision != nil {
offender := currRenderFrame . Players [ meleeBullet . OffenderPlayerId ]
for _ , obj := range collision . Objects {
2022-11-25 00:21:03 +00:00
defenderShape := obj . Shape . ( * resolv . ConvexPolygon )
2022-11-24 09:48:07 +00:00
switch t := obj . Data . ( type ) {
2022-11-25 00:21:03 +00:00
case * Player :
2022-11-24 09:48:07 +00:00
if meleeBullet . OffenderPlayerId != t . Id {
if overlapped , _ , _ , _ := CalcPushbacks ( 0 , 0 , bulletShape , defenderShape ) ; overlapped {
xfac := float64 ( 1.0 ) // By now, straight Punch offset doesn't respect "y-axis"
if 0 > offender . DirX {
xfac = float64 ( - 1.0 )
}
bulletPushbacks [ t . JoinIndex - 1 ] . X += xfac * meleeBullet . Pushback
nextRenderFramePlayers [ t . Id ] . CharacterState = ATK_CHARACTER_STATE_ATKED1
oldFramesToRecover := nextRenderFramePlayers [ t . Id ] . FramesToRecover
if meleeBullet . HitStunFrames > oldFramesToRecover {
nextRenderFramePlayers [ t . Id ] . FramesToRecover = meleeBullet . HitStunFrames
}
2022-11-25 00:21:03 +00:00
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 ) ) )
2022-11-24 09:48:07 +00:00
}
}
default :
2022-11-25 00:21:03 +00:00
Logger . Debug ( fmt . Sprintf ( "Bullet %v collided with non-player %v: roomId=%v, currRenderFrame.Id=%v, delayedInputFrame.Id=%v, objDataType=%t, objData=%v" , ConvexPolygonStr ( bulletShape ) , ConvexPolygonStr ( defenderShape ) , pR . Id , currRenderFrame . Id , delayedInputFrame . InputFrameId , obj . Data , obj . Data ) )
2022-11-24 09:48:07 +00:00
}
}
shouldRemove = true
}
if shouldRemove {
removedBulletsAtCurrFrame [ collisionBulletIndex ] = 1
}
}
2022-11-23 14:11:28 +00:00
for _ , meleeBullet := range currRenderFrame . MeleeBullets {
collisionBulletIndex := COLLISION_BULLET_INDEX_PREFIX + meleeBullet . BattleLocalId
if bulletCollider , existent := collisionSysMap [ collisionBulletIndex ] ; existent {
bulletCollider . Space . Remove ( bulletCollider )
delete ( collisionSysMap , collisionBulletIndex )
}
if _ , existent := removedBulletsAtCurrFrame [ collisionBulletIndex ] ; existent {
continue
}
toRet . MeleeBullets = append ( toRet . MeleeBullets , meleeBullet )
2022-11-10 13:28:46 +00:00
}
2022-11-10 10:18:00 +00:00
2022-11-10 13:28:46 +00:00
if nil != delayedInputFrame {
2022-11-24 09:48:07 +00:00
var delayedInputFrameForPrevRenderFrame * InputFrameDownsync = nil
tmp := pR . InputsBuffer . GetByFrameId ( pR . ConvertToInputFrameId ( currRenderFrame . Id - 1 , pR . InputDelayFrames ) )
if nil != tmp {
delayedInputFrameForPrevRenderFrame = tmp . ( * InputFrameDownsync )
}
2022-11-10 13:28:46 +00:00
inputList := delayedInputFrame . InputList
2022-11-23 14:11:28 +00:00
// Process player inputs
2022-12-04 15:36:55 +00:00
for _ , player := range pR . PlayersArr {
playerId := player . Id
2022-11-11 05:27:48 +00:00
joinIndex := player . JoinIndex
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
2022-11-10 13:28:46 +00:00
playerCollider := collisionSysMap [ collisionPlayerIndex ]
2022-11-24 09:48:07 +00:00
thatPlayerInNextFrame := nextRenderFramePlayers [ playerId ]
if 0 < thatPlayerInNextFrame . FramesToRecover {
2022-11-23 14:11:28 +00:00
// No need to process inputs for this player, but there might be bullet pushbacks on this player
2022-11-26 16:00:39 +00:00
// Also note that in this case we keep "CharacterState" of this player from last render frame
2022-11-23 14:11:28 +00:00
playerCollider . X += bulletPushbacks [ joinIndex - 1 ] . X
playerCollider . Y += bulletPushbacks [ joinIndex - 1 ] . Y
2022-11-24 09:48:07 +00:00
// Update in the collision system
playerCollider . Update ( )
2022-11-24 11:45:48 +00:00
if 0 != bulletPushbacks [ joinIndex - 1 ] . X || 0 != bulletPushbacks [ joinIndex - 1 ] . Y {
2022-11-30 16:30:35 +00:00
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 ) )
2022-11-24 11:45:48 +00:00
}
2022-11-23 14:11:28 +00:00
continue
}
currPlayerDownsync := currRenderFrame . Players [ playerId ]
decodedInput := pR . decodeInput ( inputList [ joinIndex - 1 ] )
2022-11-24 09:48:07 +00:00
prevBtnALevel := int32 ( 0 )
if nil != delayedInputFrameForPrevRenderFrame {
prevDecodedInput := pR . decodeInput ( delayedInputFrameForPrevRenderFrame . InputList [ joinIndex - 1 ] )
prevBtnALevel = prevDecodedInput . BtnALevel
}
2022-11-11 05:27:48 +00:00
2022-11-24 09:48:07 +00:00
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
toRet . MeleeBullets = append ( toRet . MeleeBullets , & newMeleeBullet )
thatPlayerInNextFrame . FramesToRecover = newMeleeBullet . RecoveryFrames
thatPlayerInNextFrame . CharacterState = ATK_CHARACTER_STATE_ATK1
2022-11-30 16:30:35 +00:00
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 ) )
2022-11-24 09:48:07 +00:00
} else if decodedInput . BtnALevel < prevBtnALevel {
2022-11-25 00:21:03 +00:00
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 ) )
2022-11-23 14:11:28 +00:00
} else {
2022-11-24 09:48:07 +00:00
// No bullet trigger, process movement inputs
2022-11-26 16:00:39 +00:00
// Note that by now "0 == thatPlayerInNextFrame.FramesToRecover", we should change "CharacterState" to "WALKING" or "IDLE" depending on player inputs
2022-11-24 09:48:07 +00:00
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
}
2022-11-21 09:27:32 +00:00
}
2022-11-23 14:11:28 +00:00
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 ( )
2022-11-10 13:28:46 +00:00
}
2022-11-09 04:19:29 +00:00
2022-11-10 13:28:46 +00:00
// handle pushbacks upon collision after all movements treated as simultaneous
2022-12-04 15:36:55 +00:00
for _ , player := range pR . PlayersArr {
2022-11-11 05:27:48 +00:00
joinIndex := player . JoinIndex
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
2022-11-10 13:28:46 +00:00
playerCollider := collisionSysMap [ collisionPlayerIndex ]
2022-11-12 12:34:38 +00:00
if collision := playerCollider . Check ( 0 , 0 ) ; collision != nil {
2022-11-10 13:28:46 +00:00
playerShape := playerCollider . Shape . ( * resolv . ConvexPolygon )
for _ , obj := range collision . Objects {
barrierShape := obj . Shape . ( * resolv . ConvexPolygon )
2022-11-12 12:34:38 +00:00
if overlapped , pushbackX , pushbackY , overlapResult := CalcPushbacks ( 0 , 0 , playerShape , barrierShape ) ; overlapped {
2022-11-12 14:53:35 +00:00
Logger . Debug ( fmt . Sprintf ( "Overlapped: a=%v, b=%v, pushbackX=%v, pushbackY=%v" , ConvexPolygonStr ( playerShape ) , ConvexPolygonStr ( barrierShape ) , pushbackX , pushbackY ) )
2022-11-11 05:27:48 +00:00
effPushbacks [ joinIndex - 1 ] . X += pushbackX
effPushbacks [ joinIndex - 1 ] . Y += pushbackY
2022-11-10 13:28:46 +00:00
} else {
2022-11-12 14:53:35 +00:00
Logger . Debug ( fmt . Sprintf ( "Collided BUT not overlapped: a=%v, b=%v, overlapResult=%v" , ConvexPolygonStr ( playerShape ) , ConvexPolygonStr ( barrierShape ) , overlapResult ) )
2022-11-08 13:38:23 +00:00
}
2022-10-10 04:17:23 +00:00
}
2022-09-29 04:21:04 +00:00
}
2022-11-11 05:27:48 +00:00
}
2022-09-20 15:50:01 +00:00
2022-12-04 15:36:55 +00:00
for _ , player := range pR . PlayersArr {
playerId := player . Id
2022-11-11 05:27:48 +00:00
joinIndex := player . JoinIndex
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
playerCollider := collisionSysMap [ collisionPlayerIndex ]
2022-11-09 15:46:11 +00:00
2022-11-10 13:28:46 +00:00
// Update "virtual grid position"
2022-11-12 12:34:38 +00:00
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 )
2022-11-24 09:48:07 +00:00
thatPlayerInNextFrame := nextRenderFramePlayers [ playerId ]
thatPlayerInNextFrame . VirtualGridX , thatPlayerInNextFrame . VirtualGridY = newVx , newVy
2022-10-01 12:45:38 +00:00
}
2022-11-10 16:01:53 +00:00
2022-11-20 16:23:01 +00:00
Logger . Debug ( fmt . Sprintf ( "After applyInputFrameDownsyncDynamicsOnSingleRenderFrame: currRenderFrame.Id=%v, inputList=%v, currRenderFrame.Players=%v, nextRenderFramePlayers=%v" , currRenderFrame . Id , inputList , currRenderFrame . Players , nextRenderFramePlayers ) )
2022-09-20 15:50:01 +00:00
}
2022-11-10 13:28:46 +00:00
return toRet
2022-09-20 15:50:01 +00:00
}
2022-11-20 10:53:33 +00:00
func ( pR * Room ) decodeInput ( encodedInput uint64 ) * InputFrameDecoded {
2022-11-20 16:23:01 +00:00
encodedDirection := ( encodedInput & uint64 ( 15 ) )
btnALevel := int32 ( ( encodedInput >> 4 ) & 1 )
return & InputFrameDecoded {
Dx : DIRECTION_DECODER [ encodedDirection ] [ 0 ] ,
Dy : DIRECTION_DECODER [ encodedDirection ] [ 1 ] ,
BtnALevel : btnALevel ,
}
2022-11-20 10:53:33 +00:00
}
2022-09-29 04:21:04 +00:00
func ( pR * Room ) inputFrameIdDebuggable ( inputFrameId int32 ) bool {
return 0 == ( inputFrameId % 10 )
2022-09-20 15:50:01 +00:00
}
2022-11-09 04:19:29 +00:00
func ( pR * Room ) refreshColliders ( spaceW , spaceH int32 ) {
2022-10-01 12:45:38 +00:00
// Kindly note that by now, we've already got all the shapes in the tmx file into "pR.(Players | Barriers)" from "ParseTmxLayersAndGroups"
2022-10-10 13:58:29 +00:00
2022-11-25 00:21:03 +00:00
minStep := ( int ( float64 ( pR . PlayerDefaultSpeed ) * pR . VirtualGridToWorldRatio ) << 1 ) // the approx minimum distance a player can move per frame in world coordinate
2022-11-24 13:56:34 +00:00
pR . Space = resolv . NewSpace ( int ( spaceW ) , int ( spaceH ) , minStep , minStep ) // allocate a new collision space everytime after a battle is settled
2022-09-29 04:21:04 +00:00
for _ , player := range pR . Players {
2022-11-12 12:34:38 +00:00
wx , wy := VirtualGridToWorldPos ( player . VirtualGridX , player . VirtualGridY , pR . VirtualGridToWorldRatio )
2022-11-27 13:33:34 +00:00
colliderWidth , colliderHeight := player . ColliderRadius * 2 , player . ColliderRadius * 3
playerCollider := GenerateRectCollider ( wx , wy , colliderWidth , colliderHeight , pR . collisionSpaceOffsetX , pR . collisionSpaceOffsetY , "Player" )
2022-11-24 09:48:07 +00:00
playerCollider . Data = player
2022-11-24 13:56:34 +00:00
pR . Space . Add ( playerCollider )
2022-10-01 12:45:38 +00:00
// Keep track of the collider in "pR.CollisionSysMap"
2022-09-29 04:21:04 +00:00
joinIndex := player . JoinIndex
pR . PlayersArr [ joinIndex - 1 ] = player
2022-10-01 12:45:38 +00:00
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
pR . CollisionSysMap [ collisionPlayerIndex ] = playerCollider
}
for _ , barrier := range pR . Barriers {
2022-10-21 14:39:08 +00:00
boundaryUnaligned := barrier . Boundary
2022-11-09 04:19:29 +00:00
barrierCollider := GenerateConvexPolygonCollider ( boundaryUnaligned , pR . collisionSpaceOffsetX , pR . collisionSpaceOffsetY , "Barrier" )
2022-11-24 13:56:34 +00:00
pR . Space . Add ( barrierCollider )
2022-09-29 04:21:04 +00:00
}
2022-09-20 15:50:01 +00:00
}
2022-10-10 04:17:23 +00:00
func ( pR * Room ) printBarrier ( barrierCollider * resolv . Object ) {
Logger . Info ( fmt . Sprintf ( "Barrier in roomId=%v: w=%v, h=%v, shape=%v" , pR . Id , barrierCollider . W , barrierCollider . H , barrierCollider . Shape ) )
}
2022-11-29 16:04:52 +00:00
2022-12-04 15:36:55 +00:00
func ( pR * Room ) doBattleMainLoopPerTickBackendDynamicsWithProperLocking ( pDynamicsDuration * int64 ) ( uint64 , * RingBuffer ) {
Logger . Debug ( fmt . Sprintf ( "doBattleMainLoopPerTickBackendDynamicsWithProperLocking-InputsBufferLock to about lock: roomId=%v" , pR . Id ) )
pR . InputsBufferLock . Lock ( )
Logger . Debug ( fmt . Sprintf ( "doBattleMainLoopPerTickBackendDynamicsWithProperLocking-InputsBufferLock locked: roomId=%v" , pR . Id ) )
defer func ( ) {
// [WARNING] Unlock before calling "downsyncToAllPlayers -> downsyncToSinglePlayer" to avoid unlocking lags due to blocking network I/O
pR . InputsBufferLock . Unlock ( )
Logger . Debug ( fmt . Sprintf ( "doBattleMainLoopPerTickBackendDynamicsWithProperLocking-InputsBufferLock unlocked: roomId=%v" , pR . Id ) )
} ( )
if pR . shouldPrefabInputFrameDownsync ( pR . RenderFrameId ) {
noDelayInputFrameId := pR . ConvertToInputFrameId ( pR . RenderFrameId , 0 )
if existingInputFrame := pR . InputsBuffer . GetByFrameId ( noDelayInputFrameId ) ; nil == existingInputFrame {
pR . prefabInputFrameDownsync ( noDelayInputFrameId )
}
}
// Force setting all-confirmed of buffered inputFrames periodically, kindly note that if "pR.BackendDynamicsEnabled", what we want to achieve is "recovery upon reconnection", which certainly requires "forceConfirmationIfApplicable" to move "pR.LastAllConfirmedInputFrameId" forward as much as possible
unconfirmedMask := pR . forceConfirmationIfApplicable ( )
if 0 <= pR . LastAllConfirmedInputFrameId {
dynamicsStartedAt := utils . UnixtimeNano ( )
// Apply "all-confirmed inputFrames" to move forward "pR.CurDynamicsRenderFrameId"
nextDynamicsRenderFrameId := pR . ConvertToLastUsedRenderFrameId ( pR . LastAllConfirmedInputFrameId , pR . InputDelayFrames )
Logger . Debug ( fmt . Sprintf ( "roomId=%v, room.RenderFrameId=%v, room.CurDynamicsRenderFrameId=%v, LastAllConfirmedInputFrameId=%v, InputDelayFrames=%v, nextDynamicsRenderFrameId=%v" , pR . Id , pR . RenderFrameId , pR . CurDynamicsRenderFrameId , pR . LastAllConfirmedInputFrameId , pR . InputDelayFrames , nextDynamicsRenderFrameId ) )
pR . applyInputFrameDownsyncDynamics ( pR . CurDynamicsRenderFrameId , nextDynamicsRenderFrameId , pR . collisionSpaceOffsetX , pR . collisionSpaceOffsetY )
* pDynamicsDuration = utils . UnixtimeNano ( ) - dynamicsStartedAt
}
var inputsBufferSnapshot * RingBuffer = nil
if 0 < unconfirmedMask {
// [WARNING] This condition should be rarely met!
// Otherwise there's no need to create the inputsBufferSnapshot
inputsBufferSnapshot = pR . createInputsBufferSnapshot ( )
}
return unconfirmedMask , inputsBufferSnapshot
}
func ( pR * Room ) downsyncToAllPlayers ( upperToSendInputFrameId int32 , unconfirmedMask uint64 , inputsBufferSnapshot * RingBuffer ) {
// [WARNING] This function MUST BE called while "pR.InputsBufferLock" is unlocked -- otherwise the blocking "sendSafely" might cause significant lag!
for _ , player := range pR . PlayersArr {
2022-12-01 04:17:30 +00:00
thatPlayerBattleState := atomic . LoadInt32 ( & ( player . BattleState ) )
switch thatPlayerBattleState {
case PlayerBattleStateIns . DISCONNECTED :
case PlayerBattleStateIns . LOST :
case PlayerBattleStateIns . EXPELLED_DURING_GAME :
case PlayerBattleStateIns . EXPELLED_IN_DISMISSAL :
case PlayerBattleStateIns . READDED_PENDING_BATTLE_COLLIDER_ACK : // This is the reason why battleState filter is put at "downsyncToAllPlayers" instead of "downsyncToSinglePlayer"
continue
}
2022-12-04 15:36:55 +00:00
pR . downsyncToSinglePlayer ( player . Id , player , pR . LastAllConfirmedInputFrameId , unconfirmedMask , inputsBufferSnapshot )
2022-11-30 13:51:06 +00:00
}
}
2022-12-04 15:36:55 +00:00
func ( pR * Room ) downsyncToSinglePlayer ( playerId int32 , player * Player , upperToSendInputFrameId int32 , unconfirmedMask uint64 , inputsBufferSnapshot * RingBuffer ) {
// [WARNING] This function MUST BE called while "pR.InputsBufferLock" is unlocked -- otherwise the blocking "sendSafely" might cause significant lag!
2022-11-29 16:04:52 +00:00
// [WARNING] Websocket is TCP-based, thus no need to re-send a previously sent inputFrame to a same player!
2022-11-30 13:51:06 +00:00
lowerToSentInputFrameId := player . LastSentInputFrameId + 1
2022-11-29 16:04:52 +00:00
/ *
2022-12-01 03:35:56 +00:00
[ WARNING ]
Upon resynced on frontend , "refRenderFrameId" is now set to as advanced as possible , and it ' s the frontend ' s responsibility now to pave way for the "gap inputFrames"
2022-11-29 16:04:52 +00:00
2022-12-01 03:35:56 +00:00
If "NstDelayFrames" becomes larger , "pR.RenderFrameId - refRenderFrameId" possibly becomes larger because the force confirmation is delayed more .
2022-11-29 16:04:52 +00:00
2022-12-01 03:35:56 +00:00
Upon resync , it ' s still possible that "refRenderFrameId < frontend.chaserRenderFrameId" -- and this is allowed .
2022-11-29 16:04:52 +00:00
* /
2022-12-01 03:35:56 +00:00
refRenderFrameId := pR . CurDynamicsRenderFrameId - 1
shouldResync1 := ( MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED == player . LastSentInputFrameId )
shouldResync2 := ( 0 < ( unconfirmedMask & uint64 ( 1 << uint32 ( player . JoinIndex - 1 ) ) ) ) // This condition is critical, if we don't send resync upon this condition, the "reconnected or slowly-clocking player" might never get its input synced
// shouldResync2 := (0 < unconfirmedMask) // An easier version of the above, might keep sending "refRenderFrame"s to still connected players when any player is disconnected
shouldResyncOverall := ( shouldResync1 || shouldResync2 )
2022-11-29 16:04:52 +00:00
2022-12-01 03:35:56 +00:00
if shouldResyncOverall {
2022-11-29 16:04:52 +00:00
// A rejoined player, should guarantee that when it resyncs to "refRenderFrameId" a matching inputFrame to apply exists
2022-11-30 13:51:06 +00:00
lowerToSentInputFrameId = pR . ConvertToInputFrameId ( refRenderFrameId , pR . InputDelayFrames )
2022-12-01 03:35:56 +00:00
Logger . Info ( fmt . Sprintf ( "Resyncing player: roomId=%v, playerId=%v, playerJoinIndex=%v, renderFrameId=%v, curDynamicsRenderFrameId=%v, playerLastSentInputFrameId=%v, refRenderFrameId=%v, lowerToSentInputFrameId=%v, upperToSendInputFrameId=%v" , pR . Id , playerId , player . JoinIndex , pR . RenderFrameId , pR . CurDynamicsRenderFrameId , player . LastSentInputFrameId , refRenderFrameId , lowerToSentInputFrameId , upperToSendInputFrameId ) )
2022-11-30 16:30:35 +00:00
// TODO: What if "lowerToSentInputFrameId > pR.InputsBuffer.StFrameId" now?
2022-11-30 13:51:06 +00:00
}
if lowerToSentInputFrameId > upperToSendInputFrameId {
2022-12-01 03:35:56 +00:00
Logger . Debug ( fmt . Sprintf ( "Not sending due to potentially empty toSendInputFrameDownsyncs: roomId=%v, playerId=%v, playerLastSentInputFrameId=%v, refRenderFrameId=%v, lowerToSentInputFrameId=%v, upperToSendInputFrameId=%v, lastSentInputFrameId=%v, playerAckingInputFrameId=%v" , pR . Id , playerId , player . LastSentInputFrameId , refRenderFrameId , lowerToSentInputFrameId , upperToSendInputFrameId , player . LastSentInputFrameId , player . AckingInputFrameId ) )
2022-11-30 13:51:06 +00:00
return
2022-11-29 16:04:52 +00:00
}
// [WARNING] EDGE CASE HERE: Upon initialization, all of "lastAllConfirmedInputFrameId", "lastAllConfirmedInputFrameIdWithChange" and "anchorInputFrameId" are "-1", thus "j" starts with "0", however "inputFrameId: 0" might not have been all confirmed!
2022-11-30 13:51:06 +00:00
// [WARNING] Clone to deliberately avoid holding "pR.InputsBufferLock" while using network I/O
2022-12-04 15:36:55 +00:00
j , toSendInputFrameDownsyncs := pR . cloneInputFrameDownsyncsByFrameIdRange ( lowerToSentInputFrameId , upperToSendInputFrameId + 1 , inputsBufferSnapshot )
2022-11-29 16:04:52 +00:00
2022-11-30 13:51:06 +00:00
if 0 >= len ( toSendInputFrameDownsyncs ) {
2022-12-01 03:35:56 +00:00
Logger . Debug ( fmt . Sprintf ( "Not sending due to actually empty toSendInputFrameDownsyncs: roomId=%v, playerId=%v, playerLastSentInputFrameId=%v, refRenderFrameId=%v, lowerToSentInputFrameId=%v, upperToSendInputFrameId=%v, j=%v, lastSentInputFrameId=%v, playerAckingInputFrameId=%v" , pR . Id , playerId , player . LastSentInputFrameId , refRenderFrameId , lowerToSentInputFrameId , upperToSendInputFrameId , j , player . LastSentInputFrameId , player . AckingInputFrameId ) )
2022-11-29 16:04:52 +00:00
return
}
/ *
Resync helps
1. when player with a slower frontend clock lags significantly behind and thus wouldn ' t get its inputUpsync recognized due to faster "forceConfirmation"
2. reconnection
* /
2022-12-01 03:35:56 +00:00
if pR . BackendDynamicsEnabled && shouldResyncOverall {
2022-11-29 16:04:52 +00:00
tmp := pR . RenderFrameBuffer . GetByFrameId ( refRenderFrameId )
if nil == tmp {
2022-12-01 03:35:56 +00:00
panic ( fmt . Sprintf ( "Required refRenderFrameId=%v for roomId=%v, renderFrameId=%v, playerId=%v, playerLastSentInputFrameId=%v, j=%v doesn't exist! InputsBuffer=%v, RenderFrameBuffer=%v" , refRenderFrameId , pR . Id , pR . RenderFrameId , playerId , player . LastSentInputFrameId , j , pR . InputsBufferString ( false ) , pR . RenderFrameBufferString ( ) ) )
2022-11-29 16:04:52 +00:00
}
2022-11-30 13:51:06 +00:00
2022-11-29 16:04:52 +00:00
refRenderFrame := tmp . ( * RoomDownsyncFrame )
2022-12-04 15:36:55 +00:00
for _ , player := range pR . PlayersArr {
refRenderFrame . Players [ player . Id ] . ColliderRadius = player . ColliderRadius // hardcoded for now
2022-12-01 03:35:56 +00:00
}
2022-11-29 16:04:52 +00:00
refRenderFrame . BackendUnconfirmedMask = unconfirmedMask
2022-12-01 04:17:30 +00:00
Logger . Warn ( fmt . Sprintf ( "Sending refRenderFrameId=%v for roomId=%v, , playerId=%v, playerJoinIndex=%v, renderFrameId=%v, curDynamicsRenderFrameId=%v, playerLastSentInputFrameId=%v, lowerToSentInputFrameId=%v, upperToSendInputFrameId=%v, j=%v: InputsBuffer=%v" , refRenderFrameId , pR . Id , playerId , player . JoinIndex , pR . RenderFrameId , pR . CurDynamicsRenderFrameId , player . LastSentInputFrameId , lowerToSentInputFrameId , upperToSendInputFrameId , j , pR . InputsBufferString ( false ) ) )
2022-11-30 13:51:06 +00:00
pR . sendSafely ( refRenderFrame , toSendInputFrameDownsyncs , DOWNSYNC_MSG_ACT_FORCED_RESYNC , playerId )
2022-11-29 16:04:52 +00:00
} else {
2022-11-30 13:51:06 +00:00
pR . sendSafely ( nil , toSendInputFrameDownsyncs , DOWNSYNC_MSG_ACT_INPUT_BATCH , playerId )
2022-11-29 16:04:52 +00:00
}
player . LastSentInputFrameId = j - 1
}
2022-12-04 15:36:55 +00:00
func ( pR * Room ) createInputsBufferSnapshot ( ) * RingBuffer {
// [WARNING] This function MUST BE called while "pR.InputsBufferLock" is locked!
snapshot := NewRingBuffer ( pR . InputsBuffer . N )
for j := pR . InputsBuffer . StFrameId ; j < pR . InputsBuffer . Ed ; j ++ {
tmp := pR . InputsBuffer . GetByFrameId ( j )
snapshot . Put ( tmp )
}
return snapshot
}
func ( pR * Room ) cloneInputFrameDownsyncsByFrameIdRange ( stFrameId , edFrameId int32 , src * RingBuffer ) ( int32 , [ ] * InputFrameDownsync ) {
dst := make ( [ ] * InputFrameDownsync , 0 , src . Cnt )
prevFrameFound := true
j := stFrameId
for j < edFrameId {
tmp := src . GetByFrameId ( j )
if nil == tmp {
if false == prevFrameFound {
// The "id"s are always consecutive
break
} else {
prevFrameFound = false
continue
}
}
foo := tmp . ( * InputFrameDownsync )
bar := & InputFrameDownsync {
InputFrameId : foo . InputFrameId ,
InputList : make ( [ ] uint64 , len ( foo . InputList ) ) ,
ConfirmedList : foo . ConfirmedList ,
}
for i , input := range foo . InputList {
bar . InputList [ i ] = input
}
dst = append ( dst , bar )
j ++
}
return j , dst
}