Compare commits

...

18 Commits

Author SHA1 Message Date
genxium
16c27b0ce0 Minor fix. 2023-02-19 21:26:49 +08:00
genxium
a44535cad2 Fixes for revival dynamics. 2023-02-19 21:05:25 +08:00
genxium
8b5a96e825 Enhanced exception handling on frontend. 2023-02-19 13:42:25 +08:00
genxium
618531f5c6 Minor fix. 2023-02-18 13:21:34 +08:00
genxium
8345d55e76 Updated collision constant setup. 2023-02-18 11:46:01 +08:00
genxium
a48e2f3cc0 Fixed bullet cloning. 2023-02-18 11:13:33 +08:00
yflu
83419a6f23 Fixed debug boundary drawing. 2023-02-17 22:44:21 +08:00
genxium
b19549b0a8 Enhanced inplace rdf update. 2023-02-17 18:54:51 +08:00
genxium
60866674b5 Fixed multiplayer mode for new dynamics calculation. 2023-02-17 15:38:37 +08:00
genxium
a4941c1273 Minor fix. 2023-02-17 14:35:42 +08:00
genxium
2b304eaa75 A temp dirty commit having mysterious left moving players. 2023-02-17 12:26:07 +08:00
genxium
c6b98855af Reduced externalization cost. 2023-02-16 22:23:12 +08:00
genxium
4e0928cb1b Drafted inplace memory version of game dynamics. 2023-02-16 17:13:09 +08:00
genxium
fb42533f55 Temp update to remove use of MakeFullWrapper for reducing redundant CPU time in profiling. 2023-02-16 10:14:37 +08:00
genxium
9dff989e02 Drafted inplace_ring_buff. 2023-02-15 21:34:27 +08:00
genxium
5b7f35b874 Applied ringbuff to resolv_tailored for reducing memory usage. 2023-02-15 15:38:20 +08:00
genxium
2d179d0cdf Minor update. 2023-02-14 23:27:21 +08:00
genxium
f4b303eb91 Started preparation of in-place clone utilities in jsexport. 2023-02-14 15:38:21 +08:00
38 changed files with 1592 additions and 1059 deletions

View File

@@ -4,9 +4,24 @@ ROOT_DIR=.
GOPROXY=https://goproxy.io
all: help
# To install `gojson` executable
# ```
# go install github.com/ChimeraCoder/gojson/gojson@latest
# ```
#
# OS detection reference https://stackoverflow.com/a/12099167
gen-constants:
gojson -pkg common -name constants -input common/constants.json -o common/constants_struct.go
ifeq ($(OS),Windows_NT)
sed -i 's/int64/int/g' common/constants_struct.go
else
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
sed -i '' -e 's/int64/int/g' common/constants_struct.go
else
sed -i 's/int64/int/g' common/constants_struct.go
endif
endif
run-test: build
ServerEnv=TEST ./$(PROJECTNAME)

View File

@@ -44,10 +44,10 @@
"PASSWORD_RESET_CODE_GENERATION_PER_EMAIL_TOO_FREQUENTLY": 2020,
"TRADE_CREATION_TOO_FREQUENTLY": 2021,
"MAP_NOT_UNLOCKED": 2022,
"GET_SMS_CAPTCHA_RESP_ERROR_CODE": 2023,
"SMS_CAPTCHA_REQUESTED_TOO_FREQUENTLY": 2024,
"SMS_CAPTCHA_NOT_MATCH": 2025,
"SAME_PLAYER_ALREADY_IN_SAME_ROOM": 2026,
"NOT_IMPLEMENTED_YET": 65535
},

View File

@@ -17,6 +17,7 @@ type constants struct {
RetCode struct {
ActiveWatchdog int `json:"ACTIVE_WATCHDOG"`
BattleStopped int `json:"BATTLE_STOPPED"`
ClientMismatchedRenderFrame int `json:"CLIENT_MISMATCHED_RENDER_FRAME"`
Duplicated int `json:"DUPLICATED"`
FailedToCreate int `json:"FAILED_TO_CREATE"`
FailedToDelete int `json:"FAILED_TO_DELETE"`
@@ -51,6 +52,7 @@ type constants struct {
PlayerNotAddableToRoom int `json:"PLAYER_NOT_ADDABLE_TO_ROOM"`
PlayerNotFound int `json:"PLAYER_NOT_FOUND"`
PlayerNotReaddableToRoom int `json:"PLAYER_NOT_READDABLE_TO_ROOM"`
SamePlayerAlreadyInSameRoom int `json:"SAME_PLAYER_ALREADY_IN_SAME_ROOM"`
SendEmailTimeout int `json:"SEND_EMAIL_TIMEOUT"`
SmsCaptchaNotMatch int `json:"SMS_CAPTCHA_NOT_MATCH"`
SmsCaptchaRequestedTooFrequently int `json:"SMS_CAPTCHA_REQUESTED_TOO_FREQUENTLY"`

View File

@@ -3,6 +3,7 @@ module battle_srv
go 1.18
require (
dnmshared v0.0.0
github.com/Masterminds/squirrel v0.0.0-20180815162352-8a7e65843414
github.com/davecgh/go-spew v1.1.1
github.com/gin-contrib/cors v0.0.0-20180514151808-6f0a820f94be
@@ -19,14 +20,12 @@ require (
github.com/thoas/go-funk v0.0.0-20180716193722-1060394a7713
go.uber.org/zap v1.9.1
google.golang.org/protobuf v1.28.1
dnmshared v0.0.0
jsexport v0.0.0
resolv v0.0.0
jsexport v0.0.0
resolv v0.0.0
)
require (
github.com/ChimeraCoder/gojson v1.0.0 // indirect
github.com/ChimeraCoder/gojson v1.1.0 // indirect
github.com/fatih/color v1.7.0 // indirect
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 // indirect
github.com/githubnemo/CompileDaemon v1.0.0 // indirect
@@ -44,11 +43,11 @@ require (
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 // indirect
gopkg.in/go-playground/validator.v8 v8.18.2 // indirect
gopkg.in/yaml.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
replace (
dnmshared => ../dnmshared
jsexport => ../jsexport
resolv => ../resolv_tailored
jsexport => ../jsexport
resolv => ../resolv_tailored
)

View File

@@ -2,6 +2,8 @@ github.com/ByteArena/box2d v1.0.2 h1:f7f9KEQWhCs1n516DMLzi5w6u0MeeE78Mes4fWMcj9k
github.com/ByteArena/box2d v1.0.2/go.mod h1:LzEuxY9iCz+tskfWCY3o0ywYBRafDDugdSj+/YGI6sE=
github.com/ChimeraCoder/gojson v1.0.0 h1:gAYKGTV+xfQ4+l/4C/nazPbiQDUidG0G3ukAJnE7LNE=
github.com/ChimeraCoder/gojson v1.0.0/go.mod h1:nYbTQlu6hv8PETM15J927yM0zGj3njIldp72UT1MqSw=
github.com/ChimeraCoder/gojson v1.1.0 h1:/6S8djl/jColpJGTYniA3xrqJWuKeyEozzPtpr5L4Pw=
github.com/ChimeraCoder/gojson v1.1.0/go.mod h1:nYbTQlu6hv8PETM15J927yM0zGj3njIldp72UT1MqSw=
github.com/Masterminds/squirrel v0.0.0-20180815162352-8a7e65843414 h1:VHdYPA0V0YgL97gdjHevN6IVPRX+fOoNMqcYvUAzwNU=
github.com/Masterminds/squirrel v0.0.0-20180815162352-8a7e65843414/go.mod h1:xnKTFzjGUiZtiOagBsfnvomW+nJg2usB1ZpordQWqNM=
github.com/Pallinder/go-randomdata v0.0.0-20180616180521-15df0648130a h1:0OnS8GR4uI3nau+f/juCZlAq+zCrsHXRJlENrUQ4eU8=
@@ -92,3 +94,5 @@ gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2G
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View File

@@ -135,9 +135,9 @@ type Room struct {
CurDynamicsRenderFrameId int32 // [WARNING] The dynamics of backend is ALWAYS MOVING FORWARD BY ALL-CONFIRMED INPUTFRAMES (either by upsync or forced), i.e. no rollback; Moreover when "true == BackendDynamicsEnabled" we always have "Room.CurDynamicsRenderFrameId >= Room.RenderFrameId" because each "all-confirmed inputFrame" is applied on "all applicable renderFrames" in one-go hence often sees a future "renderFrame" earlier
EffectivePlayerCount int32
DismissalWaitGroup sync.WaitGroup
InputsBuffer *battle.RingBuffer // Indices are STRICTLY consecutive
InputsBuffer *resolv.RingBuffer // Indices are STRICTLY consecutive
InputsBufferLock sync.Mutex // Guards [InputsBuffer, LatestPlayerUpsyncedInputFrameId, LastAllConfirmedInputFrameId, LastAllConfirmedInputList, LastAllConfirmedInputFrameIdWithChange, LastIndividuallyConfirmedInputList, player.LastReceivedInputFrameId, player.LastUdpReceivedInputFrameId]
RenderFrameBuffer *battle.RingBuffer // Indices are STRICTLY consecutive
RenderFrameBuffer *resolv.RingBuffer // Indices are STRICTLY consecutive
LatestPlayerUpsyncedInputFrameId int32
LastAllConfirmedInputFrameId int32
LastAllConfirmedInputFrameIdWithChange int32
@@ -162,22 +162,28 @@ type Room struct {
BattleUdpTunnelLock sync.Mutex
BattleUdpTunnelAddr *pb.PeerUdpAddr
BattleUdpTunnel *net.UDPConn
collisionHolder *resolv.Collision
effPushbacks []*battle.Vec2D
hardPushbackNormsArr [][]*battle.Vec2D
jumpedOrNotList []bool
dynamicRectangleColliders []*resolv.Object
}
func (pR *Room) updateScore() {
pR.Score = calRoomScore(pR.EffectivePlayerCount, pR.Capacity, pR.State)
}
func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, speciesId int, session *websocket.Conn, signalToCloseConnOfThisPlayer SignalToCloseConnCbType) bool {
func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, speciesId int, session *websocket.Conn, signalToCloseConnOfThisPlayer SignalToCloseConnCbType) int {
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
return Constants.RetCode.PlayerNotAddableToRoom
}
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
return Constants.RetCode.SamePlayerAlreadyInSameRoom
}
defer pR.onPlayerAdded(playerId, speciesId)
@@ -204,19 +210,19 @@ func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, speciesId int, se
})
newWatchdog.Stop()
pR.PlayerActiveWatchdogDict[playerId] = newWatchdog
return true
return Constants.RetCode.Ok
}
func (pR *Room) ReAddPlayerIfPossible(pTmpPlayerInstance *Player, session *websocket.Conn, signalToCloseConnOfThisPlayer SignalToCloseConnCbType) bool {
func (pR *Room) ReAddPlayerIfPossible(pTmpPlayerInstance *Player, session *websocket.Conn, signalToCloseConnOfThisPlayer SignalToCloseConnCbType) int {
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
return Constants.RetCode.PlayerNotReaddableToRoom
}
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
return Constants.RetCode.PlayerNotReaddableToRoom
}
/*
* WARNING: The "pTmpPlayerInstance *Player" used here is a temporarily constructed
@@ -245,7 +251,7 @@ func (pR *Room) ReAddPlayerIfPossible(pTmpPlayerInstance *Player, session *webso
}) // For ReAdded player the new watchdog starts immediately
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))
return true
return Constants.RetCode.Ok
}
func (pR *Room) ChooseStage() error {
@@ -420,11 +426,10 @@ func (pR *Room) StartBattle() {
// Initialize the "collisionSys" as well as "RenderFrameBuffer"
pR.CurDynamicsRenderFrameId = 0
kickoffFrameJs := &battle.RoomDownsyncFrame{
Id: pR.RenderFrameId,
PlayersArr: toJsPlayers(pR.Players),
CountdownNanos: pR.BattleDurationNanos,
}
kickoffFrameJs := battle.NewPreallocatedRoomDownsyncFrame(len(pR.Players), 64, 64)
battle.CloneRoomDownsyncFrame(pR.RenderFrameId, toJsPlayers(pR.Players), 0, make([]*battle.MeleeBullet, 0), make([]*battle.FireballBullet, 0), kickoffFrameJs)
kickoffFrameJs.CountdownNanos = pR.BattleDurationNanos
pR.RenderFrameBuffer.Put(kickoffFrameJs)
// Refresh "Colliders"
@@ -772,7 +777,7 @@ func (pR *Room) OnDismissed() {
// Always instantiates new HeapRAM blocks and let the old blocks die out due to not being retained by any root reference.
pR.BulletBattleLocalIdCounter = 0
pR.CollisionMinStep = 8 // the approx minimum distance a player can move per frame in world coordinate
pR.CollisionMinStep = 16 // the approx minimum distance a player can move per frame in world coordinate
pR.Players = make(map[int32]*Player)
pR.PlayersArr = make([]*Player, pR.Capacity)
pR.SpeciesIdList = make([]int32, pR.Capacity)
@@ -795,9 +800,9 @@ func (pR *Room) OnDismissed() {
pR.PlayerSignalToCloseDict = make(map[int32]SignalToCloseConnCbType)
pR.PlayerSecondarySignalToCloseDict = make(map[int32]SignalToCloseConnCbType)
pR.JoinIndexBooleanArr = make([]bool, pR.Capacity)
pR.RenderCacheSize = 1024
pR.RenderFrameBuffer = battle.NewRingBuffer(pR.RenderCacheSize)
pR.InputsBuffer = battle.NewRingBuffer((pR.RenderCacheSize >> 1) + 1)
pR.RenderCacheSize = 256
pR.RenderFrameBuffer = resolv.NewRingBuffer(pR.RenderCacheSize)
pR.InputsBuffer = resolv.NewRingBuffer((pR.RenderCacheSize >> 1) + 1)
pR.rdfIdToActuallyUsedInput = make(map[int32]*pb.InputFrameDownsync)
pR.LastIndividuallyConfirmedInputList = make([]uint64, pR.Capacity)
@@ -810,6 +815,24 @@ func (pR *Room) OnDismissed() {
pR.CurDynamicsRenderFrameId = 0
pR.NstDelayFrames = 24
pR.collisionHolder = resolv.NewCollision()
pR.effPushbacks = make([]*battle.Vec2D, pR.Capacity)
for i := 0; i < len(pR.effPushbacks); i++ {
pR.effPushbacks[i] = &battle.Vec2D{X: 0, Y: 0}
}
pR.hardPushbackNormsArr = make([][]*battle.Vec2D, pR.Capacity)
for i := 0; i < pR.Capacity; i++ {
pR.hardPushbackNormsArr[i] = make([]*battle.Vec2D, 5)
for j := 0; j < len(pR.hardPushbackNormsArr[i]); j++ {
pR.hardPushbackNormsArr[i][j] = &battle.Vec2D{X: 0, Y: 0}
}
}
pR.jumpedOrNotList = make([]bool, pR.Capacity)
pR.dynamicRectangleColliders = make([]*resolv.Object, 64)
for i := 0; i < len(pR.dynamicRectangleColliders); i++ {
pR.dynamicRectangleColliders[i] = battle.GenerateRectCollider(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, nil, "")
}
serverFps := 60
pR.RollbackEstimatedDtMillis = 16.667 // Use fixed-and-low-precision to mitigate the inconsistent floating-point-number issue between Golang and JavaScript
pR.RollbackEstimatedDtNanos = 16666666 // A little smaller than the actual per frame time, just for logging FAST FRAME
@@ -1356,8 +1379,7 @@ func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRende
}
}
nextRenderFrame := battle.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(pR.InputsBuffer, currRenderFrame, pR.Space, pR.CollisionSysMap, pR.SpaceOffsetX, pR.SpaceOffsetY, pR.CharacterConfigsArr)
pR.RenderFrameBuffer.Put(nextRenderFrame)
battle.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(pR.InputsBuffer, currRenderFrame.Id, pR.Space, pR.CollisionSysMap, pR.SpaceOffsetX, pR.SpaceOffsetY, pR.CharacterConfigsArr, pR.RenderFrameBuffer, pR.collisionHolder, pR.effPushbacks, pR.hardPushbackNormsArr, pR.jumpedOrNotList, pR.dynamicRectangleColliders)
pR.CurDynamicsRenderFrameId++
}
}
@@ -1771,7 +1793,7 @@ func (pR *Room) startBattleUdpTunnel() {
}
_, wrerr := conn.WriteTo(bytes, otherPlayer.BattleUdpTunnelAddr)
if nil != wrerr {
Logger.Warn(fmt.Sprintf("`BattleUdpTunnel` for roomId=%d failed to forward upsync from (playerId:%d, joinIndex:%d, addr:%s) to (otherPlayerId:%d, otherPlayerJoinIndex:%d, otherPlayerAddr:%s)\n", pR.Id, playerId, peerJoinIndex, remote, otherPlayer.Id, otherPlayer.JoinIndex, otherPlayer.BattleUdpTunnelAddr))
//Logger.Debug(fmt.Sprintf("`BattleUdpTunnel` for roomId=%d failed to forward upsync from (playerId:%d, joinIndex:%d, addr:%s) to (otherPlayerId:%d, otherPlayerJoinIndex:%d, otherPlayerAddr:%s)\n", pR.Id, playerId, peerJoinIndex, remote, otherPlayer.Id, otherPlayer.JoinIndex, otherPlayer.BattleUdpTunnelAddr))
}
}
pR.OnBattleCmdReceived(pReq, true) // To help advance "pR.LastAllConfirmedInputFrameId" asap, and even if "pR.LastAllConfirmedInputFrameId" is not advanced due to packet loss, these UDP packets would help prefill the "InputsBuffer" with correct player "future inputs (compared to ws session)" such that when "forceConfirmation" occurs we have as many correct predictions as possible

View File

@@ -188,34 +188,33 @@ func Serve(c *gin.Context) {
}()
Logger.Debug("Acquired RoomHeapMux for player:", zap.Any("playerId", playerId))
// Logger.Info("The RoomHeapManagerIns has:", zap.Any("addr", fmt.Sprintf("%p", models.RoomHeapManagerIns)), zap.Any("size", len(*(models.RoomHeapManagerIns))))
playerSuccessfullyAddedToRoom := false
playerRoomRelation := Constants.RetCode.UnknownError
if 0 < boundRoomId {
if tmpPRoom, existent := (*models.RoomMapManagerIns)[int32(boundRoomId)]; existent {
pRoom = tmpPRoom
res := pRoom.ReAddPlayerIfPossible(pPlayer, conn, signalToCloseConnOfThisPlayer)
if !res {
playerRoomRelation = pRoom.ReAddPlayerIfPossible(pPlayer, conn, signalToCloseConnOfThisPlayer)
if Constants.RetCode.Ok != playerRoomRelation {
Logger.Warn("Failed to get:\n", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Any("forBoundRoomId", boundRoomId))
} else {
playerSuccessfullyAddedToRoom = true
}
}
} else if 0 < expectedRoomId {
if tmpRoom, existent := (*models.RoomMapManagerIns)[int32(expectedRoomId)]; existent {
pRoom = tmpRoom
if pRoom.ReAddPlayerIfPossible(pPlayer, conn, signalToCloseConnOfThisPlayer) {
playerSuccessfullyAddedToRoom = true
} else if pRoom.AddPlayerIfPossible(pPlayer, speciesId, conn, signalToCloseConnOfThisPlayer) {
playerSuccessfullyAddedToRoom = true
} else {
Logger.Warn("Failed to get:\n", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Any("forExpectedRoomId", expectedRoomId))
playerSuccessfullyAddedToRoom = false
playerRoomRelation = pRoom.ReAddPlayerIfPossible(pPlayer, conn, signalToCloseConnOfThisPlayer)
if Constants.RetCode.Ok != playerRoomRelation {
playerRoomRelation = pRoom.AddPlayerIfPossible(pPlayer, speciesId, conn, signalToCloseConnOfThisPlayer)
}
if Constants.RetCode.Ok != playerRoomRelation {
Logger.Warn("Failed to get:\n", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Any("forExpectedRoomId", expectedRoomId))
}
}
}
if false == playerSuccessfullyAddedToRoom {
if Constants.RetCode.SamePlayerAlreadyInSameRoom == playerRoomRelation {
signalToCloseConnOfThisPlayer(playerRoomRelation, fmt.Sprintf("playerId == %v is already in a room, this account is possibly stolen!", playerId))
}
if Constants.RetCode.Ok != playerRoomRelation {
defer func() {
if pRoom != nil {
heap.Push(models.RoomHeapManagerIns, pRoom)
@@ -229,9 +228,9 @@ func Serve(c *gin.Context) {
} else {
pRoom = tmpRoom
Logger.Info("Successfully popped:\n", zap.Any("roomId", pRoom.Id), zap.Any("forPlayerId", playerId))
res := pRoom.AddPlayerIfPossible(pPlayer, speciesId, conn, signalToCloseConnOfThisPlayer)
if !res {
signalToCloseConnOfThisPlayer(Constants.RetCode.PlayerNotAddableToRoom, fmt.Sprintf("AddPlayerIfPossible returns false for roomId == %v, playerId == %v!", pRoom.Id, playerId))
playerRoomRelation = pRoom.AddPlayerIfPossible(pPlayer, speciesId, conn, signalToCloseConnOfThisPlayer)
if Constants.RetCode.Ok != playerRoomRelation {
signalToCloseConnOfThisPlayer(playerRoomRelation, fmt.Sprintf("AddPlayerIfPossible returns false for roomId == %v, playerId == %v!", pRoom.Id, playerId))
}
}
}

View File

@@ -12,8 +12,9 @@ func NormVec2D(dx, dy float64) Vec2D {
}
func ConvexPolygonStr(body *resolv.ConvexPolygon) string {
var s []string = make([]string, len(body.Points))
for i, p := range body.Points {
var s []string = make([]string, body.Points.Cnt)
for i := int32(0); i < body.Points.Cnt; i++ {
p := body.GetPointByOffset(i)
s[i] = fmt.Sprintf("[%.2f, %.2f]", p[0]+body.X, p[1]+body.Y)
}

View File

@@ -109,6 +109,7 @@ var constants = {
"GET_SMS_CAPTCHA_RESP_ERROR_CODE": 2023,
"SMS_CAPTCHA_REQUESTED_TOO_FREQUENTLY": 2024,
"SMS_CAPTCHA_NOT_MATCH": 2025,
"SAME_PLAYER_ALREADY_IN_SAME_ROOM": 2026,
"NOT_IMPLEMENTED_YET": 65535
},

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.2" tiledversion="1.2.3" orientation="orthogonal" renderorder="right-down" width="128" height="64" tilewidth="16" tileheight="16" infinite="0" nextlayerid="7" nextobjectid="137">
<map version="1.2" tiledversion="1.2.3" orientation="orthogonal" renderorder="right-down" width="128" height="64" tilewidth="16" tileheight="16" infinite="0" nextlayerid="9" nextobjectid="138">
<tileset firstgid="1" source="tiles0.tsx"/>
<tileset firstgid="65" source="tiles1.tsx"/>
<tileset firstgid="129" source="tiles2.tsx"/>
<layer id="6" name="Ground" width="128" height="64">
<layer id="8" name="Ground" width="128" height="64">
<data encoding="base64" compression="zlib">
eJzt201uwjAQhuEIxCY7KugeqTepumHXC/T+xyCqYimy4sQ4E2bE9y6eDf+e1w4bOHZddwQAAAAAAAAAAACAF/jBP+8Onv29P4M35Rkor50ZaK+dGWivnRlor50ZtK39UmnL5/owegz94/X/on8Ykfsv9V27n/72a0/drPqfBucZeeMSjxm8m5b+W1m9Dv3fv//c9eG88f3p397fytr1vNR9j31A/1hq21vtg4gziNz/ZvQY+vubrr0fefdv3QOt+0O9/3cm3ZffntwqlZ4fTZqBdwuv/odROv9/g3vBwUDpjFq8dst7q5//vP/0O2B6m1V/T/Qv979nvedY9/jcwTPXHPov9+lXZldjrb/1fqL/c/2vo6W5bTn/S3tg7/Nfsz/pX+5v+Z1L/3i8f3cdhXcHz/6/4uivjf7a6K+N/tror43+2uivjf7a6K+N/tror43+2uivjf7a6K+N/tror43+2uivjf7a6K+N/trSf6C8W9Cf/vSn/6v7Y7/+Dyz1uAA=
eJzt2j1uwkAQgFEESkNHlKRHyk2iNHRcIPc/RlCEJWSxtrHHmRX7itfw651v1zTsNpvNDgAAAAAAAOAffPMnu0Nm/+xryNbyDFpeuxm0vXYzaHvtZtD22s1g3trfJlpyXa9Br9G/vv6f+lej5v5Dfcee1z9+7V23qP4vF4c7+o1LMmbwbOb0Xyrqc/R//v737g+Hhd+v//z+Ucbu56Xua+wD/esytX3UPqhxBjX3Pwa9Rv98t2vfX2X3n7sH5u6P1vt/9XTP9R/vHCcqvb823QyyW2T131515//n4lSwDVA6oxGfPee7Wz///f63vwG3j0X1z6R/uf+p1/ue6B4fK3jknqP/cJ/9yOymGOsfvZ/0f6z/+9XQ3Jac/6E9sPb5n7I/9S/3j/zN1b8+2f+7rkV2h8z+58bp37Y1+x8qlz37WmR30F9//fXXX38AAAAAAAAAAAAg1y8jXMSV
</data>
</layer>
<objectgroup id="1" name="PlayerStartingPos">
@@ -84,12 +84,12 @@
<property name="boundary_type" value="barrier"/>
</properties>
</object>
<object id="84" x="640" y="224" width="16" height="800">
<object id="84" x="640" y="224" width="16" height="416">
<properties>
<property name="boundary_type" value="barrier"/>
</properties>
</object>
<object id="85" x="1680" y="224" width="16" height="800">
<object id="85" x="1680" y="224" width="16" height="416">
<properties>
<property name="boundary_type" value="barrier"/>
</properties>
@@ -149,17 +149,12 @@
<property name="boundary_type" value="barrier"/>
</properties>
</object>
<object id="113" x="640" y="1008" width="1056" height="16">
<properties>
<property name="boundary_type" value="barrier"/>
</properties>
</object>
<object id="114" x="640" y="224" width="1056" height="16">
<properties>
<property name="boundary_type" value="barrier"/>
</properties>
</object>
<object id="119" x="656" y="592" width="1024" height="416">
<object id="119" x="656" y="592" width="512" height="48">
<properties>
<property name="boundary_type" value="barrier"/>
</properties>
@@ -169,5 +164,10 @@
<property name="boundary_type" value="barrier"/>
</properties>
</object>
<object id="137" x="1168" y="592" width="512" height="48">
<properties>
<property name="boundary_type" value="barrier"/>
</properties>
</object>
</objectgroup>
</map>

View File

@@ -22,6 +22,9 @@
},
{
"__id__": 5
},
{
"__id__": 11
}
],
"_active": true,
@@ -59,7 +62,7 @@
"ctor": "Float64Array",
"array": [
480,
320,
480,
0,
0,
0,
@@ -192,102 +195,28 @@
"fileId": "ab6G+s0otA4rXhUsO3czRN",
"sync": false
},
{
"__type__": "cc.Node",
"_name": "VerticalLayout",
"_objFlags": 0,
"_parent": {
"__id__": 1
},
"_children": [
{
"__id__": 6
},
{
"__id__": 12
}
],
"_active": true,
"_components": [
{
"__id__": 53
},
{
"__id__": 54
}
],
"_prefab": {
"__id__": 55
},
"_opacity": 255,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_contentSize": {
"__type__": "cc.Size",
"width": 960,
"height": 265
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_trs": {
"__type__": "TypedArray",
"ctor": "Float64Array",
"array": [
0,
128,
0,
0,
0,
0,
1,
1,
1,
1
]
},
"_eulerAngles": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_skewX": 0,
"_skewY": 0,
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": ""
},
{
"__type__": "cc.Node",
"_name": "exitButton",
"_objFlags": 0,
"_parent": {
"__id__": 5
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 6
},
{
"__id__": 7
},
{
"__id__": 8
},
{
"__id__": 10
"__id__": 9
}
],
"_prefab": {
"__id__": 11
"__id__": 10
},
"_opacity": 255,
"_color": {
@@ -312,7 +241,7 @@
"ctor": "Float64Array",
"array": [
-379.577,
100.5,
90.576,
0,
0,
0,
@@ -341,7 +270,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 6
"__id__": 5
},
"_enabled": true,
"_materials": [
@@ -375,7 +304,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 6
"__id__": 5
},
"_enabled": true,
"_normalMaterial": null,
@@ -384,7 +313,7 @@
"zoomScale": 1.2,
"clickEvents": [
{
"__id__": 9
"__id__": 8
}
],
"_N$interactable": true,
@@ -440,7 +369,7 @@
"hoverSprite": null,
"_N$disabledSprite": null,
"_N$target": {
"__id__": 6
"__id__": 5
},
"_id": ""
},
@@ -459,7 +388,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 6
"__id__": 5
},
"_enabled": true,
"alignMode": 1,
@@ -489,15 +418,86 @@
"asset": {
"__uuid__": "dc804c5c-ff76-445e-ac69-52269055c3c5"
},
"fileId": "1cUg34ZtdK9JfdGIG+lpdF",
"fileId": "3cdlb7LxhMzLDzxdQv8Z/x",
"sync": false
},
{
"__type__": "cc.Node",
"_name": "VerticalLayout",
"_objFlags": 0,
"_parent": {
"__id__": 1
},
"_children": [
{
"__id__": 12
}
],
"_active": true,
"_components": [
{
"__id__": 53
},
{
"__id__": 54
}
],
"_prefab": {
"__id__": 55
},
"_opacity": 255,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_contentSize": {
"__type__": "cc.Size",
"width": 960,
"height": 137
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_trs": {
"__type__": "TypedArray",
"ctor": "Float64Array",
"array": [
0,
-68.939,
0,
0,
0,
0,
1,
1,
1,
1
]
},
"_eulerAngles": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_skewX": 0,
"_skewY": 0,
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": ""
},
{
"__type__": "cc.Node",
"_name": "HorizontalLayout",
"_objFlags": 0,
"_parent": {
"__id__": 5
"__id__": 11
},
"_children": [
{
@@ -545,7 +545,7 @@
"ctor": "Float64Array",
"array": [
0,
-64,
0,
0,
0,
0,
@@ -1953,7 +1953,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 5
"__id__": 11
},
"_enabled": true,
"_materials": [
@@ -1983,13 +1983,13 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 5
"__id__": 11
},
"_enabled": true,
"_layoutSize": {
"__type__": "cc.Size",
"width": 960,
"height": 265
"height": 137
},
"_resize": 1,
"_N$layoutType": 2,
@@ -2040,7 +2040,7 @@
"__id__": 43
},
"exitBtnNode": {
"__id__": 6
"__id__": 5
},
"_id": ""
},

View File

@@ -536,7 +536,7 @@
"array": [
0,
0,
216.65450766436658,
216.05530045313827,
0,
0,
0,

View File

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

View File

@@ -72,11 +72,14 @@
"__id__": 3
},
{
"__id__": 10
"__id__": 9
}
],
"_active": true,
"_components": [
{
"__id__": 50
},
{
"__id__": 51
},
@@ -88,9 +91,6 @@
},
{
"__id__": 54
},
{
"__id__": 55
}
],
"_prefab": null,
@@ -156,9 +156,6 @@
},
{
"__id__": 5
},
{
"__id__": 6
}
],
"_prefab": null,
@@ -220,34 +217,6 @@
"_tmxFile": null,
"_id": "c8MqKDLJdKz7VhPwMjScDw"
},
{
"__type__": "09e1b/tEy5K2qaPIpqHDbae",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 3
},
"_enabled": true,
"BGMEffect": {
"__uuid__": "64a79efa-97de-4cb5-b2a9-01500c60573a"
},
"crashedByTrapBullet": {
"__uuid__": "1d604e42-8cee-466f-884d-e74cae21ce3b"
},
"highScoreTreasurePicked": {
"__uuid__": "0164d22c-d965-461f-867e-b30e2d56cc5c"
},
"treasurePicked": {
"__uuid__": "7704b97e-6367-420c-b7af-d0750a2bbb30"
},
"countDown10SecToEnd": {
"__uuid__": "261d1d7d-a5cc-4cb7-a737-194427055fd4"
},
"mapNode": {
"__id__": 3
},
"_id": "3crA1nz5xPSLAnCSLQIPOq"
},
{
"__type__": "b3810kDSWtD14RhiYzulYVI",
"_name": "",
@@ -266,7 +235,7 @@
"__uuid__": "d92d4831-cd65-4eb5-90bd-b77021aec35b"
},
"joystickInputControllerNode": {
"__id__": 7
"__id__": 6
},
"confirmLogoutPrefab": null,
"simplePressToGoDialogPrefab": null,
@@ -279,19 +248,19 @@
"forceBigEndianFloatingNumDecoding": false,
"renderFrameIdLagTolerance": 4,
"inputFrameFrontLabel": {
"__id__": 14
"__id__": 13
},
"sendingQLabel": {
"__id__": 16
"__id__": 15
},
"inputFrameDownsyncQLabel": {
"__id__": 18
"__id__": 17
},
"peerInputFrameUpsyncQLabel": {
"__id__": 20
"__id__": 19
},
"rollbackFramesLabel": {
"__id__": 22
"__id__": 21
},
"skippedRenderFrameCntLabel": null,
"_id": "e5xQdv12xLoIRr0b36Pie+"
@@ -301,17 +270,17 @@
"_name": "JoystickContainer",
"_objFlags": 0,
"_parent": {
"__id__": 8
"__id__": 7
},
"_children": [
{
"__id__": 45
"__id__": 44
}
],
"_active": true,
"_components": [
{
"__id__": 50
"__id__": 49
}
],
"_prefab": null,
@@ -367,26 +336,26 @@
"_name": "Interactive",
"_objFlags": 0,
"_parent": {
"__id__": 9
"__id__": 8
},
"_children": [
{
"__id__": 7
"__id__": 6
},
{
"__id__": 34
"__id__": 33
},
{
"__id__": 36
"__id__": 35
},
{
"__id__": 40
"__id__": 39
}
],
"_active": true,
"_components": [
{
"__id__": 44
"__id__": 43
}
],
"_prefab": null,
@@ -442,20 +411,20 @@
"_name": "WidgetsAboveAll",
"_objFlags": 0,
"_parent": {
"__id__": 10
"__id__": 9
},
"_children": [
{
"__id__": 12
"__id__": 11
},
{
"__id__": 8
"__id__": 7
}
],
"_active": true,
"_components": [
{
"__id__": 33
"__id__": 32
}
],
"_prefab": null,
@@ -515,13 +484,13 @@
},
"_children": [
{
"__id__": 9
"__id__": 8
}
],
"_active": true,
"_components": [
{
"__id__": 11
"__id__": 10
}
],
"_prefab": null,
@@ -549,7 +518,7 @@
"array": [
0,
0,
210.4441731196186,
216.50635094610968,
0,
0,
0,
@@ -577,7 +546,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 10
"__id__": 9
},
"_enabled": true,
"_cullingMask": 4294967295,
@@ -613,35 +582,35 @@
"_name": "DebugInfo",
"_objFlags": 0,
"_parent": {
"__id__": 9
"__id__": 8
},
"_children": [
{
"__id__": 13
"__id__": 12
},
{
"__id__": 15
"__id__": 14
},
{
"__id__": 17
"__id__": 16
},
{
"__id__": 19
"__id__": 18
},
{
"__id__": 21
"__id__": 20
},
{
"__id__": 23
"__id__": 22
},
{
"__id__": 25
"__id__": 24
}
],
"_active": true,
"_components": [
{
"__id__": 32
"__id__": 31
}
],
"_prefab": null,
@@ -697,13 +666,13 @@
"_name": "inputFrameFront",
"_objFlags": 0,
"_parent": {
"__id__": 12
"__id__": 11
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 14
"__id__": 13
}
],
"_prefab": null,
@@ -759,7 +728,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 13
"__id__": 12
},
"_enabled": true,
"_materials": [
@@ -789,13 +758,13 @@
"_name": "sendingQ",
"_objFlags": 0,
"_parent": {
"__id__": 12
"__id__": 11
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 16
"__id__": 15
}
],
"_prefab": null,
@@ -851,7 +820,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 15
"__id__": 14
},
"_enabled": true,
"_materials": [
@@ -881,13 +850,13 @@
"_name": "inputFrameDownsyncQ",
"_objFlags": 0,
"_parent": {
"__id__": 12
"__id__": 11
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 18
"__id__": 17
}
],
"_prefab": null,
@@ -943,7 +912,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 17
"__id__": 16
},
"_enabled": true,
"_materials": [
@@ -973,13 +942,13 @@
"_name": "peerInputFrameUpsyncQ",
"_objFlags": 0,
"_parent": {
"__id__": 12
"__id__": 11
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 20
"__id__": 19
}
],
"_prefab": null,
@@ -1035,7 +1004,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 19
"__id__": 18
},
"_enabled": true,
"_materials": [
@@ -1065,13 +1034,13 @@
"_name": "rollbackFrames",
"_objFlags": 0,
"_parent": {
"__id__": 12
"__id__": 11
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 22
"__id__": 21
}
],
"_prefab": null,
@@ -1127,7 +1096,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 21
"__id__": 20
},
"_enabled": true,
"_materials": [
@@ -1157,13 +1126,13 @@
"_name": "skippedCnt",
"_objFlags": 0,
"_parent": {
"__id__": 12
"__id__": 11
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 24
"__id__": 23
}
],
"_prefab": null,
@@ -1219,7 +1188,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 23
"__id__": 22
},
"_enabled": true,
"_materials": [
@@ -1251,23 +1220,23 @@
"_name": "RoomIdIndicator",
"_objFlags": 0,
"_parent": {
"__id__": 12
"__id__": 11
},
"_children": [
{
"__id__": 26
"__id__": 25
},
{
"__id__": 28
"__id__": 27
}
],
"_active": false,
"_components": [
{
"__id__": 30
"__id__": 29
},
{
"__id__": 31
"__id__": 30
}
],
"_prefab": null,
@@ -1323,13 +1292,13 @@
"_name": "label",
"_objFlags": 0,
"_parent": {
"__id__": 25
"__id__": 24
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 27
"__id__": 26
}
],
"_prefab": null,
@@ -1385,7 +1354,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 26
"__id__": 25
},
"_enabled": true,
"_materials": [],
@@ -1413,13 +1382,13 @@
"_name": "BoundRoomIdLabel",
"_objFlags": 0,
"_parent": {
"__id__": 25
"__id__": 24
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 29
"__id__": 28
}
],
"_prefab": null,
@@ -1475,7 +1444,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 28
"__id__": 27
},
"_enabled": true,
"_materials": [],
@@ -1503,7 +1472,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 25
"__id__": 24
},
"_enabled": true,
"_layoutSize": {
@@ -1536,7 +1505,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 25
"__id__": 24
},
"_enabled": true,
"_materials": [],
@@ -1566,7 +1535,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 12
"__id__": 11
},
"_enabled": true,
"_layoutSize": {
@@ -1599,7 +1568,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 9
"__id__": 8
},
"_enabled": true,
"alignMode": 1,
@@ -1626,13 +1595,13 @@
"_name": "CountdownSeconds",
"_objFlags": 0,
"_parent": {
"__id__": 8
"__id__": 7
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 35
"__id__": 34
}
],
"_prefab": null,
@@ -1688,7 +1657,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 34
"__id__": 33
},
"_enabled": true,
"_materials": [
@@ -1718,11 +1687,11 @@
"_name": "BtnA",
"_objFlags": 0,
"_parent": {
"__id__": 8
"__id__": 7
},
"_children": [
{
"__id__": 37
"__id__": 36
}
],
"_active": true,
@@ -1780,16 +1749,16 @@
"_name": "Background",
"_objFlags": 0,
"_parent": {
"__id__": 36
"__id__": 35
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 38
"__id__": 37
},
{
"__id__": 39
"__id__": 38
}
],
"_prefab": null,
@@ -1845,7 +1814,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 37
"__id__": 36
},
"_enabled": true,
"_materials": [
@@ -1879,7 +1848,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 37
"__id__": 36
},
"_enabled": true,
"alignMode": 0,
@@ -1906,11 +1875,11 @@
"_name": "BtnB",
"_objFlags": 0,
"_parent": {
"__id__": 8
"__id__": 7
},
"_children": [
{
"__id__": 41
"__id__": 40
}
],
"_active": true,
@@ -1968,16 +1937,16 @@
"_name": "Background",
"_objFlags": 0,
"_parent": {
"__id__": 40
"__id__": 39
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 42
"__id__": 41
},
{
"__id__": 43
"__id__": 42
}
],
"_prefab": null,
@@ -2033,7 +2002,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 41
"__id__": 40
},
"_enabled": true,
"_materials": [
@@ -2067,7 +2036,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 41
"__id__": 40
},
"_enabled": true,
"alignMode": 0,
@@ -2094,7 +2063,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 8
"__id__": 7
},
"_enabled": true,
"alignMode": 1,
@@ -2121,20 +2090,20 @@
"_name": "JoystickBG",
"_objFlags": 0,
"_parent": {
"__id__": 7
"__id__": 6
},
"_children": [
{
"__id__": 46
"__id__": 45
}
],
"_active": true,
"_components": [
{
"__id__": 48
"__id__": 47
},
{
"__id__": 49
"__id__": 48
}
],
"_prefab": null,
@@ -2190,13 +2159,13 @@
"_name": "Joystick",
"_objFlags": 0,
"_parent": {
"__id__": 45
"__id__": 44
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 47
"__id__": 46
}
],
"_prefab": null,
@@ -2252,7 +2221,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 46
"__id__": 45
},
"_enabled": true,
"_materials": [
@@ -2286,7 +2255,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 45
"__id__": 44
},
"_enabled": true,
"_materials": [
@@ -2320,7 +2289,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 45
"__id__": 44
},
"_enabled": true,
"alignMode": 0,
@@ -2347,7 +2316,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 7
"__id__": 6
},
"_enabled": true,
"alignMode": 0,
@@ -2449,16 +2418,16 @@
},
"_enabled": true,
"translationListenerNode": {
"__id__": 7
"__id__": 6
},
"zoomingListenerNode": {
"__id__": 3
},
"stickhead": {
"__id__": 46
"__id__": 45
},
"base": {
"__id__": 45
"__id__": 44
},
"joyStickEps": 0.1,
"magicLeanLowerBound": 0.414,
@@ -2479,10 +2448,10 @@
"linearMovingEps": 0.1,
"scaleByEps": 0.0375,
"btnA": {
"__id__": 36
"__id__": 35
},
"btnB": {
"__id__": 40
"__id__": 39
},
"_id": "e9oVYTr7ROlpp/IrNjBUmR"
}

View File

@@ -64,9 +64,6 @@ cc.Class({
ctor() {
this.speciesName = null;
this.hp = 100;
this.maxHp = 100;
this.inAir = true;
},
setSpecies(speciesName) {
@@ -86,17 +83,17 @@ cc.Class({
updateCharacterAnim(rdfPlayer, prevRdfPlayer, forceAnimSwitch, chConfig) {
// As this function might be called after many frames of a rollback, it's possible that the playing animation was predicted, different from "prevRdfPlayer.CharacterState" but same as "newCharacterState". More granular checks are needed to determine whether we should interrupt the playing animation.
let newCharacterState = rdfPlayer.CharacterState;
let newCharacterState = rdfPlayer.GetCharacterState();
// Update directions
if (this.animComp && this.animComp.node) {
if (0 > rdfPlayer.DirX) {
if (0 > rdfPlayer.GetDirX()) {
this.animNode.scaleX = (-1.0);
} else if (0 < rdfPlayer.DirX) {
} else if (0 < rdfPlayer.GetDirX()) {
this.animNode.scaleX = (+1.0);
}
if (ATK_CHARACTER_STATE.OnWall[0] == newCharacterState || ATK_CHARACTER_STATE.TurnAround1[0] == newCharacterState) {
if (0 < rdfPlayer.OnWallNormX) {
if (0 < rdfPlayer.GetOnWallNormX()) {
this.animNode.scaleX = (-1.0);
} else {
this.animNode.scaleX = (+1.0);
@@ -116,7 +113,6 @@ cc.Class({
playingAnimName = (!underlyingAnimationCtrl ? null : underlyingAnimationCtrl.name);
//}
// It turns out that "prevRdfPlayer.CharacterState" is not useful in this function :)
if (newAnimName == playingAnimName && window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) {
// No need to interrupt
// console.warn(`JoinIndex=${rdfPlayer.joinIndex}, not interrupting ${newAnimName} while the playing anim is also ${playingAnimName}, player rdf changed from: ${null == prevRdfPlayer ? null : JSON.stringify(prevRdfPlayer)}, to: ${JSON.stringify(rdfPlayer)}`);
@@ -151,7 +147,7 @@ cc.Class({
}
// The "playTimes" counterpart is managed by each "cc.AnimationClip.wrapMode", already preset in the editor.
const targetClip = this.animComp.getClips()[newCharacterState]; // The clips follow the exact order in ATK_CHARACTER_STATE
let frameIdxInAnim = rdfPlayer.FramesInChState;
let frameIdxInAnim = rdfPlayer.GetFramesInChState();
if (window.ATK_CHARACTER_STATE.InAirIdle1ByJump == newCharacterState && null != chConfig) {
frameIdxInAnim = chConfig.InAirIdleFrameIdxTurningPoint + (frameIdxInAnim - chConfig.InAirIdleFrameIdxTurningPoint) % chConfig.InAirIdleFrameIdxTurnedCycle; // TODO: Anyway to avoid using division here?
}

View File

@@ -25,7 +25,7 @@ cc.Class({
if (!self.mapScriptIns) return;
if (!self.mapScriptIns.selfPlayerInfo) return;
if (!self.mapScriptIns.playerRichInfoDict) return;
const selfPlayerRichInfo = self.mapScriptIns.playerRichInfoDict.get(self.mapScriptIns.selfPlayerInfo.Id);
const selfPlayerRichInfo = self.mapScriptIns.playerRichInfoDict.get(self.mapScriptIns.selfPlayerInfo.id);
if (!selfPlayerRichInfo) return;
const selfPlayerNode = selfPlayerRichInfo.node;
if (!selfPlayerNode) return;

View File

@@ -13,9 +13,9 @@ cc.Class({
if (speciesName == this.speciesName) return;
if (null != this.speciesName) {
for (let k in this.animNode.children) {
const child = this.children[k];
const child = this.animNode.children[k];
if (!child.active) continue;
if (child == effAnimNode || child.name == speciesName) continue;
if (child == this.effAnimNode || child.name == speciesName) continue;
child.active = false;
}
}

View File

@@ -337,6 +337,7 @@ cc.Class({
const date = Number(res.expiresAt);
const selfPlayer = {
expiresAt: date,
id: res.playerId,
playerId: res.playerId,
intAuthToken: res.intAuthToken,
avatar: res.avatar,

View File

@@ -44,7 +44,7 @@ window.onUdpMessage = (args) => {
if (req.act && window.UPSYNC_MSG_ACT_PLAYER_CMD == req.act) {
let effCnt = 0;
const peerJoinIndex = req.joinIndex;
if (peerJoinIndex == self.selfPlayerInfo.JoinIndex) return;
if (peerJoinIndex == self.selfPlayerInfo.joinIndex) return;
const batch = req.inputFrameUpsyncBatch;
self.onPeerInputFrameUpsync(peerJoinIndex, batch, true);
}
@@ -156,11 +156,11 @@ cc.Class({
let previousSelfInput = null,
currSelfInput = null;
const joinIndex = self.selfPlayerInfo.JoinIndex;
const joinIndex = self.selfPlayerInfo.joinIndex;
const selfJoinIndexMask = (1 << (joinIndex - 1));
const existingInputFrame = self.recentInputCache.GetByFrameId(inputFrameId);
const previousInputFrameDownsync = self.recentInputCache.GetByFrameId(inputFrameId - 1);
previousSelfInput = (null == previousInputFrameDownsync ? null : previousInputFrameDownsync.InputList[joinIndex - 1]);
const existingInputFrame = gopkgs.GetInputFrameDownsync(self.recentInputCache, inputFrameId);
const previousInputFrameDownsync = gopkgs.GetInputFrameDownsync(self.recentInputCache, (inputFrameId - 1));
previousSelfInput = (null == previousInputFrameDownsync ? null : gopkgs.GetInput(previousInputFrameDownsync, joinIndex - 1));
if (
null != existingInputFrame
&&
@@ -168,7 +168,7 @@ cc.Class({
) {
// This could happen upon either [type#1] or [type#2] forceConfirmation, where "refRenderFrame" is accompanied by some "inputFrameDownsyncs". The check here also guarantees that we don't override history
//console.log(`noDelayInputFrameId=${inputFrameId} already exists in recentInputCache: recentInputCache=${self._stringifyRecentInputCache(false)}`);
return [previousSelfInput, existingInputFrame.InputList[joinIndex - 1]];
return [previousSelfInput, gopkgs.GetInput(existingInputFrame, joinIndex - 1)];
}
const prefabbedInputList = new Array(self.playerRichInfoDict.size).fill(0);
@@ -176,31 +176,44 @@ cc.Class({
for (let k = 0; k < window.boundRoomCapacity; ++k) {
if (null != existingInputFrame) {
// When "null != existingInputFrame", it implies that "true == canConfirmSelf" here, we just have to assign "prefabbedInputList[(joinIndex-1)]" specifically and copy all others
prefabbedInputList[k] = existingInputFrame.InputList[k];
prefabbedInputList[k] = gopkgs.GetInput(existingInputFrame, k);
} else if (self.lastIndividuallyConfirmedInputFrameId[k] <= inputFrameId) {
prefabbedInputList[k] = self.lastIndividuallyConfirmedInputList[k];
// Don't predict "btnA & btnB"!
prefabbedInputList[k] = (prefabbedInputList[k] & 15);
} else if (null != previousInputFrameDownsync) {
// When "self.lastIndividuallyConfirmedInputFrameId[k] > inputFrameId", don't use it to predict a historical input!
prefabbedInputList[k] = previousInputFrameDownsync.InputList[k];
prefabbedInputList[k] = gopkgs.GetInput(previousInputFrameDownsync, k);
// Don't predict "btnA & btnB"!
prefabbedInputList[k] = (prefabbedInputList[k] & 15);
}
}
// [WARNING] Do not blindly use "selfJoinIndexMask" here, as the "actuallyUsedInput for self" couldn't be confirmed while prefabbing, otherwise we'd have confirmed a wrong self input by "_markConfirmationIfApplicable()"!
let initConfirmedList = 0;
if (null != existingInputFrame) {
// When "null != existingInputFrame", it implies that "true == canConfirmSelf" here
initConfirmedList = (existingInputFrame.ConfirmedList | selfJoinIndexMask);
initConfirmedList = (existingInputFrame.GetConfirmedList() | selfJoinIndexMask);
}
currSelfInput = self.ctrl.getEncodedInput(); // When "null == existingInputFrame", it'd be safe to say that the realtime "self.ctrl.getEncodedInput()" is for the requested "inputFrameId"
prefabbedInputList[(joinIndex - 1)] = currSelfInput;
while (self.recentInputCache.EdFrameId <= inputFrameId) {
while (self.recentInputCache.GetEdFrameId() <= inputFrameId) {
// Fill the gap
// [WARNING] Do not blindly use "selfJoinIndexMask" here, as the "actuallyUsedInput for self" couldn't be confirmed while prefabbing, otherwise we'd have confirmed a wrong self input by "_markConfirmationIfApplicable()"!
const prefabbedInputFrameDownsync = gopkgs.NewInputFrameDownsync(self.recentInputCache.EdFrameId, prefabbedInputList.slice(), initConfirmedList);
// console.log(`Prefabbed inputFrameId=${prefabbedInputFrameDownsync.InputFrameId}`);
self.recentInputCache.Put(prefabbedInputFrameDownsync);
const gapInputFrameId = self.recentInputCache.GetEdFrameId();
self.recentInputCache.DryPut();
let ifdHolder = gopkgs.GetInputFrameDownsync(self.recentInputCache, gapInputFrameId);
if (null == ifdHolder) {
// Lazy heap alloc, calling "gopkgs.NewInputFrameDownsync" would trigger not only heap alloc but also "gopherjs $externalize", neither is efficient T_T
const prefabbedInputFrameDownsync = gopkgs.NewInputFrameDownsync(gapInputFrameId, prefabbedInputList, initConfirmedList);
// console.log(`Prefabbed inputFrameId=${prefabbedInputFrameDownsync.GetInputFrameId()}`);
self.recentInputCache.SetByFrameId(prefabbedInputFrameDownsync, gapInputFrameId);
} else {
gopkgs.SetInputFrameId(ifdHolder, gapInputFrameId);
for (let k = 0; k < window.boundRoomCapacity; ++k) {
gopkgs.SetInput(ifdHolder, k, prefabbedInputList[k]);
}
gopkgs.SetConfirmedList(ifdHolder, initConfirmedList);
}
}
return [previousSelfInput, currSelfInput];
@@ -223,9 +236,9 @@ cc.Class({
const self = this;
let inputFrameUpsyncBatch = [];
let batchInputFrameIdSt = self.lastUpsyncInputFrameId + 1;
if (batchInputFrameIdSt < self.recentInputCache.StFrameId) {
if (batchInputFrameIdSt < self.recentInputCache.GetStFrameId()) {
// Upon resync, "self.lastUpsyncInputFrameId" might not have been updated properly.
batchInputFrameIdSt = self.recentInputCache.StFrameId;
batchInputFrameIdSt = self.recentInputCache.GetStFrameId();
}
self.networkDoctor.logSending(batchInputFrameIdSt, latestLocalInputFrameId);
for (let i = batchInputFrameIdSt; i <= latestLocalInputFrameId; ++i) {
@@ -235,7 +248,7 @@ cc.Class({
} else {
const inputFrameUpsync = {
inputFrameId: i,
encoded: inputFrameDownsync.InputList[self.selfPlayerInfo.JoinIndex - 1],
encoded: inputFrameDownsync.InputList[self.selfPlayerInfo.joinIndex - 1],
};
inputFrameUpsyncBatch.push(inputFrameUpsync);
}
@@ -244,19 +257,19 @@ cc.Class({
// console.info(`inputFrameUpsyncBatch: ${JSON.stringify(inputFrameUpsyncBatch)}`);
const reqData = window.pb.protos.WsReq.encode({
msgId: Date.now(),
playerId: self.selfPlayerInfo.Id,
playerId: self.selfPlayerInfo.id,
act: window.UPSYNC_MSG_ACT_PLAYER_CMD,
joinIndex: self.selfPlayerInfo.JoinIndex,
joinIndex: self.selfPlayerInfo.joinIndex,
ackingInputFrameId: self.lastAllConfirmedInputFrameId,
inputFrameUpsyncBatch: inputFrameUpsyncBatch,
authKey: self.selfPlayerInfo.udpTunnelAuthKey,
}).finish();
if (cc.sys.isNative) {
DelayNoMore.UdpSession.broadcastInputFrameUpsync(reqData, window.boundRoomCapacity, self.selfPlayerInfo.JoinIndex);
DelayNoMore.UdpSession.broadcastInputFrameUpsync(reqData, window.boundRoomCapacity, self.selfPlayerInfo.joinIndex);
}
window.sendSafely(reqData);
self.lastUpsyncInputFrameId = latestLocalInputFrameId;
if (self.lastUpsyncInputFrameId >= self.recentInputCache.EdFrameId) {
if (self.lastUpsyncInputFrameId >= self.recentInputCache.GetEdFrameId()) {
throw `noDelayInputFrameId=${self.lastUpsyncInputFrameId} == latestLocalInputFrameId=${latestLocalInputFrameId} seems not properly dumped #2: recentInputCache=${self._stringifyRecentInputCache(false)}`;
}
},
@@ -280,7 +293,7 @@ cc.Class({
}
},
popupSimplePressToGo(labelString, hideYesButton) {
popupSimplePressToGo(labelString, hideYesButton, additionalOnDismissalCb) {
const self = this;
self.state = ALL_MAP_STATES.SHOWING_MODAL_POPUP;
@@ -293,6 +306,9 @@ cc.Class({
const postDismissalByYes = () => {
self.transitToState(ALL_MAP_STATES.VISUAL);
canvasNode.removeChild(simplePressToGoDialogNode);
if (additionalOnDismissalCb) {
additionalOnDismissalCb();
}
}
simplePressToGoDialogNode.getChildByName("Hint").getComponent(cc.Label).string = labelString;
yesButton.once("click", simplePressToGoDialogScriptIns.dismissDialog.bind(simplePressToGoDialogScriptIns, postDismissalByYes));
@@ -365,7 +381,24 @@ cc.Class({
self.lastIndividuallyConfirmedInputFrameId = new Array(window.boundRoomCapacity).fill(-1);
self.lastIndividuallyConfirmedInputList = new Array(window.boundRoomCapacity).fill(0);
self.recentRenderCache = new RingBuffer(self.renderCacheSize);
self.collisionHolder = gopkgs.NewCollisionHolder();
// [WARNING] For "effPushbacks", "hardPushbackNormsArr" and "jumpedOrNotList", use array literal instead of "new Array" for compliance when passing into "gopkgs.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs"!
self.effPushbacks = [];
for (let i = 0; i < window.boundRoomCapacity; i++) self.effPushbacks.push(gopkgs.NewVec2DJs(0, 0));
self.hardPushbackNormsArr = [];
for (let i = 0; i < window.boundRoomCapacity; i++) {
const single = [];
for (let j = 0; j < 5; j++) {
single.push(gopkgs.NewVec2DJs(0, 0));
}
self.hardPushbackNormsArr.push(single);
}
self.jumpedOrNotList = [];
for (let i = 0; i < window.boundRoomCapacity; i++) self.jumpedOrNotList.push(false);
self.dynamicRectangleColliders = gopkgs.NewDynamicRectangleColliders(64);
self.recentRenderCache = gopkgs.NewRingBufferJs(self.renderCacheSize);
self.recentInputCache = gopkgs.NewRingBufferJs((self.renderCacheSize >> 1) + 1);
@@ -408,9 +441,9 @@ cc.Class({
for (let k in collisionSpaceObjs) {
const body = collisionSpaceObjs[k];
let padding = 0;
if (null != body.Data && null != body.Data.JoinIndex) {
if (null != body.GetData() && null != body.GetData().GetJoinIndex) {
// character
if (1 == body.Data.JoinIndex) {
if (1 == body.GetData().GetJoinIndex()) {
g1.strokeColor = cc.Color.BLUE;
} else {
g1.strokeColor = cc.Color.RED;
@@ -420,8 +453,9 @@ cc.Class({
// barrier
g1.strokeColor = cc.Color.WHITE;
}
const points = body.Shape.Points;
const wpos = [body.X - self.spaceOffsetX, body.Y - self.spaceOffsetY];
const points = body.GetShape().Points;
const [bodyX, bodyY] = body.Position();
const wpos = [bodyX - self.spaceOffsetX, bodyY - self.spaceOffsetY];
g1.moveTo(wpos[0], wpos[1]);
const cnt = points.length;
for (let j = 0; j < cnt; j += 1) {
@@ -444,7 +478,7 @@ cc.Class({
},
onLoad() {
cc.game.setFrameRate(60);
cc.game.setFrameRate(59.9);
cc.view.setOrientation(cc.macro.ORIENTATION_LANDSCAPE);
cc.view.enableAutoFullScreen(true);
@@ -478,7 +512,8 @@ cc.Class({
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
window.initPersistentSessionClient(self.initAfterWSConnected, null /* Deliberately NOT passing in any `expectedRoomId`. -- YFLu */ );
};
resultPanelScriptIns.onCloseDelegate = () => {};
resultPanelScriptIns.onCloseDelegate = () => {
};
self.gameRuleNode = cc.instantiate(self.gameRulePrefab);
self.gameRuleNode.width = self.canvasNode.width;
@@ -568,7 +603,6 @@ cc.Class({
const newBarrierCollider = gopkgs.GenerateConvexPolygonColliderJs(gopkgsBoundary, self.spaceOffsetX, self.spaceOffsetY, gopkgsBarrier, "Barrier");
self.gopkgsCollisionSys.Add(newBarrierCollider);
// console.log("Created barrier: ", newBarrierCollider);
++barrierIdCounter;
const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter);
self.gopkgsCollisionSysMap[collisionBarrierIndex] = newBarrierCollider;
@@ -663,7 +697,7 @@ cc.Class({
}
// This function is also applicable to "re-joining".
const rdf = gopkgs.NewRoomDownsyncFrameJs(pbRdf.id, jsPlayersArr, pbRdf.bulletLocalIdCounter, jsMeleeBulletsArr, jsFireballBulletsArr);
const rdf = gopkgs.NewRoomDownsyncFrameJs(pbRdf.id, jsPlayersArr, pbRdf.bulletLocalIdCounter, jsMeleeBulletsArr, jsFireballBulletsArr); // TODO: Check whether a "proper" preallocated rdf is available and reuse it to avoid redundant heap alloc. By "proper" I mean "pbRdf.id" should yield a "non window.RING_BUFF_FAILED_TO_SET" result, yet currently it's a bit difficult to sort out the following codes for efficient reuse, thus I'm keeping it as-is.
const self = window.mapIns;
self.onInputFrameDownsyncBatch(accompaniedInputFrameDownsyncBatch); // Important to do this step before setting IN_BATTLE
if (!self.recentRenderCache) {
@@ -672,14 +706,15 @@ cc.Class({
if (ALL_BATTLE_STATES.IN_SETTLEMENT == self.battleState) {
return;
}
const shouldForceDumping1 = (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.Id);
let shouldForceDumping2 = (rdf.Id >= self.renderFrameId + self.renderFrameIdLagTolerance);
const rdfId = rdf.GetId();
const shouldForceDumping1 = (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdfId);
let shouldForceDumping2 = (rdfId >= self.renderFrameId + self.renderFrameIdLagTolerance);
let shouldForceResync = pbRdf.shouldForceResync;
const notSelfUnconfirmed = (0 == (pbRdf.backendUnconfirmedMask & (1 << (self.selfPlayerInfo.JoinIndex - 1))));
const notSelfUnconfirmed = (0 == (pbRdf.backendUnconfirmedMask & (1 << (self.selfPlayerInfo.joinIndex - 1))));
if (notSelfUnconfirmed) {
shouldForceDumping2 = false;
shouldForceResync = false;
self.othersForcedDownsyncRenderFrameDict.set(rdf.Id, rdf);
self.othersForcedDownsyncRenderFrameDict.set(rdfId, rdf);
}
/*
TODO
@@ -687,11 +722,11 @@ cc.Class({
If "BackendUnconfirmedMask" is non-all-1 and contains the current player, show a label/button to hint manual reconnection. Note that the continuity of "recentInputCache" is not a good indicator, because due to network delay upon a [type#1 forceConfirmation] a player might just lag in upsync networking and have all consecutive inputFrameIds locally.
*/
const [dumpRenderCacheRet, oldStRenderFrameId, oldEdRenderFrameId] = (shouldForceDumping1 || shouldForceDumping2 || shouldForceResync) ? self.recentRenderCache.setByFrameId(rdf, rdf.Id) : [window.RING_BUFF_CONSECUTIVE_SET, null, null];
const [dumpRenderCacheRet, oldStRenderFrameId, oldEdRenderFrameId] = (shouldForceDumping1 || shouldForceDumping2 || shouldForceResync) ? self.recentRenderCache.SetByFrameId(rdf, rdfId) : [window.RING_BUFF_CONSECUTIVE_SET, null, null];
if (window.RING_BUFF_FAILED_TO_SET == dumpRenderCacheRet) {
throw `Failed to dump render cache#1 (maybe recentRenderCache too small)! rdf.id=${rdf.id}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
throw `Failed to dump render cache#1 (maybe recentRenderCache too small)! rdf.GetId()=${rdfId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
}
if (!shouldForceResync && (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START < rdf.id && window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet)) {
if (!shouldForceResync && (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START < rdfId && window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet)) {
/*
Don't change
- chaserRenderFrameId, it's updated only in "rollbackAndChase & onInputFrameDownsyncBatch" (except for when RING_BUFF_NON_CONSECUTIVE_SET)
@@ -699,27 +734,27 @@ cc.Class({
return dumpRenderCacheRet;
}
// The logic below applies to (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id || window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet)
// The logic below applies to (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdfId || window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet)
if (null == pbRdf.speciesIdList) {
console.error(`pbRdf.speciesIdList is required for starting or resyncing battle!`);
}
self.chConfigsOrderedByJoinIndex = gopkgs.GetCharacterConfigsOrderedByJoinIndex(pbRdf.speciesIdList);
self._initPlayerRichInfoDict(rdf.PlayersArr);
self._initPlayerRichInfoDict(rdf);
if (shouldForceDumping1 || shouldForceDumping2 || shouldForceResync) {
// In fact, not having "window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet" should already imply that "self.renderFrameId <= rdf.id", but here we double check and log the anomaly
// In fact, not having "window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet" should already imply that "self.renderFrameId <= rdfId", but here we double check and log the anomaly
if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.Id) {
console.log('On battle started! renderFrameId=', rdf.Id);
if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdfId) {
console.log('On battle started! renderFrameId=', rdfId);
} else {
self.hideFindingPlayersGUI();
console.warn('On battle resynced! renderFrameId=', rdf.Id);
console.warn('On battle resynced! renderFrameId=', rdf.GetId());
}
self.renderFrameId = rdf.Id;
self.renderFrameId = rdfId;
self.lastRenderFrameIdTriggeredAt = performance.now();
// In this case it must be true that "rdf.id > chaserRenderFrameId".
self.chaserRenderFrameId = rdf.Id;
// In this case it must be true that "rdfId > chaserRenderFrameId".
self.chaserRenderFrameId = rdfId;
self.networkDoctor.logRollbackFrames(0);
const canvasNode = self.canvasNode;
@@ -727,7 +762,7 @@ cc.Class({
self.enableInputControls();
self.transitToState(ALL_MAP_STATES.VISUAL);
const selfPlayerRichInfo = self.playerRichInfoDict.get(self.selfPlayerInfo.Id);
const selfPlayerRichInfo = self.playerRichInfoDict.get(self.selfPlayerInfo.id);
const newMapPos = cc.v2().sub(selfPlayerRichInfo.node.position);
self.node.setPosition(newMapPos);
self.battleState = ALL_BATTLE_STATES.IN_BATTLE;
@@ -760,19 +795,20 @@ cc.Class({
if (null == lhs || null == rhs) return false;
if (null == lhs && null != rhs) return false;
if (null != lhs && null == rhs) return false;
if (lhs.VirtualGridX != rhs.VirtualGridX) return false;
if (lhs.VirtualGridY != rhs.VirtualGridY) return false;
if (lhs.DirX != rhs.DirX) return false;
if (lhs.DirY != rhs.DirY) return false;
if (lhs.VelX != rhs.VelX) return false;
if (lhs.VelY != rhs.VelY) return false;
if (lhs.Speed != rhs.Speed) return false;
if (lhs.Hp != rhs.Hp) return false;
if (lhs.MaxHp != rhs.MaxHp) return false;
if (lhs.CharacterState != rhs.CharacterState) return false;
if (lhs.InAir != rhs.InAir) return false;
if (lhs.FramesToRecover != rhs.FramesToRecover) return false;
if (lhs.FramesInChState != rhs.FramesInChState) return false;
if (lhs.GetVirtualGridX() != rhs.GetVirtualGridX()) return false;
if (lhs.GetVirtualGridY() != rhs.GetVirtualGridY()) return false;
if (lhs.GetDirX() != rhs.GetDirX()) return false;
if (lhs.GetDirY() != rhs.GetDirY()) return false;
if (lhs.GetVelX() != rhs.GetVelX()) return false;
if (lhs.GetVelY() != rhs.GetVelY()) return false;
if (lhs.GetSpeed() != rhs.GetSpeed()) return false;
if (lhs.GetHp() != rhs.GetHp()) return false;
if (lhs.GetMaxHp() != rhs.GetMaxHp()) return false;
if (lhs.GetCharacterState() != rhs.GetCharacterState()) return false;
if (lhs.GetInAir() != rhs.GetInAir()) return false;
if (lhs.GetOnWall() != rhs.GetOnWall()) return false;
if (lhs.GetFramesToRecover() != rhs.GetFramesToRecover()) return false;
if (lhs.GetFramesInChState() != rhs.GetFramesInChState()) return false;
return true;
},
@@ -780,9 +816,9 @@ cc.Class({
if (null == lhs || null == rhs) return false;
if (null == lhs && null != rhs) return false;
if (null != lhs && null == rhs) return false;
if (lhs.BattleAttr.BulletLocalId != rhs.BattleAttr.BulletLocalId) return false;
if (lhs.BattleAttr.OffenderJoinIndex != rhs.BattleAttr.OffenderJoinIndex) return false;
if (lhs.BattleAttr.OriginatedRenderFrameId != rhs.BattleAttr.OriginatedRenderFrameId) return false;
if (lhs.GetBulletLocalId() != rhs.GetBulletLocalId()) return false;
if (lhs.GetOffenderJoinIndex() != rhs.GetOffenderJoinIndex()) return false;
if (lhs.GetOriginatedRenderFrameId() != rhs.GetOriginatedRenderFrameId()) return false;
return true;
},
@@ -790,31 +826,39 @@ cc.Class({
if (null == lhs || null == rhs) return false;
if (null == lhs && null != rhs) return false;
if (null != lhs && null == rhs) return false;
if (lhs.BattleAttr.BulletLocalId != rhs.BattleAttr.BulletLocalId) return false;
if (lhs.BattleAttr.OffenderJoinIndex != rhs.BattleAttr.OffenderJoinIndex) return false;
if (lhs.BattleAttr.OriginatedRenderFrameId != rhs.BattleAttr.OriginatedRenderFrameId) return false;
if (lhs.GetBulletLocalId() != rhs.GetBulletLocalId()) return false;
if (lhs.GetOffenderJoinIndex() != rhs.GetOffenderJoinIndex()) return false;
if (lhs.GetOriginatedRenderFrameId() != rhs.GetOriginatedRenderFrameId()) return false;
if (lhs.VirtualGridX != rhs.Bullet.VirtualGridX) return false;
if (lhs.VirtualGridY != rhs.Bullet.VirtualGridY) return false;
if (lhs.DirX != rhs.DirX) return false;
if (lhs.DirY != rhs.DirY) return false;
if (lhs.VelX != rhs.VelX) return false;
if (lhs.VelY != rhs.VelY) return false;
if (lhs.Speed != rhs.Speed) return false;
if (lhs.GetVirtualGridX() != rhs.GetVirtualGridX()) return false;
if (lhs.GetVirtualGridY() != rhs.GetVirtualGridY()) return false;
if (lhs.GetDirX() != rhs.GetDirX()) return false;
if (lhs.GetDirY() != rhs.GetDirY()) return false;
if (lhs.GetVelX() != rhs.GetVelX()) return false;
if (lhs.GetVelY() != rhs.GetVelY()) return false;
if (lhs.GetSpeed() != rhs.GetSpeed()) return false;
return true;
},
equalRoomDownsyncFrames(lhs, rhs) {
if (null == lhs || null == rhs) return false;
for (let k in lhs.PlayersArr) {
if (!this.equalPlayers(lhs.PlayersArr[k], rhs.PlayersArr[k])) return false;
for (let k = 0; k < window.boundRoomCapacity; k++) {
const lp = gopkgs.GetPlayer(lhs, k);
const rp = gopkgs.GetPlayer(rhs, k);
if (!this.equalPlayers(lp, rp)) return false;
}
for (let k in lhs.MeleeBullets) {
if (!this.equalMeleeBullets(lhs.MeleeBullets[k], rhs.MeleeBullets[k])) return false;
for (let k = 0;; k++) {
const lblt = gopkgs.GetMeleeBullet(lhs, k);
const rblt = gopkgs.GetMeleeBullet(rhs, k);
if (null == lblt && null == rblt) break;
if (!this.equalMeleeBullets(lblt, rblt)) return false;
}
for (let k in lhs.fireballBullet) {
if (!this.equalFireballBullets(lhs.FireballBullets[k], rhs.FireballBullets[k])) return false;
for (let k = 0;; k++) {
const lblt = gopkgs.GetFireballBullet(lhs, k);
const rblt = gopkgs.GetFireballBullet(rhs, k);
if (null == lblt && null == rblt) break;
if (!this.equalFireballBullets(lblt, rblt)) return false;
}
return true;
},
@@ -822,7 +866,7 @@ cc.Class({
_markConfirmationIfApplicable() {
const self = this;
let newAllConfirmedCnt = 0;
while (self.recentInputCache.StFrameId <= self.lastAllConfirmedInputFrameId && self.lastAllConfirmedInputFrameId < self.recentInputCache.EdFrameId) {
while (self.recentInputCache.GetStFrameId() <= self.lastAllConfirmedInputFrameId && self.lastAllConfirmedInputFrameId < self.recentInputCache.GetEdFrameId()) {
const inputFrameDownsync = self.recentInputCache.GetByFrameId(self.lastAllConfirmedInputFrameId);
if (null == inputFrameDownsync) break;
if (self._allConfirmed(inputFrameDownsync.ConfirmedList)) break;
@@ -1019,7 +1063,7 @@ fromUDP=${fromUDP}`);
const newPlayerNode = cc.instantiate(self.controlledCharacterPrefab)
const playerScriptIns = newPlayerNode.getComponent("ControlledCharacter");
const chConfig = self.chConfigsOrderedByJoinIndex[joinIndex - 1];
playerScriptIns.setSpecies(chConfig.SpeciesName);
playerScriptIns.setSpecies(chConfig.GetSpeciesName());
if (1 == joinIndex) {
newPlayerNode.color = cc.Color.RED;
@@ -1111,16 +1155,16 @@ fromUDP=${fromUDP}`);
console.debug("Non-trivial chase ended, prevChaserRenderFrameId=" + prevChaserRenderFrameId + ", nextChaserRenderFrameId=" + nextChaserRenderFrameId);
}
*/
// [WARNING] Don't try to get "prevRdf(i.e. renderFrameId == latest-1)" by "self.recentRenderCache.getByFrameId(...)" here, as the cache might have been updated by asynchronous "onRoomDownsyncFrame(...)" calls!
if (self.othersForcedDownsyncRenderFrameDict.has(rdf.Id)) {
const delayedInputFrameId = gopkgs.ConvertToDelayedInputFrameId(rdf.Id);
const othersForcedDownsyncRenderFrame = self.othersForcedDownsyncRenderFrameDict.get(rdf.Id);
// [WARNING] Don't try to get "prevRdf(i.e. renderFrameId == latest-1)" by "self.recentRenderCache.GetByFrameId(...)" here, as the cache might have been updated by asynchronous "onRoomDownsyncFrame(...)" calls!
if (self.othersForcedDownsyncRenderFrameDict.has(rdf.GetId())) {
const delayedInputFrameId = gopkgs.ConvertToDelayedInputFrameId(rdf.GetId());
const othersForcedDownsyncRenderFrame = self.othersForcedDownsyncRenderFrameDict.get(rdf.GetId());
if (self.lastAllConfirmedInputFrameId >= delayedInputFrameId && !self.equalRoomDownsyncFrames(othersForcedDownsyncRenderFrame, rdf)) {
console.warn(`Mismatched render frame@rdf.id=${rdf.Id} w/ inputFrameId=${delayedInputFrameId}:
console.warn(`Mismatched render frame@rdf.id=${rdf.GetId()} w/ inputFrameId=${delayedInputFrameId}:
rdf=${JSON.stringify(rdf)}
othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame)}`);
rdf = othersForcedDownsyncRenderFrame;
self.othersForcedDownsyncRenderFrameDict.delete(rdf.Id);
self.othersForcedDownsyncRenderFrameDict.delete(rdf.GetId());
}
}
self.applyRoomDownsyncFrameDynamics(rdf, prevRdf);
@@ -1267,17 +1311,16 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
applyRoomDownsyncFrameDynamics(rdf, prevRdf) {
const self = this;
const playersArr = rdf.PlayersArr;
for (let k in playersArr) {
const currPlayerDownsync = playersArr[k];
for (let k = 0; k < window.boundRoomCapacity; k++) {
const currPlayerDownsync = gopkgs.GetPlayer(rdf, k);
const prevRdfPlayer = (null == prevRdf ? null : gopkgs.GetPlayer(prevRdf, k));
const chConfig = self.chConfigsOrderedByJoinIndex[k];
const prevRdfPlayer = (null == prevRdf ? null : prevRdf.PlayersArr[k]);
const [wx, wy] = gopkgs.VirtualGridToWorldPos(currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY);
const [wx, wy] = gopkgs.VirtualGridToWorldPos(currPlayerDownsync.GetVirtualGridX(), currPlayerDownsync.GetVirtualGridY());
const playerRichInfo = self.playerRichInfoArr[k];
playerRichInfo.node.setPosition(wx, wy);
playerRichInfo.scriptIns.updateSpeed(currPlayerDownsync.Speed);
playerRichInfo.scriptIns.updateSpeed(currPlayerDownsync.GetSpeed());
playerRichInfo.scriptIns.updateCharacterAnim(currPlayerDownsync, prevRdfPlayer, false, chConfig);
playerRichInfo.scriptIns.hpBar.progress = (currPlayerDownsync.Hp * 1.0) / currPlayerDownsync.MaxHp;
playerRichInfo.scriptIns.hpBar.progress = (currPlayerDownsync.GetHp() * 1.0) / currPlayerDownsync.GetMaxHp();
}
// Move all to infinitely far away first
@@ -1286,68 +1329,74 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
const fireball = pqNode.value;
fireball.node.setPosition(cc.v2(Number.MAX_VALUE, Number.MAX_VALUE));
}
for (let k in rdf.MeleeBullets) {
const meleeBullet = rdf.MeleeBullets[k];
const isExploding = (window.BULLET_STATE.Exploding == meleeBullet.BlState && meleeBullet.FramesInBlState < meleeBullet.Bullet.ExplosionFrames);
for (let k = 0;; k++) {
const meleeBullet = gopkgs.GetMeleeBullet(rdf, k);
if (null == meleeBullet) {
break;
}
const isExploding = (window.BULLET_STATE.Exploding == meleeBullet.GetBlState() && meleeBullet.GetFramesInBlState() < meleeBullet.GetExplosionFrames());
if (isExploding) {
let pqNode = self.cachedFireballs.popAny(meleeBullet.BattleAttr.BulletLocalId);
let pqNode = self.cachedFireballs.popAny(meleeBullet.GetBulletLocalId());
let speciesName = `MeleeExplosion`;
let animName = `MeleeExplosion${meleeBullet.Bullet.SpeciesId}`;
let animName = `MeleeExplosion${meleeBullet.GetSpeciesId()}`;
const offender = rdf.PlayersArr[meleeBullet.BattleAttr.OffenderJoinIndex - 1];
const offender = gopkgs.GetPlayer(rdf, meleeBullet.GetOffenderJoinIndex() - 1);
let xfac = 1; // By now, straight Punch offset doesn't respect "y-axis"
if (0 > offender.DirX) {
if (0 > offender.GetDirX()) {
xfac = -1;
}
const [wx, wy] = gopkgs.VirtualGridToWorldPos(offender.VirtualGridX + xfac * meleeBullet.Bullet.HitboxOffsetX, offender.VirtualGridY);
const [wx, wy] = gopkgs.VirtualGridToWorldPos(offender.GetVirtualGridX() + xfac * meleeBullet.GetHitboxOffsetX(), offender.GetVirtualGridY());
if (null == pqNode) {
pqNode = self.cachedFireballs.pop();
//console.log(`@rdf.Id=${rdf.Id}, origRdfId=${meleeBullet.BattleAttr.OriginatedRenderFrameId}, startupFrames=${meleeBullet.Bullet.StartupFrames}, using a new fireball node for rendering for bulletLocalId=${meleeBullet.BattleAttr.BulletLocalId} at wpos=(${wx},${wy})`);
//console.log(`@rdf.GetId()=${rdf.GetId()}, origRdfId=${meleeBullet.GetOriginatedRenderFrameId()}, startupFrames=${meleeBullet.GetStartupFrames()}, using a new fireball node for rendering for bulletLocalId=${meleeBullet.GetBulletLocalId()} at wpos=(${wx},${wy})`);
} else {
//console.log(`@rdf.Id=${rdf.Id}, origRdfId=${meleeBullet.BattleAttr.OriginatedRenderFrameId}, startupFrames=${meleeBullet.Bullet.StartupFrames}, using a cached fireball node for rendering for bulletLocalId=${meleeBullet.BattleAttr.BulletLocalId} at wpos=(${wx},${wy})`);
//console.log(`@rdf.GetId()=${rdf.GetId()}, origRdfId=${meleeBullet.GetOriginatedRenderFrameId()}, startupFrames=${meleeBullet.GetStartupFrames()}, using a cached fireball node for rendering for bulletLocalId=${meleeBullet.GetBulletLocalId()} at wpos=(${wx},${wy})`);
}
const cachedFireball = pqNode.value;
cachedFireball.setSpecies(speciesName, meleeBullet, rdf);
const newAnimIdx = meleeBullet.Bullet.SpeciesId - 1;
cachedFireball.updateAnim(animName, meleeBullet.FramesInBlState, offender.DirX, false, rdf, newAnimIdx);
const newAnimIdx = meleeBullet.GetSpeciesId() - 1;
cachedFireball.updateAnim(animName, meleeBullet.GetFramesInBlState(), offender.GetDirX(), false, rdf, newAnimIdx);
cachedFireball.lastUsed = self.renderFrameId;
cachedFireball.bulletLocalId = meleeBullet.BattleAttr.BulletLocalId;
cachedFireball.bulletLocalId = meleeBullet.GetBulletLocalId();
cachedFireball.node.setPosition(cc.v2(wx, wy));
self.cachedFireballs.push(cachedFireball.lastUsed, cachedFireball, meleeBullet.BattleAttr.BulletLocalId);
self.cachedFireballs.push(cachedFireball.lastUsed, cachedFireball, meleeBullet.GetBulletLocalId());
} else {
//console.log(`@rdf.Id=${rdf.Id}, origRdfId=${meleeBullet.BattleAttr.OriginatedRenderFrameId}, startupFrames=${meleeBullet.Bullet.StartupFrames}, activeFrames=${meleeBullet.Bullet.ActiveFrames}, not rendering melee node for bulletLocalId=${meleeBullet.BattleAttr.BulletLocalId}`);
//console.log(`@rdf.GetId()=${rdf.GetId()}, origRdfId=${meleeBullet.GetOriginatedRenderFrameId()}, startupFrames=${meleeBullet.GetStartupFrames()}, activeFrames=${meleeBullet.GetActiveFrames()}, not rendering melee node for bulletLocalId=${meleeBullet.GetBulletLocalId()}`);
}
}
for (let k in rdf.FireballBullets) {
const fireballBullet = rdf.FireballBullets[k];
const isExploding = (window.BULLET_STATE.Exploding == fireballBullet.BlState);
if (gopkgs.IsFireballBulletActive(fireballBullet, rdf) || isExploding) {
let pqNode = self.cachedFireballs.popAny(fireballBullet.BattleAttr.BulletLocalId);
let speciesName = `Fireball${fireballBullet.Bullet.SpeciesId}`;
let animName = (BULLET_STATE.Exploding == fireballBullet.BlState ? `Fireball${fireballBullet.Bullet.SpeciesId}Explosion` : speciesName);
for (let k = 0;; k++) {
const fireballBullet = gopkgs.GetFireballBullet(rdf, k);
if (null == fireballBullet) {
break;
}
const isExploding = (window.BULLET_STATE.Exploding == fireballBullet.GetBlState());
if (gopkgs.IsGeneralBulletActive(fireballBullet.GetBlState(), fireballBullet.GetOriginatedRenderFrameId(), fireballBullet.GetStartupFrames(), fireballBullet.GetActiveFrames(), rdf.GetId()) || isExploding) {
let pqNode = self.cachedFireballs.popAny(fireballBullet.GetBulletLocalId());
let speciesName = `Fireball${fireballBullet.GetSpeciesId()}`;
let animName = (isExploding ? `Fireball${fireballBullet.GetSpeciesId()}Explosion` : speciesName);
const [wx, wy] = gopkgs.VirtualGridToWorldPos(fireballBullet.VirtualGridX, fireballBullet.VirtualGridY);
const [wx, wy] = gopkgs.VirtualGridToWorldPos(fireballBullet.GetVirtualGridX(), fireballBullet.GetVirtualGridY());
if (null == pqNode) {
pqNode = self.cachedFireballs.pop();
//console.log(`@rdf.Id=${rdf.Id}, origRdfId=${fireballBullet.BattleAttr.OriginatedRenderFrameId}, startupFrames=${fireballBullet.Bullet.StartupFrames}, using a new fireball node for rendering for bulletLocalId=${fireballBullet.BattleAttr.BulletLocalId} at wpos=(${wx},${wy})`);
//console.log(`@rdf.GetId()=${rdf.GetId()}, origRdfId=${fireballBullet.GetOriginatedRenderFrameId()}, startupFrames=${fireballBullet.GetStartupFrames()}, using a new fireball node for rendering for bulletLocalId=${fireballBullet.GetBulletLocalId()} at wpos=(${wx},${wy})`);
} else {
//console.log(`@rdf.Id=${rdf.Id}, origRdfId=${fireballBullet.BattleAttr.OriginatedRenderFrameId}, startupFrames=${fireballBullet.Bullet.StartupFrames}, using a cached fireball node for rendering for bulletLocalId=${fireballBullet.BattleAttr.BulletLocalId} at wpos=(${wx},${wy})`);
//console.log(`@rdf.GetId()=${rdf.GetId()}, origRdfId=${fireballBullet.GetOriginatedRenderFrameId()}, startupFrames=${fireballBullet.GetStartupFrames()}, using a cached fireball node for rendering for bulletLocalId=${fireballBullet.GetBulletLocalId()} at wpos=(${wx},${wy})`);
}
const cachedFireball = pqNode.value;
cachedFireball.setSpecies(speciesName, fireballBullet, rdf);
const spontaneousLooping = !isExploding;
const newAnimIdx = (spontaneousLooping ? 0 : 1);
cachedFireball.updateAnim(animName, fireballBullet.FramesInBlState, fireballBullet.DirX, spontaneousLooping, rdf, newAnimIdx);
cachedFireball.updateAnim(animName, fireballBullet.GetFramesInBlState(), fireballBullet.GetDirX(), spontaneousLooping, rdf, newAnimIdx);
cachedFireball.lastUsed = self.renderFrameId;
cachedFireball.bulletLocalId = fireballBullet.BattleAttr.BulletLocalId;
cachedFireball.bulletLocalId = fireballBullet.GetBulletLocalId();
cachedFireball.node.setPosition(cc.v2(wx, wy));
self.cachedFireballs.push(cachedFireball.lastUsed, cachedFireball, fireballBullet.BattleAttr.BulletLocalId);
self.cachedFireballs.push(cachedFireball.lastUsed, cachedFireball, fireballBullet.GetBulletLocalId());
} else {
//console.log(`@rdf.Id=${rdf.Id}, origRdfId=${fireballBullet.BattleAttr.OriginatedRenderFrameId}, startupFrames=${fireballBullet.Bullet.StartupFrames}, activeFrames=${fireballBullet.Bullet.ActiveFrames}, not rendering fireball node for bulletLocalId=${fireballBullet.BattleAttr.BulletLocalId}`);
//console.log(`@rdf.GetId()=${rdf.GetId()}, origRdfId=${fireballBullet.GetOriginatedRenderFrameId()}, startupFrames=${fireballBullet.GetStartupFrames()}, activeFrames=${fireballBullet.GetActiveFrames()}, not rendering fireball node for bulletLocalId=${fireballBullet.GetBulletLocalId()}`);
}
}
@@ -1363,31 +1412,31 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
let prevLatestRdf = null,
latestRdf = null;
for (let i = renderFrameIdSt; i < renderFrameIdEd; i++) {
const currRdf = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing" and using Firefox, this function could be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"!
const currRdf = gopkgs.GetRoomDownsyncFrame(self.recentRenderCache, i); // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing" and using Firefox, this function could be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"!
if (null == currRdf) {
throw `Couldn't find renderFrame for i=${i} to rollback (are you using Firefox?), self.renderFrameId=${self.renderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, might've been interruptted by onRoomDownsyncFrame`;
}
const j = gopkgs.ConvertToDelayedInputFrameId(i);
const delayedInputFrame = self.recentInputCache.GetByFrameId(j);
const delayedInputFrame = gopkgs.GetInputFrameDownsync(self.recentInputCache, j);
if (self.frameDataLoggingEnabled) {
const actuallyUsedInputClone = delayedInputFrame.InputList.slice();
const actuallyUsedInputClone = delayedInputFrame.GetInputList();
const inputFrameDownsyncClone = {
inputFrameId: delayedInputFrame.InputFrameId,
inputFrameId: j,
inputList: actuallyUsedInputClone,
confirmedList: delayedInputFrame.ConfirmedList,
};
self.rdfIdToActuallyUsedInput.set(currRdf.Id, inputFrameDownsyncClone);
self.rdfIdToActuallyUsedInput.set(i, inputFrameDownsyncClone);
}
const nextRdf = gopkgs.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(self.recentInputCache, currRdf, collisionSys, collisionSysMap, self.spaceOffsetX, self.spaceOffsetY, self.chConfigsOrderedByJoinIndex);
const renderRes = gopkgs.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(self.recentInputCache, i, collisionSys, collisionSysMap, self.spaceOffsetX, self.spaceOffsetY, self.chConfigsOrderedByJoinIndex, self.recentRenderCache, self.collisionHolder, self.effPushbacks, self.hardPushbackNormsArr, self.jumpedOrNotList, self.dynamicRectangleColliders);
const nextRdf = gopkgs.GetRoomDownsyncFrame(self.recentRenderCache, i + 1);
if (true == isChasing) {
// [WARNING] Move the cursor "self.chaserRenderFrameId" when "true == isChasing", keep in mind that "self.chaserRenderFrameId" is not monotonic!
self.chaserRenderFrameId = nextRdf.Id;
} else if (nextRdf.Id == self.chaserRenderFrameId + 1) {
self.chaserRenderFrameId = nextRdf.Id; // To avoid redundant calculation
self.chaserRenderFrameId = nextRdf.GetId();
} else if (nextRdf.GetId() == self.chaserRenderFrameId + 1) {
self.chaserRenderFrameId = nextRdf.GetId(); // To avoid redundant calculation
}
self.recentRenderCache.setByFrameId(nextRdf, nextRdf.Id);
prevLatestRdf = currRdf;
latestRdf = nextRdf;
}
@@ -1395,16 +1444,16 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
return [prevLatestRdf, latestRdf];
},
_initPlayerRichInfoDict(playersArr) {
_initPlayerRichInfoDict(rdf) {
const self = this;
for (let k in playersArr) {
const immediatePlayerInfo = playersArr[k];
const playerId = immediatePlayerInfo.Id;
for (let k = 0; k < window.boundRoomCapacity; k++) {
const immediatePlayerInfo = gopkgs.GetPlayer(rdf, k);
const playerId = immediatePlayerInfo.GetId();
if (self.playerRichInfoDict.has(playerId)) continue; // Skip already put keys
self.playerRichInfoDict.set(playerId, immediatePlayerInfo);
const joinIndex = immediatePlayerInfo.joinIndex || immediatePlayerInfo.JoinIndex;
const vx = immediatePlayerInfo.virtualGridX || immediatePlayerInfo.VirtualGridX;
const vy = immediatePlayerInfo.virtualGridY || immediatePlayerInfo.VirtualGridY;
const joinIndex = immediatePlayerInfo.GetJoinIndex();
const vx = immediatePlayerInfo.GetVirtualGridX();
const vy = immediatePlayerInfo.GetVirtualGridY();
const nodeAndScriptIns = self.spawnPlayerNode(joinIndex, vx, vy, immediatePlayerInfo);
Object.assign(self.playerRichInfoDict.get(playerId), {
@@ -1412,15 +1461,15 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
scriptIns: nodeAndScriptIns[1],
});
const selfPlayerId = self.selfPlayerInfo.Id;
const selfPlayerId = self.selfPlayerInfo.id;
if (selfPlayerId == playerId) {
self.selfPlayerInfo.JoinIndex = immediatePlayerInfo.JoinIndex; // Update here in case of any change during WAITING phase
self.selfPlayerInfo.joinIndex = joinIndex; // Update here in case of any change during WAITING phase
nodeAndScriptIns[1].showArrowTipNode();
}
}
self.playerRichInfoArr = new Array(self.playerRichInfoDict.size);
self.playerRichInfoDict.forEach((playerRichInfo, playerId) => {
self.playerRichInfoArr[playerRichInfo.JoinIndex - 1] = playerRichInfo;
self.playerRichInfoArr[playerRichInfo.GetJoinIndex() - 1] = playerRichInfo;
});
},
@@ -1428,23 +1477,23 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
const self = this;
if (true == usefullOutput) {
let s = [];
for (let i = self.recentInputCache.StFrameId; i < self.recentInputCache.EdFrameId; ++i) {
for (let i = self.recentInputCache.GetStFrameId(); i < self.recentInputCache.GetEdFrameId(); ++i) {
s.push(JSON.stringify(self.recentInputCache.GetByFrameId(i)));
}
return s.join('\n');
}
return `[stInputFrameId=${self.recentInputCache.StFrameId}, edInputFrameId=${self.recentInputCache.EdFrameId})`;
return `[stInputFrameId=${self.recentInputCache.GetStFrameId()}, edInputFrameId=${self.recentInputCache.GetEdFrameId()})`;
},
_stringifyGopkgRoomDownsyncFrame(rdf) {
let s = [];
s.push(`{`);
s.push(` id: ${rdf.Id}`);
s.push(` id: ${rdf.GetId()}`);
s.push(` players: [`);
for (let k in rdf.PlayersArr) {
const player = rdf.PlayersArr[k];
s.push(` {joinIndex: ${player.JoinIndex}, id: ${player.Id}, vx: ${player.VirtualGridX}, vy: ${player.VirtualGridY}, velX: ${player.VelX}, velY: ${player.VelY}}`);
for (let k in rdf.GetPlayersArr()) {
const player = rdf.GetPlayersArr()[k];
s.push(` {joinIndex: ${player.GetJoinIndex()}, id: ${player.Id}, vx: ${player.GetVirtualGridX()}, vy: ${player.GetVirtualGridY()}, velX: ${player.GetVelX()}, velY: ${player.GetVelY()}}`);
}
s.push(` ]`);
s.push(`}`);
@@ -1455,24 +1504,24 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
const self = this;
if (true == usefullOutput) {
let s = [];
for (let i = self.recentRenderCache.stFrameId; i < self.recentRenderCache.edFrameId; ++i) {
const rdf = self.recentRenderCache.getByFrameId(i);
for (let i = self.recentRenderCache.GetStFrameId(); i < self.recentRenderCache.GetEdFrameId(); ++i) {
const rdf = self.recentRenderCache.GetByFrameId(i);
s.push(self._stringifyGopkgRoomDownsyncFrame(rdf));
}
return s.join("\n");
}
return `[stRenderFrameId=${self.recentRenderCache.stFrameId}, edRenderFrameId=${self.recentRenderCache.edFrameId})`;
return `[stRenderFrameId=${self.recentRenderCache.GetStFrameId()}, edRenderFrameId=${self.recentRenderCache.GetEdFrameId()})`;
},
playerDownsyncStr(playerDownsync) {
if (null == playerDownsync) return "";
return `{${playerDownsync.JoinIndex},${playerDownsync.VirtualGridX},${playerDownsync.VirtualGridY},${playerDownsync.VelX},${playerDownsync.VelY},${playerDownsync.FramesToRecover},${playerDownsync.InAir ? 1 : 0},${playerDownsync.OnWall ? 1 : 0}}`;
return `{${playerDownsync.GetJoinIndex()},${playerDownsync.GetVirtualGridX()},${playerDownsync.GetVirtualGridY()},${playerDownsync.GetVelX()},${playerDownsync.GetVelY()},${playerDownsync.GetFramesToRecover()},${playerDownsync.GetInAir() ? 1 : 0},${playerDownsync.GetOnWall() ? 1 : 0}}`;
},
fireballDownsyncStr(fireball) {
if (null == fireball) return "";
return `{${fireball.BattleAttr.BulletLocalId},${fireball.BattleAttr.OriginatedRenderFrameId},${fireball.BattleAttr.OffenderJoinIndex},${fireball.VirtualGridX},${fireball.VirtualGridY},${fireball.VelX},${fireball.VelY},${fireball.DirX},${fireball.DirY},${fireball.Bullet.HitboxSizeX},${fireball.Bullet.HitboxSizeY}}`;
return `{${fireball.GetBulletLocalId()},${fireball.GetOriginatedRenderFrameId()},${fireball.GetOffenderJoinIndex()},${fireball.GetVirtualGridX()},${fireball.GetVirtualGridY()},${fireball.GetVelX()},${fireball.GetVelY()},${fireball.GetDirX()},${fireball.GetDirY()},${fireball.GetHitboxSizeX()},${fireball.GetHitboxSizeY()}}`;
},
inputFrameDownsyncStr(inputFrameDownsync) {
@@ -1509,12 +1558,12 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
_stringifyRdfIdToActuallyUsedInput() {
const self = this;
let s = [];
for (let i = self.recentRenderCache.stFrameId; i < self.recentRenderCache.edFrameId; i++) {
for (let i = self.recentRenderCache.GetStFrameId(); i < self.recentRenderCache.GetEdFrameId(); i++) {
const actuallyUsedInputClone = self.rdfIdToActuallyUsedInput.get(i);
const rdf = self.recentRenderCache.getByFrameId(i);
const rdf = self.recentRenderCache.GetByFrameId(i);
const playersStrBldr = [];
for (let k in rdf.PlayersArr) {
playersStrBldr.push(self.playerDownsyncStr(rdf.PlayersArr[k]));
for (let k in rdf.GetPlayersArr()) {
playersStrBldr.push(self.playerDownsyncStr(rdf.GetPlayersArr()[k]));
}
const fireballsStrBldr = [];
for (let k in rdf.FireballBullets) {
@@ -1544,30 +1593,30 @@ actuallyUsedinputList:{${self.inputFrameDownsyncStr(actuallyUsedInputClone)}}`);
let g2 = self.g2;
g2.clear();
for (let k in rdf.PlayersArr) {
const player = rdf.PlayersArr[k];
if (1 == player.JoinIndex) {
for (let k = 0; k < window.boundRoomCapacity; k++) {
const player = gopkgs.GetPlayer(rdf, k);
if (1 == player.GetJoinIndex()) {
g2.strokeColor = cc.Color.BLUE;
} else {
g2.strokeColor = cc.Color.RED;
}
let [colliderWidth, colliderHeight] = [player.ColliderRadius * 2, player.ColliderRadius * 4];
switch (player.CharacterState) {
let [colliderWidth, colliderHeight] = [player.GetColliderRadius() * 2, player.GetColliderRadius() * 4];
switch (player.GetCharacterState()) {
case ATK_CHARACTER_STATE.LayDown1[0]:
[colliderWidth, colliderHeight] = [player.ColliderRadius * 4, player.ColliderRadius * 2];
[colliderWidth, colliderHeight] = [player.GetColliderRadius() * 4, player.GetColliderRadius() * 2];
break;
case ATK_CHARACTER_STATE.BlownUp1[0]:
case ATK_CHARACTER_STATE.InAirIdle1NoJump[0]:
case ATK_CHARACTER_STATE.InAirIdle1ByJump[0]:
case ATK_CHARACTER_STATE.OnWall[0]:
[colliderWidth, colliderHeight] = [player.ColliderRadius * 2, player.ColliderRadius * 2];
[colliderWidth, colliderHeight] = [player.GetColliderRadius() * 2, player.GetColliderRadius() * 2];
break;
}
const [halfColliderWidth, halfColliderHeight] = gopkgs.VirtualGridToWorldPos((colliderWidth >> 1), (colliderHeight >> 1));
const [wx, wy] = gopkgs.VirtualGridToWorldPos(player.VirtualGridX, player.VirtualGridY);
const [wx, wy] = gopkgs.VirtualGridToWorldPos(player.GetVirtualGridX(), player.GetVirtualGridY());
const [cx, cy] = gopkgs.WorldToPolygonColliderBLPos(wx, wy, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, 0, 0);
const pts = [[0, 0], [leftPadding + halfColliderWidth * 2 + rightPadding, 0], [leftPadding + halfColliderWidth * 2 + rightPadding, bottomPadding + halfColliderHeight * 2 + topPadding], [0, bottomPadding + halfColliderHeight * 2 + topPadding]];
@@ -1579,22 +1628,25 @@ actuallyUsedinputList:{${self.inputFrameDownsyncStr(actuallyUsedInputClone)}}`);
g2.stroke();
}
for (let k in rdf.MeleeBullets) {
const meleeBullet = rdf.MeleeBullets[k];
if (gopkgs.IsMeleeBulletActive(meleeBullet, rdf)) {
const offender = rdf.PlayersArr[meleeBullet.BattleAttr.OffenderJoinIndex - 1];
if (1 == offender.JoinIndex) {
for (let k = 0;; k++) {
const meleeBullet = gopkgs.GetMeleeBullet(rdf, k);
if (null == meleeBullet) {
break;
}
if (gopkgs.IsGeneralBulletActive(meleeBullet.GetBlState(), meleeBullet.GetOriginatedRenderFrameId(), meleeBullet.GetStartupFrames(), meleeBullet.GetActiveFrames(), rdf.GetId())) {
const offender = gopkgs.GetPlayer(rdf, meleeBullet.GetOffenderJoinIndex() - 1);
if (1 == offender.GetJoinIndex()) {
g2.strokeColor = cc.Color.BLUE;
} else {
g2.strokeColor = cc.Color.RED;
}
let xfac = 1; // By now, straight Punch offset doesn't respect "y-axis"
if (0 > offender.DirX) {
if (0 > offender.GetDirX()) {
xfac = -1;
}
const [bulletWx, bulletWy] = gopkgs.VirtualGridToWorldPos(offender.VirtualGridX + xfac * meleeBullet.Bullet.HitboxOffsetX, offender.VirtualGridY);
const [halfColliderWidth, halfColliderHeight] = gopkgs.VirtualGridToWorldPos((meleeBullet.Bullet.HitboxSizeX >> 1), (meleeBullet.Bullet.HitboxSizeY >> 1));
const [bulletWx, bulletWy] = gopkgs.VirtualGridToWorldPos(offender.GetVirtualGridX() + xfac * meleeBullet.GetHitboxOffsetX(), offender.GetVirtualGridY());
const [halfColliderWidth, halfColliderHeight] = gopkgs.VirtualGridToWorldPos((meleeBullet.GetHitboxSizeX() >> 1), (meleeBullet.GetHitboxSizeY() >> 1));
const [bulletCx, bulletCy] = gopkgs.WorldToPolygonColliderBLPos(bulletWx, bulletWy, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, 0, 0);
const pts = [[0, 0], [leftPadding + halfColliderWidth * 2 + rightPadding, 0], [leftPadding + halfColliderWidth * 2 + rightPadding, bottomPadding + halfColliderHeight * 2 + topPadding], [0, bottomPadding + halfColliderHeight * 2 + topPadding]];
@@ -1607,18 +1659,21 @@ actuallyUsedinputList:{${self.inputFrameDownsyncStr(actuallyUsedInputClone)}}`);
}
}
for (let k in rdf.FireballBullets) {
const fireballBullet = rdf.FireballBullets[k];
if (gopkgs.IsFireballBulletActive(fireballBullet, rdf)) {
const offender = rdf.PlayersArr[fireballBullet.BattleAttr.OffenderJoinIndex - 1];
if (1 == offender.JoinIndex) {
for (let k = 0;; k++) {
const fireballBullet = gopkgs.GetFireballBullet(rdf, k);
if (null == fireballBullet) {
break;
}
if (gopkgs.IsGeneralBulletActive(fireballBullet.GetBlState(), fireballBullet.GetOriginatedRenderFrameId(), fireballBullet.GetStartupFrames(), fireballBullet.GetActiveFrames(), rdf.GetId())) {
const offender = gopkgs.GetPlayer(rdf, fireballBullet.GetOffenderJoinIndex() - 1);
if (1 == offender.GetJoinIndex()) {
g2.strokeColor = cc.Color.BLUE;
} else {
g2.strokeColor = cc.Color.RED;
}
const [bulletWx, bulletWy] = gopkgs.VirtualGridToWorldPos(fireballBullet.VirtualGridX, fireballBullet.VirtualGridY);
const [halfColliderWidth, halfColliderHeight] = gopkgs.VirtualGridToWorldPos((fireballBullet.Bullet.HitboxSizeX >> 1), (fireballBullet.Bullet.HitboxSizeY >> 1));
const [bulletWx, bulletWy] = gopkgs.VirtualGridToWorldPos(fireballBullet.GetVirtualGridX(), fireballBullet.GetVirtualGridY());
const [halfColliderWidth, halfColliderHeight] = gopkgs.VirtualGridToWorldPos((fireballBullet.GetHitboxSizeX() >> 1), (fireballBullet.GetHitboxSizeY() >> 1));
const [bulletCx, bulletCy] = gopkgs.WorldToPolygonColliderBLPos(bulletWx, bulletWy, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, 0, 0);
const pts = [[0, 0], [leftPadding + halfColliderWidth * 2 + rightPadding, 0], [leftPadding + halfColliderWidth * 2 + rightPadding, bottomPadding + halfColliderHeight * 2 + topPadding], [0, bottomPadding + halfColliderHeight * 2 + topPadding]];

View File

@@ -1,66 +0,0 @@
cc.Class({
extends: cc.Component,
properties: {
BGMEffect: {
type: cc.AudioClip,
default: null
},
crashedByTrapBullet: {
type: cc.AudioClip,
default: null
},
highScoreTreasurePicked: {
type: cc.AudioClip,
default: null
},
treasurePicked: {
type: cc.AudioClip,
default: null
},
countDown10SecToEnd: {
type: cc.AudioClip,
default: null
},
mapNode: {
type: cc.Node,
default: null
},
},
// LIFE-CYCLE CALLBACKS:
onLoad() {
cc.audioEngine.setEffectsVolume(1);
cc.audioEngine.setMusicVolume(0.5);
},
stopAllMusic() {
cc.audioEngine.stopAll();
},
playBGM() {
if(this.BGMEffect) {
cc.audioEngine.playMusic(this.BGMEffect, true);
}
},
playCrashedByTrapBullet() {
if(this.crashedByTrapBullet) {
cc.audioEngine.playEffect(this.crashedByTrapBullet, false);
}
},
playHighScoreTreasurePicked() {
if(this.highScoreTreasurePicked) {
cc.audioEngine.playEffect(this.highScoreTreasurePicked, false);
}
},
playTreasurePicked() {
if(this.treasurePicked) {
cc.audioEngine.playEffect(this.treasurePicked, false);
}
},
playCountDown10SecToEnd() {
if(this.countDown10SecToEnd) {
cc.audioEngine.playEffect(this.countDown10SecToEnd, false);
}
},
});

View File

@@ -1,9 +0,0 @@
{
"ver": "1.0.5",
"uuid": "09e1bfed-132e-4ada-a68f-229a870db69e",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@@ -91,7 +91,7 @@ NetworkDoctor.prototype.isTooFast = function(mapIns) {
// Don't send too fast
if (CC_DEBUG) {
// Printing of this message might induce a performance impact.
console.log(`Sending too fast, sendingFps=${sendingFps}`);
// console.log(`Sending too fast, sendingFps=${sendingFps}`);
}
return [true, inputFrameIdFront, sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, skippedRenderFrameCnt];
} else {
@@ -109,7 +109,7 @@ NetworkDoctor.prototype.isTooFast = function(mapIns) {
// first comparison condition is to avoid numeric overflow
if (CC_DEBUG) {
// Printing of this message might induce a performance impact.
console.log(`Game logic ticking too fast, selfInputFrameIdFront=${inputFrameIdFront}, minInputFrameIdFront=${minInputFrameIdFront}, inputFrameUpsyncDelayTolerance=${mapIns.inputFrameUpsyncDelayTolerance}`);
// console.log(`Game logic ticking too fast, selfInputFrameIdFront=${inputFrameIdFront}, minInputFrameIdFront=${minInputFrameIdFront}, inputFrameUpsyncDelayTolerance=${mapIns.inputFrameUpsyncDelayTolerance}`);
}
return [true, inputFrameIdFront, sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, skippedRenderFrameCnt];
}

View File

@@ -11,13 +11,13 @@ cc.Class({
},
onLoad() {
cc.game.setFrameRate(60);
cc.game.setFrameRate(59.9);
cc.view.setOrientation(cc.macro.ORIENTATION_LANDSCAPE);
cc.view.enableAutoFullScreen(true);
const self = this;
window.mapIns = self;
self.showCriticalCoordinateLabels = false;
self.showNetworkDoctorInfo = true;
self.showNetworkDoctorInfo = false;
const mapNode = self.node;
const canvasNode = mapNode.parent;
@@ -33,9 +33,9 @@ cc.Class({
/** Init required prefab ended. */
self.inputFrameUpsyncDelayTolerance = 2;
self.collisionMinStep = 2;
self.collisionMinStep = 16;
self.renderCacheSize = 1024;
self.renderCacheSize = 128;
self.serverFps = 60;
self.rollbackEstimatedDt = 0.016667;
self.rollbackEstimatedDtMillis = 16.667;
@@ -73,6 +73,7 @@ cc.Class({
self.spaceOffsetX = ((newMapSize.width * newTileSize.width) >> 1);
self.spaceOffsetY = ((newMapSize.height * newTileSize.height) >> 1);
window.boundRoomCapacity = 2;
self._resetCurrentMatch();
let barrierIdCounter = 0;
const boundaryObjs = tileCollisionManager.extractBoundaryObjects(self.node);
@@ -98,7 +99,7 @@ cc.Class({
const p2Vpos = gopkgs.WorldToVirtualGridPos(boundaryObjs.playerStartingPositions[1].x, boundaryObjs.playerStartingPositions[1].y);
const colliderRadiusV = gopkgs.WorldToVirtualGridPos(12.0, 0);
const speciesIdList = [4096, 0];
const speciesIdList = [1, 0];
const chConfigsOrderedByJoinIndex = gopkgs.GetCharacterConfigsOrderedByJoinIndex(speciesIdList);
const startRdf = window.pb.protos.RoomDownsyncFrame.create({
@@ -111,7 +112,7 @@ cc.Class({
virtualGridY: p1Vpos[1],
revivalVirtualGridX: p1Vpos[0],
revivalVirtualGridY: p1Vpos[1],
speed: chConfigsOrderedByJoinIndex[0].Speed,
speed: chConfigsOrderedByJoinIndex[0].GetSpeed(),
colliderRadius: colliderRadiusV[0],
characterState: window.ATK_CHARACTER_STATE.InAirIdle1NoJump[0],
framesToRecover: 0,
@@ -131,7 +132,7 @@ cc.Class({
virtualGridY: p2Vpos[1],
revivalVirtualGridX: p2Vpos[0],
revivalVirtualGridY: p2Vpos[1],
speed: chConfigsOrderedByJoinIndex[1].Speed,
speed: chConfigsOrderedByJoinIndex[1].GetSpeed(),
colliderRadius: colliderRadiusV[0],
characterState: window.ATK_CHARACTER_STATE.InAirIdle1NoJump[0],
framesToRecover: 0,
@@ -149,8 +150,8 @@ cc.Class({
});
self.selfPlayerInfo = {
Id: 10,
JoinIndex: 1,
id: 10,
joinIndex: 1,
};
if (cc.sys.isNative) {
window.onUdpMessage = (args) => {
@@ -163,7 +164,7 @@ cc.Class({
const echoed = window.pb.protos.HolePunchUpsync.decode(ui8Arr);
cc.log(`#2 Js called back by CPP: onUdpMessage: ${JSON.stringify(echoed)}`);
};
const res1 = DelayNoMore.UdpSession.openUdpSession(8888 + self.selfPlayerInfo.JoinIndex);
const res1 = DelayNoMore.UdpSession.openUdpSession(8888 + self.selfPlayerInfo.joinIndex);
const holePunchData = window.pb.protos.HolePunchUpsync.encode({
boundRoomId: 22,
intAuthToken: "foobar",

View File

@@ -87,8 +87,8 @@ window.handleHbRequirements = function(resp) {
if (constants.RET_CODE.OK != resp.ret) return;
// The assignment of "window.mapIns" is inside "Map.onLoad", which precedes "initPersistentSessionClient".
window.mapIns.selfPlayerInfo = JSON.parse(cc.sys.localStorage.getItem('selfPlayer')); // This field is kept for distinguishing "self" and "others".
window.mapIns.selfPlayerInfo.Id = window.mapIns.selfPlayerInfo.playerId;
window.mapIns.selfPlayerInfo.JoinIndex = resp.peerJoinIndex;
window.mapIns.selfPlayerInfo.id = window.mapIns.selfPlayerInfo.playerId;
window.mapIns.selfPlayerInfo.joinIndex = resp.peerJoinIndex;
console.log(`Handle hb requirements #2`);
if (null == window.boundRoomId || null == window.boundRoomCapacity) {
window.boundRoomId = resp.bciFrame.boundRoomId;
@@ -109,7 +109,7 @@ window.handleHbRequirements = function(resp) {
window.initSecondarySession(null, window.boundRoomId);
} else {
console.log(`Handle hb requirements #5, native, bciFrame.battleUdpTunnel=${resp.bciFrame.battleUdpTunnel}, selfPlayerInfo=${JSON.stringify(window.mapIns.selfPlayerInfo)}`);
const res1 = DelayNoMore.UdpSession.openUdpSession(8888 + window.mapIns.selfPlayerInfo.JoinIndex);
const res1 = DelayNoMore.UdpSession.openUdpSession(8888 + window.mapIns.selfPlayerInfo.joinIndex);
window.mapIns.selfPlayerInfo.udpTunnelAuthKey = resp.bciFrame.battleUdpTunnel.authKey;
const intAuthToken = window.mapIns.selfPlayerInfo.intAuthToken;
const authKey = Math.floor(Math.random() * 65535);
@@ -121,7 +121,7 @@ window.handleHbRequirements = function(resp) {
}).finish();
const udpTunnelHolePunchData = window.pb.protos.WsReq.encode({
msgId: Date.now(),
playerId: window.mapIns.selfPlayerInfo.Id,
playerId: window.mapIns.selfPlayerInfo.id,
act: window.UPSYNC_MSG_ACT_PLAYER_CMD,
authKey: resp.bciFrame.battleUdpTunnel.authKey,
}).finish();
@@ -250,7 +250,7 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
console.log(`Got DOWNSYNC_MSG_ACT_PEER_UDP_ADDR peerAddrList=${JSON.stringify(peerAddrList)}; boundRoomCapacity=${window.boundRoomCapacity}`);
for (let j = 0; j < 3; ++j) {
setTimeout(() => {
DelayNoMore.UdpSession.upsertPeerUdpAddr(peerAddrList, window.boundRoomCapacity, window.mapIns.selfPlayerInfo.JoinIndex); // In C++ impl it actually broadcasts the peer-punching message to all known peers within "window.boundRoomCapacity"
DelayNoMore.UdpSession.upsertPeerUdpAddr(peerAddrList, window.boundRoomCapacity, window.mapIns.selfPlayerInfo.joinIndex); // In C++ impl it actually broadcasts the peer-punching message to all known peers within "window.boundRoomCapacity"
}, j * 500);
}
}
@@ -271,13 +271,6 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
clientSession.onclose = function(evt) {
// [WARNING] The callback "onclose" might be called AFTER the webpage is refreshed with "1001 == evt.code".
console.warn(`The WS clientSession is closed: evt=${JSON.stringify(evt)}, evt.code=${evt.code}`);
if (cc.sys.isNative) {
if (mapIns.frameDataLoggingEnabled) {
console.warn(`${mapIns._stringifyRdfIdToActuallyUsedInput()}
`);
}
DelayNoMore.UdpSession.closeUdpSession();
}
switch (evt.code) {
case constants.RET_CODE.CLIENT_MISMATCHED_RENDER_FRAME:
break;
@@ -288,13 +281,21 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
`);
}
break;
case constants.RET_CODE.SAME_PLAYER_ALREADY_IN_SAME_ROOM:
mapIns.popupSimplePressToGo("You just logged into a conflicting account, please use a different account to retry", false, () => {
window.clearLocalStorageAndBackToLoginScene(true);
});
break;
case constants.RET_CODE.PLAYER_NOT_ADDABLE_TO_ROOM:
case constants.RET_CODE.PLAYER_NOT_READDABLE_TO_ROOM:
window.clearBoundRoomIdInBothVolatileAndPersistentStorage(); // To favor the player to join other rooms
mapIns.onManualRejoinRequired("Couldn't join any room at the moment, please retry");
mapIns.popupSimplePressToGo("Couldn't join any room at the moment, please retry", false, () => {
window.clearLocalStorageAndBackToLoginScene(true);
});
break;
case constants.RET_CODE.ACTIVE_WATCHDOG:
mapIns.onManualRejoinRequired("Disconnected due to long-time inactivity, please rejoin");
mapIns.popupSimplePressToGo("Couldn't join any room at the moment, please retry", false, () => {
window.clearLocalStorageAndBackToLoginScene(true);
});
break;
case constants.RET_CODE.UNKNOWN_ERROR:
case constants.RET_CODE.MYSQL_ERROR:
@@ -305,9 +306,21 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
console.warn(`${mapIns._stringifyRdfIdToActuallyUsedInput()}
`);
}
window.clearLocalStorageAndBackToLoginScene(true);
break;
mapIns.popupSimplePressToGo("Disconnected unexpectedly, please retry", false, () => {
window.clearLocalStorageAndBackToLoginScene(true);
});
default:
if (cc.sys.isNative) {
// [WARNING] This could be a BUG in CocosCreator JSB implementation of WebSocket client, the "evt.code" is always "undefined" in the "onclose" callback!
if (mapIns.frameDataLoggingEnabled) {
console.warn(`${mapIns._stringifyRdfIdToActuallyUsedInput()}
`);
}
DelayNoMore.UdpSession.closeUdpSession();
mapIns.popupSimplePressToGo("Disconnected unexpectedly, please retry", false, () => {
window.clearLocalStorageAndBackToLoginScene(true);
});
}
break;
}
};

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
{
"ver": "1.0.5",
"uuid": "eeaa56f4-bd6c-4208-bec4-6ab1aa39ca93",
"uuid": "02c5cdc1-9797-49ab-bc11-963215909926",
"isPlugin": true,
"loadPluginInWeb": true,
"loadPluginInNative": true,

View File

@@ -27,8 +27,7 @@
android:label="@string/app_name"
android:usesCleartextTraffic="true"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:launchMode="singleTask"
android:taskAffinity="" >
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@@ -76,7 +76,7 @@
"shelter_z_reducer",
"shelter"
],
"last-module-event-record-time": 1675852779064,
"last-module-event-record-time": 1676513919950,
"simulator-orientation": false,
"simulator-resolution": {
"height": 640,

View File

@@ -16,7 +16,7 @@ const (
PATTERN_ID_UNABLE_TO_OP = -2
PATTERN_ID_NO_OP = -1
WORLD_TO_VIRTUAL_GRID_RATIO = float64(100.0)
WORLD_TO_VIRTUAL_GRID_RATIO = float64(10.0)
VIRTUAL_GRID_TO_WORLD_RATIO = float64(1.0) / WORLD_TO_VIRTUAL_GRID_RATIO
GRAVITY_X = int32(0)
@@ -43,6 +43,11 @@ const (
NO_SKILL_HIT = -1
NO_LOCK_VEL = int32(-1)
// Used in preallocated RoomDownsyncFrame to check termination
TERMINATING_BULLET_LOCAL_ID = int32(-1)
TERMINATING_PLAYER_ID = int32(-1)
TERMINATING_RENDER_FRAME_ID = int32(-1)
)
// These directions are chosen such that when speed is changed to "(speedX+delta, speedY+delta)" for any of them, the direction is unchanged.
@@ -96,6 +101,7 @@ var inAirSet = map[int32]bool{
ATK_CHARACTER_STATE_INAIR_ATKED1: true,
ATK_CHARACTER_STATE_BLOWN_UP1: true,
ATK_CHARACTER_STATE_ONWALL: true,
ATK_CHARACTER_STATE_DASHING: true, // Yes dashing is an inair state even if you dashed on the ground :)
}
var noOpSet = map[int32]bool{
@@ -211,26 +217,43 @@ func calcPushbacks(oldDx, oldDy float64, playerShape, barrierShape *resolv.Conve
}
func isPolygonPairOverlapped(a, b *resolv.ConvexPolygon, result *SatResult) bool {
aCnt, bCnt := len(a.Points), len(b.Points)
aCnt, bCnt := a.Points.Cnt, b.Points.Cnt
// Single point case
if 1 == aCnt && 1 == bCnt {
if nil != result {
result.Overlap = 0
}
return a.Points[0][0] == b.Points[0][0] && a.Points[0][1] == b.Points[0][1]
aPoint := a.GetPointByOffset(0)
bPoint := b.GetPointByOffset(0)
return aPoint[0] == bPoint[0] && aPoint[1] == bPoint[1]
}
if 1 < aCnt {
for _, axis := range a.SATAxes() {
if isPolygonPairSeparatedByDir(a, b, axis.Unit(), result) {
// Deliberately using "Points" instead of "SATAxes" to avoid unnecessary heap memory alloc
for i := int32(0); i < a.Points.Cnt; i++ {
u, v := a.GetPointByOffset(i), a.GetPointByOffset(0)
if i != a.Points.Cnt-1 {
v = a.GetPointByOffset(i + 1)
}
dy := v[1] - u[1]
dx := v[0] - u[0]
axis := resolv.Vector{dy, -dx}.Unit()
if isPolygonPairSeparatedByDir(a, b, axis, result) {
return false
}
}
}
if 1 < bCnt {
for _, axis := range b.SATAxes() {
if isPolygonPairSeparatedByDir(a, b, axis.Unit(), result) {
for i := int32(0); i < b.Points.Cnt; i++ {
u, v := b.GetPointByOffset(i), b.GetPointByOffset(0)
if i != b.Points.Cnt-1 {
v = b.GetPointByOffset(i + 1)
}
dy := v[1] - u[1]
dx := v[0] - u[0]
axis := resolv.Vector{dy, -dx}.Unit()
if isPolygonPairSeparatedByDir(a, b, axis, result) {
return false
}
}
@@ -239,11 +262,15 @@ func isPolygonPairOverlapped(a, b *resolv.ConvexPolygon, result *SatResult) bool
return true
}
func IsMeleeBulletActive(meleeBullet *MeleeBullet, currRenderFrame *RoomDownsyncFrame) bool {
if BULLET_EXPLODING == meleeBullet.BlState {
func IsGeneralBulletActive(blState, originatedRenderFrameId, startupFrames, activeFrames, renderFrameId int32) bool {
if BULLET_EXPLODING == blState {
return false
}
return (meleeBullet.BattleAttr.OriginatedRenderFrameId+meleeBullet.Bullet.StartupFrames <= currRenderFrame.Id) && (meleeBullet.BattleAttr.OriginatedRenderFrameId+meleeBullet.Bullet.StartupFrames+meleeBullet.Bullet.ActiveFrames > currRenderFrame.Id)
return (originatedRenderFrameId+startupFrames < renderFrameId) && (originatedRenderFrameId+startupFrames+activeFrames > renderFrameId)
}
func IsMeleeBulletActive(meleeBullet *MeleeBullet, currRenderFrame *RoomDownsyncFrame) bool {
return IsGeneralBulletActive(meleeBullet.BlState, meleeBullet.BattleAttr.OriginatedRenderFrameId, meleeBullet.Bullet.StartupFrames, meleeBullet.Bullet.ActiveFrames, currRenderFrame.Id)
}
func IsMeleeBulletAlive(meleeBullet *MeleeBullet, currRenderFrame *RoomDownsyncFrame) bool {
@@ -254,10 +281,7 @@ func IsMeleeBulletAlive(meleeBullet *MeleeBullet, currRenderFrame *RoomDownsyncF
}
func IsFireballBulletActive(fireballBullet *FireballBullet, currRenderFrame *RoomDownsyncFrame) bool {
if BULLET_EXPLODING == fireballBullet.BlState {
return false
}
return (fireballBullet.BattleAttr.OriginatedRenderFrameId+fireballBullet.Bullet.StartupFrames < currRenderFrame.Id) && (fireballBullet.BattleAttr.OriginatedRenderFrameId+fireballBullet.Bullet.StartupFrames+fireballBullet.Bullet.ActiveFrames > currRenderFrame.Id)
return IsGeneralBulletActive(fireballBullet.BlState, fireballBullet.BattleAttr.OriginatedRenderFrameId, fireballBullet.Bullet.StartupFrames, fireballBullet.Bullet.ActiveFrames, currRenderFrame.Id)
}
func IsFireballBulletAlive(fireballBullet *FireballBullet, currRenderFrame *RoomDownsyncFrame) bool {
@@ -285,7 +309,8 @@ func isPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e resolv.Vector, re
*/
var aStart, aEnd, bStart, bEnd float64 = MAX_FLOAT64, -MAX_FLOAT64, MAX_FLOAT64, -MAX_FLOAT64
for _, p := range a.Points {
for i := int32(0); i < a.Points.Cnt; i++ {
p := a.GetPointByOffset(i)
dot := (p[0]+a.X)*e[0] + (p[1]+a.Y)*e[1]
if aStart > dot {
@@ -297,7 +322,8 @@ func isPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e resolv.Vector, re
}
}
for _, p := range b.Points {
for i := int32(0); i < b.Points.Cnt; i++ {
p := b.GetPointByOffset(i)
dot := (p[0]+b.X)*e[0] + (p[1]+b.Y)*e[1]
if bStart > dot {
@@ -406,8 +432,7 @@ func VirtualGridToPolygonColliderBLPos(vx, vy int32, halfBoundingW, halfBounding
return WorldToPolygonColliderBLPos(wx, wy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY)
}
func calcHardPushbacksNorms(joinIndex int32, currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync, playerCollider *resolv.Object, playerShape *resolv.ConvexPolygon, snapIntoPlatformOverlap float64, pEffPushback *Vec2D) *[]Vec2D {
ret := make([]Vec2D, 0, 10) // no one would simultaneously have more than 5 hardPushbacks
func calcHardPushbacksNorms(joinIndex int32, currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync, playerCollider *resolv.Object, playerShape *resolv.ConvexPolygon, snapIntoPlatformOverlap float64, effPushback *Vec2D, hardPushbackNorms []*Vec2D, collision *resolv.Collision) int {
virtualGripToWall := float64(0)
if ATK_CHARACTER_STATE_ONWALL == currPlayerDownsync.CharacterState && 0 == thatPlayerInNextFrame.VelX && currPlayerDownsync.DirX == thatPlayerInNextFrame.DirX {
/*
@@ -424,14 +449,19 @@ func calcHardPushbacksNorms(joinIndex int32, currPlayerDownsync, thatPlayerInNex
}
virtualGripToWall = xfac * float64(currPlayerDownsync.Speed) * VIRTUAL_GRID_TO_WORLD_RATIO
}
collision := playerCollider.Check(virtualGripToWall, 0)
if nil == collision {
return &ret
retCnt := 0
collided := playerCollider.CheckAllWithHolder(virtualGripToWall, 0, collision)
if !collided {
return retCnt
}
//playerColliderCenterX, playerColliderCenterY := playerCollider.Center()
//fmt.Printf("joinIndex=%d calcHardPushbacksNorms has non-empty collision;playerColliderPos=(%.2f,%.2f)\n", joinIndex, playerColliderCenterX, playerColliderCenterY)
for _, obj := range collision.Objects {
for true {
obj := collision.PopFirstCollidedObject()
if nil == obj {
break
}
isBarrier := false
switch obj.Data.(type) {
case *PlayerDownsync, *MeleeBullet, *FireballBullet:
@@ -451,15 +481,16 @@ func calcHardPushbacksNorms(joinIndex int32, currPlayerDownsync, thatPlayerInNex
// ALWAY snap into hardPushbacks!
// [OverlapX, OverlapY] is the unit vector that points into the platform
pushbackX, pushbackY = (overlapResult.Overlap-snapIntoPlatformOverlap)*overlapResult.OverlapX, (overlapResult.Overlap-snapIntoPlatformOverlap)*overlapResult.OverlapY
ret = append(ret, Vec2D{X: overlapResult.OverlapX, Y: overlapResult.OverlapY})
pEffPushback.X += pushbackX
pEffPushback.Y += pushbackY
hardPushbackNorms[retCnt].X, hardPushbackNorms[retCnt].Y = overlapResult.OverlapX, overlapResult.OverlapY
effPushback.X += pushbackX
effPushback.Y += pushbackY
retCnt++
//fmt.Printf("joinIndex=%d calcHardPushbacksNorms found one hardpushback; immediatePushback=(%.2f,%.2f)\n", joinIndex, pushbackX, pushbackY)
}
return &ret
return retCnt
}
func deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync, currRenderFrame *RoomDownsyncFrame, chConfig *CharacterConfig, inputsBuffer *RingBuffer) (int, bool, int32, int32) {
func deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync, currRenderFrame *RoomDownsyncFrame, chConfig *CharacterConfig, inputsBuffer *resolv.RingBuffer) (int, bool, int32, int32) {
// returns (patternId, jumpedOrNot, effectiveDx, effectiveDy)
delayedInputFrameId := ConvertToDelayedInputFrameId(currRenderFrame.Id)
delayedInputFrameIdForPrevRdf := ConvertToDelayedInputFrameId(currRenderFrame.Id - 1)
@@ -531,79 +562,51 @@ func deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync,
/*
[LONG TERM PERFORMANCE ENHANCEMENT PLAN]
The function "ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame" is creating new heap-memory blocks at 60fps, e.g. nextRenderFramePlayers & nextRenderFrameMeleeBullets & nextRenderFrameFireballBullets & effPushbacks & hardPushbackNorms & jumpedOrNotList & playerColliders & bulletColliders, which would induce "possibly performance impacting garbage collections" when many rooms are running simultaneously.
It's not easy to remove all of the dynamic heap-memory blocks allocation/deallocation, but we can reduce them to some extent. For example, the creation of new "RoomDownsyncFrame" in heap-memory can be avoided by adding
```
func overwriteRoomDownsyncFrame(src *RoomDownsyncFrame, dst *RoomDownsyncFrame) {
// Copy "src" into "dst" down to every primitive field; as for a same room, the "RenderFrameBuffer" is always accessed (R & W) by a same kernel thread (both frontend & backend), no thread-safety concern here
}
type Room struct {
newRoomDownsyncFrameHolder *RoomDownsyncFrame
}
func (pR *Room) provisionNewRoomDownsyncFrameHolder(src *RoomDownsyncFrame) {
overwriteRoomDownsyncFrame(src, pR.newRoomDownsyncFrameHolder)
}
```
then pass in the whole "renderFrameBuffer *SpecificRingBuffer" to this function and overwrite the target slot IN-PLACE, i.e. need write new "SpecificRingBuffer.Put/SetByFrameId" to use the new function "overwriteRoomDownsyncFrame(src, dst)" to keep "%p of every SpecificRingBuffer.Eles[i]" constant.
However, the enhancement for "playerColliders & bulletColliders" of each room is even more difficult, because the feasibility of doing in-place overwrites depends on the collision library in use.
The function "ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame" is creating new heap-memory blocks at 60fps, e.g. nextRenderFramePlayers & nextRenderFrameMeleeBullets & nextRenderFrameFireballBullets & effPushbacks & hardPushbackNorms & jumpedOrNotList & dynamicRectangleColliders("player" & "bullet"), which would induce "possibly performance impacting garbage collections" when many rooms are running simultaneously.
*/
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer, currRenderFrame *RoomDownsyncFrame, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, collisionSpaceOffsetX, collisionSpaceOffsetY float64, chConfigsOrderedByJoinIndex []*CharacterConfig) *RoomDownsyncFrame {
// [WARNING] On backend this function MUST BE called while "InputsBufferLock" is locked!
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.RingBuffer, currRenderFrameId int32, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, collisionSpaceOffsetX, collisionSpaceOffsetY float64, chConfigsOrderedByJoinIndex []*CharacterConfig, renderFrameBuffer *resolv.RingBuffer, collision *resolv.Collision, effPushbacks []*Vec2D, hardPushbackNormsArr [][]*Vec2D, jumpedOrNotList []bool, dynamicRectangleColliders []*resolv.Object) bool {
currRenderFrame := renderFrameBuffer.GetByFrameId(currRenderFrameId).(*RoomDownsyncFrame)
nextRenderFrameId := currRenderFrameId + 1
roomCapacity := len(currRenderFrame.PlayersArr)
nextRenderFramePlayers := make([]*PlayerDownsync, roomCapacity)
var ret *RoomDownsyncFrame = nil
candidate := renderFrameBuffer.GetByFrameId(nextRenderFrameId)
if nil == candidate {
if nextRenderFrameId == renderFrameBuffer.EdFrameId {
renderFrameBuffer.DryPut()
candidate = renderFrameBuffer.GetByFrameId(nextRenderFrameId)
if nil == candidate {
// Lazy alloc heap-mem for holder
ret = NewPreallocatedRoomDownsyncFrame(roomCapacity, 64, 64)
renderFrameBuffer.SetByFrameId(ret, nextRenderFrameId)
} else {
ret = candidate.(*RoomDownsyncFrame)
}
} else {
panic("Invalid nextRenderFrameId=" + string(nextRenderFrameId) + "!")
}
} else {
ret = candidate.(*RoomDownsyncFrame)
}
// [WARNING] On backend this function MUST BE called while "InputsBufferLock" is locked!
nextRenderFramePlayers := ret.PlayersArr
// Make a copy first
for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
nextRenderFramePlayers[i] = &PlayerDownsync{
Id: currPlayerDownsync.Id,
VirtualGridX: currPlayerDownsync.VirtualGridX,
VirtualGridY: currPlayerDownsync.VirtualGridY,
DirX: currPlayerDownsync.DirX,
DirY: currPlayerDownsync.DirY,
VelX: currPlayerDownsync.VelX,
VelY: currPlayerDownsync.VelY,
CharacterState: currPlayerDownsync.CharacterState,
InAir: true,
OnWall: false,
Speed: currPlayerDownsync.Speed,
BattleState: currPlayerDownsync.BattleState,
Score: currPlayerDownsync.Score,
Removed: currPlayerDownsync.Removed,
JoinIndex: currPlayerDownsync.JoinIndex,
Hp: currPlayerDownsync.Hp,
MaxHp: currPlayerDownsync.MaxHp,
FramesToRecover: currPlayerDownsync.FramesToRecover - 1,
FramesInChState: currPlayerDownsync.FramesInChState + 1,
ActiveSkillId: currPlayerDownsync.ActiveSkillId,
ActiveSkillHit: currPlayerDownsync.ActiveSkillHit,
FramesInvinsible: currPlayerDownsync.FramesInvinsible - 1,
BulletTeamId: currPlayerDownsync.BulletTeamId,
ChCollisionTeamId: currPlayerDownsync.ChCollisionTeamId,
RevivalVirtualGridX: currPlayerDownsync.RevivalVirtualGridX,
RevivalVirtualGridY: currPlayerDownsync.RevivalVirtualGridY,
ColliderRadius: currPlayerDownsync.ColliderRadius,
OnWallNormX: currPlayerDownsync.OnWallNormX,
OnWallNormY: currPlayerDownsync.OnWallNormY,
CapturedByInertia: currPlayerDownsync.CapturedByInertia,
for i, src := range currRenderFrame.PlayersArr {
framesToRecover := src.FramesToRecover - 1
framesInChState := src.FramesInChState + 1
framesInvinsible := src.FramesInvinsible - 1
if framesToRecover < 0 {
framesToRecover = 0
}
if nextRenderFramePlayers[i].FramesToRecover < 0 {
nextRenderFramePlayers[i].FramesToRecover = 0
}
if nextRenderFramePlayers[i].FramesInvinsible < 0 {
nextRenderFramePlayers[i].FramesInvinsible = 0
if framesInvinsible < 0 {
framesInvinsible = 0
}
ClonePlayerDownsync(src.Id, src.VirtualGridX, src.VirtualGridY, src.DirX, src.DirY, src.VelX, src.VelY, framesToRecover, framesInChState, src.ActiveSkillId, src.ActiveSkillHit, framesInvinsible, src.Speed, src.BattleState, src.CharacterState, src.JoinIndex, src.Hp, src.MaxHp, src.ColliderRadius, true, false, src.OnWallNormX, src.OnWallNormY, src.CapturedByInertia, src.BulletTeamId, src.ChCollisionTeamId, src.RevivalVirtualGridX, src.RevivalVirtualGridY, nextRenderFramePlayers[i])
}
nextRenderFrameMeleeBullets := make([]*MeleeBullet, 0, len(currRenderFrame.MeleeBullets)) // Is there any better way to reduce malloc/free impact, e.g. smart prediction for fixed memory allocation?
nextRenderFrameFireballBullets := make([]*FireballBullet, 0, len(currRenderFrame.FireballBullets))
effPushbacks := make([]Vec2D, roomCapacity)
hardPushbackNorms := make([]*[]Vec2D, roomCapacity)
jumpedOrNotList := make([]bool, roomCapacity)
meleeBulletCnt := 0
nextRenderFrameMeleeBullets := ret.MeleeBullets
fireballBulletCnt := 0
nextRenderFrameFireballBullets := ret.FireballBullets
bulletLocalId := currRenderFrame.BulletLocalIdCounter
// 1. Process player inputs
@@ -628,16 +631,9 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
// Hardcoded to use only the first hit for now
switch v := skillConfig.Hits[thatPlayerInNextFrame.ActiveSkillHit].(type) {
case *MeleeBullet:
var newBullet MeleeBullet = *v // Copied primitive fields into an onstack variable
newBullet.BattleAttr = &BulletBattleAttr{
BulletLocalId: bulletLocalId,
OriginatedRenderFrameId: currRenderFrame.Id,
OffenderJoinIndex: joinIndex,
TeamId: currPlayerDownsync.BulletTeamId,
}
CloneMeleeBullet(BULLET_STARTUP, 0, bulletLocalId, currRenderFrameId, joinIndex, currPlayerDownsync.BulletTeamId, v.Bullet, nextRenderFrameMeleeBullets[meleeBulletCnt])
bulletLocalId++
newBullet.BlState = BULLET_STARTUP
nextRenderFrameMeleeBullets = append(nextRenderFrameMeleeBullets, &newBullet)
meleeBulletCnt++
if NO_LOCK_VEL != v.Bullet.SelfLockVelX {
hasLockVel = true
thatPlayerInNextFrame.VelX = xfac * v.Bullet.SelfLockVelX
@@ -647,23 +643,9 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
thatPlayerInNextFrame.VelY = v.Bullet.SelfLockVelY
}
case *FireballBullet:
var newBullet FireballBullet = *v // Copied primitive fields into an onstack variable
newBullet.BattleAttr = &BulletBattleAttr{
BulletLocalId: bulletLocalId,
OriginatedRenderFrameId: currRenderFrame.Id,
OffenderJoinIndex: joinIndex,
TeamId: currPlayerDownsync.BulletTeamId,
}
CloneFireballBullet(BULLET_STARTUP, 0, currPlayerDownsync.VirtualGridX+xfac*v.Bullet.HitboxOffsetX, currPlayerDownsync.VirtualGridY+v.Bullet.HitboxOffsetY, xfac, 0, v.Speed*xfac, 0, v.Speed, bulletLocalId, currRenderFrameId, joinIndex, currPlayerDownsync.BulletTeamId, v.Bullet, nextRenderFrameFireballBullets[fireballBulletCnt])
bulletLocalId++
newBullet.VirtualGridX, newBullet.VirtualGridY = currPlayerDownsync.VirtualGridX+xfac*newBullet.Bullet.HitboxOffsetX, currPlayerDownsync.VirtualGridY+newBullet.Bullet.HitboxOffsetY
newBullet.DirX = xfac
newBullet.DirY = 0
newBullet.VelX = newBullet.Speed * xfac
newBullet.VelY = 0
newBullet.BlState = BULLET_STARTUP
nextRenderFrameFireballBullets = append(nextRenderFrameFireballBullets, &newBullet)
//fmt.Printf("Created new fireball @currRenderFrame.Id=%d, %p, bulletLocalId=%d, virtualGridX=%d, virtualGridY=%d, offenderVpos=(%d,%d)\n", currRenderFrame.Id, &newBullet, bulletLocalId, newBullet.VirtualGridX, newBullet.VirtualGridY, currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY)
fireballBulletCnt++
if NO_LOCK_VEL != v.Bullet.SelfLockVelX {
hasLockVel = true
thatPlayerInNextFrame.VelX = xfac * v.Bullet.SelfLockVelX
@@ -744,8 +726,14 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
}
}
/*
[WARNING]
1. The dynamic colliders will all be removed from "Space" at the end of this function due to the need for being rollback-compatible.
2. To achieve "zero gc" in "ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame", I deliberately chose a collision system that doesn't use dynamic tree node alloc.
*/
colliderCnt := 0
// 2. Process player movement
playerColliders := make([]*resolv.Object, len(currRenderFrame.PlayersArr), len(currRenderFrame.PlayersArr)) // Will all be removed at the end of this function due to the need for being rollback-compatible
for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
joinIndex := currPlayerDownsync.JoinIndex
effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y = float64(0), float64(0)
@@ -804,11 +792,12 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
colliderWorldWidth, colliderWorldHeight := VirtualGridToWorldPos(colliderWidth, colliderHeight)
playerCollider := GenerateRectCollider(wx, wy, colliderWorldWidth, colliderWorldHeight, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, collisionSpaceOffsetX, collisionSpaceOffsetY, currPlayerDownsync, "Player") // the coords of all barrier boundaries are multiples of tileWidth(i.e. 16), by adding snapping y-padding when "landedOnGravityPushback" all "playerCollider.Y" would be a multiple of 1.0
playerColliders[i] = playerCollider
playerCollider := dynamicRectangleColliders[colliderCnt]
UpdateRectCollider(playerCollider, wx, wy, colliderWorldWidth, colliderWorldHeight, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, collisionSpaceOffsetX, collisionSpaceOffsetY, currPlayerDownsync, "Player") // the coords of all barrier boundaries are multiples of tileWidth(i.e. 16), by adding snapping y-padding when "landedOnGravityPushback" all "playerCollider.Y" would be a multiple of 1.0
colliderCnt++
// Add to collision system
collisionSys.Add(playerCollider)
collisionSys.AddSingle(playerCollider)
if currPlayerDownsync.InAir {
if ATK_CHARACTER_STATE_ONWALL == currPlayerDownsync.CharacterState && !jumpedOrNotList[i] {
@@ -823,30 +812,25 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
}
}
// 3. Add bullet colliders into collision system
// 3. Add bullet colliders into collision system; [DIRTY TRICK] Players always precede bullets in "dynamicRectangleColliders".
// [WARNING] For rollback compatibility, static data of "BulletConfig" & "BattleAttr(static since instantiated)" can just be copies of the pointers in "RenderFrameBuffer", however, FireballBullets movement data as well as bullet animation data must be copies of instances for each RenderFrame!
bulletColliders := make([]*resolv.Object, 0, ((len(currRenderFrame.MeleeBullets) + len(currRenderFrame.FireballBullets)) << 1)) // Will all be removed at the end of this function due to the need for being rollback-compatible
for _, prevFireball := range currRenderFrame.FireballBullets {
fireballBullet := &FireballBullet{
VirtualGridX: prevFireball.VirtualGridX,
VirtualGridY: prevFireball.VirtualGridY,
DirX: prevFireball.DirX,
DirY: prevFireball.DirY,
VelX: prevFireball.VelX,
VelY: prevFireball.VelY,
Speed: prevFireball.Speed,
Bullet: prevFireball.Bullet,
BattleAttr: prevFireball.BattleAttr,
FramesInBlState: prevFireball.FramesInBlState + 1,
BlState: prevFireball.BlState,
if TERMINATING_BULLET_LOCAL_ID == prevFireball.BattleAttr.BulletLocalId {
break
}
fireballBullet := nextRenderFrameFireballBullets[fireballBulletCnt]
CloneFireballBullet(prevFireball.BlState, prevFireball.FramesInBlState+1, prevFireball.VirtualGridX, prevFireball.VirtualGridY, prevFireball.DirX, prevFireball.DirY, prevFireball.VelX, prevFireball.VelY, prevFireball.Speed, prevFireball.BattleAttr.BulletLocalId, prevFireball.BattleAttr.OriginatedRenderFrameId, prevFireball.BattleAttr.OffenderJoinIndex, prevFireball.BattleAttr.TeamId, prevFireball.Bullet, fireballBullet)
if IsFireballBulletAlive(fireballBullet, currRenderFrame) {
if IsFireballBulletActive(fireballBullet, currRenderFrame) {
bulletWx, bulletWy := VirtualGridToWorldPos(fireballBullet.VirtualGridX, fireballBullet.VirtualGridY)
hitboxSizeWx, hitboxSizeWy := VirtualGridToWorldPos(fireballBullet.Bullet.HitboxSizeX, fireballBullet.Bullet.HitboxSizeY)
newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, hitboxSizeWx, hitboxSizeWy, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, collisionSpaceOffsetX, collisionSpaceOffsetY, fireballBullet, "FireballBullet")
collisionSys.Add(newBulletCollider)
bulletColliders = append(bulletColliders, newBulletCollider)
newBulletCollider := dynamicRectangleColliders[colliderCnt]
UpdateRectCollider(newBulletCollider, bulletWx, bulletWy, hitboxSizeWx, hitboxSizeWy, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, collisionSpaceOffsetX, collisionSpaceOffsetY, fireballBullet, "FireballBullet")
colliderCnt++
collisionSys.AddSingle(newBulletCollider)
fireballBullet.BlState = BULLET_ACTIVE
if fireballBullet.BlState != prevFireball.BlState {
fireballBullet.FramesInBlState = 0
@@ -861,17 +845,19 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
}
//fmt.Printf("Pushing non-active fireball to next frame @currRenderFrame.Id=%d, bulletLocalId=%d, virtualGridX=%d, virtualGridY=%d, blState=%d\n", currRenderFrame.Id, fireballBullet.BattleAttr.BulletLocalId, fireballBullet.VirtualGridX, fireballBullet.VirtualGridY, fireballBullet.BlState)
}
nextRenderFrameFireballBullets = append(nextRenderFrameFireballBullets, fireballBullet)
fireballBulletCnt++
}
}
// Explicitly specify termination of fireball bullets
nextRenderFrameFireballBullets[fireballBulletCnt].BattleAttr.BulletLocalId = TERMINATING_BULLET_LOCAL_ID
for _, prevMelee := range currRenderFrame.MeleeBullets {
meleeBullet := &MeleeBullet{
Bullet: prevMelee.Bullet,
BattleAttr: prevMelee.BattleAttr,
FramesInBlState: prevMelee.FramesInBlState + 1,
BlState: prevMelee.BlState,
if TERMINATING_BULLET_LOCAL_ID == prevMelee.BattleAttr.BulletLocalId {
break
}
meleeBullet := nextRenderFrameMeleeBullets[meleeBulletCnt]
CloneMeleeBullet(prevMelee.BlState, prevMelee.FramesInBlState+1, prevMelee.BattleAttr.BulletLocalId, prevMelee.BattleAttr.OriginatedRenderFrameId, prevMelee.BattleAttr.OffenderJoinIndex, prevMelee.BattleAttr.TeamId, prevMelee.Bullet, meleeBullet)
if IsMeleeBulletAlive(meleeBullet, currRenderFrame) {
offender := currRenderFrame.PlayersArr[meleeBullet.BattleAttr.OffenderJoinIndex-1]
if _, existent := noOpSet[offender.CharacterState]; existent {
@@ -885,30 +871,39 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
}
bulletWx, bulletWy := VirtualGridToWorldPos(offender.VirtualGridX+xfac*meleeBullet.Bullet.HitboxOffsetX, offender.VirtualGridY)
hitboxSizeWx, hitboxSizeWy := VirtualGridToWorldPos(meleeBullet.Bullet.HitboxSizeX, meleeBullet.Bullet.HitboxSizeY)
newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, hitboxSizeWx, hitboxSizeWy, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, collisionSpaceOffsetX, collisionSpaceOffsetY, meleeBullet, "MeleeBullet")
collisionSys.Add(newBulletCollider)
bulletColliders = append(bulletColliders, newBulletCollider)
newBulletCollider := dynamicRectangleColliders[colliderCnt]
UpdateRectCollider(newBulletCollider, bulletWx, bulletWy, hitboxSizeWx, hitboxSizeWy, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, collisionSpaceOffsetX, collisionSpaceOffsetY, meleeBullet, "MeleeBullet")
colliderCnt++
collisionSys.AddSingle(newBulletCollider)
meleeBullet.BlState = BULLET_ACTIVE
if meleeBullet.BlState != prevMelee.BlState {
meleeBullet.FramesInBlState = 0
}
}
nextRenderFrameMeleeBullets = append(nextRenderFrameMeleeBullets, meleeBullet)
meleeBulletCnt++
}
}
// Explicitly specify termination of melee bullets
nextRenderFrameMeleeBullets[meleeBulletCnt].BattleAttr.BulletLocalId = TERMINATING_BULLET_LOCAL_ID
// 4. Calc pushbacks for each player (after its movement) w/o bullets
for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
joinIndex := currPlayerDownsync.JoinIndex
playerCollider := playerColliders[i]
playerCollider := dynamicRectangleColliders[i]
playerShape := playerCollider.Shape.(*resolv.ConvexPolygon)
thatPlayerInNextFrame := nextRenderFramePlayers[i]
hardPushbackNorms[joinIndex-1] = calcHardPushbacksNorms(joinIndex, currPlayerDownsync, thatPlayerInNextFrame, playerCollider, playerShape, SNAP_INTO_PLATFORM_OVERLAP, &(effPushbacks[joinIndex-1]))
hardPushbackCnt := calcHardPushbacksNorms(joinIndex, currPlayerDownsync, thatPlayerInNextFrame, playerCollider, playerShape, SNAP_INTO_PLATFORM_OVERLAP, effPushbacks[joinIndex-1], hardPushbackNormsArr[joinIndex-1], collision)
chConfig := chConfigsOrderedByJoinIndex[i]
landedOnGravityPushback := false
if collision := playerCollider.Check(0, 0); nil != collision {
for _, obj := range collision.Objects {
if collided := playerCollider.CheckAllWithHolder(0, 0, collision); collided {
for true {
obj := collision.PopFirstCollidedObject()
if nil == obj {
break
}
isBarrier, isAnotherPlayer, isBullet := false, false, false
switch v := obj.Data.(type) {
case *PlayerDownsync:
@@ -938,7 +933,8 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
// [WARNING] The "zero overlap collision" might be randomly detected/missed on either frontend or backend, to have deterministic result we added paddings to all sides of a playerCollider. As each velocity component of (velX, velY) being a multiple of 0.5 at any renderFrame, each position component of (x, y) can only be a multiple of 0.5 too, thus whenever a 1-dimensional collision happens between players from [player#1: i*0.5, player#2: j*0.5, not collided yet] to [player#1: (i+k)*0.5, player#2: j*0.5, collided], the overlap becomes (i+k-j)*0.5+2*s, and after snapping subtraction the effPushback magnitude for each player is (i+k-j)*0.5, resulting in 0.5-multiples-position for the next renderFrame.
pushbackX, pushbackY = (overlapResult.Overlap-SNAP_INTO_PLATFORM_OVERLAP*2)*overlapResult.OverlapX, (overlapResult.Overlap-SNAP_INTO_PLATFORM_OVERLAP*2)*overlapResult.OverlapY
}
for _, hardPushbackNorm := range *hardPushbackNorms[joinIndex-1] {
for i := 0; i < hardPushbackCnt; i++ {
hardPushbackNorm := hardPushbackNormsArr[joinIndex-1][i]
projectedMagnitude := pushbackX*hardPushbackNorm.X + pushbackY*hardPushbackNorm.Y
if isBarrier || (isAnotherPlayer && 0 > projectedMagnitude) {
pushbackX -= projectedMagnitude * hardPushbackNorm.X
@@ -951,10 +947,11 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
if SNAP_INTO_PLATFORM_THRESHOLD < normAlignmentWithGravity {
landedOnGravityPushback = true
//playerColliderCenterX, playerColliderCenterY := playerCollider.Center()
//fmt.Printf("joinIndex=%d landedOnGravityPushback\n{renderFrame.id: %d, isBarrier: %v, isAnotherPlayer: %v}\nhardPushbackNormsOfThisPlayer=%v, playerColliderPos=(%.2f,%.2f), immediatePushback={%.3f, %.3f}, effPushback={%.3f, %.3f}, overlapMag=%.4f\n", joinIndex, currRenderFrame.Id, isBarrier, isAnotherPlayer, *hardPushbackNorms[joinIndex-1], playerColliderCenterX, playerColliderCenterY, pushbackX, pushbackY, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y, overlapResult.Overlap)
//fmt.Printf("joinIndex=%d landedOnGravityPushback\n{renderFrame.id: %d, isBarrier: %v, isAnotherPlayer: %v}\nhardPushbackNormsOfThisPlayer=%v, playerColliderPos=(%.2f,%.2f), immediatePushback={%.3f, %.3f}, effPushback={%.3f, %.3f}, overlapMag=%.4f\n", joinIndex, currRenderFrame.Id, isBarrier, isAnotherPlayer, hardPushbackNorms[joinIndex-1], playerColliderCenterX, playerColliderCenterY, pushbackX, pushbackY, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y, overlapResult.Overlap)
}
}
}
if landedOnGravityPushback {
thatPlayerInNextFrame.InAir = false
fallStopping := (currPlayerDownsync.InAir && 0 >= currPlayerDownsync.VelY)
@@ -962,6 +959,8 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
thatPlayerInNextFrame.VelY = 0
thatPlayerInNextFrame.VelX = 0
if ATK_CHARACTER_STATE_DYING == thatPlayerInNextFrame.CharacterState {
// No update needed for Dying
} else if ATK_CHARACTER_STATE_BLOWN_UP1 == thatPlayerInNextFrame.CharacterState {
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_LAY_DOWN1
thatPlayerInNextFrame.FramesToRecover = chConfig.LayDownFramesToRecover
@@ -980,7 +979,8 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
// landedOnGravityPushback not fallStopping, could be in LayDown or GetUp or Dying
if _, existent := nonAttackingSet[thatPlayerInNextFrame.CharacterState]; existent {
if ATK_CHARACTER_STATE_DYING == thatPlayerInNextFrame.CharacterState {
// No update needed for Dying
thatPlayerInNextFrame.VelY = 0
thatPlayerInNextFrame.VelX = 0
} else if ATK_CHARACTER_STATE_LAY_DOWN1 == thatPlayerInNextFrame.CharacterState {
if 0 == thatPlayerInNextFrame.FramesToRecover {
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_GET_UP1
@@ -1001,7 +1001,8 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
// [WARNING] Sticking to wall MUST BE based on "InAir", otherwise we would get gravity reduction from ground up incorrectly!
if _, existent := noOpSet[currPlayerDownsync.CharacterState]; !existent {
// [WARNING] Sticking to wall could only be triggered by proactive player input
for _, hardPushbackNorm := range *hardPushbackNorms[joinIndex-1] {
for i := 0; i < hardPushbackCnt; i++ {
hardPushbackNorm := hardPushbackNormsArr[joinIndex-1][i]
normAlignmentWithHorizon1 := (hardPushbackNorm.X*float64(1.0) + hardPushbackNorm.Y*float64(0.0))
normAlignmentWithHorizon2 := (hardPushbackNorm.X*float64(-1.0) + hardPushbackNorm.Y*float64(0.0))
if VERTICAL_PLATFORM_THRESHOLD < normAlignmentWithHorizon1 {
@@ -1021,19 +1022,19 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
thatPlayerInNextFrame.OnWallNormX, thatPlayerInNextFrame.OnWallNormY = 0, 0
}
}
}
// 5. Check bullet-anything collisions
for _, bulletCollider := range bulletColliders {
collision := bulletCollider.Check(0, 0)
bulletCollider.Space.Remove(bulletCollider) // Make sure that the bulletCollider is always removed for each renderFrame
exploded := false
explodedOnAnotherPlayer := false
if nil == collision {
for i := len(nextRenderFramePlayers); i < colliderCnt; i++ {
bulletCollider := dynamicRectangleColliders[i]
collided := bulletCollider.CheckAllWithHolder(0, 0, collision)
if !collided {
continue
}
exploded := false
explodedOnAnotherPlayer := false
var bulletStaticAttr *BulletConfig = nil
var bulletBattleAttr *BulletBattleAttr = nil
switch v := bulletCollider.Data.(type) {
@@ -1047,7 +1048,11 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
bulletShape := bulletCollider.Shape.(*resolv.ConvexPolygon)
offender := currRenderFrame.PlayersArr[bulletBattleAttr.OffenderJoinIndex-1]
for _, obj := range collision.Objects {
for true {
obj := collision.PopFirstCollidedObject()
if nil == obj {
break
}
defenderShape := obj.Shape.(*resolv.ConvexPolygon)
switch t := obj.Data.(type) {
case *PlayerDownsync:
@@ -1072,15 +1077,15 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
}
atkedPlayerInNextFrame := nextRenderFramePlayers[t.JoinIndex-1]
atkedPlayerInNextFrame.Hp -= bulletStaticAttr.Damage
pushbackVelX, pushbackVelY := xfac*bulletStaticAttr.PushbackVelX, bulletStaticAttr.PushbackVelY
atkedPlayerInNextFrame.VelX = pushbackVelX
atkedPlayerInNextFrame.VelY = pushbackVelY
if 0 >= atkedPlayerInNextFrame.Hp {
// [WARNING] We don't have "dying in air" animation for now, and for better graphical recognition, play the same dying animation even in air
atkedPlayerInNextFrame.Hp = 0
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_DYING
atkedPlayerInNextFrame.FramesToRecover = DYING_FRAMES_TO_RECOVER
} else {
pushbackVelX, pushbackVelY := xfac*bulletStaticAttr.PushbackVelX, bulletStaticAttr.PushbackVelY
atkedPlayerInNextFrame.VelX = pushbackVelX
atkedPlayerInNextFrame.VelY = pushbackVelY
if bulletStaticAttr.BlowUp {
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_BLOWN_UP1
} else {
@@ -1118,7 +1123,7 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
// 6. Get players out of stuck barriers if there's any
for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
joinIndex := currPlayerDownsync.JoinIndex
playerCollider := playerColliders[i]
playerCollider := dynamicRectangleColliders[i]
// Update "virtual grid position"
thatPlayerInNextFrame := nextRenderFramePlayers[i]
thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY = PolygonColliderBLToVirtualGridPos(playerCollider.X-effPushbacks[joinIndex-1].X, playerCollider.Y-effPushbacks[joinIndex-1].Y, playerCollider.W*0.5, playerCollider.H*0.5, 0, 0, 0, 0, collisionSpaceOffsetX, collisionSpaceOffsetY)
@@ -1164,17 +1169,15 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
}
}
for _, playerCollider := range playerColliders {
playerCollider.Space.Remove(playerCollider)
for i := 0; i < colliderCnt; i++ {
dynamicCollider := dynamicRectangleColliders[i]
dynamicCollider.Space.RemoveSingle(dynamicCollider)
}
return &RoomDownsyncFrame{
Id: currRenderFrame.Id + 1,
PlayersArr: nextRenderFramePlayers,
BulletLocalIdCounter: bulletLocalId,
MeleeBullets: nextRenderFrameMeleeBullets,
FireballBullets: nextRenderFrameFireballBullets,
}
ret.Id = nextRenderFrameId
ret.BulletLocalIdCounter = bulletLocalId
return true
}
func GenerateRectCollider(wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) *resolv.Object {
@@ -1183,13 +1186,23 @@ func GenerateRectCollider(wx, wy, w, h, topPadding, bottomPadding, leftPadding,
}
func generateRectColliderInCollisionSpace(blX, blY, w, h float64, data interface{}, tag string) *resolv.Object {
collider := resolv.NewObject(blX, blY, w, h, tag) // Unlike its frontend counter part, the position of a "resolv.Object" must be specified by "bottom-left point" because "w" and "h" must be positive, see "resolv.Object.BoundsToSpace" for details
collider := resolv.NewObjectSingleTag(blX, blY, w, h, tag) // Unlike its frontend counter part, the position of a "resolv.Object" must be specified by "bottom-left point" because "w" and "h" must be positive, see "resolv.Object.BoundsToSpace" for details
shape := resolv.NewRectangle(0, 0, w, h)
collider.SetShape(shape)
collider.Data = data
return collider
}
func UpdateRectCollider(collider *resolv.Object, wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) {
blX, blY := WorldToPolygonColliderBLPos(wx, wy, w*0.5, h*0.5, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY)
effW, effH := leftPadding+w+rightPadding, bottomPadding+h+topPadding
collider.X, collider.Y, collider.W, collider.H = blX, blY, effW, effH
rectShape := collider.Shape.(*resolv.ConvexPolygon)
rectShape.UpdateAsRectangle(0, 0, effW, effH)
collider.Data = data
// Ignore "tag" for now
}
func GenerateConvexPolygonCollider(unalignedSrc *Polygon2D, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) *resolv.Object {
aligned := AlignPolygon2DToBoundingBox(unalignedSrc)
var w, h float64 = 0, 0
@@ -1294,13 +1307,15 @@ func NewMeleeBullet(bulletLocalId, originatedRenderFrameId, offenderJoinIndex, s
func NewFireballBullet(bulletLocalId, originatedRenderFrameId, offenderJoinIndex, startupFrames, cancellableStFrame, cancellableEdFrame, activeFrames, hitStunFrames, blockStunFrames, pushbackVelX, pushbackVelY, damage, selfLockVelX, selfLockVelY, hitboxOffsetX, hitboxOffsetY, hitboxSizeX, hitboxSizeY int32, blowUp bool, teamId int32, virtualGridX, virtualGridY, dirX, dirY, velX, velY, speed, blState, framesInBlState, explosionFrames, speciesId int32) *FireballBullet {
return &FireballBullet{
VirtualGridX: virtualGridX,
VirtualGridY: virtualGridY,
DirX: dirX,
DirY: dirY,
VelX: velX,
VelY: velY,
Speed: speed,
BlState: blState,
FramesInBlState: framesInBlState,
VirtualGridX: virtualGridX,
VirtualGridY: virtualGridY,
DirX: dirX,
DirY: dirY,
VelX: velX,
VelY: velY,
Speed: speed,
BattleAttr: &BulletBattleAttr{
BulletLocalId: bulletLocalId,
OriginatedRenderFrameId: originatedRenderFrameId,
@@ -1333,3 +1348,150 @@ func NewFireballBullet(bulletLocalId, originatedRenderFrameId, offenderJoinIndex
},
}
}
func NewPlayerDownsync(id, virtualGridX, virtualGridY, dirX, dirY, velX, velY, framesToRecover, framesInChState, activeSkillId, activeSkillHit, framesInvinsible, speed, battleState, characterState, joinIndex, hp, maxHp, colliderRadius int32, inAir, onWall bool, onWallNormX, onWallNormY int32, capturedByInertia bool, bulletTeamId, chCollisionTeamId int32, revivalVirtualGridX, revivalVirtualGridY int32) *PlayerDownsync {
return &PlayerDownsync{
Id: id,
VirtualGridX: virtualGridX,
VirtualGridY: virtualGridY,
DirX: dirX,
DirY: dirY,
VelX: velX,
VelY: velY,
FramesToRecover: framesToRecover,
FramesInChState: framesInChState,
ActiveSkillId: activeSkillId,
ActiveSkillHit: activeSkillHit,
FramesInvinsible: framesInvinsible,
Speed: speed,
BattleState: battleState,
CharacterState: characterState,
JoinIndex: joinIndex,
Hp: hp,
MaxHp: maxHp,
ColliderRadius: colliderRadius,
InAir: inAir,
OnWall: onWall,
OnWallNormX: onWallNormX,
OnWallNormY: onWallNormY,
CapturedByInertia: capturedByInertia,
BulletTeamId: bulletTeamId,
ChCollisionTeamId: chCollisionTeamId,
RevivalVirtualGridX: revivalVirtualGridX,
RevivalVirtualGridY: revivalVirtualGridY,
}
}
func CloneMeleeBullet(blState, framesInBlState, bulletLocalId, originatedRenderFrameId, offenderJoinIndex, teamId int32, staticBulletConfig *BulletConfig, dst *MeleeBullet /* preallocated */) {
dst.BlState = blState
dst.FramesInBlState = framesInBlState
dst.BattleAttr.BulletLocalId = bulletLocalId
dst.BattleAttr.OriginatedRenderFrameId = originatedRenderFrameId
dst.BattleAttr.OffenderJoinIndex = offenderJoinIndex
dst.BattleAttr.TeamId = teamId
dst.Bullet = staticBulletConfig // It's OK to just assign the pointer here, static bullet config is meant to be passed this way
}
func CloneFireballBullet(blState, framesInBlState, virtualGridX, virtualGridY, dirX, dirY, velX, velY, speed, bulletLocalId, originatedRenderFrameId, offenderJoinIndex, teamId int32, staticBulletConfig *BulletConfig, dst *FireballBullet /* preallocated */) {
dst.BlState = blState
dst.FramesInBlState = framesInBlState
dst.VirtualGridX = virtualGridX
dst.VirtualGridY = virtualGridY
dst.DirX = dirX
dst.DirY = dirY
dst.VelX = velX
dst.VelY = velY
dst.Speed = speed
dst.BattleAttr.BulletLocalId = bulletLocalId
dst.BattleAttr.OriginatedRenderFrameId = originatedRenderFrameId
dst.BattleAttr.OffenderJoinIndex = offenderJoinIndex
dst.BattleAttr.TeamId = teamId
dst.Bullet = staticBulletConfig // It's OK to just assign the pointer here, static bullet config is meant to be passed this way
}
func ClonePlayerDownsync(id, virtualGridX, virtualGridY, dirX, dirY, velX, velY, framesToRecover, framesInChState, activeSkillId, activeSkillHit, framesInvinsible, speed, battleState, characterState, joinIndex, hp, maxHp, colliderRadius int32, inAir, onWall bool, onWallNormX, onWallNormY int32, capturedByInertia bool, bulletTeamId, chCollisionTeamId, revivalVirtualGridX, revivalVirtualGridY int32, dst *PlayerDownsync) {
dst.Id = id
dst.VirtualGridX = virtualGridX
dst.VirtualGridY = virtualGridY
dst.DirX = dirX
dst.DirY = dirY
dst.VelX = velX
dst.VelY = velY
dst.FramesToRecover = framesToRecover
dst.FramesInChState = framesInChState
dst.ActiveSkillId = activeSkillId
dst.ActiveSkillHit = activeSkillHit
dst.FramesInvinsible = framesInvinsible
dst.Speed = speed
dst.BattleState = battleState
dst.CharacterState = characterState
dst.JoinIndex = joinIndex
dst.Hp = hp
dst.MaxHp = maxHp
dst.ColliderRadius = colliderRadius
dst.InAir = inAir
dst.OnWall = onWall
dst.OnWallNormX = onWallNormX
dst.OnWallNormY = onWallNormY
dst.CapturedByInertia = capturedByInertia
dst.BulletTeamId = bulletTeamId
dst.ChCollisionTeamId = chCollisionTeamId
dst.RevivalVirtualGridX = revivalVirtualGridX
dst.RevivalVirtualGridY = revivalVirtualGridY
}
func CloneRoomDownsyncFrame(id int32, playersArr []*PlayerDownsync, bulletLocalIdCounter int32, meleeBullets []*MeleeBullet, fireballBullets []*FireballBullet, dst *RoomDownsyncFrame) {
dst.Id = id
dst.BulletLocalIdCounter = bulletLocalIdCounter
for i := 0; i < len(playersArr); i++ {
src := playersArr[i]
if nil == src || TERMINATING_PLAYER_ID == src.Id {
break
}
ClonePlayerDownsync(src.Id, src.VirtualGridX, src.VirtualGridY, src.DirX, src.DirY, src.VelX, src.VelY, src.FramesToRecover, src.FramesInChState, src.ActiveSkillId, src.ActiveSkillHit, src.FramesInvinsible, src.Speed, src.BattleState, src.CharacterState, src.JoinIndex, src.Hp, src.MaxHp, src.ColliderRadius, src.InAir, src.OnWall, src.OnWallNormX, src.OnWallNormY, src.CapturedByInertia, src.BulletTeamId, src.ChCollisionTeamId, src.RevivalVirtualGridX, src.RevivalVirtualGridY, dst.PlayersArr[i])
}
for i := 0; i < len(meleeBullets); i++ {
src := meleeBullets[i]
if nil == src || TERMINATING_BULLET_LOCAL_ID == src.BattleAttr.BulletLocalId {
break
}
CloneMeleeBullet(src.BlState, src.FramesInBlState, src.BattleAttr.BulletLocalId, src.BattleAttr.OriginatedRenderFrameId, src.BattleAttr.OffenderJoinIndex, src.BattleAttr.TeamId, src.Bullet, dst.MeleeBullets[i])
}
for i := 0; i < len(fireballBullets); i++ {
src := fireballBullets[i]
if nil == src || TERMINATING_BULLET_LOCAL_ID == src.BattleAttr.BulletLocalId {
break
}
CloneFireballBullet(src.BlState, src.FramesInBlState, src.VirtualGridX, src.VirtualGridY, src.DirX, src.DirY, src.VelX, src.VelY, src.Speed, src.BattleAttr.BulletLocalId, src.BattleAttr.OriginatedRenderFrameId, src.BattleAttr.OffenderJoinIndex, src.BattleAttr.TeamId, src.Bullet, dst.FireballBullets[i])
}
}
func NewPreallocatedRoomDownsyncFrame(roomCapacity, preallocMeleeBulletCount int, preallocFireballBulletCount int) *RoomDownsyncFrame {
preallocatedPlayers := make([]*PlayerDownsync, roomCapacity)
for i := 0; i < roomCapacity; i++ {
preallocatedPlayer := NewPlayerDownsync(TERMINATING_PLAYER_ID, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false, false, 0, 0, false, 0, 0, 0, 0)
preallocatedPlayers[i] = preallocatedPlayer
}
preallocatedMeleeBullets := make([]*MeleeBullet, preallocMeleeBulletCount)
for i := 0; i < preallocMeleeBulletCount; i++ {
preallocatedMelee := NewMeleeBullet(TERMINATING_BULLET_LOCAL_ID, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false, 0, 0, 0, 0, 0)
preallocatedMeleeBullets[i] = preallocatedMelee
}
preallocatedFireballBullets := make([]*FireballBullet, preallocFireballBulletCount)
for i := 0; i < preallocFireballBulletCount; i++ {
preallocatedFireball := NewFireballBullet(TERMINATING_BULLET_LOCAL_ID, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
preallocatedFireballBullets[i] = preallocatedFireball
}
return &RoomDownsyncFrame{
Id: TERMINATING_RENDER_FRAME_ID,
BulletLocalIdCounter: TERMINATING_BULLET_LOCAL_ID,
PlayersArr: preallocatedPlayers,
MeleeBullets: preallocatedMeleeBullets,
FireballBullets: preallocatedFireballBullets,
}
}

View File

@@ -107,7 +107,7 @@ var Characters = map[int]*CharacterConfig{
GetUpInvinsibleFrames: int32(10),
GetUpFramesToRecover: int32(27),
Speed: int32(float64(2.19) * WORLD_TO_VIRTUAL_GRID_RATIO), // I don't know why "2.2" is so special that it throws a compile error
Speed: int32(float64(2.2) * WORLD_TO_VIRTUAL_GRID_RATIO), // I don't know why "2.2" is so special that it throws a compile error
JumpingInitVelY: int32(float64(7.5) * WORLD_TO_VIRTUAL_GRID_RATIO),
JumpingFramesToRecover: int32(2),
@@ -234,7 +234,7 @@ var skills = map[int]*Skill{
HitStunFrames: int32(13),
BlockStunFrames: int32(9),
Damage: int32(5),
SelfLockVelX: int32(float64(0.05) * WORLD_TO_VIRTUAL_GRID_RATIO),
SelfLockVelX: int32(float64(0.1) * WORLD_TO_VIRTUAL_GRID_RATIO),
SelfLockVelY: NO_LOCK_VEL,
PushbackVelX: int32(float64(0.5) * WORLD_TO_VIRTUAL_GRID_RATIO),
PushbackVelY: int32(0),
@@ -332,7 +332,7 @@ var skills = map[int]*Skill{
HitStunFrames: int32(13),
BlockStunFrames: int32(9),
Damage: int32(5),
SelfLockVelX: int32(float64(0.05) * WORLD_TO_VIRTUAL_GRID_RATIO),
SelfLockVelX: int32(float64(0.1) * WORLD_TO_VIRTUAL_GRID_RATIO),
SelfLockVelY: NO_LOCK_VEL,
PushbackVelX: int32(float64(0.5) * WORLD_TO_VIRTUAL_GRID_RATIO),
PushbackVelY: int32(0),

264
jsexport/battle/getter.go Normal file
View File

@@ -0,0 +1,264 @@
package battle
// CharacterConfig
func (c *CharacterConfig) GetSpeed() int32 {
return c.Speed
}
func (c *CharacterConfig) GetSpeciesId() int {
return c.SpeciesId
}
func (c *CharacterConfig) GetSpeciesName() string {
return c.SpeciesName
}
// InputFrameDownsync
func (ifd *InputFrameDownsync) GetInputFrameId() int32 {
return ifd.InputFrameId
}
func (ifd *InputFrameDownsync) GetInputList() []uint64 {
return ifd.InputList
}
func (ifd *InputFrameDownsync) GetConfirmedList() uint64 {
return ifd.ConfirmedList
}
// PlayerDownsync
func (p *PlayerDownsync) GetId() int32 {
return p.Id
}
func (p *PlayerDownsync) GetJoinIndex() int32 {
return p.JoinIndex
}
func (p *PlayerDownsync) GetVirtualGridX() int32 {
return p.VirtualGridX
}
func (p *PlayerDownsync) GetVirtualGridY() int32 {
return p.VirtualGridY
}
func (p *PlayerDownsync) GetDirX() int32 {
return p.DirX
}
func (p *PlayerDownsync) GetDirY() int32 {
return p.DirY
}
func (p *PlayerDownsync) GetVelX() int32 {
return p.VelX
}
func (p *PlayerDownsync) GetVelY() int32 {
return p.VelY
}
func (p *PlayerDownsync) GetSpeed() int32 {
return p.Speed
}
func (p *PlayerDownsync) GetHp() int32 {
return p.Hp
}
func (p *PlayerDownsync) GetMaxHp() int32 {
return p.MaxHp
}
func (p *PlayerDownsync) GetCharacterState() int32 {
return p.CharacterState
}
func (p *PlayerDownsync) GetFramesToRecover() int32 {
return p.FramesToRecover
}
func (p *PlayerDownsync) GetFramesInChState() int32 {
return p.FramesInChState
}
func (p *PlayerDownsync) GetInAir() bool {
return p.InAir
}
func (p *PlayerDownsync) GetOnWall() bool {
return p.OnWall
}
func (p *PlayerDownsync) GetOnWallNormX() int32 {
return p.OnWallNormX
}
func (p *PlayerDownsync) GetColliderRadius() int32 {
return p.ColliderRadius
}
// MeleeBullet
func (b *MeleeBullet) GetBlState() int32 {
return b.BlState
}
func (b *MeleeBullet) GetFramesInBlState() int32 {
return b.FramesInBlState
}
func (b *MeleeBullet) GetBulletLocalId() int32 {
return b.BattleAttr.BulletLocalId
}
func (b *MeleeBullet) GetOffenderJoinIndex() int32 {
return b.BattleAttr.OffenderJoinIndex
}
func (b *MeleeBullet) GetOriginatedRenderFrameId() int32 {
return b.BattleAttr.OriginatedRenderFrameId
}
func (b *MeleeBullet) GetStartupFrames() int32 {
return b.Bullet.StartupFrames
}
func (b *MeleeBullet) GetActiveFrames() int32 {
return b.Bullet.ActiveFrames
}
func (b *MeleeBullet) GetHitboxSizeX() int32 {
return b.Bullet.HitboxSizeX
}
func (b *MeleeBullet) GetHitboxSizeY() int32 {
return b.Bullet.HitboxSizeY
}
func (b *MeleeBullet) GetHitboxOffsetX() int32 {
return b.Bullet.HitboxOffsetX
}
func (b *MeleeBullet) GetHitboxOffsetY() int32 {
return b.Bullet.HitboxOffsetY
}
func (b *MeleeBullet) GetExplosionFrames() int32 {
return b.Bullet.ExplosionFrames
}
func (b *MeleeBullet) GetSpeciesId() int32 {
return b.Bullet.SpeciesId
}
// FireballBullet
func (p *FireballBullet) GetVirtualGridX() int32 {
return p.VirtualGridX
}
func (p *FireballBullet) GetVirtualGridY() int32 {
return p.VirtualGridY
}
func (p *FireballBullet) GetDirX() int32 {
return p.DirX
}
func (p *FireballBullet) GetDirY() int32 {
return p.DirY
}
func (p *FireballBullet) GetVelX() int32 {
return p.VelX
}
func (p *FireballBullet) GetVelY() int32 {
return p.VelY
}
func (p *FireballBullet) GetSpeed() int32 {
return p.Speed
}
func (b *FireballBullet) GetBlState() int32 {
return b.BlState
}
func (b *FireballBullet) GetFramesInBlState() int32 {
return b.FramesInBlState
}
func (b *FireballBullet) GetBulletLocalId() int32 {
return b.BattleAttr.BulletLocalId
}
func (b *FireballBullet) GetOffenderJoinIndex() int32 {
return b.BattleAttr.OffenderJoinIndex
}
func (b *FireballBullet) GetOriginatedRenderFrameId() int32 {
return b.BattleAttr.OriginatedRenderFrameId
}
func (b *FireballBullet) GetStartupFrames() int32 {
return b.Bullet.StartupFrames
}
func (b *FireballBullet) GetActiveFrames() int32 {
return b.Bullet.ActiveFrames
}
func (b *FireballBullet) GetHitboxSizeX() int32 {
return b.Bullet.HitboxSizeX
}
func (b *FireballBullet) GetHitboxSizeY() int32 {
return b.Bullet.HitboxSizeY
}
func (b *FireballBullet) GetHitboxOffsetX() int32 {
return b.Bullet.HitboxOffsetX
}
func (b *FireballBullet) GetHitboxOffsetY() int32 {
return b.Bullet.HitboxOffsetY
}
func (b *FireballBullet) GetExplosionFrames() int32 {
return b.Bullet.ExplosionFrames
}
func (b *FireballBullet) GetSpeciesId() int32 {
return b.Bullet.SpeciesId
}
// RoomDownsyncFrame
func (r *RoomDownsyncFrame) GetId() int32 {
return r.Id
}
func (r *RoomDownsyncFrame) GetCountdownNanos() int64 {
return r.CountdownNanos
}
func (r *RoomDownsyncFrame) GetBackendUnconfirmedMask() uint64 {
return r.BackendUnconfirmedMask
}
func (r *RoomDownsyncFrame) GetBulletLocalIdCounter() int32 {
return r.BulletLocalIdCounter
}
func (r *RoomDownsyncFrame) GetShouldForceResync() bool {
return r.ShouldForceResync
}
func (r *RoomDownsyncFrame) GetPlayersArr() []*PlayerDownsync {
return r.PlayersArr
}
func (r *RoomDownsyncFrame) GetMeleeBullets() []*MeleeBullet {
return r.MeleeBullets
}
func (r *RoomDownsyncFrame) GetFireballBullets() []*FireballBullet {
return r.FireballBullets
}

View File

@@ -6,8 +6,23 @@ import (
"resolv"
)
/*
[WARNING] Should avoid using "MakeFullWrapper" as much as possible, and completely remove its usage in 60fps calls like "update(dt)" on frontend!
*/
func NewDynamicRectangleColliders(cnt int) []*js.Object {
ret := make([]*js.Object, cnt)
for i := 0; i < cnt; i++ {
ret[i] = js.MakeWrapper(GenerateRectCollider(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, nil, ""))
}
return ret
}
func NewCollisionHolder() *js.Object {
return js.MakeWrapper(resolv.NewCollision())
}
func NewInputFrameDownsync(inputFrameId int32, inputList []uint64, confirmedList uint64) *js.Object {
return js.MakeFullWrapper(&InputFrameDownsync{
return js.MakeWrapper(&InputFrameDownsync{
InputFrameId: inputFrameId,
InputList: inputList,
ConfirmedList: confirmedList,
@@ -15,7 +30,7 @@ func NewInputFrameDownsync(inputFrameId int32, inputList []uint64, confirmedList
}
func NewRingBufferJs(n int32) *js.Object {
return js.MakeFullWrapper(NewRingBuffer(n))
return js.MakeWrapper(resolv.NewRingBuffer(n))
}
func NewCollisionSpaceJs(spaceW, spaceH, minStepW, minStepH int) *js.Object {
@@ -23,14 +38,14 @@ func NewCollisionSpaceJs(spaceW, spaceH, minStepW, minStepH int) *js.Object {
}
func NewVec2DJs(x, y float64) *js.Object {
return js.MakeFullWrapper(&Vec2D{
return js.MakeWrapper(&Vec2D{
X: x,
Y: y,
})
}
func NewPolygon2DJs(anchor *Vec2D, points []*Vec2D) *js.Object {
return js.MakeFullWrapper(&Polygon2D{
return js.MakeWrapper(&Polygon2D{
Anchor: anchor,
Points: points,
})
@@ -43,36 +58,7 @@ func NewBarrierJs(boundary *Polygon2D) *js.Object {
}
func NewPlayerDownsyncJs(id, virtualGridX, virtualGridY, dirX, dirY, velX, velY, framesToRecover, framesInChState, activeSkillId, activeSkillHit, framesInvinsible, speed, battleState, characterState, joinIndex, hp, maxHp, colliderRadius int32, inAir, onWall bool, onWallNormX, onWallNormY int32, capturedByInertia bool, bulletTeamId, chCollisionTeamId int32, revivalVirtualGridX, revivalVirtualGridY int32) *js.Object {
return js.MakeWrapper(&PlayerDownsync{
Id: id,
VirtualGridX: virtualGridX,
VirtualGridY: virtualGridY,
DirX: dirX,
DirY: dirY,
VelX: velX,
VelY: velY,
FramesToRecover: framesToRecover,
FramesInChState: framesInChState,
ActiveSkillId: activeSkillId,
ActiveSkillHit: activeSkillHit,
FramesInvinsible: framesInvinsible,
Speed: speed,
BattleState: battleState,
CharacterState: characterState,
JoinIndex: joinIndex,
Hp: hp,
MaxHp: maxHp,
ColliderRadius: colliderRadius,
InAir: inAir,
OnWall: onWall,
OnWallNormX: onWallNormX,
OnWallNormY: onWallNormY,
CapturedByInertia: capturedByInertia,
BulletTeamId: bulletTeamId,
ChCollisionTeamId: chCollisionTeamId,
RevivalVirtualGridX: revivalVirtualGridX,
RevivalVirtualGridY: revivalVirtualGridY,
})
return js.MakeWrapper(NewPlayerDownsync(id, virtualGridX, virtualGridY, dirX, dirY, velX, velY, framesToRecover, framesInChState, activeSkillId, activeSkillHit, framesInvinsible, speed, battleState, characterState, joinIndex, hp, maxHp, colliderRadius, inAir, onWall, onWallNormX, onWallNormY, capturedByInertia, bulletTeamId, chCollisionTeamId, revivalVirtualGridX, revivalVirtualGridY))
}
func NewMeleeBulletJs(bulletLocalId, originatedRenderFrameId, offenderJoinIndex, startupFrames, cancellableStFrame, cancellableEdFrame, activeFrames, hitStunFrames, blockStunFrames, pushbackVelX, pushbackVelY, damage, selfLockVelX, selfLockVelY, hitboxOffsetX, hitboxOffsetY, hitboxSizeX, hitboxSizeY int32, blowUp bool, teamId, blState, framesInBlState, explosionFrames, speciesId int32) *js.Object {
@@ -84,7 +70,7 @@ func NewFireballBulletJs(bulletLocalId, originatedRenderFrameId, offenderJoinInd
}
func NewNpcPatrolCue(flAct, frAct uint64, x, y float64) *js.Object {
return js.MakeFullWrapper(&NpcPatrolCue{
return js.MakeWrapper(&NpcPatrolCue{
FlAct: flAct,
FrAct: frAct,
X: x,
@@ -93,45 +79,23 @@ func NewNpcPatrolCue(flAct, frAct uint64, x, y float64) *js.Object {
}
func NewRoomDownsyncFrameJs(id int32, playersArr []*PlayerDownsync, bulletLocalIdCounter int32, meleeBullets []*MeleeBullet, fireballBullets []*FireballBullet) *js.Object {
// [WARNING] Avoid using "pb.RoomDownsyncFrame" here, in practive "MakeFullWrapper" doesn't expose the public fields for a "protobuf struct" as expected and requires helper functions like "GetCollisionSpaceObjsJs".
return js.MakeFullWrapper(&RoomDownsyncFrame{
Id: id,
PlayersArr: playersArr,
BulletLocalIdCounter: bulletLocalIdCounter,
MeleeBullets: meleeBullets,
FireballBullets: fireballBullets,
})
preallocatedRdf := NewPreallocatedRoomDownsyncFrame(len(playersArr), 64, 64)
CloneRoomDownsyncFrame(id, playersArr, bulletLocalIdCounter, meleeBullets, fireballBullets, preallocatedRdf)
return js.MakeWrapper(preallocatedRdf)
}
func GetCollisionSpaceObjsJs(space *resolv.Space) []*js.Object {
// [WARNING] We couldn't just use the existing method "space.Objects()" to access them in JavaScript, there'd a stackoverflow error
objs := space.Objects()
ret := make([]*js.Object, 0, len(objs))
for _, obj := range objs {
ret = append(ret, js.MakeFullWrapper(obj))
ret := make([]*js.Object, len(objs))
for i, obj := range objs {
ret[i] = js.MakeWrapper(obj)
}
return ret
}
func GenerateRectColliderJs(wx, wy, w, h, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) *js.Object {
/*
[WARNING] It's important to note that we don't need "js.MakeFullWrapper" for a call sequence as follows.
```
var space = gopkgs.NewCollisionSpaceJs(2048, 2048, 8, 8);
var a = gopkgs.GenerateRectColliderJs(189, 497, 48, 48, spaceOffsetX, spaceOffsetY, "Player");
space.Add(a);
```
The "space" variable doesn't need access to the field of "a" in JavaScript level to run "space.Add(...)" method, which is good.
However, the full wrapper access here is used for updating "collider.X/collider.Y" at JavaScript runtime.
*/
topPadding, bottomPadding, leftPadding, rightPadding := SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP
return js.MakeFullWrapper(GenerateRectCollider(wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, data, tag))
}
func GenerateConvexPolygonColliderJs(unalignedSrc *Polygon2D, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) *js.Object {
return js.MakeFullWrapper(GenerateConvexPolygonCollider(unalignedSrc, spaceOffsetX, spaceOffsetY, data, tag))
return js.MakeWrapper(GenerateConvexPolygonCollider(unalignedSrc, spaceOffsetX, spaceOffsetY, data, tag))
}
func GetCharacterConfigsOrderedByJoinIndex(speciesIdList []int) []*js.Object {
@@ -142,41 +106,111 @@ func GetCharacterConfigsOrderedByJoinIndex(speciesIdList []int) []*js.Object {
return ret
}
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(inputsBuffer *RingBuffer, currRenderFrame *RoomDownsyncFrame, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, collisionSpaceOffsetX, collisionSpaceOffsetY float64, chConfigsOrderedByJoinIndex []*CharacterConfig) *js.Object {
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(inputsBuffer *resolv.RingBuffer, currRenderFrameId int32, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, collisionSpaceOffsetX, collisionSpaceOffsetY float64, chConfigsOrderedByJoinIndex []*CharacterConfig, renderFrameBuffer *resolv.RingBuffer, collision *resolv.Collision, effPushbacks []*Vec2D, hardPushbackNormsArr [][]*Vec2D, jumpedOrNotList []bool, dynamicRectangleColliders []*resolv.Object) bool {
// We need access to all fields of RoomDownsyncFrame for displaying in frontend
return js.MakeFullWrapper(ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer, currRenderFrame, collisionSys, collisionSysMap, collisionSpaceOffsetX, collisionSpaceOffsetY, chConfigsOrderedByJoinIndex))
return ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer, currRenderFrameId, collisionSys, collisionSysMap, collisionSpaceOffsetX, collisionSpaceOffsetY, chConfigsOrderedByJoinIndex, renderFrameBuffer, collision, effPushbacks, hardPushbackNormsArr, jumpedOrNotList, dynamicRectangleColliders)
}
func GetRoomDownsyncFrame(renderFrameBuffer *resolv.RingBuffer, frameId int32) *js.Object {
// [WARNING] Calling "renderFrameBuffer.GetByFrameId(frameId)" directly from transpiled frontend code would automatically invoke the expensive "$externalize" and "$mapArray"! See profiling result for more details.
candidate := renderFrameBuffer.GetByFrameId(frameId)
if nil == candidate {
return nil
}
return js.MakeWrapper(candidate.(*RoomDownsyncFrame))
}
func GetInputFrameDownsync(inputsBuffer *resolv.RingBuffer, inputFrameId int32) *js.Object {
candidate := inputsBuffer.GetByFrameId(inputFrameId)
if nil == candidate {
return nil
}
return js.MakeWrapper(candidate.(*InputFrameDownsync))
}
func GetInput(ifd *InputFrameDownsync, i int) uint64 {
// [WARNING] Calling "ifd.GetInputList()" directly from transpiled frontend code would make a copy of the array.
return ifd.InputList[i]
}
func SetInputFrameId(ifd *InputFrameDownsync, newVal int32) bool {
// [WARNING] This function should be only used by frontend which is single-threaded; on the backend more rigorous thread-safety concerns are taken care of by proper locking.
ifd.InputFrameId = newVal
return true
}
func SetInput(ifd *InputFrameDownsync, i int, newVal uint64) bool {
// [WARNING] This function should be only used by frontend which is single-threaded; on the backend more rigorous thread-safety concerns are taken care of by proper locking.
if i >= len(ifd.InputList) {
return false
}
ifd.InputList[i] = newVal
return true
}
func SetConfirmedList(ifd *InputFrameDownsync, newVal uint64) bool {
// [WARNING] This function should be only used by frontend which is single-threaded; on the backend more rigorous thread-safety concerns are taken care of by proper locking.
ifd.ConfirmedList = newVal
return true
}
func GetPlayer(rdf *RoomDownsyncFrame, i int) *js.Object {
// [WARNING] Calling "rdf.GetPlayersArr()" directly from transpiled frontend code would automatically invoke the expensive "$externalize" and "$mapArray"! See profiling result for more details.
return js.MakeWrapper(rdf.PlayersArr[i])
}
func GetMeleeBullet(rdf *RoomDownsyncFrame, i int) *js.Object {
if TERMINATING_BULLET_LOCAL_ID == rdf.MeleeBullets[i].GetBulletLocalId() {
return nil
}
return js.MakeWrapper(rdf.MeleeBullets[i])
}
func GetFireballBullet(rdf *RoomDownsyncFrame, i int) *js.Object {
if TERMINATING_BULLET_LOCAL_ID == rdf.FireballBullets[i].GetBulletLocalId() {
return nil
}
return js.MakeWrapper(rdf.FireballBullets[i])
}
func main() {
js.Global.Set("gopkgs", map[string]interface{}{
"NewVec2DJs": NewVec2DJs,
"NewPolygon2DJs": NewPolygon2DJs,
"NewBarrierJs": NewBarrierJs,
"NewPlayerDownsyncJs": NewPlayerDownsyncJs,
"NewMeleeBulletJs": NewMeleeBulletJs,
"NewFireballBulletJs": NewFireballBulletJs,
"NewNpcPatrolCue": NewNpcPatrolCue,
"NewRoomDownsyncFrameJs": NewRoomDownsyncFrameJs,
"NewCollisionSpaceJs": NewCollisionSpaceJs,
"NewInputFrameDownsync": NewInputFrameDownsync,
"NewRingBufferJs": NewRingBufferJs,
"GenerateRectColliderJs": GenerateRectColliderJs,
"GenerateConvexPolygonColliderJs": GenerateConvexPolygonColliderJs,
"GetCollisionSpaceObjsJs": GetCollisionSpaceObjsJs,
"WorldToPolygonColliderBLPos": WorldToPolygonColliderBLPos, // No need to wrap primitive return types
"PolygonColliderBLToWorldPos": PolygonColliderBLToWorldPos,
"WorldToVirtualGridPos": WorldToVirtualGridPos,
"VirtualGridToWorldPos": VirtualGridToWorldPos,
"GetCharacterConfigsOrderedByJoinIndex": GetCharacterConfigsOrderedByJoinIndex,
"NewVec2DJs": NewVec2DJs,
"NewPolygon2DJs": NewPolygon2DJs,
"NewBarrierJs": NewBarrierJs,
"NewPlayerDownsyncJs": NewPlayerDownsyncJs,
"NewMeleeBulletJs": NewMeleeBulletJs,
"NewFireballBulletJs": NewFireballBulletJs,
"NewNpcPatrolCue": NewNpcPatrolCue,
"NewRoomDownsyncFrameJs": NewRoomDownsyncFrameJs,
"NewCollisionSpaceJs": NewCollisionSpaceJs,
"NewCollisionHolder": NewCollisionHolder,
"NewInputFrameDownsync": NewInputFrameDownsync,
"NewRingBufferJs": NewRingBufferJs,
"GenerateConvexPolygonColliderJs": GenerateConvexPolygonColliderJs,
"GetCollisionSpaceObjsJs": GetCollisionSpaceObjsJs,
"WorldToPolygonColliderBLPos": WorldToPolygonColliderBLPos, // No need to wrap primitive return types
"PolygonColliderBLToWorldPos": PolygonColliderBLToWorldPos,
"WorldToVirtualGridPos": WorldToVirtualGridPos,
"VirtualGridToWorldPos": VirtualGridToWorldPos,
"GetCharacterConfigsOrderedByJoinIndex": GetCharacterConfigsOrderedByJoinIndex,
"ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs": ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs,
"ConvertToDelayedInputFrameId": ConvertToDelayedInputFrameId,
"ConvertToNoDelayInputFrameId": ConvertToNoDelayInputFrameId,
"ConvertToFirstUsedRenderFrameId": ConvertToFirstUsedRenderFrameId,
"ConvertToLastUsedRenderFrameId": ConvertToLastUsedRenderFrameId,
"ShouldGenerateInputFrameUpsync": ShouldGenerateInputFrameUpsync,
"IsMeleeBulletActive": IsMeleeBulletActive,
"IsMeleeBulletAlive": IsMeleeBulletAlive,
"IsFireballBulletActive": IsFireballBulletActive,
"IsFireballBulletAlive": IsFireballBulletAlive,
"IsGeneralBulletActive": IsGeneralBulletActive,
"GetRoomDownsyncFrame": GetRoomDownsyncFrame,
"GetInputFrameDownsync": GetInputFrameDownsync,
"GetPlayer": GetPlayer,
"GetMeleeBullet": GetMeleeBullet,
"GetFireballBullet": GetFireballBullet,
"GetInput": GetInput,
"NewDynamicRectangleColliders": NewDynamicRectangleColliders,
"SetInputFrameId": SetInputFrameId,
"SetInput": SetInput,
"SetConfirmedList": SetConfirmedList,
})
}

View File

@@ -2,8 +2,8 @@ package resolv
// Cell is used to contain and organize Object information.
type Cell struct {
X, Y int // The X and Y position of the cell in the Space - note that this is in Grid position, not World position.
Objects []*Object // The Objects that a Cell contains.
X, Y int // The X and Y position of the cell in the Space - note that this is in Grid position, not World position.
Objects *RingBuffer // The Objects that a Cell contains.
}
// newCell creates a new cell at the specified X and Y position. Should not be used directly.
@@ -11,25 +11,27 @@ func newCell(x, y int) *Cell {
return &Cell{
X: x,
Y: y,
Objects: []*Object{},
Objects: NewRingBuffer(16), // A single cell is so small thus wouldn't have many touching objects simultaneously
}
}
// register registers an object with a Cell. Should not be used directly.
func (cell *Cell) register(obj *Object) {
if !cell.Contains(obj) {
cell.Objects = append(cell.Objects, obj)
cell.Objects.Put(obj)
}
}
// unregister unregisters an object from a Cell. Should not be used directly.
func (cell *Cell) unregister(obj *Object) {
for i, o := range cell.Objects {
rb := cell.Objects
for i := rb.StFrameId; i < rb.EdFrameId; i++ {
o := rb.GetByFrameId(i).(*Object)
if o == obj {
cell.Objects[i] = cell.Objects[len(cell.Objects)-1]
cell.Objects = cell.Objects[:len(cell.Objects)-1]
// swap with the st element
rb.SetByFrameId(rb.GetByFrameId(rb.StFrameId), i)
// pop the current st element
rb.Pop()
break
}
@@ -39,7 +41,9 @@ func (cell *Cell) unregister(obj *Object) {
// Contains returns whether a Cell contains the specified Object at its position.
func (cell *Cell) Contains(obj *Object) bool {
for _, o := range cell.Objects {
rb := cell.Objects
for i := rb.StFrameId; i < rb.EdFrameId; i++ {
o := rb.GetByFrameId(i).(*Object)
if o == obj {
return true
}
@@ -49,7 +53,9 @@ func (cell *Cell) Contains(obj *Object) bool {
// ContainsTags returns whether a Cell contains an Object that has the specified tag at its position.
func (cell *Cell) ContainsTags(tags ...string) bool {
for _, o := range cell.Objects {
rb := cell.Objects
for i := rb.StFrameId; i < rb.EdFrameId; i++ {
o := rb.GetByFrameId(i).(*Object)
if o.HasTags(tags...) {
return true
}
@@ -59,5 +65,5 @@ func (cell *Cell) ContainsTags(tags ...string) bool {
// Occupied returns whether a Cell contains any Objects at all.
func (cell *Cell) Occupied() bool {
return len(cell.Objects) > 0
return 0 < cell.Objects.Cnt
}

View File

@@ -3,23 +3,39 @@ package resolv
// Collision contains the results of an Object.Check() call, and represents a collision between an Object and cells that contain other Objects.
// The Objects array indicate the Objects collided with.
type Collision struct {
checkingObject *Object // The checking object
dx, dy float64 // The delta the checking object was moving on that caused this collision
Objects []*Object // Slice of objects that were collided with; sorted according to distance to calling Object.
Cells []*Cell // Slice of cells that were collided with; sorted according to distance to calling Object.
checkingObject *Object // The checking object
dx, dy float64 // The delta the checking object was moving on that caused this collision
Objects *RingBuffer // Slice of objects that were collided with; sorted according to distance to calling Object.
Cells *RingBuffer // Slice of cells that were collided with; sorted according to distance to calling Object.
}
func NewCollision() *Collision {
return &Collision{
Objects: []*Object{},
Objects: NewRingBuffer(16), // I don't expect it to exceed 10 actually
Cells: NewRingBuffer(16),
}
}
func (cc *Collision) Clear() {
cc.checkingObject = nil
cc.dx = 0
cc.dy = 0
cc.Objects.Clear()
cc.Cells.Clear()
}
func (cc *Collision) PopFirstCollidedObject() *Object {
if 0 >= cc.Objects.Cnt {
return nil
}
return cc.Objects.Pop().(*Object)
}
// HasTags returns whether any objects within the Collision have all of the specified tags. This slice does not contain the Object that called Check().
func (cc *Collision) HasTags(tags ...string) bool {
for _, o := range cc.Objects {
rb := cc.Objects
for i := rb.StFrameId; i < rb.EdFrameId; i++ {
o := rb.GetByFrameId(i).(*Object)
if o == cc.checkingObject {
continue
}
@@ -38,8 +54,9 @@ func (cc *Collision) ObjectsByTags(tags ...string) []*Object {
objects := []*Object{}
for _, o := range cc.Objects {
rb := cc.Objects
for i := rb.StFrameId; i < rb.EdFrameId; i++ {
o := rb.GetByFrameId(i).(*Object)
if o == cc.checkingObject {
continue
}
@@ -105,7 +122,7 @@ func (cc *Collision) SlideAgainstCell(cell *Cell, avoidTags ...string) Vector {
sp := cc.checkingObject.Space
collidingCell := cc.Cells[0]
collidingCell := cc.Cells.GetByFrameId(cc.Cells.StFrameId).(*Cell)
ccX, ccY := sp.SpaceToWorld(collidingCell.X, collidingCell.Y)
hX := float64(sp.CellWidth) / 2.0
hY := float64(sp.CellHeight) / 2.0

View File

@@ -2,7 +2,6 @@ package resolv
import (
"math"
//"sort"
)
// Object represents an object that can be spread across one or more Cells in a Space. An Object is essentially an AABB (Axis-Aligned Bounding Box) Rectangle.
@@ -10,21 +9,36 @@ type Object struct {
Shape Shape // A shape for more specific collision-checking.
Space *Space // Reference to the Space the Object exists within
X, Y, W, H float64 // Position and size of the Object in the Space
TouchingCells []*Cell // An array of Cells the Object is touching
TouchingCells *RingBuffer // An array of Cells the Object is touching
Data interface{} // A pointer to a user-definable object
ignoreList map[*Object]bool // Set of Objects to ignore when checking for collisions
tags []string // A list of tags the Object has
}
// NewObject returns a new Object of the specified position and size.
func NewObjectSingleTag(x, y, w, h float64, tag string) *Object {
o := &Object{
X: x,
Y: y,
W: w,
H: h,
TouchingCells: NewRingBuffer(512), // [WARNING] Should make N large enough to cover all "TouchingCells", otherwise some cells would fail to unregister an object, resulting in memory corruption and incorrect detection result!
tags: []string{tag},
ignoreList: map[*Object]bool{},
}
return o
}
func NewObject(x, y, w, h float64, tags ...string) *Object {
o := &Object{
X: x,
Y: y,
W: w,
H: h,
tags: []string{},
ignoreList: map[*Object]bool{},
X: x,
Y: y,
W: w,
H: h,
TouchingCells: NewRingBuffer(512),
tags: []string{},
ignoreList: map[*Object]bool{},
}
if len(tags) > 0 {
@@ -34,6 +48,18 @@ func NewObject(x, y, w, h float64, tags ...string) *Object {
return o
}
func (obj *Object) GetData() interface{} {
return obj.Data
}
func (obj *Object) GetShape() *Shape {
return &(obj.Shape)
}
func (obj *Object) Position() (float64, float64) {
return obj.X, obj.Y
}
// Clone clones the Object with its properties into another Object. It also clones the Object's Shape (if it has one).
func (obj *Object) Clone() *Object {
newObj := NewObject(obj.X, obj.Y, obj.W, obj.H, obj.Tags()...)
@@ -59,7 +85,7 @@ func (obj *Object) Update() {
space := obj.Space
obj.Space.Remove(obj)
obj.Space.RemoveSingle(obj)
obj.Space = space
@@ -73,7 +99,7 @@ func (obj *Object) Update() {
if c != nil {
c.register(obj)
obj.TouchingCells = append(obj.TouchingCells, c)
obj.TouchingCells.Put(c)
}
}
@@ -154,17 +180,22 @@ func (obj *Object) BoundsToSpace(dx, dy float64) (int, int, int, int) {
// SharesCells returns whether the Object occupies a cell shared by the specified other Object.
func (obj *Object) SharesCells(other *Object) bool {
for _, cell := range obj.TouchingCells {
rb := obj.TouchingCells
for i := rb.StFrameId; i < rb.EdFrameId; i++ {
cell := rb.GetByFrameId(i).(*Cell)
if cell.Contains(other) {
return true
}
}
return false
}
// SharesCellsTags returns if the Cells the Object occupies have an object with the specified tags.
func (obj *Object) SharesCellsTags(tags ...string) bool {
for _, cell := range obj.TouchingCells {
rb := obj.TouchingCells
for i := rb.StFrameId; i < rb.EdFrameId; i++ {
cell := rb.GetByFrameId(i).(*Cell)
if cell.ContainsTags(tags...) {
return true
}
@@ -218,13 +249,12 @@ func (obj *Object) SetBounds(topLeft, bottomRight Vector) {
// Check checks the space around the object using the designated delta movement (dx and dy). This is done by querying the containing Space's Cells
// so that it can see if moving it would coincide with a cell that houses another Object (filtered using the given selection of tag strings). If so,
// Check returns a Collision. If no objects are found or the Object does not exist within a Space, this function returns nil.
func (obj *Object) Check(dx, dy float64, tags ...string) *Collision {
func (obj *Object) CheckAllWithHolder(dx, dy float64, cc *Collision) bool {
if obj.Space == nil {
return nil
return false
}
cc := NewCollision()
cc.Clear()
cc.checkingObject = obj
if dx < 0 {
@@ -253,63 +283,36 @@ func (obj *Object) Check(dx, dy float64, tags ...string) *Collision {
if c := obj.Space.Cell(x, y); c != nil {
for _, o := range c.Objects {
rb := c.Objects
for i := rb.StFrameId; i < rb.EdFrameId; i++ {
o := rb.GetByFrameId(i).(*Object)
// We only want cells that have objects other than the checking object, or that aren't on the ignore list.
if ignored := obj.ignoreList[o]; o == obj || ignored {
continue
}
if _, added := objectsAdded[o]; (len(tags) == 0 || o.HasTags(tags...)) && !added {
cc.Objects = append(cc.Objects, o)
if _, added := objectsAdded[o]; !added {
cc.Objects.Put(o)
objectsAdded[o] = true
if _, added := cellsAdded[c]; !added {
cc.Cells = append(cc.Cells, c)
cc.Cells.Put(c)
cellsAdded[c] = true
}
continue
}
}
}
}
}
if len(cc.Objects) == 0 {
return nil
if 0 >= cc.Objects.Cnt {
return false
}
/*
// In my use case, order of objects within a collision instance is not needed, and this also favors both runtime performance & size reduction of `jsexport.js`.
ox, oy := cc.checkingObject.Center()
oc := Vector{ox, oy}
sort.Slice(cc.Objects, func(i, j int) bool {
ix, iy := cc.Objects[i].Center()
jx, jy := cc.Objects[j].Center()
return Vector{ix, iy}.Sub(oc).Magnitude2() < Vector{jx, jy}.Sub(oc).Magnitude2()
})
cw := cc.checkingObject.Space.CellWidth
ch := cc.checkingObject.Space.CellHeight
sort.Slice(cc.Cells, func(i, j int) bool {
return Vector{float64(cc.Cells[i].X*cw + (cw / 2)), float64(cc.Cells[i].Y*ch + (ch / 2))}.Sub(oc).Magnitude2() <
Vector{float64(cc.Cells[j].X*cw + (cw / 2)), float64(cc.Cells[j].Y*ch + (ch / 2))}.Sub(oc).Magnitude2()
})
*/
return cc
return true
}
// Overlaps returns if an Object overlaps another Object.

View File

@@ -1,4 +1,4 @@
package battle
package resolv
const (
RING_BUFF_CONSECUTIVE_SET = int32(0)
@@ -18,11 +18,26 @@ type RingBuffer struct {
func NewRingBuffer(n int32) *RingBuffer {
return &RingBuffer{
Ed: 0,
St: 0,
N: n,
Cnt: 0,
Eles: make([]interface{}, n),
Ed: 0,
St: 0,
EdFrameId: 0,
StFrameId: 0,
N: n,
Cnt: 0,
Eles: make([]interface{}, n),
}
}
func (rb *RingBuffer) DryPut() {
for 0 < rb.Cnt && rb.Cnt >= rb.N {
// Make room for the new element
rb.Pop()
}
rb.EdFrameId++
rb.Cnt++
rb.Ed++
if rb.Ed >= rb.N {
rb.Ed -= rb.N // Deliberately not using "%" operator for performance concern
}
}
@@ -122,3 +137,25 @@ func (rb *RingBuffer) SetByFrameId(pItem interface{}, frameId int32) (int32, int
return ret, oldStFrameId, oldEdFrameId
}
func (rb *RingBuffer) Clear() {
for 0 < rb.Cnt {
rb.Pop()
}
rb.St = 0
rb.Ed = 0
rb.StFrameId = 0
rb.EdFrameId = 0
}
func (rb *RingBuffer) GetStFrameId() int32 {
return rb.StFrameId
}
func (rb *RingBuffer) GetEdFrameId() int32 {
return rb.EdFrameId
}
func (rb *RingBuffer) GetCnt() int32 {
return rb.Cnt
}

View File

@@ -38,8 +38,9 @@ func (line *Line) Project(axis Vector) Vector {
}
func (line *Line) Normal() Vector {
v := line.Vector()
return Vector{v[1], -v[0]}.Unit()
dy := line.End[1] - line.Start[1]
dx := line.End[0] - line.Start[0]
return Vector{dy, -dx}.Unit()
}
func (line *Line) Vector() Vector {
@@ -124,7 +125,7 @@ func (line *Line) IntersectionPointsCircle(circle *Circle) []Vector {
}
type ConvexPolygon struct {
Points []Vector
Points *RingBuffer
X, Y float64
Closed bool
}
@@ -134,67 +135,89 @@ type ConvexPolygon struct {
// polygon square, with the vertices at {0,0}, {10,0}, {10, 10}, and {0, 10}.
func NewConvexPolygon(points ...float64) *ConvexPolygon {
// if len(points)/2 < 2 {
// return nil
// }
cp := &ConvexPolygon{Points: []Vector{}, Closed: true}
cp := &ConvexPolygon{
Points: NewRingBuffer(6), // I don't expected more points to be coped with in this particular game
Closed: true,
}
cp.AddPoints(points...)
return cp
}
func (cp *ConvexPolygon) Clone() Shape {
points := []Vector{}
for _, point := range cp.Points {
points = append(points, point.Clone())
func (cp *ConvexPolygon) GetPointByOffset(offset int32) Vector {
if cp.Points.Cnt <= offset {
return nil
}
return cp.Points.GetByFrameId(cp.Points.StFrameId + offset).(Vector)
}
func (cp *ConvexPolygon) Clone() Shape {
newPoly := NewConvexPolygon()
newPoly.X = cp.X
newPoly.Y = cp.Y
newPoly.AddPointsVec(points...)
for i := int32(0); i < cp.Points.Cnt; i++ {
newPoly.Points.Put(cp.GetPointByOffset(i))
}
newPoly.Closed = cp.Closed
return newPoly
}
// AddPointsVec allows you to add points to the ConvexPolygon with a slice of Vectors, each indicating a point / vertex.
func (cp *ConvexPolygon) AddPointsVec(points ...Vector) {
cp.Points = append(cp.Points, points...)
}
// AddPoints allows you to add points to the ConvexPolygon with a slice or selection of float64s, with each pair indicating an X or Y value for
// a point / vertex (i.e. AddPoints(0, 1, 2, 3) would add two points - one at {0, 1}, and another at {2, 3}).
func (cp *ConvexPolygon) AddPoints(vertexPositions ...float64) {
for v := 0; v < len(vertexPositions); v += 2 {
cp.Points = append(cp.Points, Vector{vertexPositions[v], vertexPositions[v+1]})
// "resolv.Vector" is an alias of "[]float64", thus already a pointer type
cp.Points.Put(Vector{vertexPositions[v], vertexPositions[v+1]})
}
}
func (cp *ConvexPolygon) UpdateAsRectangle(x, y, w, h float64) bool {
// This function might look ugly but it's a fast in-place update!
if 4 != cp.Points.Cnt {
panic("ConvexPolygon not having exactly 4 vertices to form a rectangle#1!")
}
for i := int32(0); i < cp.Points.Cnt; i++ {
thatVec := cp.GetPointByOffset(i)
if nil == thatVec {
panic("ConvexPolygon not having exactly 4 vertices to form a rectangle#2!")
}
switch i {
case 0:
thatVec[0] = x
thatVec[1] = y
case 1:
thatVec[0] = x + w
thatVec[1] = y
case 2:
thatVec[0] = x + w
thatVec[1] = y + h
case 3:
thatVec[0] = x
thatVec[1] = y + h
}
}
return true
}
// Lines returns a slice of transformed Lines composing the ConvexPolygon.
func (cp *ConvexPolygon) Lines() []*Line {
lines := []*Line{}
vertices := cp.Transformed()
linesCnt := len(vertices)
if !cp.Closed {
linesCnt -= 1
}
lines := make([]*Line, linesCnt)
for i := 0; i < len(vertices); i++ {
for i := 0; i < linesCnt; i++ {
start, end := vertices[i], vertices[0]
if i < len(vertices)-1 {
end = vertices[i+1]
} else if !cp.Closed {
break
}
line := NewLine(start[0], start[1], end[0], end[1])
lines = append(lines, line)
lines[i] = line
}
return lines
@@ -203,9 +226,10 @@ func (cp *ConvexPolygon) Lines() []*Line {
// Transformed returns the ConvexPolygon's points / vertices, transformed according to the ConvexPolygon's position.
func (cp *ConvexPolygon) Transformed() []Vector {
transformed := []Vector{}
for _, point := range cp.Points {
transformed = append(transformed, Vector{point[0] + cp.X, point[1] + cp.Y})
transformed := make([]Vector, cp.Points.Cnt)
for i := int32(0); i < cp.Points.Cnt; i++ {
point := cp.GetPointByOffset(i)
transformed[i] = Vector{point[0] + cp.X, point[1] + cp.Y}
}
return transformed
}
@@ -275,12 +299,14 @@ func (cp *ConvexPolygon) Center() Vector {
pos := Vector{0, 0}
for _, v := range cp.Transformed() {
vertices := cp.Transformed()
for _, v := range vertices {
pos.Add(v)
}
pos[0] /= float64(len(cp.Transformed()))
pos[1] /= float64(len(cp.Transformed()))
denom := float64(len(vertices))
pos[0] /= denom
pos[1] /= denom
return pos
@@ -305,10 +331,10 @@ func (cp *ConvexPolygon) Project(axis Vector) Projection {
// SATAxes returns the axes of the ConvexPolygon for SAT intersection testing.
func (cp *ConvexPolygon) SATAxes() []Vector {
axes := []Vector{}
for _, line := range cp.Lines() {
axes = append(axes, line.Normal())
lines := cp.Lines()
axes := make([]Vector, len(lines))
for i, line := range lines {
axes[i] = line.Normal()
}
return axes
@@ -550,42 +576,6 @@ func (cp *ConvexPolygon) ContainedBy(otherShape Shape) bool {
return true
}
// FlipH flips the ConvexPolygon's vertices horizontally according to their initial offset when adding the points.
func (cp *ConvexPolygon) FlipH() {
for _, v := range cp.Points {
v[0] = -v[0]
}
// We have to reverse vertex order after flipping the vertices to ensure the winding order is consistent between Objects (so that the normals are consistently outside or inside, which is important
// when doing Intersection tests). If we assume that the normal of a line, going from vertex A to vertex B, is one direction, then the normal would be inverted if the vertices were flipped in position,
// but not in order. This would make Intersection tests drive objects into each other, instead of giving the delta to move away.
cp.ReverseVertexOrder()
}
// FlipV flips the ConvexPolygon's vertices vertically according to their initial offset when adding the points.
func (cp *ConvexPolygon) FlipV() {
for _, v := range cp.Points {
v[1] = -v[1]
}
cp.ReverseVertexOrder()
}
// ReverseVertexOrder reverses the vertex ordering of the ConvexPolygon.
func (cp *ConvexPolygon) ReverseVertexOrder() {
verts := []Vector{cp.Points[0]}
for i := len(cp.Points) - 1; i >= 1; i-- {
verts = append(verts, cp.Points[i])
}
cp.Points = verts
}
// NewRectangle returns a rectangular ConvexPolygon with the vertices in clockwise order. In actuality, an AABBRectangle should be its own
// "thing" with its own optimized Intersection code check.
func NewRectangle(x, y, w, h float64) *ConvexPolygon {

View File

@@ -30,7 +30,20 @@ func NewSpace(spaceWidth, spaceHeight, cellWidth, cellHeight int) *Space {
}
// [WARNING] The slice type boxing/unboxing is proved by profiling to be heavy after transpiled to JavaScript, thus adding some "XxxSingle" shortcuts here.
// Add adds the specified Objects to the Space, updating the Space's cells to refer to the Object.
func (sp *Space) AddSingle(obj *Object) {
if sp == nil {
panic("ERROR: space is nil")
}
obj.Space = sp
// We call Update() once to make sure the object gets its cells added.
obj.Update()
}
func (sp *Space) Add(objects ...*Object) {
if sp == nil {
@@ -50,6 +63,20 @@ func (sp *Space) Add(objects ...*Object) {
// Remove removes the specified Objects from being associated with the Space. This should be done whenever an Object is removed from the
// game.
func (sp *Space) RemoveSingle(obj *Object) {
if sp == nil {
panic("ERROR: space is nil")
}
for 0 < obj.TouchingCells.Cnt {
cell := obj.TouchingCells.Pop().(*Cell)
cell.unregister(obj)
}
obj.Space = nil
}
func (sp *Space) Remove(objects ...*Object) {
if sp == nil {
@@ -57,13 +84,11 @@ func (sp *Space) Remove(objects ...*Object) {
}
for _, obj := range objects {
for _, cell := range obj.TouchingCells {
for 0 < obj.TouchingCells.Cnt {
cell := obj.TouchingCells.Pop().(*Cell)
cell.unregister(obj)
}
obj.TouchingCells = []*Cell{}
obj.Space = nil
}
@@ -80,16 +105,14 @@ func (sp *Space) Objects() []*Object {
for cy := range sp.Cells {
for cx := range sp.Cells[cy] {
for _, o := range sp.Cells[cy][cx].Objects {
rb := sp.Cells[cy][cx].Objects
for i := rb.StFrameId; i < rb.EdFrameId; i++ {
o := rb.GetByFrameId(i).(*Object)
if _, added := objectsAdded[o]; !added {
objects = append(objects, o)
objectsAdded[o] = true
}
}
}
}
@@ -100,19 +123,13 @@ func (sp *Space) Objects() []*Object {
// Resize resizes the internal Cells array.
func (sp *Space) Resize(width, height int) {
sp.Cells = [][]*Cell{}
sp.Cells = make([][]*Cell, height)
for y := 0; y < height; y++ {
sp.Cells = append(sp.Cells, []*Cell{})
sp.Cells[y] = make([]*Cell, width)
for x := 0; x < width; x++ {
sp.Cells[y] = append(sp.Cells[y], newCell(x, y))
sp.Cells[y][x] = newCell(x, y)
}
}
}
// Cell returns the Cell at the given cellular / spatial (not world) X and Y position in the Space. If the X and Y position are
@@ -137,25 +154,23 @@ func (sp *Space) CheckCells(x, y, w, h int, tags ...string) *Object {
cell := sp.Cell(ix, iy)
if cell != nil {
rb := cell.Objects
if len(tags) > 0 {
if cell.ContainsTags(tags...) {
for _, obj := range cell.Objects {
for i := rb.StFrameId; i < rb.EdFrameId; i++ {
obj := rb.GetByFrameId(i).(*Object)
if obj.HasTags(tags...) {
return obj
}
}
}
} else if cell.Occupied() {
return cell.Objects[0]
return rb.GetByFrameId(rb.StFrameId).(*Object)
}
}
}
}
return nil
@@ -178,10 +193,13 @@ func (sp *Space) CheckCellsWorld(x, y, w, h float64, tags ...string) *Object {
func (sp *Space) UnregisterAllObjects() {
for y := 0; y < len(sp.Cells); y++ {
for x := 0; x < len(sp.Cells[y]); x++ {
cell := sp.Cells[y][x]
sp.Remove(cell.Objects...)
rb := cell.Objects
for i := rb.StFrameId; i < rb.EdFrameId; i++ {
o := rb.GetByFrameId(i).(*Object)
sp.RemoveSingle(o)
}
}
}