Merge pull request #17 from genxium/multihit

Added cancellable skills.
This commit is contained in:
Wing 2023-01-05 10:23:28 +08:00 committed by GitHub
commit 938ca7e57d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
86 changed files with 11458 additions and 2564 deletions

7
MULTIHIT_ROADMAP.md Normal file
View File

@ -0,0 +1,7 @@
Major goals
- Create several skills that can be chained by pressing "btnA" 3 times, while the 2nd skill cancels the last hit of 1st skill before the latters' FramesToRecover is over, same goes with 2nd->3rd transition.
- Note that each skill can contain "multihit".
Minor goals
- Split jumping anim into "once" part and "keep" part, depending on "character.framesElapsedInChState" we should play the corresponding part
- Add new "chState = STUNNED", which is applicable to both on ground and in air, while "inAir && STUNNED", "character.FramesToRecover" is regarded as infinite. If implemented, make the last hit of the aforementioned 3rd skill "pushback opponent to air".

View File

@ -10,69 +10,71 @@ func toPbRoomDownsyncFrame(rdf *battle.RoomDownsyncFrame) *pb.RoomDownsyncFrame
return nil return nil
} }
ret := &pb.RoomDownsyncFrame{ ret := &pb.RoomDownsyncFrame{
Id: rdf.Id, Id: rdf.Id,
PlayersArr: make([]*pb.PlayerDownsync, len(rdf.PlayersArr), len(rdf.PlayersArr)), PlayersArr: make([]*pb.PlayerDownsync, len(rdf.PlayersArr), len(rdf.PlayersArr)),
MeleeBullets: make([]*pb.MeleeBullet, len(rdf.MeleeBullets), len(rdf.MeleeBullets)), MeleeBullets: make([]*pb.MeleeBullet, len(rdf.MeleeBullets), len(rdf.MeleeBullets)),
CountdownNanos: rdf.CountdownNanos, CountdownNanos: rdf.CountdownNanos,
BackendUnconfirmedMask: rdf.BackendUnconfirmedMask, BackendUnconfirmedMask: rdf.BackendUnconfirmedMask,
ShouldForceResync: rdf.ShouldForceResync, ShouldForceResync: rdf.ShouldForceResync,
PlayerOpPatternToSkillId: make(map[int32]int32),
} }
for i, last := range rdf.PlayersArr { for i, last := range rdf.PlayersArr {
pbPlayer := &pb.PlayerDownsync{ pbPlayer := &pb.PlayerDownsync{
Id: last.Id, Id: last.Id,
VirtualGridX: last.VirtualGridX, VirtualGridX: last.VirtualGridX,
VirtualGridY: last.VirtualGridY, VirtualGridY: last.VirtualGridY,
DirX: last.DirX, DirX: last.DirX,
DirY: last.DirY, DirY: last.DirY,
VelX: last.VelX, VelX: last.VelX,
VelY: last.VelY, VelY: last.VelY,
Speed: last.Speed, FramesToRecover: last.FramesToRecover,
BattleState: last.BattleState, FramesInChState: last.FramesInChState,
CharacterState: last.CharacterState, ActiveSkillId: last.ActiveSkillId,
InAir: last.InAir, ActiveSkillHit: last.ActiveSkillHit,
JoinIndex: last.JoinIndex, FramesInvinsible: last.FramesInvinsible,
ColliderRadius: last.ColliderRadius, Speed: last.Speed,
Score: last.Score, BattleState: last.BattleState,
FramesToRecover: last.FramesToRecover, CharacterState: last.CharacterState,
Hp: last.Hp, InAir: last.InAir,
MaxHp: last.MaxHp, JoinIndex: last.JoinIndex,
Removed: last.Removed, Hp: last.Hp,
MaxHp: last.MaxHp,
ColliderRadius: last.ColliderRadius,
Score: last.Score,
Removed: last.Removed,
} }
ret.PlayersArr[i] = pbPlayer ret.PlayersArr[i] = pbPlayer
} }
for i, last := range rdf.MeleeBullets { for i, last := range rdf.MeleeBullets {
pbBullet := &pb.MeleeBullet{ pbBullet := &pb.MeleeBullet{
BattleLocalId: last.BattleLocalId, OriginatedRenderFrameId: last.OriginatedRenderFrameId,
StartupFrames: last.StartupFrames, OffenderJoinIndex: last.OffenderJoinIndex,
ActiveFrames: last.ActiveFrames,
RecoveryFrames: last.RecoveryFrames,
RecoveryFramesOnBlock: last.RecoveryFramesOnBlock,
RecoveryFramesOnHit: last.RecoveryFramesOnHit,
HitboxOffset: last.HitboxOffset,
HitStunFrames: last.HitStunFrames,
BlockStunFrames: last.BlockStunFrames,
Pushback: last.Pushback,
ReleaseTriggerType: last.ReleaseTriggerType,
Damage: last.Damage,
SelfMoveforwardX: last.SelfMoveforwardX, StartupFrames: last.StartupFrames,
SelfMoveforwardY: last.SelfMoveforwardY, CancellableStFrame: last.CancellableStFrame,
HitboxSizeX: last.HitboxSizeX, CancellableEdFrame: last.CancellableEdFrame,
HitboxSizeY: last.HitboxSizeY, ActiveFrames: last.ActiveFrames,
OffenderJoinIndex: last.OffenderJoinIndex, HitStunFrames: last.HitStunFrames,
OffenderPlayerId: last.OffenderPlayerId, BlockStunFrames: last.BlockStunFrames,
PushbackVelX: last.PushbackVelX,
PushbackVelY: last.PushbackVelY,
Damage: last.Damage,
SelfLockVelX: last.SelfLockVelX,
SelfLockVelY: last.SelfLockVelY,
HitboxOffsetX: last.HitboxOffsetX,
HitboxOffsetY: last.HitboxOffsetY,
HitboxSizeX: last.HitboxSizeX,
HitboxSizeY: last.HitboxSizeY,
BlowUp: last.BlowUp,
} }
ret.MeleeBullets[i] = pbBullet ret.MeleeBullets[i] = pbBullet
} }
for i, last := range rdf.PlayerOpPatternToSkillId {
ret.PlayerOpPatternToSkillId[int32(i)] = int32(last)
}
return ret return ret
} }
@ -84,22 +86,26 @@ func toPbPlayers(modelInstances map[int32]*Player, withMetaInfo bool) []*pb.Play
for _, last := range modelInstances { for _, last := range modelInstances {
pbPlayer := &pb.PlayerDownsync{ pbPlayer := &pb.PlayerDownsync{
Id: last.Id, Id: last.Id,
VirtualGridX: last.VirtualGridX, VirtualGridX: last.VirtualGridX,
VirtualGridY: last.VirtualGridY, VirtualGridY: last.VirtualGridY,
DirX: last.DirX, DirX: last.DirX,
DirY: last.DirY, DirY: last.DirY,
VelX: last.VelX, VelX: last.VelX,
VelY: last.VelY, VelY: last.VelY,
Speed: last.Speed, FramesToRecover: last.FramesToRecover,
BattleState: last.BattleState, FramesInChState: last.FramesInChState,
CharacterState: last.CharacterState, ActiveSkillId: last.ActiveSkillId,
InAir: last.InAir, ActiveSkillHit: last.ActiveSkillHit,
JoinIndex: last.JoinIndex, FramesInvinsible: last.FramesInvinsible,
ColliderRadius: last.ColliderRadius, Speed: last.Speed,
Score: last.Score, BattleState: last.BattleState,
Removed: last.Removed, CharacterState: last.CharacterState,
FramesToRecover: last.FramesToRecover, InAir: last.InAir,
JoinIndex: last.JoinIndex,
ColliderRadius: last.ColliderRadius,
Score: last.Score,
Removed: last.Removed,
} }
if withMetaInfo { if withMetaInfo {
pbPlayer.Name = last.Name pbPlayer.Name = last.Name
@ -120,21 +126,28 @@ func toJsPlayers(modelInstances map[int32]*Player) []*battle.PlayerDownsync {
for _, last := range modelInstances { for _, last := range modelInstances {
toRet[last.JoinIndex-1] = &battle.PlayerDownsync{ toRet[last.JoinIndex-1] = &battle.PlayerDownsync{
Id: last.Id, Id: last.Id,
VirtualGridX: last.VirtualGridX, VirtualGridX: last.VirtualGridX,
VirtualGridY: last.VirtualGridY, VirtualGridY: last.VirtualGridY,
DirX: last.DirX, DirX: last.DirX,
DirY: last.DirY, DirY: last.DirY,
VelX: last.VelX, VelX: last.VelX,
VelY: last.VelY, VelY: last.VelY,
Speed: last.Speed, FramesToRecover: last.FramesToRecover,
BattleState: last.BattleState, FramesInChState: last.FramesInChState,
CharacterState: last.CharacterState, ActiveSkillId: last.ActiveSkillId,
InAir: last.InAir, ActiveSkillHit: last.ActiveSkillHit,
JoinIndex: last.JoinIndex, FramesInvinsible: last.FramesInvinsible,
ColliderRadius: last.ColliderRadius, Speed: last.Speed,
Score: last.Score, BattleState: last.BattleState,
Removed: last.Removed, CharacterState: last.CharacterState,
JoinIndex: last.JoinIndex,
Hp: last.Hp,
MaxHp: last.MaxHp,
ColliderRadius: last.ColliderRadius,
InAir: last.InAir,
Score: last.Score,
Removed: last.Removed,
} }
} }

View File

@ -49,7 +49,7 @@ const (
) )
const ( const (
DEFAULT_PLAYER_RADIUS = float64(12) DEFAULT_PLAYER_RADIUS = int32(float64(12) * battle.WORLD_TO_VIRTUAL_GRID_RATIO)
) )
type RoomBattleState struct { type RoomBattleState struct {
@ -88,15 +88,16 @@ func calRoomScore(inRoomPlayerCount int32, roomPlayerCnt int, currentRoomBattleS
} }
type Room struct { type Room struct {
Id int32 Id int32
Capacity int Capacity int
collisionSpaceOffsetX float64 BattleDurationFrames int32
collisionSpaceOffsetY float64 NstDelayFrames int32
playerOpPatternToSkillId map[int]int Players map[int32]*Player
Players map[int32]*Player PlayersArr []*Player // ordered by joinIndex
PlayersArr []*Player // ordered by joinIndex SpeciesIdList []int32 // ordered by joinIndex
Space *resolv.Space CharacterConfigsArr []*battle.CharacterConfig // ordered by joinIndex
CollisionSysMap map[int32]*resolv.Object Space *resolv.Space
CollisionSysMap map[int32]*resolv.Object
/** /**
* The following `PlayerDownsyncSessionDict` is NOT individually put * The following `PlayerDownsyncSessionDict` is NOT individually put
* under `type Player struct` for a reason. * under `type Player struct` for a reason.
@ -138,7 +139,6 @@ type Room struct {
BackendDynamicsEnabled bool BackendDynamicsEnabled bool
ForceAllResyncOnAnyActiveSlowTicker bool ForceAllResyncOnAnyActiveSlowTicker bool
LastRenderFrameIdTriggeredAt int64 LastRenderFrameIdTriggeredAt int64
PlayerDefaultSpeed int32
BulletBattleLocalIdCounter int32 BulletBattleLocalIdCounter int32
dilutedRollbackEstimatedDtNanos int64 dilutedRollbackEstimatedDtNanos int64
@ -168,11 +168,12 @@ func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, session *websocke
} }
defer pR.onPlayerAdded(playerId) defer pR.onPlayerAdded(playerId)
pPlayerFromDbInit.AckingFrameId = -1 pPlayerFromDbInit.AckingFrameId = -1
pPlayerFromDbInit.AckingInputFrameId = -1 pPlayerFromDbInit.AckingInputFrameId = -1
pPlayerFromDbInit.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED pPlayerFromDbInit.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED
pPlayerFromDbInit.BattleState = PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK pPlayerFromDbInit.BattleState = PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK
pPlayerFromDbInit.Speed = pR.PlayerDefaultSpeed // Hardcoded
pPlayerFromDbInit.ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded pPlayerFromDbInit.ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded
pPlayerFromDbInit.InAir = true // Hardcoded pPlayerFromDbInit.InAir = true // Hardcoded
@ -210,7 +211,7 @@ func (pR *Room) ReAddPlayerIfPossible(pTmpPlayerInstance *Player, session *webso
pEffectiveInRoomPlayerInstance.AckingInputFrameId = -1 pEffectiveInRoomPlayerInstance.AckingInputFrameId = -1
pEffectiveInRoomPlayerInstance.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED pEffectiveInRoomPlayerInstance.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED
pEffectiveInRoomPlayerInstance.BattleState = PlayerBattleStateIns.READDED_PENDING_BATTLE_COLLIDER_ACK pEffectiveInRoomPlayerInstance.BattleState = PlayerBattleStateIns.READDED_PENDING_BATTLE_COLLIDER_ACK
pEffectiveInRoomPlayerInstance.Speed = pR.PlayerDefaultSpeed // Hardcoded
pEffectiveInRoomPlayerInstance.ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded pEffectiveInRoomPlayerInstance.ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded
pEffectiveInRoomPlayerInstance.InAir = true // Hardcoded pEffectiveInRoomPlayerInstance.InAir = true // Hardcoded
@ -289,35 +290,14 @@ func (pR *Room) ChooseStage() error {
//Logger.Info("parsed tmx:", zap.Any("stageDiscreteW", stageDiscreteW), zap.Any("strToVec2DListMap", strToVec2DListMap), zap.Any("strToPolygon2DListMap", strToPolygon2DListMap)) //Logger.Info("parsed tmx:", zap.Any("stageDiscreteW", stageDiscreteW), zap.Any("strToVec2DListMap", strToVec2DListMap), zap.Any("strToPolygon2DListMap", strToPolygon2DListMap))
pR.StageDiscreteW = stageDiscreteW pR.SpaceOffsetX = float64((stageDiscreteW * stageTileW) >> 1)
pR.StageDiscreteH = stageDiscreteH pR.SpaceOffsetY = float64((stageDiscreteH * stageTileH) >> 1)
pR.StageTileW = stageTileW
pR.StageTileH = stageTileH
pR.TmxPointsMap = strToVec2DListMap pR.TmxPointsMap = strToVec2DListMap
pR.TmxPolygonsMap = strToPolygon2DListMap pR.TmxPolygonsMap = strToPolygon2DListMap
return nil return nil
} }
func (pR *Room) ConvertToInputFrameId(renderFrameId int32, inputDelayFrames int32) int32 {
if renderFrameId < inputDelayFrames {
return 0
}
return ((renderFrameId - inputDelayFrames) >> pR.InputScaleFrames)
}
func (pR *Room) ConvertToGeneratingRenderFrameId(inputFrameId int32) int32 {
return (inputFrameId << pR.InputScaleFrames)
}
func (pR *Room) ConvertToFirstUsedRenderFrameId(inputFrameId int32, inputDelayFrames int32) int32 {
return ((inputFrameId << pR.InputScaleFrames) + inputDelayFrames)
}
func (pR *Room) ConvertToLastUsedRenderFrameId(inputFrameId int32, inputDelayFrames int32) int32 {
return ((inputFrameId << pR.InputScaleFrames) + inputDelayFrames + (1 << pR.InputScaleFrames) - 1)
}
func (pR *Room) RenderFrameBufferString() string { func (pR *Room) RenderFrameBufferString() string {
return fmt.Sprintf("{renderFrameId: %d, stRenderFrameId: %d, edRenderFrameId: %d, curDynamicsRenderFrameId: %d}", pR.RenderFrameId, pR.RenderFrameBuffer.StFrameId, pR.RenderFrameBuffer.EdFrameId, pR.CurDynamicsRenderFrameId) return fmt.Sprintf("{renderFrameId: %d, stRenderFrameId: %d, edRenderFrameId: %d, curDynamicsRenderFrameId: %d}", pR.RenderFrameId, pR.RenderFrameBuffer.StFrameId, pR.RenderFrameBuffer.EdFrameId, pR.CurDynamicsRenderFrameId)
} }
@ -353,7 +333,7 @@ func (pR *Room) playerDownsyncStr(player *battle.PlayerDownsync) string {
if player.InAir { if player.InAir {
inAirInt = 1 inAirInt = 1
} }
s := fmt.Sprintf("{%d,%d,%d,%d,%d,%d,%d}", player.JoinIndex, player.VirtualGridX, player.VirtualGridY, player.VelX, player.VelY, player.FramesToRecover,inAirInt) s := fmt.Sprintf("{%d,%d,%d,%d,%d,%d,%d}", player.JoinIndex, player.VirtualGridX, player.VirtualGridY, player.VelX, player.VelY, player.FramesToRecover, inAirInt)
return s return s
} }
@ -397,28 +377,25 @@ func (pR *Room) StartBattle() {
pR.RenderFrameId = 0 pR.RenderFrameId = 0
// [WARNING] Only since battle starts do we have all players bound to certain joinIndexes.
for _, player := range pR.Players { for _, player := range pR.Players {
opJoinIndexPrefix := (int(player.JoinIndex) << uint(8)) speciesId := int(player.JoinIndex - 1) // FIXME: Hardcoded the values for now
pR.playerOpPatternToSkillId[opJoinIndexPrefix+0] = 1 // Hardcoded for now chosenCh := battle.Characters[speciesId]
pR.CharacterConfigsArr[player.JoinIndex-1] = chosenCh
pR.SpeciesIdList[player.JoinIndex-1] = int32(speciesId)
} }
Logger.Info("[StartBattle] ", zap.Any("roomId", pR.Id), zap.Any("roomState", pR.State), zap.Any("SpeciesIdList", pR.SpeciesIdList))
// Initialize the "collisionSys" as well as "RenderFrameBuffer" // Initialize the "collisionSys" as well as "RenderFrameBuffer"
pR.CurDynamicsRenderFrameId = 0 pR.CurDynamicsRenderFrameId = 0
kickoffFrameJs := &battle.RoomDownsyncFrame{ kickoffFrameJs := &battle.RoomDownsyncFrame{
Id: pR.RenderFrameId, Id: pR.RenderFrameId,
PlayersArr: toJsPlayers(pR.Players), PlayersArr: toJsPlayers(pR.Players),
PlayerOpPatternToSkillId: pR.playerOpPatternToSkillId, CountdownNanos: pR.BattleDurationNanos,
CountdownNanos: pR.BattleDurationNanos,
} }
pR.RenderFrameBuffer.Put(kickoffFrameJs) pR.RenderFrameBuffer.Put(kickoffFrameJs)
// Refresh "Colliders" // Refresh "Colliders"
spaceW := pR.StageDiscreteW * pR.StageTileW pR.refreshColliders()
spaceH := pR.StageDiscreteH * pR.StageTileH
pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY = float64(spaceW)*0.5, float64(spaceH)*0.5
pR.refreshColliders(spaceW, spaceH)
/* /*
Will be triggered from a goroutine which executes the critical `Room.AddPlayerIfPossible`, thus the `battleMainLoop` should be detached. Will be triggered from a goroutine which executes the critical `Room.AddPlayerIfPossible`, thus the `battleMainLoop` should be detached.
@ -432,7 +409,7 @@ func (pR *Room) StartBattle() {
Logger.Error("battleMainLoop, recovery spot#1, recovered from: ", zap.Any("roomId", pR.Id), zap.Any("panic", r)) Logger.Error("battleMainLoop, recovery spot#1, recovered from: ", zap.Any("roomId", pR.Id), zap.Any("panic", r))
} }
pR.StopBattleForSettlement() pR.StopBattleForSettlement()
Logger.Info(fmt.Sprintf("The `battleMainLoop` for roomId=%v is stopped@renderFrameId=%v, with battleDurationFrames=%v:\n%v", pR.Id, pR.RenderFrameId, pR.BattleDurationFrames, pR.InputsBufferString(false))) // This takes sometime to print Logger.Info(fmt.Sprintf("The `battleMainLoop` for roomId=%v is stopped@renderFrameId=%v:\n%v", pR.Id, pR.RenderFrameId, pR.InputsBufferString(false))) // This takes sometime to print
if pR.FrameDataLoggingEnabled { if pR.FrameDataLoggingEnabled {
rdfIdToActuallyUsedInputDump := pR.rdfIdToActuallyUsedInputString() rdfIdToActuallyUsedInputDump := pR.rdfIdToActuallyUsedInputString()
os.WriteFile(fmt.Sprintf("room_%d.txt", pR.Id), []byte(rdfIdToActuallyUsedInputDump), 0644) // DEBUG ONLY os.WriteFile(fmt.Sprintf("room_%d.txt", pR.Id), []byte(rdfIdToActuallyUsedInputDump), 0644) // DEBUG ONLY
@ -481,7 +458,9 @@ func (pR *Room) StartBattle() {
continue continue
} }
kickoffFrameJs := pR.RenderFrameBuffer.GetByFrameId(0).(*battle.RoomDownsyncFrame) kickoffFrameJs := pR.RenderFrameBuffer.GetByFrameId(0).(*battle.RoomDownsyncFrame)
pR.sendSafely(toPbRoomDownsyncFrame(kickoffFrameJs), nil, DOWNSYNC_MSG_ACT_BATTLE_START, playerId, true) pbKickOffRenderFrame := toPbRoomDownsyncFrame(kickoffFrameJs)
pbKickOffRenderFrame.SpeciesIdList = pR.SpeciesIdList
pR.sendSafely(pbKickOffRenderFrame, nil, DOWNSYNC_MSG_ACT_BATTLE_START, playerId, true)
} }
Logger.Info(fmt.Sprintf("In `battleMainLoop` for roomId=%v sent out kickoffFrame", pR.Id)) Logger.Info(fmt.Sprintf("In `battleMainLoop` for roomId=%v sent out kickoffFrame", pR.Id))
} }
@ -550,10 +529,6 @@ func (pR *Room) StartBattle() {
}) })
} }
func (pR *Room) toDiscreteInputsBufferIndex(inputFrameId int32, joinIndex int32) int32 {
return (inputFrameId << 2) + joinIndex // allowing joinIndex upto 15
}
func (pR *Room) OnBattleCmdReceived(pReq *pb.WsReq) { func (pR *Room) OnBattleCmdReceived(pReq *pb.WsReq) {
/* /*
[WARNING] This function "OnBattleCmdReceived" could be called by different ws sessions and thus from different threads! [WARNING] This function "OnBattleCmdReceived" could be called by different ws sessions and thus from different threads!
@ -744,14 +719,11 @@ 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. // 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.BulletBattleLocalIdCounter = 0
pR.WorldToVirtualGridRatio = float64(100) pR.CollisionMinStep = 8 // the approx minimum distance a player can move per frame in world coordinate
pR.VirtualGridToWorldRatio = float64(1.0) / pR.WorldToVirtualGridRatio // this is a one-off computation, should avoid division in iterations
pR.SpAtkLookupFrames = 5
pR.PlayerDefaultSpeed = int32(float64(1) * pR.WorldToVirtualGridRatio) // in virtual grids per frame
pR.CollisionMinStep = (int32(float64(pR.PlayerDefaultSpeed)*pR.VirtualGridToWorldRatio) << 3) // the approx minimum distance a player can move per frame in world coordinate
pR.playerOpPatternToSkillId = make(map[int]int)
pR.Players = make(map[int32]*Player) pR.Players = make(map[int32]*Player)
pR.PlayersArr = make([]*Player, pR.Capacity) pR.PlayersArr = make([]*Player, pR.Capacity)
pR.SpeciesIdList = make([]int32, pR.Capacity)
pR.CharacterConfigsArr = make([]*battle.CharacterConfig, pR.Capacity)
pR.CollisionSysMap = make(map[int32]*resolv.Object) pR.CollisionSysMap = make(map[int32]*resolv.Object)
pR.PlayerDownsyncSessionDict = make(map[int32]*websocket.Conn) pR.PlayerDownsyncSessionDict = make(map[int32]*websocket.Conn)
for _, oldWatchdog := range pR.PlayerActiveWatchdogDict { for _, oldWatchdog := range pR.PlayerActiveWatchdogDict {
@ -776,26 +748,20 @@ func (pR *Room) OnDismissed() {
pR.RenderFrameId = 0 pR.RenderFrameId = 0
pR.CurDynamicsRenderFrameId = 0 pR.CurDynamicsRenderFrameId = 0
pR.InputDelayFrames = 8
pR.NstDelayFrames = 16 pR.NstDelayFrames = 16
pR.InputScaleFrames = uint32(2)
pR.ServerFps = 60 serverFps := 60
pR.RollbackEstimatedDtMillis = 16.667 // Use fixed-and-low-precision to mitigate the inconsistent floating-point-number issue between Golang and JavaScript 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 pR.RollbackEstimatedDtNanos = 16666666 // A little smaller than the actual per frame time, just for logging FAST FRAME
dilutedServerFps := float64(58.0) // Don't set this value too small, otherwise we might miss force confirmation needs for slow tickers! dilutedServerFps := float64(58.0) // Don't set this value too small, otherwise we might miss force confirmation needs for slow tickers!
pR.dilutedRollbackEstimatedDtNanos = int64(float64(pR.RollbackEstimatedDtNanos) * float64(pR.ServerFps) / dilutedServerFps) pR.dilutedRollbackEstimatedDtNanos = int64(float64(pR.RollbackEstimatedDtNanos) * float64(serverFps) / dilutedServerFps)
pR.BattleDurationFrames = 60 * pR.ServerFps pR.BattleDurationFrames = int32(60 * serverFps)
pR.BattleDurationNanos = int64(pR.BattleDurationFrames) * (pR.RollbackEstimatedDtNanos + 1) pR.BattleDurationNanos = int64(pR.BattleDurationFrames) * (pR.RollbackEstimatedDtNanos + 1)
pR.InputFrameUpsyncDelayTolerance = (pR.NstDelayFrames >> pR.InputScaleFrames) - 1 // this value should be strictly smaller than (NstDelayFrames >> InputScaleFrames), otherwise "type#1 forceConfirmation" might become a lag avalanche pR.InputFrameUpsyncDelayTolerance = battle.ConvertToNoDelayInputFrameId(pR.NstDelayFrames) - 1 // this value should be strictly smaller than (NstDelayFrames >> InputScaleFrames), otherwise "type#1 forceConfirmation" might become a lag avalanche
pR.MaxChasingRenderFramesPerUpdate = 12 // Don't set this value too high to avoid exhausting frontend CPU within a single frame pR.MaxChasingRenderFramesPerUpdate = 12 // Don't set this value too high to avoid exhausting frontend CPU within a single frame
pR.BackendDynamicsEnabled = true // [WARNING] When "false", recovery upon reconnection wouldn't work! pR.BackendDynamicsEnabled = true // [WARNING] When "false", recovery upon reconnection wouldn't work!
pR.ForceAllResyncOnAnyActiveSlowTicker = true // See tradeoff discussion in "downsyncToAllPlayers" pR.ForceAllResyncOnAnyActiveSlowTicker = true // See tradeoff discussion in "downsyncToAllPlayers"
pR.SnapIntoPlatformOverlap = float64(0.1)
pR.SnapIntoPlatformThreshold = float64(0.5)
pR.JumpingInitVelY = int32(float64(7) * pR.WorldToVirtualGridRatio)
pR.GravityX = 0
pR.GravityY = -int32(float64(0.5) * pR.WorldToVirtualGridRatio) // makes all "playerCollider.Y" a multiple of 0.5 in all cases
pR.FrameDataLoggingEnabled = false // [WARNING] DON'T ENABLE ON LONG BATTLE DURATION! It consumes A LOT OF MEMORY! pR.FrameDataLoggingEnabled = false // [WARNING] DON'T ENABLE ON LONG BATTLE DURATION! It consumes A LOT OF MEMORY!
@ -914,6 +880,10 @@ func (pR *Room) onPlayerAdded(playerId int32) {
pR.Players[playerId].JoinIndex = int32(index) + 1 pR.Players[playerId].JoinIndex = int32(index) + 1
pR.JoinIndexBooleanArr[index] = true pR.JoinIndexBooleanArr[index] = true
speciesId := index // FIXME
chosenCh := battle.Characters[speciesId]
pR.Players[playerId].Speed = chosenCh.Speed
// Lazily assign the initial position of "Player" for "RoomDownsyncFrame". // Lazily assign the initial position of "Player" for "RoomDownsyncFrame".
playerPosList := *pR.TmxPointsMap["PlayerStartingPos"] playerPosList := *pR.TmxPointsMap["PlayerStartingPos"]
if index > len(playerPosList) { if index > len(playerPosList) {
@ -924,7 +894,7 @@ func (pR *Room) onPlayerAdded(playerId int32) {
if nil == playerPos { if nil == playerPos {
panic(fmt.Sprintf("onPlayerAdded error, nil == playerPos, roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v", pR.Id, playerId, pR.State, pR.EffectivePlayerCount)) panic(fmt.Sprintf("onPlayerAdded error, nil == playerPos, roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v", pR.Id, playerId, pR.State, pR.EffectivePlayerCount))
} }
pR.Players[playerId].VirtualGridX, pR.Players[playerId].VirtualGridY = battle.WorldToVirtualGridPos(playerPos.X, playerPos.Y, pR.WorldToVirtualGridRatio) pR.Players[playerId].VirtualGridX, pR.Players[playerId].VirtualGridY = battle.WorldToVirtualGridPos(playerPos.X, playerPos.Y)
// Hardcoded initial character orientation/facing // Hardcoded initial character orientation/facing
if 0 == (pR.Players[playerId].JoinIndex % 2) { if 0 == (pR.Players[playerId].JoinIndex % 2) {
pR.Players[playerId].DirX = -2 pR.Players[playerId].DirX = -2
@ -1043,15 +1013,6 @@ func (pR *Room) sendSafely(roomDownsyncFrame *pb.RoomDownsyncFrame, toSendInputF
} }
} }
func (pR *Room) shouldPrefabInputFrameDownsync(prevRenderFrameId int32, renderFrameId int32) (bool, int32) {
for i := prevRenderFrameId + 1; i <= renderFrameId; i++ {
if (0 <= i) && (0 == (i & ((1 << pR.InputScaleFrames) - 1))) {
return true, i
}
}
return false, -1
}
func (pR *Room) getOrPrefabInputFrameDownsync(inputFrameId int32) *battle.InputFrameDownsync { func (pR *Room) getOrPrefabInputFrameDownsync(inputFrameId int32) *battle.InputFrameDownsync {
/* /*
[WARNING] This function MUST BE called while "pR.InputsBufferLock" is locked. [WARNING] This function MUST BE called while "pR.InputsBufferLock" is locked.
@ -1170,7 +1131,7 @@ func (pR *Room) markConfirmationIfApplicable(inputFrameUpsyncBatch []*pb.InputFr
*/ */
snapshotStFrameId := (pR.LastAllConfirmedInputFrameId - newAllConfirmedCount) snapshotStFrameId := (pR.LastAllConfirmedInputFrameId - newAllConfirmedCount)
refRenderFrameIdIfNeeded := pR.CurDynamicsRenderFrameId - 1 refRenderFrameIdIfNeeded := pR.CurDynamicsRenderFrameId - 1
refSnapshotStFrameId := pR.ConvertToInputFrameId(refRenderFrameIdIfNeeded, pR.InputDelayFrames) refSnapshotStFrameId := battle.ConvertToDelayedInputFrameId(refRenderFrameIdIfNeeded)
if refSnapshotStFrameId < snapshotStFrameId { if refSnapshotStFrameId < snapshotStFrameId {
snapshotStFrameId = refSnapshotStFrameId snapshotStFrameId = refSnapshotStFrameId
} }
@ -1186,7 +1147,7 @@ func (pR *Room) forceConfirmationIfApplicable(prevRenderFrameId int32) uint64 {
totPlayerCnt := uint32(pR.Capacity) totPlayerCnt := uint32(pR.Capacity)
allConfirmedMask := uint64((1 << totPlayerCnt) - 1) allConfirmedMask := uint64((1 << totPlayerCnt) - 1)
unconfirmedMask := uint64(0) unconfirmedMask := uint64(0)
if pR.LatestPlayerUpsyncedInputFrameId > (pR.LastAllConfirmedInputFrameId + (pR.NstDelayFrames >> pR.InputScaleFrames)) { if pR.LatestPlayerUpsyncedInputFrameId > (pR.LastAllConfirmedInputFrameId + pR.InputFrameUpsyncDelayTolerance + 1) {
// Type#1 check whether there's a significantly slow ticker among players // Type#1 check whether there's a significantly slow ticker among players
oldLastAllConfirmedInputFrameId := pR.LastAllConfirmedInputFrameId oldLastAllConfirmedInputFrameId := pR.LastAllConfirmedInputFrameId
for j := pR.LastAllConfirmedInputFrameId + 1; j <= pR.LatestPlayerUpsyncedInputFrameId; j++ { for j := pR.LastAllConfirmedInputFrameId + 1; j <= pR.LatestPlayerUpsyncedInputFrameId; j++ {
@ -1200,7 +1161,7 @@ func (pR *Room) forceConfirmationIfApplicable(prevRenderFrameId int32) uint64 {
pR.onInputFrameDownsyncAllConfirmed(inputFrameDownsync, -1) pR.onInputFrameDownsyncAllConfirmed(inputFrameDownsync, -1)
} }
if 0 < unconfirmedMask { if 0 < unconfirmedMask {
Logger.Info(fmt.Sprintf("[type#1 forceConfirmation] For roomId=%d@renderFrameId=%d, curDynamicsRenderFrameId=%d, LatestPlayerUpsyncedInputFrameId:%d, oldLastAllConfirmedInputFrameId:%d, newLastAllConfirmedInputFrameId:%d, (pR.NstDelayFrames >> pR.InputScaleFrames):%d, InputFrameUpsyncDelayTolerance:%d, unconfirmedMask=%d; there's a slow ticker suspect, forcing all-confirmation", pR.Id, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, pR.LatestPlayerUpsyncedInputFrameId, oldLastAllConfirmedInputFrameId, pR.LastAllConfirmedInputFrameId, (pR.NstDelayFrames >> pR.InputScaleFrames), pR.InputFrameUpsyncDelayTolerance, unconfirmedMask)) Logger.Info(fmt.Sprintf("[type#1 forceConfirmation] For roomId=%d@renderFrameId=%d, curDynamicsRenderFrameId=%d, LatestPlayerUpsyncedInputFrameId:%d, oldLastAllConfirmedInputFrameId:%d, newLastAllConfirmedInputFrameId:%d, InputFrameUpsyncDelayTolerance:%d, unconfirmedMask=%d; there's a slow ticker suspect, forcing all-confirmation", pR.Id, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, pR.LatestPlayerUpsyncedInputFrameId, oldLastAllConfirmedInputFrameId, pR.LastAllConfirmedInputFrameId, pR.InputFrameUpsyncDelayTolerance, unconfirmedMask))
} }
} else { } else {
// Type#2 helps resolve the edge case when all players are disconnected temporarily // Type#2 helps resolve the edge case when all players are disconnected temporarily
@ -1237,7 +1198,7 @@ func (pR *Room) produceInputsBufferSnapshotWithCurDynamicsRenderFrameAsRef(uncon
} }
} }
func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRenderFrameId int32, spaceOffsetX, spaceOffsetY float64) { func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRenderFrameId int32) {
// [WARNING] This function MUST BE called while "pR.InputsBufferLock" is locked! // [WARNING] This function MUST BE called while "pR.InputsBufferLock" is locked!
if fromRenderFrameId >= toRenderFrameId { if fromRenderFrameId >= toRenderFrameId {
return return
@ -1251,7 +1212,7 @@ func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRende
panic(fmt.Sprintf("collisionSysRenderFrameId=%v doesn't exist for roomId=%v, this is abnormal because it's to be used for applying dynamics to [fromRenderFrameId:%v, toRenderFrameId:%v)! RenderFrameBuffer=%v", collisionSysRenderFrameId, pR.Id, fromRenderFrameId, toRenderFrameId, pR.RenderFrameBufferString())) panic(fmt.Sprintf("collisionSysRenderFrameId=%v doesn't exist for roomId=%v, this is abnormal because it's to be used for applying dynamics to [fromRenderFrameId:%v, toRenderFrameId:%v)! RenderFrameBuffer=%v", collisionSysRenderFrameId, pR.Id, fromRenderFrameId, toRenderFrameId, pR.RenderFrameBufferString()))
} }
currRenderFrame := currRenderFrameTmp.(*battle.RoomDownsyncFrame) currRenderFrame := currRenderFrameTmp.(*battle.RoomDownsyncFrame)
delayedInputFrameId := pR.ConvertToInputFrameId(collisionSysRenderFrameId, pR.InputDelayFrames) delayedInputFrameId := battle.ConvertToDelayedInputFrameId(collisionSysRenderFrameId)
if 0 <= delayedInputFrameId { if 0 <= delayedInputFrameId {
if delayedInputFrameId > pR.LastAllConfirmedInputFrameId { if delayedInputFrameId > pR.LastAllConfirmedInputFrameId {
panic(fmt.Sprintf("delayedInputFrameId=%v is not yet all-confirmed for roomId=%v, this is abnormal because it's to be used for applying dynamics to [fromRenderFrameId:%v, toRenderFrameId:%v) @ collisionSysRenderFrameId=%v! InputsBuffer=%v", delayedInputFrameId, pR.Id, fromRenderFrameId, toRenderFrameId, collisionSysRenderFrameId, pR.InputsBufferString(false))) panic(fmt.Sprintf("delayedInputFrameId=%v is not yet all-confirmed for roomId=%v, this is abnormal because it's to be used for applying dynamics to [fromRenderFrameId:%v, toRenderFrameId:%v) @ collisionSysRenderFrameId=%v! InputsBuffer=%v", delayedInputFrameId, pR.Id, fromRenderFrameId, toRenderFrameId, collisionSysRenderFrameId, pR.InputsBufferString(false)))
@ -1275,29 +1236,16 @@ func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRende
} }
} }
nextRenderFrame := battle.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(pR.InputsBuffer, currRenderFrame, pR.Space, pR.CollisionSysMap, pR.GravityX, pR.GravityY, pR.JumpingInitVelY, pR.InputDelayFrames, pR.InputScaleFrames, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, pR.SnapIntoPlatformOverlap, pR.SnapIntoPlatformThreshold, pR.WorldToVirtualGridRatio, pR.VirtualGridToWorldRatio, pR.playerOpPatternToSkillId) nextRenderFrame := battle.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(pR.InputsBuffer, currRenderFrame, pR.Space, pR.CollisionSysMap, pR.SpaceOffsetX, pR.SpaceOffsetY, pR.CharacterConfigsArr)
pR.RenderFrameBuffer.Put(nextRenderFrame) pR.RenderFrameBuffer.Put(nextRenderFrame)
pR.CurDynamicsRenderFrameId++ pR.CurDynamicsRenderFrameId++
} }
} }
func (pR *Room) refreshColliders(spaceW, spaceH int32) { func (pR *Room) refreshColliders() {
// Kindly note that by now, we've already got all the shapes in the tmx file into "pR.(Players | Barriers)" from "ParseTmxLayersAndGroups" // Kindly note that by now, we've already got all the shapes in the tmx file into "pR.(Players | Barriers)" from "ParseTmxLayersAndGroups"
topPadding, bottomPadding, leftPadding, rightPadding := pR.SnapIntoPlatformOverlap, pR.SnapIntoPlatformOverlap, pR.SnapIntoPlatformOverlap, pR.SnapIntoPlatformOverlap pR.Space = resolv.NewSpace(int(pR.SpaceOffsetX*2), int(pR.SpaceOffsetY*2), int(pR.CollisionMinStep), int(pR.CollisionMinStep)) // allocate a new collision space everytime after a battle is settled
pR.Space = resolv.NewSpace(int(spaceW), int(spaceH), int(pR.CollisionMinStep), int(pR.CollisionMinStep)) // allocate a new collision space everytime after a battle is settled
jsPlayers := toJsPlayers(pR.Players)
for _, player := range jsPlayers {
wx, wy := battle.VirtualGridToWorldPos(player.VirtualGridX, player.VirtualGridY, pR.VirtualGridToWorldRatio)
colliderWidth, colliderHeight := player.ColliderRadius*2, player.ColliderRadius*4
playerCollider := battle.GenerateRectCollider(wx, wy, colliderWidth, colliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, player, "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
pR.Space.Add(playerCollider)
// Keep track of the collider in "pR.CollisionSysMap"
joinIndex := player.JoinIndex
collisionPlayerIndex := battle.COLLISION_PLAYER_INDEX_PREFIX + joinIndex
pR.CollisionSysMap[collisionPlayerIndex] = playerCollider
}
for _, player := range pR.Players { for _, player := range pR.Players {
joinIndex := player.JoinIndex joinIndex := player.JoinIndex
@ -1310,7 +1258,7 @@ func (pR *Room) refreshColliders(spaceW, spaceH int32) {
// For debug-printing only. // For debug-printing only.
Logger.Info("ChooseStage printing polygon2D for barrierPolygon2DList", zap.Any("barrierLocalIdInBattle", barrierLocalIdInBattle), zap.Any("polygon2D.Anchor", polygon2D.Anchor), zap.Any("polygon2D.Points", polygon2D.Points)) Logger.Info("ChooseStage printing polygon2D for barrierPolygon2DList", zap.Any("barrierLocalIdInBattle", barrierLocalIdInBattle), zap.Any("polygon2D.Anchor", polygon2D.Anchor), zap.Any("polygon2D.Points", polygon2D.Points))
*/ */
barrierCollider := battle.GenerateConvexPolygonCollider(polygon2DUnaligned, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, nil, "Barrier") barrierCollider := battle.GenerateConvexPolygonCollider(polygon2DUnaligned, pR.SpaceOffsetX, pR.SpaceOffsetY, nil, "Barrier")
pR.Space.Add(barrierCollider) pR.Space.Add(barrierCollider)
} }
} }
@ -1329,8 +1277,8 @@ func (pR *Room) doBattleMainLoopPerTickBackendDynamicsWithProperLocking(prevRend
Logger.Debug(fmt.Sprintf("doBattleMainLoopPerTickBackendDynamicsWithProperLocking-InputsBufferLock unlocked: roomId=%v", pR.Id)) Logger.Debug(fmt.Sprintf("doBattleMainLoopPerTickBackendDynamicsWithProperLocking-InputsBufferLock unlocked: roomId=%v", pR.Id))
}() }()
if ok, thatRenderFrameId := pR.shouldPrefabInputFrameDownsync(prevRenderFrameId, pR.RenderFrameId); ok { if ok, thatRenderFrameId := battle.ShouldPrefabInputFrameDownsync(prevRenderFrameId, pR.RenderFrameId); ok {
noDelayInputFrameId := pR.ConvertToInputFrameId(thatRenderFrameId, 0) noDelayInputFrameId := battle.ConvertToNoDelayInputFrameId(thatRenderFrameId)
pR.getOrPrefabInputFrameDownsync(noDelayInputFrameId) pR.getOrPrefabInputFrameDownsync(noDelayInputFrameId)
} }
@ -1341,9 +1289,9 @@ func (pR *Room) doBattleMainLoopPerTickBackendDynamicsWithProperLocking(prevRend
if 0 <= pR.LastAllConfirmedInputFrameId { if 0 <= pR.LastAllConfirmedInputFrameId {
dynamicsStartedAt := utils.UnixtimeNano() dynamicsStartedAt := utils.UnixtimeNano()
// Apply "all-confirmed inputFrames" to move forward "pR.CurDynamicsRenderFrameId" // Apply "all-confirmed inputFrames" to move forward "pR.CurDynamicsRenderFrameId"
nextDynamicsRenderFrameId := pR.ConvertToLastUsedRenderFrameId(pR.LastAllConfirmedInputFrameId, pR.InputDelayFrames) + 1 nextDynamicsRenderFrameId := battle.ConvertToLastUsedRenderFrameId(pR.LastAllConfirmedInputFrameId) + 1
Logger.Debug(fmt.Sprintf("roomId=%v, room.RenderFrameId=%v, room.CurDynamicsRenderFrameId=%v, LastAllConfirmedInputFrameId=%v, InputDelayFrames=%v, nextDynamicsRenderFrameId=%v", pR.Id, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, pR.LastAllConfirmedInputFrameId, pR.InputDelayFrames, nextDynamicsRenderFrameId)) Logger.Debug(fmt.Sprintf("roomId=%v, room.RenderFrameId=%v, room.CurDynamicsRenderFrameId=%v, LastAllConfirmedInputFrameId=%v, nextDynamicsRenderFrameId=%v", pR.Id, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, pR.LastAllConfirmedInputFrameId, nextDynamicsRenderFrameId))
pR.applyInputFrameDownsyncDynamics(pR.CurDynamicsRenderFrameId, nextDynamicsRenderFrameId, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY) pR.applyInputFrameDownsyncDynamics(pR.CurDynamicsRenderFrameId, nextDynamicsRenderFrameId)
*pDynamicsDuration = utils.UnixtimeNano() - dynamicsStartedAt *pDynamicsDuration = utils.UnixtimeNano() - dynamicsStartedAt
} }
@ -1360,7 +1308,7 @@ func (pR *Room) doBattleMainLoopPerTickBackendDynamicsWithProperLocking(prevRend
if 0 < unconfirmedMask { if 0 < unconfirmedMask {
// [WARNING] As "pR.CurDynamicsRenderFrameId" was just incremented above, "refSnapshotStFrameId" is most possibly larger than "oldLastAllConfirmedInputFrameId + 1", therefore this initial assignment is critical for `ACTIVE NORMAL TICKER`s to receive consecutive ids of inputFrameDownsync. // [WARNING] As "pR.CurDynamicsRenderFrameId" was just incremented above, "refSnapshotStFrameId" is most possibly larger than "oldLastAllConfirmedInputFrameId + 1", therefore this initial assignment is critical for `ACTIVE NORMAL TICKER`s to receive consecutive ids of inputFrameDownsync.
snapshotStFrameId := oldLastAllConfirmedInputFrameId + 1 snapshotStFrameId := oldLastAllConfirmedInputFrameId + 1
refSnapshotStFrameId := pR.ConvertToInputFrameId(pR.CurDynamicsRenderFrameId-1, pR.InputDelayFrames) refSnapshotStFrameId := battle.ConvertToDelayedInputFrameId(pR.CurDynamicsRenderFrameId - 1)
if refSnapshotStFrameId < snapshotStFrameId { if refSnapshotStFrameId < snapshotStFrameId {
snapshotStFrameId = refSnapshotStFrameId snapshotStFrameId = refSnapshotStFrameId
} }
@ -1472,15 +1420,13 @@ func (pR *Room) downsyncToSinglePlayer(playerId int32, player *Player, refRender
} }
refRenderFrame := tmp.(*battle.RoomDownsyncFrame) refRenderFrame := tmp.(*battle.RoomDownsyncFrame)
refRenderFrame.PlayerOpPatternToSkillId = pR.playerOpPatternToSkillId
for i, player := range pR.PlayersArr {
refRenderFrame.PlayersArr[i].ColliderRadius = player.ColliderRadius // hardcoded for now
}
if shouldResync3 { if shouldResync3 {
refRenderFrame.ShouldForceResync = true refRenderFrame.ShouldForceResync = true
} }
refRenderFrame.BackendUnconfirmedMask = unconfirmedMask refRenderFrame.BackendUnconfirmedMask = unconfirmedMask
pR.sendSafely(toPbRoomDownsyncFrame(refRenderFrame), toSendInputFrameDownsyncsSnapshot, DOWNSYNC_MSG_ACT_FORCED_RESYNC, playerId, false) pbRefRenderFrame := toPbRoomDownsyncFrame(refRenderFrame)
pbRefRenderFrame.SpeciesIdList = pR.SpeciesIdList
pR.sendSafely(pbRefRenderFrame, toSendInputFrameDownsyncsSnapshot, DOWNSYNC_MSG_ACT_FORCED_RESYNC, playerId, false)
//Logger.Warn(fmt.Sprintf("Sent refRenderFrameId=%v & inputFrameIds [%d, %d), for roomId=%v, playerId=%d, playerJoinIndex=%d, renderFrameId=%d, curDynamicsRenderFrameId=%d, playerLastSentInputFrameId=%d: InputsBuffer=%v", refRenderFrameId, toSendInputFrameIdSt, toSendInputFrameIdEd, pR.Id, playerId, player.JoinIndex, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, player.LastSentInputFrameId, pR.InputsBufferString(false))) //Logger.Warn(fmt.Sprintf("Sent refRenderFrameId=%v & inputFrameIds [%d, %d), for roomId=%v, playerId=%d, playerJoinIndex=%d, renderFrameId=%d, curDynamicsRenderFrameId=%d, playerLastSentInputFrameId=%d: InputsBuffer=%v", refRenderFrameId, toSendInputFrameIdSt, toSendInputFrameIdEd, pR.Id, playerId, player.JoinIndex, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, player.LastSentInputFrameId, pR.InputsBufferString(false)))
if shouldResync1 { if shouldResync1 {
Logger.Warn(fmt.Sprintf("Sent refRenderFrameId=%v & inputFrameIds [%d, %d), for roomId=%v, playerId=%d, playerJoinIndex=%d, renderFrameId=%d, curDynamicsRenderFrameId=%d, playerLastSentInputFrameId=%d: shouldResync1=%v, shouldResync2=%v, shouldResync3=%v, playerBattleState=%d", refRenderFrameId, toSendInputFrameIdSt, toSendInputFrameIdEd, pR.Id, playerId, player.JoinIndex, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, player.LastSentInputFrameId, shouldResync1, shouldResync2, shouldResync3, playerBattleState)) Logger.Warn(fmt.Sprintf("Sent refRenderFrameId=%v & inputFrameIds [%d, %d), for roomId=%v, playerId=%d, playerJoinIndex=%d, renderFrameId=%d, curDynamicsRenderFrameId=%d, playerLastSentInputFrameId=%d: shouldResync1=%v, shouldResync2=%v, shouldResync3=%v, playerBattleState=%d", refRenderFrameId, toSendInputFrameIdSt, toSendInputFrameIdEd, pR.Id, playerId, player.JoinIndex, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, player.LastSentInputFrameId, shouldResync1, shouldResync2, shouldResync3, playerBattleState))

File diff suppressed because it is too large Load Diff

View File

@ -240,37 +240,23 @@ func Serve(c *gin.Context) {
// Construct "battleColliderInfo" to downsync // Construct "battleColliderInfo" to downsync
bciFrame := &pb.BattleColliderInfo{ bciFrame := &pb.BattleColliderInfo{
BoundRoomId: pRoom.Id, BoundRoomId: pRoom.Id,
StageName: pRoom.StageName, StageName: pRoom.StageName,
StageDiscreteW: pRoom.StageDiscreteW,
StageDiscreteH: pRoom.StageDiscreteH, IntervalToPing: int32(Constants.Ws.IntervalToPing),
StageTileW: pRoom.StageTileW, WillKickIfInactiveFor: int32(Constants.Ws.WillKickIfInactiveFor),
StageTileH: pRoom.StageTileH, BattleDurationNanos: pRoom.BattleDurationNanos,
IntervalToPing: int32(Constants.Ws.IntervalToPing),
WillKickIfInactiveFor: int32(Constants.Ws.WillKickIfInactiveFor),
BattleDurationNanos: pRoom.BattleDurationNanos,
ServerFps: pRoom.ServerFps,
InputDelayFrames: pRoom.InputDelayFrames,
InputScaleFrames: pRoom.InputScaleFrames,
NstDelayFrames: pRoom.NstDelayFrames,
InputFrameUpsyncDelayTolerance: pRoom.InputFrameUpsyncDelayTolerance, InputFrameUpsyncDelayTolerance: pRoom.InputFrameUpsyncDelayTolerance,
MaxChasingRenderFramesPerUpdate: pRoom.MaxChasingRenderFramesPerUpdate, MaxChasingRenderFramesPerUpdate: pRoom.MaxChasingRenderFramesPerUpdate,
PlayerBattleState: pThePlayer.BattleState, // For frontend to know whether it's rejoining
RollbackEstimatedDtMillis: pRoom.RollbackEstimatedDtMillis, RollbackEstimatedDtMillis: pRoom.RollbackEstimatedDtMillis,
RollbackEstimatedDtNanos: pRoom.RollbackEstimatedDtNanos, RollbackEstimatedDtNanos: pRoom.RollbackEstimatedDtNanos,
WorldToVirtualGridRatio: pRoom.WorldToVirtualGridRatio, SpaceOffsetX: pRoom.SpaceOffsetX,
VirtualGridToWorldRatio: pRoom.VirtualGridToWorldRatio, SpaceOffsetY: pRoom.SpaceOffsetY,
SpAtkLookupFrames: pRoom.SpAtkLookupFrames, RenderCacheSize: pRoom.RenderCacheSize,
RenderCacheSize: pRoom.RenderCacheSize, CollisionMinStep: pRoom.CollisionMinStep,
SnapIntoPlatformOverlap: pRoom.SnapIntoPlatformOverlap,
SnapIntoPlatformThreshold: pRoom.SnapIntoPlatformThreshold,
JumpingInitVelY: pRoom.JumpingInitVelY,
GravityX: pRoom.GravityX,
GravityY: pRoom.GravityY,
CollisionMinStep: pRoom.CollisionMinStep,
FrameDataLoggingEnabled: pRoom.FrameDataLoggingEnabled, FrameDataLoggingEnabled: pRoom.FrameDataLoggingEnabled,
} }

View File

@ -14,3 +14,15 @@ ffmpeg -vsync vfr -i input.gif output%d.png
``` ```
The `-vsync vfr` tells ffmpeg to disrespect the original delays set within the GIF file, otherwise many duplicate frame will be extracted by the default 60FPS. The `-vsync vfr` tells ffmpeg to disrespect the original delays set within the GIF file, otherwise many duplicate frame will be extracted by the default 60FPS.
More complicated transparent padding example (used when alignment in image source is much more preferred than aligning in codes)
```
ffmpeg -vsync vfr -i LayDown1.gif -vf "scale=iw:188:force_original_aspect_ratio=decrease,pad=iw:188:0:(oh-ih):color=#00000000,format=rgba" pngs/LayDown1_%d.png
```
The command above uses same input-output width, but pads the output height with a top transparent section such that the output height is fixed to 188px.
Similarly to crop a gif into pngs.
```
ffmpeg -vsync vfr -i Idle1.gif -vf "crop=70:ih:(iw-ow-10):0" pngs/Idle1_%d.png
```

View File

@ -0,0 +1 @@
This module is no longer useful, as we can now use GopherJs+OfflineMap to visualize experimental game dynamics.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
{
"ver": "1.0.1",
"uuid": "15043c55-01a9-408c-b985-910c5de144c7",
"isSubpackage": false,
"subpackageName": "",
"subMetas": {}
}

View File

@ -0,0 +1,91 @@
{
"__type__": "cc.AnimationClip",
"_name": "Atk1",
"_objFlags": 0,
"_native": "",
"_duration": 0.6,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "6b2a1a09-8236-4972-8aee-1781b6547d4e"
}
},
{
"frame": 0.06666666666666667,
"value": {
"__uuid__": "5b269b2e-0800-4bf5-b5db-055b78cf318d"
}
},
{
"frame": 0.11666666666666667,
"value": {
"__uuid__": "e184f831-3f90-47cb-8ca5-ad9446ba0398"
}
},
{
"frame": 0.18333333333333332,
"value": {
"__uuid__": "f260f82b-10cf-49af-b46c-cd8dc7a3503f"
}
},
{
"frame": 0.23333333333333334,
"value": {
"__uuid__": "09f43e75-0986-4b8f-907b-e5a5a73b7c54"
}
},
{
"frame": 0.2833333333333333,
"value": {
"__uuid__": "d7432402-fcbd-4e2e-b2c1-33364f31948b"
}
},
{
"frame": 0.3333333333333333,
"value": {
"__uuid__": "8acf1c5a-6910-47d6-aa18-1e6f07546d7a"
}
},
{
"frame": 0.38333333333333336,
"value": {
"__uuid__": "76120dbd-6390-4706-9580-dcec9687e83e"
}
},
{
"frame": 0.43333333333333335,
"value": {
"__uuid__": "f9a7a04a-2369-4946-8313-c2da896da51e"
}
},
{
"frame": 0.48333333333333334,
"value": {
"__uuid__": "c8fb14e2-745e-4a2b-b235-d43195052652"
}
},
{
"frame": 0.5333333333333333,
"value": {
"__uuid__": "1f23e0e5-830c-4d07-8f80-cbb98a2a1954"
}
},
{
"frame": 0.5833333333333334,
"value": {
"__uuid__": "4c512398-4290-4fdf-bb21-2b0062a9071b"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "829b17c6-9365-4e97-b14f-fa266bd5ecbe",
"subMetas": {}
}

View File

@ -0,0 +1,91 @@
{
"__type__": "cc.AnimationClip",
"_name": "Atk2",
"_objFlags": 0,
"_native": "",
"_duration": 1.0166666666666666,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "60f9f7d4-5cf7-4098-9455-0f6a74963fc6"
}
},
{
"frame": 0.06666666666666667,
"value": {
"__uuid__": "dd9a00aa-ddbc-4b01-a7cb-3c43c3a655b6"
}
},
{
"frame": 0.11666666666666667,
"value": {
"__uuid__": "f66e83bd-1afc-4957-bb16-488d70566ed1"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "bd682c41-dc62-49ff-a96a-18b33e50a6de"
}
},
{
"frame": 0.23333333333333334,
"value": {
"__uuid__": "94ccab85-e32f-4e13-b0e5-72c798f78ad1"
}
},
{
"frame": 0.3,
"value": {
"__uuid__": "e80d3a01-5048-42b7-a280-cb6aa01602c2"
}
},
{
"frame": 0.36666666666666664,
"value": {
"__uuid__": "d899088c-be62-47b4-9ebf-0a89a2261565"
}
},
{
"frame": 0.4166666666666667,
"value": {
"__uuid__": "5b1e5aa7-fd82-47ae-a5b2-6d4983d848ed"
}
},
{
"frame": 0.48333333333333334,
"value": {
"__uuid__": "c2945988-b4bb-4583-a5ef-2fa02b23a347"
}
},
{
"frame": 0.5666666666666667,
"value": {
"__uuid__": "070ea1e3-9c07-4735-8b94-515ef70216ad"
}
},
{
"frame": 0.6666666666666666,
"value": {
"__uuid__": "3b8bc5c0-26df-4218-b7dc-134a36080a35"
}
},
{
"frame": 1,
"value": {
"__uuid__": "3898259f-a3b0-490d-b260-f86ab5109dfe"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "52b8e47d-715c-4c12-a2c9-6f553e14dc42",
"subMetas": {}
}

View File

@ -0,0 +1,79 @@
{
"__type__": "cc.AnimationClip",
"_name": "Atk3",
"_objFlags": 0,
"_native": "",
"_duration": 1.0166666666666666,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "51ab185b-2271-48b5-a897-af79721d566c"
}
},
{
"frame": 0.05,
"value": {
"__uuid__": "c29fd95d-7467-4138-9e01-6421af63dd68"
}
},
{
"frame": 0.1,
"value": {
"__uuid__": "3285b5eb-c6be-4cb9-ac60-c506645fee4b"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "13fd4a87-71f4-4b69-a5b3-413d564c35e6"
}
},
{
"frame": 0.25,
"value": {
"__uuid__": "7189e229-00d6-427a-8ea1-d05fbd97824f"
}
},
{
"frame": 0.45,
"value": {
"__uuid__": "d9ccfe33-3db7-4b3a-807c-adb2121fb7c7"
}
},
{
"frame": 0.6833333333333333,
"value": {
"__uuid__": "a51cbc29-0826-46f7-a38b-8b0f996fbace"
}
},
{
"frame": 0.8833333333333333,
"value": {
"__uuid__": "7e9f3a24-6abc-4b49-a5c5-a0c100865ffc"
}
},
{
"frame": 0.95,
"value": {
"__uuid__": "ecbaeb43-1118-483a-91c9-ff1ff01b7b33"
}
},
{
"frame": 1,
"value": {
"__uuid__": "58afa365-a916-4ec6-aab3-1c87f5332b12"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "9b500cb0-8048-4715-81db-cc975c914225",
"subMetas": {}
}

View File

@ -0,0 +1,43 @@
{
"__type__": "cc.AnimationClip",
"_name": "Atked1",
"_objFlags": 0,
"_native": "",
"_duration": 0.26666666666666666,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "9b90ae89-7fbe-4bb6-ab15-fc08462f54c5"
}
},
{
"frame": 0.1,
"value": {
"__uuid__": "aeb3553a-6de4-4197-9f06-d7cc3fa7c4cf"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "07650461-a7c0-4638-92fc-fa436752c045"
}
},
{
"frame": 0.25,
"value": {
"__uuid__": "ca22d473-83aa-4146-b732-89d0246a2968"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "2a50c853-000b-46f3-ae59-1dfb793de814",
"subMetas": {}
}

View File

@ -0,0 +1,43 @@
{
"__type__": "cc.AnimationClip",
"_name": "BlownUp1",
"_objFlags": 0,
"_native": "",
"_duration": 0.4166666666666667,
"sample": 60,
"speed": 1,
"wrapMode": 2,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "0ea84f61-4a2f-4ca2-a0b8-b580e027c142"
}
},
{
"frame": 0.11666666666666667,
"value": {
"__uuid__": "a3a31fcd-a162-456a-9c26-1f32413f87f3"
}
},
{
"frame": 0.25,
"value": {
"__uuid__": "da4ee0a0-ba66-455c-99d3-9c803a3d0f17"
}
},
{
"frame": 0.4,
"value": {
"__uuid__": "4c058d60-e727-4438-b058-6e51969df458"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "0892a3ea-9da1-4157-825b-0c8ef1c73eeb",
"subMetas": {}
}

View File

@ -0,0 +1,61 @@
{
"__type__": "cc.AnimationClip",
"_name": "GetUp1",
"_objFlags": 0,
"_native": "",
"_duration": 0.5166666666666667,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "3f26b0a4-db13-4a14-b885-5a812073eccf"
}
},
{
"frame": 0.08333333333333333,
"value": {
"__uuid__": "23eb42d6-52a2-458d-98a3-2f692dd79398"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "7bd4428c-44f9-4ff8-8d00-d3448a27a0c4"
}
},
{
"frame": 0.25,
"value": {
"__uuid__": "57640cd8-17eb-4279-a118-1ea40174da3c"
}
},
{
"frame": 0.35,
"value": {
"__uuid__": "56a2c954-6d04-433c-8ae4-f1bc7a0e9469"
}
},
{
"frame": 0.43333333333333335,
"value": {
"__uuid__": "b4dc81a3-eb90-4722-9296-5158f004fd4c"
}
},
{
"frame": 0.5,
"value": {
"__uuid__": "c156fa2c-0c4e-4413-80c1-accf8fea4779"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "7d69868f-d474-4d86-a262-01f1cdd49021",
"subMetas": {}
}

View File

@ -0,0 +1,109 @@
{
"__type__": "cc.AnimationClip",
"_name": "Idle1",
"_objFlags": 0,
"_native": "",
"_duration": 2.0166666666666666,
"sample": 60,
"speed": 1,
"wrapMode": 2,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "51ede1f1-5e65-4b69-8fbc-5b7df2a7c90f"
}
},
{
"frame": 0.11666666666666667,
"value": {
"__uuid__": "b0f1ecf7-be41-46a8-bccb-92ee53b4ef88"
}
},
{
"frame": 0.21666666666666667,
"value": {
"__uuid__": "944adaee-5e11-4c55-9573-1d529f21ed4b"
}
},
{
"frame": 0.31666666666666665,
"value": {
"__uuid__": "ede36130-8312-46a2-abe5-ba102bc97822"
}
},
{
"frame": 0.45,
"value": {
"__uuid__": "32dbb977-301c-4fbd-a19c-14a5ce0e2f5b"
}
},
{
"frame": 0.6,
"value": {
"__uuid__": "581c7ab1-451d-46b0-9eeb-24cf3b898924"
}
},
{
"frame": 0.7666666666666667,
"value": {
"__uuid__": "27005132-eda8-4c3f-9f4a-3a6004245e9f"
}
},
{
"frame": 0.9166666666666666,
"value": {
"__uuid__": "2b339657-aa80-4ab6-a4dd-83e9c12b6a54"
}
},
{
"frame": 1.0666666666666667,
"value": {
"__uuid__": "08e25a99-158d-4159-8152-6c9047fe9d54"
}
},
{
"frame": 1.2166666666666666,
"value": {
"__uuid__": "ab739541-39b5-4758-9b93-6681d6038730"
}
},
{
"frame": 1.3833333333333333,
"value": {
"__uuid__": "9ecbf97e-34bc-4c5e-b9e8-4d885a02e0d3"
}
},
{
"frame": 1.5333333333333334,
"value": {
"__uuid__": "e7d9a7c9-47ec-4335-97c9-19ed6ee3d9ce"
}
},
{
"frame": 1.7,
"value": {
"__uuid__": "cf258753-3900-4de4-a0bb-09158500fd9a"
}
},
{
"frame": 1.85,
"value": {
"__uuid__": "be7bedfe-7054-4092-89fc-ca397460f140"
}
},
{
"frame": 2,
"value": {
"__uuid__": "b1b467dd-1757-4ff1-9909-3449f74463d8"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "5afe50e0-d03f-4d48-a7c0-a350c36e14d4",
"subMetas": {}
}

View File

@ -0,0 +1,55 @@
{
"__type__": "cc.AnimationClip",
"_name": "InAirAtk1",
"_objFlags": 0,
"_native": "",
"_duration": 0.38333333333333336,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "e6a3099f-b4c3-425f-a66b-9b992ae1c7b0"
}
},
{
"frame": 0.05,
"value": {
"__uuid__": "cade5a52-282e-47e5-aca9-5abb4f6afd14"
}
},
{
"frame": 0.1,
"value": {
"__uuid__": "96b0b028-3045-4a04-bf9c-c957f8c9e9ab"
}
},
{
"frame": 0.25,
"value": {
"__uuid__": "567f6bb7-c764-4c1d-bfec-f5c0c9812192"
}
},
{
"frame": 0.31666666666666665,
"value": {
"__uuid__": "5d0c32b4-52ce-4157-9b91-dd400a7ed07a"
}
},
{
"frame": 0.36666666666666664,
"value": {
"__uuid__": "fcef9c9b-dc71-459c-a541-9273b6e3923a"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "0e7468cc-b90d-4f68-91ce-0be126b406dd",
"subMetas": {}
}

View File

@ -0,0 +1,31 @@
{
"__type__": "cc.AnimationClip",
"_name": "InAirAtked1",
"_objFlags": 0,
"_native": "",
"_duration": 0.08333333333333333,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "01e8b938-03cb-4519-a417-384c31131a27"
}
},
{
"frame": 0.06666666666666667,
"value": {
"__uuid__": "d7970a18-f3af-44c6-b216-ee55b9a1b32c"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "c86debde-118b-46b8-b483-f5ccec337315",
"subMetas": {}
}

View File

@ -0,0 +1,79 @@
{
"__type__": "cc.AnimationClip",
"_name": "InAirIdle1ByJump",
"_objFlags": 0,
"_native": "",
"_duration": 0.4666666666666667,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "6ff7b4e6-80ec-4673-b47b-e0bba7567c3c"
}
},
{
"frame": 0.05,
"value": {
"__uuid__": "4ff35932-9869-4c78-9f75-b304eee46647"
}
},
{
"frame": 0.1,
"value": {
"__uuid__": "6b1355c5-a750-4e37-9018-de1b84238f6c"
}
},
{
"frame": 0.15,
"value": {
"__uuid__": "01193448-adb0-4364-94f3-ca810897f397"
}
},
{
"frame": 0.2,
"value": {
"__uuid__": "f4f0cd5d-835b-44c3-8cc9-2651dd00cd37"
}
},
{
"frame": 0.25,
"value": {
"__uuid__": "4c32b2d6-346f-4d6e-a92e-6678e4c4b1ad"
}
},
{
"frame": 0.3,
"value": {
"__uuid__": "30ef09b2-366a-4946-b556-2c0fac359c0b"
}
},
{
"frame": 0.35,
"value": {
"__uuid__": "6ab33993-124f-429e-ba4c-b0f6824ea6c7"
}
},
{
"frame": 0.4,
"value": {
"__uuid__": "e0612a37-a743-40fe-83ff-f189971f1992"
}
},
{
"frame": 0.45,
"value": {
"__uuid__": "6ff230eb-44e8-4ca9-97d0-1b059aa1e21b"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "c4ddf3ea-2292-4511-a320-5486934ac361",
"subMetas": {}
}

View File

@ -0,0 +1,25 @@
{
"__type__": "cc.AnimationClip",
"_name": "InAirIdle1NoJump",
"_objFlags": 0,
"_native": "",
"_duration": 0.016666666666666666,
"sample": 60,
"speed": 1,
"wrapMode": 2,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "6ff230eb-44e8-4ca9-97d0-1b059aa1e21b"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "bc461a02-4d1a-46a4-9f3c-7370adc6c1c8",
"subMetas": {}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

View File

@ -0,0 +1,12 @@
{
"ver": "2.3.3",
"uuid": "385b0a2b-765c-43fc-9243-977baccfd37a",
"type": "raw",
"wrapMode": "clamp",
"filterMode": "bilinear",
"premultiplyAlpha": false,
"genMipmaps": false,
"packable": true,
"platformSettings": {},
"subMetas": {}
}

View File

@ -0,0 +1,31 @@
{
"__type__": "cc.AnimationClip",
"_name": "LayDown1",
"_objFlags": 0,
"_native": "",
"_duration": 0.06666666666666667,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "4e8a4f3c-5406-4700-9230-b2abc29a1093"
}
},
{
"frame": 0.05,
"value": {
"__uuid__": "1ae5e6f6-ba82-485d-b3c7-59fd16ece3d5"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "0a6478a6-f4cc-47cb-a1c6-761cf9141eb2",
"subMetas": {}
}

View File

@ -0,0 +1,97 @@
{
"__type__": "cc.AnimationClip",
"_name": "Walking",
"_objFlags": 0,
"_native": "",
"_duration": 1.5166666666666666,
"sample": 60,
"speed": 1.2,
"wrapMode": 2,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "c3b14ecc-a6d9-4cb3-8637-ca7b407a0f5c"
}
},
{
"frame": 0.08333333333333333,
"value": {
"__uuid__": "9435195e-4560-495e-b1ae-083c0c87e8a0"
}
},
{
"frame": 0.18333333333333332,
"value": {
"__uuid__": "ec048360-7a17-4f22-ba52-eb86ec1acae8"
}
},
{
"frame": 0.2833333333333333,
"value": {
"__uuid__": "82bb81e3-667c-4280-8710-211f4904ef2f"
}
},
{
"frame": 0.4,
"value": {
"__uuid__": "f958fb7f-ef5a-4918-81f3-564004572f45"
}
},
{
"frame": 0.5333333333333333,
"value": {
"__uuid__": "8a0ecf92-db26-4206-9a80-20e749055def"
}
},
{
"frame": 0.65,
"value": {
"__uuid__": "942f2e02-a700-4fbf-877e-08c93e4d4010"
}
},
{
"frame": 0.7666666666666667,
"value": {
"__uuid__": "30546064-1a11-499e-8523-a82c83951c73"
}
},
{
"frame": 0.9,
"value": {
"__uuid__": "515bb75f-7a1f-4500-8aa9-c895915ce19f"
}
},
{
"frame": 1.0333333333333334,
"value": {
"__uuid__": "9100da6b-7582-4afb-9698-3d67d3b2012d"
}
},
{
"frame": 1.2166666666666666,
"value": {
"__uuid__": "1257f72d-0cb3-4750-ae70-13c2d8eb2269"
}
},
{
"frame": 1.3833333333333333,
"value": {
"__uuid__": "1d34b6db-27ba-4e26-864d-0f00d501765e"
}
},
{
"frame": 1.5,
"value": {
"__uuid__": "c317a75a-52c0-4c38-9300-a064cbf4efb3"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "f7f60d3f-32c0-4b56-901e-6eec9f73fa83",
"subMetas": {}
}

View File

@ -0,0 +1,7 @@
{
"ver": "1.0.1",
"uuid": "a0021cb4-2c2e-49ca-8699-7acf4f7a531b",
"isSubpackage": false,
"subpackageName": "",
"subMetas": {}
}

View File

@ -0,0 +1,49 @@
{
"__type__": "cc.AnimationClip",
"_name": "Atk1",
"_objFlags": 0,
"_native": "",
"_duration": 0.5,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "a5615ed4-d055-4db5-b924-4c8d8b2d926f"
}
},
{
"frame": 0.05,
"value": {
"__uuid__": "12b90556-58b9-4311-b5d9-820fb76d659b"
}
},
{
"frame": 0.11666666666666667,
"value": {
"__uuid__": "72bc74a1-6e8c-48bb-9ab2-9b8f502ceffb"
}
},
{
"frame": 0.43333333333333335,
"value": {
"__uuid__": "7e619896-100d-4903-b256-e30ddb5ad397"
}
},
{
"frame": 0.48333333333333334,
"value": {
"__uuid__": "4a35e0f5-95c4-445b-8f9b-6514a060a72d"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "c3d4f508-b3a9-4deb-b2d1-8ddc7bf22e0c",
"subMetas": {}
}

View File

@ -0,0 +1,79 @@
{
"__type__": "cc.AnimationClip",
"_name": "Atk2",
"_objFlags": 0,
"_native": "",
"_duration": 0.6,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "0e3b6c28-f265-457d-b000-aeea66d05428"
}
},
{
"frame": 0.03333333333333333,
"value": {
"__uuid__": "1d9c010b-3209-4672-9f78-96527d62f4e0"
}
},
{
"frame": 0.08333333333333333,
"value": {
"__uuid__": "1fd3ca4d-ec4d-4858-a3d2-fe274e22d4be"
}
},
{
"frame": 0.11666666666666667,
"value": {
"__uuid__": "b0aaf216-b8d7-4247-be61-df9f3146ce46"
}
},
{
"frame": 0.15,
"value": {
"__uuid__": "f12c5713-540f-4874-a6b0-0162f0ffb544"
}
},
{
"frame": 0.2,
"value": {
"__uuid__": "b9a4cd85-53d1-4280-bc9e-35a8dd482fa4"
}
},
{
"frame": 0.23333333333333334,
"value": {
"__uuid__": "74e9fcd1-11dc-478e-b90a-d4c5cf551aad"
}
},
{
"frame": 0.3,
"value": {
"__uuid__": "e6c3bdcd-9ba8-4c5b-910d-2d93d9068abd"
}
},
{
"frame": 0.5166666666666667,
"value": {
"__uuid__": "c359790f-bbd1-4869-a37b-7f1c0bd91578"
}
},
{
"frame": 0.5833333333333334,
"value": {
"__uuid__": "1b9074dd-d0b0-4129-8d72-8f356bf7f68c"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "e6e1d62a-de7d-4fe7-858c-2d2725b7c2e8",
"subMetas": {}
}

View File

@ -0,0 +1,73 @@
{
"__type__": "cc.AnimationClip",
"_name": "Atk3",
"_objFlags": 0,
"_native": "",
"_duration": 1.0166666666666666,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "39ba4413-6f4a-49a5-a7ca-d11140dfe7dd"
}
},
{
"frame": 0.08333333333333333,
"value": {
"__uuid__": "26b646c7-bdbc-495e-adaf-9d52ef1b5c84"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "5a5208a0-1c29-446f-8375-739aef09fe65"
}
},
{
"frame": 0.2833333333333333,
"value": {
"__uuid__": "414628f0-13ec-4f01-83a0-b94f6a13fff1"
}
},
{
"frame": 0.4166666666666667,
"value": {
"__uuid__": "c494965a-e7e6-4c99-ac61-60642e6247dc"
}
},
{
"frame": 0.5333333333333333,
"value": {
"__uuid__": "04cafb17-39ab-4f2b-9830-3eaf42cab254"
}
},
{
"frame": 0.6666666666666666,
"value": {
"__uuid__": "fd9c7d8a-1038-4cab-a0e6-699e404701db"
}
},
{
"frame": 0.8,
"value": {
"__uuid__": "2447c6b3-292b-43b4-84e5-acc35df0c1f5"
}
},
{
"frame": 1,
"value": {
"__uuid__": "00275818-b9b6-41ab-a792-f21ff10747fa"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "e8247e2a-1b5b-4618-86f8-224b25246b55",
"subMetas": {}
}

View File

@ -0,0 +1,37 @@
{
"__type__": "cc.AnimationClip",
"_name": "Atked1",
"_objFlags": 0,
"_native": "",
"_duration": 0.05,
"sample": 60,
"speed": 0.4,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "df446561-abb9-4d91-aa79-636bdf3d9335"
}
},
{
"frame": 0.016666666666666666,
"value": {
"__uuid__": "852471cd-6270-47d2-b40b-e33a93910240"
}
},
{
"frame": 0.03333333333333333,
"value": {
"__uuid__": "7d08ee55-243c-4bb0-b614-1cd09cabf13f"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "1ec27664-cd2a-4c7a-838f-953a7713ceca",
"subMetas": {}
}

View File

@ -0,0 +1,43 @@
{
"__type__": "cc.AnimationClip",
"_name": "BlownUp1",
"_objFlags": 0,
"_native": "",
"_duration": 0.06666666666666667,
"sample": 60,
"speed": 0.2,
"wrapMode": 2,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "9d16c119-8ead-4d9e-90b3-5bbf749f71ef"
}
},
{
"frame": 0.016666666666666666,
"value": {
"__uuid__": "f2629502-5c62-4df2-9741-58fe94bfab53"
}
},
{
"frame": 0.03333333333333333,
"value": {
"__uuid__": "d6a81e68-5f14-45d1-82e2-fabf2853685a"
}
},
{
"frame": 0.05,
"value": {
"__uuid__": "9f7382dd-4da6-485c-8064-78ec031a2c0a"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "f87054fd-328f-4001-a8ff-b4c049cc1e6d",
"subMetas": {}
}

View File

@ -0,0 +1,79 @@
{
"__type__": "cc.AnimationClip",
"_name": "GetUp1",
"_objFlags": 0,
"_native": "",
"_duration": 0.16666666666666666,
"sample": 60,
"speed": 0.3,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "4b1219ad-ef64-47f6-87ca-16188343c47a"
}
},
{
"frame": 0.016666666666666666,
"value": {
"__uuid__": "49e7f092-85c2-4eb3-a62a-965e9e0d9c12"
}
},
{
"frame": 0.03333333333333333,
"value": {
"__uuid__": "73023874-a74c-4e9f-8389-d4280d7be452"
}
},
{
"frame": 0.05,
"value": {
"__uuid__": "ed62daef-540c-4b34-81d3-99c4296d6ca9"
}
},
{
"frame": 0.06666666666666667,
"value": {
"__uuid__": "cdbcadf6-4e4f-4673-8044-51b40c58da96"
}
},
{
"frame": 0.08333333333333333,
"value": {
"__uuid__": "7ae337de-a63f-41ef-9627-84f9ced77d6f"
}
},
{
"frame": 0.1,
"value": {
"__uuid__": "931626d5-01ad-4329-a8f7-9a00bd64fdff"
}
},
{
"frame": 0.11666666666666667,
"value": {
"__uuid__": "d4c2dae6-8ff6-4976-8e50-cb0b698d93d4"
}
},
{
"frame": 0.13333333333333333,
"value": {
"__uuid__": "b328d317-3721-4402-89db-34910c31a48a"
}
},
{
"frame": 0.15,
"value": {
"__uuid__": "a51ca0c2-3e3b-4b7b-b636-1cac791c2511"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "7cb4d395-c68f-4643-9e2e-8cb8e200d3a5",
"subMetas": {}
}

View File

@ -0,0 +1,85 @@
{
"__type__": "cc.AnimationClip",
"_name": "Idle1",
"_objFlags": 0,
"_native": "",
"_duration": 0.45,
"sample": 60,
"speed": 1,
"wrapMode": 2,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "104ab450-fec3-4ea8-b6c0-aca74a826f5a"
}
},
{
"frame": 0.016666666666666666,
"value": {
"__uuid__": "4bad7430-7b43-47ae-9d65-ef3a2b88f450"
}
},
{
"frame": 0.03333333333333333,
"value": {
"__uuid__": "c3675bd6-4aa6-4ec5-80d5-ef35862d15b9"
}
},
{
"frame": 0.08333333333333333,
"value": {
"__uuid__": "23446a26-dadf-4d7d-9dd3-4ca3f8f6b7a5"
}
},
{
"frame": 0.13333333333333333,
"value": {
"__uuid__": "47628469-7775-4daf-b321-74fac947ce60"
}
},
{
"frame": 0.18333333333333332,
"value": {
"__uuid__": "1c37ea82-6700-4bea-9265-c70a16d2187c"
}
},
{
"frame": 0.23333333333333334,
"value": {
"__uuid__": "c4fb2fc2-7a04-4219-b129-77249f33426b"
}
},
{
"frame": 0.2833333333333333,
"value": {
"__uuid__": "0ecf4a0c-0f13-42fa-a214-b4826acd8556"
}
},
{
"frame": 0.3333333333333333,
"value": {
"__uuid__": "cabf9cb6-99ca-426d-9a23-95cdec6f06b9"
}
},
{
"frame": 0.38333333333333336,
"value": {
"__uuid__": "94d073e8-f17f-4a66-be72-2b8cf3347ea4"
}
},
{
"frame": 0.43333333333333335,
"value": {
"__uuid__": "3a435737-51d0-4f31-ad2b-838d9d98bad3"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "a0d8b9a6-7bf7-4cf5-aecb-54b2908cbfb3",
"subMetas": {}
}

View File

@ -0,0 +1,97 @@
{
"__type__": "cc.AnimationClip",
"_name": "InAirAtk1",
"_objFlags": 0,
"_native": "",
"_duration": 0.4166666666666667,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "3631c1af-5426-420e-9c81-47d8dc4790f7"
}
},
{
"frame": 0.03333333333333333,
"value": {
"__uuid__": "f10683b4-2dd8-48d9-b11d-3e7fd12e2dee"
}
},
{
"frame": 0.06666666666666667,
"value": {
"__uuid__": "6134b002-58ed-4d78-af23-5f574661b04b"
}
},
{
"frame": 0.1,
"value": {
"__uuid__": "5db472a6-3371-4a5b-860d-e6ea6a9eaba0"
}
},
{
"frame": 0.13333333333333333,
"value": {
"__uuid__": "769179e3-d0bb-4bd0-a084-ce5ad5b57d47"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "8f09ba68-066c-4240-84be-390cdbb2d59c"
}
},
{
"frame": 0.2,
"value": {
"__uuid__": "e8ce0f1e-6af0-423b-9448-2a72c944127d"
}
},
{
"frame": 0.23333333333333334,
"value": {
"__uuid__": "1da322c5-98b8-49d0-8f96-33fdc68a23cb"
}
},
{
"frame": 0.26666666666666666,
"value": {
"__uuid__": "1e4bf242-5d08-4220-a5d9-2a2524b0e3c7"
}
},
{
"frame": 0.3,
"value": {
"__uuid__": "6de218a1-5a97-405d-b7d0-68cc9cf076f9"
}
},
{
"frame": 0.3333333333333333,
"value": {
"__uuid__": "4c16e710-abfe-4030-bd42-fded0f41f665"
}
},
{
"frame": 0.36666666666666664,
"value": {
"__uuid__": "1753c533-3a31-46b6-ab01-15b778702237"
}
},
{
"frame": 0.4,
"value": {
"__uuid__": "923cfcea-2c3e-493f-826a-0b08b447ebde"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "555d837b-4a68-4a28-b9ff-e97e393d1a05",
"subMetas": {}
}

View File

@ -0,0 +1,31 @@
{
"__type__": "cc.AnimationClip",
"_name": "InAirAtked1",
"_objFlags": 0,
"_native": "",
"_duration": 0.03333333333333333,
"sample": 60,
"speed": 0.2,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "852471cd-6270-47d2-b40b-e33a93910240"
}
},
{
"frame": 0.016666666666666666,
"value": {
"__uuid__": "7d08ee55-243c-4bb0-b614-1cd09cabf13f"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "ee0b89e9-89f0-451e-a6ef-d41d64d3d67a",
"subMetas": {}
}

View File

@ -0,0 +1,91 @@
{
"__type__": "cc.AnimationClip",
"_name": "InAirIdle1ByJump",
"_objFlags": 0,
"_native": "",
"_duration": 0.2,
"sample": 60,
"speed": 0.6,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "21c7f1b2-77c7-4f9a-b5c6-b79d99553432"
}
},
{
"frame": 0.016666666666666666,
"value": {
"__uuid__": "9106d51d-808b-4cec-b03c-1a0e9de3dd13"
}
},
{
"frame": 0.03333333333333333,
"value": {
"__uuid__": "23199f52-ded0-4271-b426-17c6037989a4"
}
},
{
"frame": 0.05,
"value": {
"__uuid__": "293367f0-6557-40d2-aff5-4f6b9faa3b14"
}
},
{
"frame": 0.06666666666666667,
"value": {
"__uuid__": "f9029e1e-91f8-4ae2-a3ef-b811c409f41a"
}
},
{
"frame": 0.08333333333333333,
"value": {
"__uuid__": "de0e3195-e56e-4bba-82f6-72635f453eea"
}
},
{
"frame": 0.1,
"value": {
"__uuid__": "93e7fb75-b42d-4da9-860d-2e7dd0930d8d"
}
},
{
"frame": 0.11666666666666667,
"value": {
"__uuid__": "1a789615-1580-4342-b739-7ef94c80b34a"
}
},
{
"frame": 0.13333333333333333,
"value": {
"__uuid__": "5331bbb0-fad4-402c-8788-cb070cc8fb7c"
}
},
{
"frame": 0.15,
"value": {
"__uuid__": "2f2bce67-0e39-45ee-aebf-b15e0d0c341f"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "77a2f890-6627-4699-9278-21d7365bd2c8"
}
},
{
"frame": 0.18333333333333332,
"value": {
"__uuid__": "ce3e8e33-4420-478b-a084-5a7f77d584b8"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "170bbc56-3cff-4a81-b48f-a037b9418758",
"subMetas": {}
}

View File

@ -0,0 +1,25 @@
{
"__type__": "cc.AnimationClip",
"_name": "InAirIdle1NoJump",
"_objFlags": 0,
"_native": "",
"_duration": 0.016666666666666666,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "ce3e8e33-4420-478b-a084-5a7f77d584b8"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "996c218a-9e18-441e-b0f8-e61c5c991b9e",
"subMetas": {}
}

View File

@ -0,0 +1,49 @@
{
"__type__": "cc.AnimationClip",
"_name": "LayDown1",
"_objFlags": 0,
"_native": "",
"_duration": 0.08333333333333333,
"sample": 60,
"speed": 0.3,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "e1f19129-8517-47fa-814d-b79c8abcd549"
}
},
{
"frame": 0.016666666666666666,
"value": {
"__uuid__": "b4127445-a13e-4244-9656-92033a5abcca"
}
},
{
"frame": 0.03333333333333333,
"value": {
"__uuid__": "64a8e39c-95ed-4181-b777-b05434d1fa6d"
}
},
{
"frame": 0.05,
"value": {
"__uuid__": "5d1458ae-8812-4c4a-a01f-0a2b901ed3f3"
}
},
{
"frame": 0.06666666666666667,
"value": {
"__uuid__": "ac0b9039-5caf-4bc1-bf29-2a733fbb7898"
}
}
]
}
}
},
"events": []
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "0978395c-8947-4830-9f68-58fadcbe5c63",
"subMetas": {}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

View File

@ -0,0 +1,12 @@
{
"ver": "2.3.3",
"uuid": "8d4cf214-f71c-4237-bdc1-ca0069909248",
"type": "raw",
"wrapMode": "clamp",
"filterMode": "bilinear",
"premultiplyAlpha": false,
"genMipmaps": false,
"packable": true,
"platformSettings": {},
"subMetas": {}
}

View File

@ -0,0 +1,129 @@
{
"__type__": "cc.AnimationClip",
"_name": "Walking",
"_objFlags": 0,
"_native": "",
"_duration": 0.6666666666666666,
"sample": 60,
"speed": 1,
"wrapMode": 2,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "60f04127-1580-4dda-828b-57d82e991398"
}
},
{
"frame": 0.05,
"value": {
"__uuid__": "37eea7f3-5a1d-4172-be38-9b492399ef44"
}
},
{
"frame": 0.1,
"value": {
"__uuid__": "0e2ad2ed-2b21-4e14-9607-5a341d9ed7e4"
}
},
{
"frame": 0.15,
"value": {
"__uuid__": "e1625aba-a6fc-4883-a696-2d9d56d3050d"
}
},
{
"frame": 0.2,
"value": {
"__uuid__": "86f805a2-a27c-4c96-8e11-acc448b0bdd0"
}
},
{
"frame": 0.25,
"value": {
"__uuid__": "63bcaa39-1cc5-461f-a175-0d7a0abd3510"
}
},
{
"frame": 0.3,
"value": {
"__uuid__": "691383d5-9199-4bd5-9803-403216331d7f"
}
},
{
"frame": 0.35,
"value": {
"__uuid__": "602818c2-1242-4a24-87ba-77f6accccbc3"
}
},
{
"frame": 0.4,
"value": {
"__uuid__": "650b8ccb-2a82-4972-bb5b-79a2cb4a3503"
}
},
{
"frame": 0.45,
"value": {
"__uuid__": "b264bcc2-71c7-45a6-8c94-be049bb0d163"
}
},
{
"frame": 0.5,
"value": {
"__uuid__": "3e2e26f7-007a-4130-8d90-154dfcde96d3"
}
},
{
"frame": 0.55,
"value": {
"__uuid__": "275387d0-70fd-47f5-b82e-14165a14c45a"
}
},
{
"frame": 0.6,
"value": {
"__uuid__": "9cf71d99-7262-4a6c-8c27-06167d841a24"
}
},
{
"frame": 0.65,
"value": {
"__uuid__": "60f04127-1580-4dda-828b-57d82e991398"
}
}
]
}
}
},
"events": [
{
"frame": 0,
"func": "",
"params": []
},
{
"frame": 0,
"func": "",
"params": []
},
{
"frame": 0,
"func": "",
"params": []
},
{
"frame": 0,
"func": "",
"params": []
},
{
"frame": 0,
"func": "",
"params": []
}
]
}

View File

@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "768d591b-4467-4a8f-95bf-4f7fcc3835b6",
"subMetas": {}
}

View File

@ -1,18 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<map version="1.2" tiledversion="1.2.3" orientation="orthogonal" renderorder="right-down" width="128" height="128" tilewidth="16" tileheight="16" infinite="0" nextlayerid="4" nextobjectid="104"> <map version="1.2" tiledversion="1.2.3" orientation="orthogonal" renderorder="right-down" width="128" height="128" tilewidth="16" tileheight="16" infinite="0" nextlayerid="4" nextobjectid="107">
<tileset firstgid="1" source="tiles0.tsx"/> <tileset firstgid="1" source="tiles0.tsx"/>
<tileset firstgid="65" source="tiles1.tsx"/> <tileset firstgid="65" source="tiles1.tsx"/>
<tileset firstgid="129" source="tiles2.tsx"/> <tileset firstgid="129" source="tiles2.tsx"/>
<layer id="2" name="Ground" width="128" height="128"> <layer id="2" name="Ground" width="128" height="128">
<data encoding="base64" compression="zlib"> <data encoding="base64" compression="zlib">
eJzt3MFq1EAcwOFli5ceikUU9NyD4Et47qle9OapLyB60ZMgvoIH776nWUggxkySnZ1kksx3+KC0S2jn9082E5ZeHQ6HKwAAAAAAAAAAAAAAAAAAiHBdy/17sJzHlqb/Y4Fyd8jZ/1hr+n+s3Accd0j/f/uH6L8/7f73I/2vV9BK//n699F/305/+/Na39p0rwm5W+m/bP8S6K9/7g45+z+LkLuZ/nn772kG9Nc/d4ct9l8T/cvs7/wvu3+KGdB/u5z/+utfbv8UM6B/+rVtnin+uNDc577++uu/jf4va/qX07/du/21/vrrn6//0xEp+7fpn7f/WPe+/r8q78/U9Hf/t57+U9s/HNz/b8XU/lPbH/XflLH+fY3fVG4C7bv9ux7OpH++/n3tb1q63efo/6Jyp/9q+g9d99v9U9N/vf1j7hHPpf/y/afMQHctU/Ydk7L/cQUt1tg/1RoPiWmv/376nzMjcxxb/23MwFz0L3sG9Ne/5P651z+30vd/udc/N/3Lpn/Z9C+b/mXTv2z6l03/sulfNv3Lpn/Z9C+b/mXTv2z6l03/9boL0H8d/ef6nFCo+xxzoP96+t+OeBKg/z7656B/fG/9t23t93/6769/6L29+fm7hNrHDF2ffP573Ida93rffD/3OXzp+a//sKH3/C3177tG6D8stOc6Gduv3U44fs72Tf/cHdbav+n8+gKX7s/nbK///Guf4hnNXO31T/88LdQ/xwxM2SOU3n+p87+x1HOiqXtE/Zft38zAnP/v5ZxnBPr/L+X9/ND+IeR7hJj2+o/3u3QOYvrHzMDPSPpPEzsHrw5x/f9UvgRaN75VPtWv1X/e/rGq/m9j+598PoT7f2297tTyd03/6f3J3wEAAAAAAAAAAAAAAABY3l9OOPTh eJzt3LGK1EAcwOGwYnPF4SEKWl8h+BLWV52Ndlb3AqKNVoL4ADYW9r6nu5BAXHeS7OwkM8l8xQfH3RLu8vtPLhOWfdQ0zSMAAAAAAAAAAAAAAAAAAIhw1cr9e7Cch56u/0OFcnfI2X/X6vp/2LsL2G2Q/v/2D9F/e/r970b6XxXQSv/5+p+i/7Yd/vZnrVPn5viakLuV/sv2r4H++ufukLP/0wi5m+mft/+WZkB//XN3WGP/kuhfZ3/rv+7+KWZA//Wy/vXXv97+KWZA//Tntnum+P1Cc699/fXXfx39X7T0r6d/v3f/a/311z9f/ycjUvbv0z9v/7Hup/r/2nt3pq6/+79y+k9tf9+4/1+Lqf2ntt/pvypj/U81fr13HWh/3P/Y/Zn0z9f/VPvrnuPuc/R/vnerfzH9h677/f6p6V9u/5h7xHPpv3z/KTNwfC5T9h2Tsv+ugBYl9k91jofEtNd/O/3PmZE5jq3/5TPwJ1GLVMfRP13/KTOg/zrpr39J3XL1z91Bf/3X3P9nQvqvr/+a6V83/eumf930r5v+ddO/bvrXTf+66V83/eumf930r5v+5boN0L+M/nO9XzTUfY450L+c/jcjHgfov43+Oegf31v/dSv9/k//7fUP/W/vfv42of4xQ9cn7/8e9751fL3vvp97DV+6/vUfNvQ/f039T10j9B8W2nMdjO3XbiYcP2f7rn/uDqX27zq/usCl+/M52+s//7lP8Yxmrvb6p3+eFuqfYwam7BFq77/U+u8s9Zxo6h5R/2X7dzMw5+e9nPOMQP//pbyfH9o/hHyLENNe//F+l85BTP+YGfgRSf9pYufgZRPX//BZAJ8DrTtf9z62r9V/3v6x9v3fxPY/+NSE+3/pve7Q8ndL/+n9yd8BAAAAAAAAAAAAAAAAWN5f3AoF6w==
</data> </data>
</layer> </layer>
<objectgroup id="1" name="PlayerStartingPos"> <objectgroup id="1" name="PlayerStartingPos">
<object id="135" x="901" y="1579"> <object id="135" x="1040.33" y="1081">
<point/> <point/>
</object> </object>
<object id="137" x="861" y="1540"> <object id="137" x="1134.67" y="1081.67">
<point/> <point/>
</object> </object>
</objectgroup> </objectgroup>
@ -200,5 +200,20 @@
<property name="boundary_type" value="barrier"/> <property name="boundary_type" value="barrier"/>
</properties> </properties>
</object> </object>
<object id="104" x="928" y="1088" width="304" height="16">
<properties>
<property name="boundary_type" value="barrier"/>
</properties>
</object>
<object id="105" x="928" y="1008" width="16" height="96">
<properties>
<property name="boundary_type" value="barrier"/>
</properties>
</object>
<object id="106" x="1216" y="1008" width="16" height="96">
<properties>
<property name="boundary_type" value="barrier"/>
</properties>
</object>
</objectgroup> </objectgroup>
</map> </map>

View File

@ -15,7 +15,7 @@ message PlayerDownsync {
int32 speed = 8; // this is the instantaneous scalar attribute of a character, different from but will be accounted in "velX" and "velY" int32 speed = 8; // this is the instantaneous scalar attribute of a character, different from but will be accounted in "velX" and "velY"
int32 battleState = 9; int32 battleState = 9;
int32 joinIndex = 10; int32 joinIndex = 10;
double colliderRadius = 11; int32 colliderRadius = 11;
bool removed = 12; bool removed = 12;
int32 score = 13; int32 score = 13;
int32 lastMoveGmtMillis = 14; int32 lastMoveGmtMillis = 14;
@ -24,10 +24,14 @@ message PlayerDownsync {
int32 maxHp = 17; int32 maxHp = 17;
int32 characterState = 18; int32 characterState = 18;
bool inAir = 19; // by design a standalone field only inferred by the collision result of "applyInputFrameDownsyncDynamicsOnSingleRenderFrame" instead of "characterState", because we need check the transition for "characterState" from this field, i.e. "inAir (prev -> curr)" bool inAir = 19; // by design a standalone field only inferred by the collision result of "applyInputFrameDownsyncDynamicsOnSingleRenderFrame" instead of "characterState", because we need check the transition for "characterState" from this field, i.e. "inAir (prev -> curr)"
int32 framesInChState = 20; // number of frames elapsed in the current character state
int32 activeSkillId = 21;
int32 activeSkillHit = 22;
int32 framesInvinsible = 23;
string name = 20; string name = 997;
string displayName = 21; string displayName = 998;
string avatar = 22; string avatar = 999;
} }
message InputFrameDecoded { message InputFrameDecoded {
@ -84,67 +88,47 @@ message MeleeBullet {
// ALL lengths are in world coordinate // ALL lengths are in world coordinate
// for offender // for offender
int32 battleLocalId = 1; int32 originatedRenderFrameId = 1;
int32 startupFrames = 2; int32 offenderJoinIndex = 2;
int32 activeFrames = 3;
int32 recoveryFrames = 4;
int32 recoveryFramesOnBlock = 5;
int32 recoveryFramesOnHit = 6;
double hitboxOffset = 7;
int32 originatedRenderFrameId = 8;
// for defender int32 startupFrames = 3;
int32 hitStunFrames = 9; int32 cancellableStFrame = 4;
int32 blockStunFrames = 10; int32 cancellableEdFrame = 5;
double pushback = 11; int32 activeFrames = 6;
int32 releaseTriggerType = 12; // 1: rising-edge, 2: falling-edge int32 hitStunFrames = 7;
int32 damage = 13; int32 blockStunFrames = 8;
int32 pushbackVelX = 9;
int32 pushbackVelY = 10;
int32 damage = 11;
int32 offenderJoinIndex = 14; int32 selfLockVelX = 12;
int32 offenderPlayerId = 15; int32 selfLockVelY = 13;
double hitboxSizeX = 16; int32 hitboxOffsetX = 14;
double hitboxSizeY = 17; int32 hitboxOffsetY = 15;
int32 hitboxSizeX = 16;
int32 hitboxSizeY = 17;
double selfMoveforwardX = 18; bool blowUp = 18;
double selfMoveforwardY = 19;
} }
message BattleColliderInfo { message BattleColliderInfo {
string stageName = 1; string stageName = 1;
int32 stageDiscreteW = 2;
int32 stageDiscreteH = 3;
int32 stageTileW = 4;
int32 stageTileH = 5;
int32 intervalToPing = 6; int32 intervalToPing = 2;
int32 willKickIfInactiveFor = 7; int32 willKickIfInactiveFor = 3;
int32 boundRoomId = 8; int32 boundRoomId = 4;
int32 battleDurationFrames = 9; int64 battleDurationNanos = 5;
int64 battleDurationNanos = 10; int32 inputFrameUpsyncDelayTolerance = 6;
int32 serverFps = 11; int32 maxChasingRenderFramesPerUpdate = 7;
int32 inputDelayFrames = 12; // in the count of render frames double rollbackEstimatedDtMillis = 8;
uint32 inputScaleFrames = 13; // inputDelayedAndScaledFrameId = ((originalFrameId - InputDelayFrames) >> InputScaleFrames) int64 rollbackEstimatedDtNanos = 9;
int32 nstDelayFrames = 14; // network-single-trip delay in the count of render frames, proposed to be (InputDelayFrames >> 1) because we expect a round-trip delay to be exactly "InputDelayFrames"
int32 inputFrameUpsyncDelayTolerance = 15;
int32 maxChasingRenderFramesPerUpdate = 16;
int32 playerBattleState = 17;
double rollbackEstimatedDtMillis = 18;
int64 rollbackEstimatedDtNanos = 19;
double worldToVirtualGridRatio = 20; int32 renderCacheSize = 10;
double virtualGridToWorldRatio = 21; double spaceOffsetX = 11;
double spaceOffsetY = 12;
int32 spAtkLookupFrames = 22; int32 collisionMinStep = 13;
int32 renderCacheSize = 23;
double snapIntoPlatformOverlap = 24;
double snapIntoPlatformThreshold = 25;
int32 jumpingInitVelY = 26;
int32 gravityX = 27;
int32 gravityY = 28;
int32 collisionMinStep = 29;
bool frameDataLoggingEnabled = 999; bool frameDataLoggingEnabled = 999;
} }
@ -156,5 +140,5 @@ message RoomDownsyncFrame {
repeated MeleeBullet meleeBullets = 4; // I don't know how to mimic inheritance/composition in protobuf by far, thus using an array for each type of bullet as a compromise repeated MeleeBullet meleeBullets = 4; // I don't know how to mimic inheritance/composition in protobuf by far, thus using an array for each type of bullet as a compromise
uint64 backendUnconfirmedMask = 5; // Indexed by "joinIndex", same compression concern as stated in InputFrameDownsync uint64 backendUnconfirmedMask = 5; // Indexed by "joinIndex", same compression concern as stated in InputFrameDownsync
bool shouldForceResync = 6; bool shouldForceResync = 6;
map<int32, int32> playerOpPatternToSkillId = 7; repeated int32 speciesIdList = 7;
} }

View File

@ -33,14 +33,14 @@
"_active": true, "_active": true,
"_components": [ "_components": [
{ {
"__id__": 23 "__id__": 21
}, },
{ {
"__id__": 24 "__id__": 22
} }
], ],
"_prefab": { "_prefab": {
"__id__": 25 "__id__": 23
}, },
"_opacity": 255, "_opacity": 255,
"_color": { "_color": {
@ -489,16 +489,13 @@
"__id__": 12 "__id__": 12
}, },
{ {
"__id__": 15 "__id__": 16
},
{
"__id__": 18
} }
], ],
"_active": true, "_active": true,
"_components": [], "_components": [],
"_prefab": { "_prefab": {
"__id__": 22 "__id__": 20
}, },
"_opacity": 255, "_opacity": 255,
"_color": { "_color": {
@ -549,253 +546,23 @@
}, },
{ {
"__type__": "cc.Node", "__type__": "cc.Node",
"_name": "SoldierWaterGhost", "_name": "MonkGirl",
"_objFlags": 0, "_objFlags": 0,
"_parent": { "_parent": {
"__id__": 11 "__id__": 11
}, },
"_children": [], "_children": [],
"_active": false, "_active": true,
"_components": [ "_components": [
{ {
"__id__": 13 "__id__": 13
}
],
"_prefab": {
"__id__": 14
},
"_opacity": 255,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_contentSize": {
"__type__": "cc.Size",
"width": 0,
"height": 0
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_trs": {
"__type__": "TypedArray",
"ctor": "Float64Array",
"array": [
0,
-24,
0,
0,
0,
0,
1,
1,
1,
1
]
},
"_eulerAngles": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_skewX": 0,
"_skewY": 0,
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": ""
},
{
"__type__": "dragonBones.ArmatureDisplay",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 12
},
"_enabled": true,
"_materials": [
{
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
}
],
"_armatureName": "SoldierWaterGhost",
"_animationName": "Idle1",
"_preCacheMode": 0,
"_cacheMode": 0,
"playTimes": -1,
"premultipliedAlpha": false,
"_armatureKey": "2ae76843-1f61-48cf-bbfb-384c0dcf77e1#e9e703e9-3589-4713-b889-28b23406d220",
"_accTime": 0,
"_playCount": 0,
"_frameCache": null,
"_curFrame": null,
"_playing": false,
"_armatureCache": null,
"_N$dragonAsset": {
"__uuid__": "2ae76843-1f61-48cf-bbfb-384c0dcf77e1"
},
"_N$dragonAtlasAsset": {
"__uuid__": "e9e703e9-3589-4713-b889-28b23406d220"
},
"_N$_defaultArmatureIndex": 0,
"_N$_animationIndex": 8,
"_N$_defaultCacheMode": 0,
"_N$timeScale": 1,
"_N$debugBones": false,
"_N$enableBatch": false,
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__uuid__": "59bff7a2-23e1-4d69-bce7-afb37eae196a"
},
"fileId": "42Rmp/YOdMOYWzJwr3ET1h",
"sync": false
},
{
"__type__": "cc.Node",
"_name": "SoldierFireGhost",
"_objFlags": 0,
"_parent": {
"__id__": 11
},
"_children": [],
"_active": false,
"_components": [
{
"__id__": 16
}
],
"_prefab": {
"__id__": 17
},
"_opacity": 255,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_contentSize": {
"__type__": "cc.Size",
"width": 0,
"height": 0
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_trs": {
"__type__": "TypedArray",
"ctor": "Float64Array",
"array": [
0,
-24,
0,
0,
0,
0,
1,
1,
1,
1
]
},
"_eulerAngles": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_skewX": 0,
"_skewY": 0,
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": ""
},
{
"__type__": "dragonBones.ArmatureDisplay",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 15
},
"_enabled": true,
"_materials": [
{
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
}
],
"_armatureName": "SoldierFireGhost",
"_animationName": "Idle1",
"_preCacheMode": 0,
"_cacheMode": 0,
"playTimes": -1,
"premultipliedAlpha": false,
"_armatureKey": "36230012-8df3-4e85-afad-76ec47d0e4d7#4a9187d5-a9ad-4464-a03c-d2f3cc277051",
"_accTime": 0,
"_playCount": 0,
"_frameCache": null,
"_curFrame": null,
"_playing": false,
"_armatureCache": null,
"_N$dragonAsset": {
"__uuid__": "36230012-8df3-4e85-afad-76ec47d0e4d7"
},
"_N$dragonAtlasAsset": {
"__uuid__": "4a9187d5-a9ad-4464-a03c-d2f3cc277051"
},
"_N$_defaultArmatureIndex": 0,
"_N$_animationIndex": 8,
"_N$_defaultCacheMode": 0,
"_N$timeScale": 1,
"_N$debugBones": false,
"_N$enableBatch": false,
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__uuid__": "59bff7a2-23e1-4d69-bce7-afb37eae196a"
},
"fileId": "3b2LJFABVL7ozO2U81FC4U",
"sync": false
},
{
"__type__": "cc.Node",
"_name": "UltramanTiga",
"_objFlags": 0,
"_parent": {
"__id__": 11
},
"_children": [],
"_active": false,
"_components": [
{
"__id__": 19
}, },
{ {
"__id__": 20 "__id__": 14
} }
], ],
"_prefab": { "_prefab": {
"__id__": 21 "__id__": 15
}, },
"_opacity": 255, "_opacity": 255,
"_color": { "_color": {
@ -813,21 +580,21 @@
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
"x": 0.5, "x": 0.5,
"y": 0.5 "y": 0
}, },
"_trs": { "_trs": {
"__type__": "TypedArray", "__type__": "TypedArray",
"ctor": "Float64Array", "ctor": "Float64Array",
"array": [ "array": [
0, 0,
0, -24,
0, 0,
0, 0,
0, 0,
0, 0,
1, 1,
0.7, 0.7,
0.7, 0.6,
1 1
] ]
}, },
@ -849,31 +616,49 @@
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 18 "__id__": 12
}, },
"_enabled": true, "_enabled": true,
"_defaultClip": null, "_defaultClip": null,
"_clips": [ "_clips": [
{ {
"__uuid__": "252b321f-81f4-485c-85bd-ea44d298cb76" "__uuid__": "a0d8b9a6-7bf7-4cf5-aecb-54b2908cbfb3"
}, },
{ {
"__uuid__": "f51bb583-0010-48f3-a6a1-451a78ac2d65" "__uuid__": "768d591b-4467-4a8f-95bf-4f7fcc3835b6"
}, },
{ {
"__uuid__": "c738236a-0702-45f8-aa38-99457b051997" "__uuid__": "c3d4f508-b3a9-4deb-b2d1-8ddc7bf22e0c"
}, },
{ {
"__uuid__": "c69bcceb-d7d1-4e33-9623-e2a374a0a6b6" "__uuid__": "1ec27664-cd2a-4c7a-838f-953a7713ceca"
}, },
{ {
"__uuid__": "43dbf141-be76-48c3-bdef-29233ccbe30d" "__uuid__": "996c218a-9e18-441e-b0f8-e61c5c991b9e"
}, },
{ {
"__uuid__": "8710591c-3f5e-4911-83e7-42cc18be6af9" "__uuid__": "170bbc56-3cff-4a81-b48f-a037b9418758"
}, },
{ {
"__uuid__": "c69bcceb-d7d1-4e33-9623-e2a374a0a6b6" "__uuid__": "555d837b-4a68-4a28-b9ff-e97e393d1a05"
},
{
"__uuid__": "ee0b89e9-89f0-451e-a6ef-d41d64d3d67a"
},
{
"__uuid__": "f87054fd-328f-4001-a8ff-b4c049cc1e6d"
},
{
"__uuid__": "0978395c-8947-4830-9f68-58fadcbe5c63"
},
{
"__uuid__": "7cb4d395-c68f-4643-9e2e-8cb8e200d3a5"
},
{
"__uuid__": "e6e1d62a-de7d-4fe7-858c-2d2725b7c2e8"
},
{
"__uuid__": "e8247e2a-1b5b-4618-86f8-224b25246b55"
} }
], ],
"playOnLoad": false, "playOnLoad": false,
@ -884,7 +669,7 @@
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 18 "__id__": 12
}, },
"_enabled": true, "_enabled": true,
"_materials": [ "_materials": [
@ -907,7 +692,7 @@
"_fillRange": 0, "_fillRange": 0,
"_isTrimmedMode": true, "_isTrimmedMode": true,
"_atlas": { "_atlas": {
"__uuid__": "5d522f7b-359b-4f38-ac35-55fdbee56cae" "__uuid__": "725c90f9-56f8-48ea-9159-4d2949cd3ce0"
}, },
"_id": "" "_id": ""
}, },
@ -919,7 +704,170 @@
"asset": { "asset": {
"__uuid__": "59bff7a2-23e1-4d69-bce7-afb37eae196a" "__uuid__": "59bff7a2-23e1-4d69-bce7-afb37eae196a"
}, },
"fileId": "17JdhftghBYr81MfV9i6cy", "fileId": "6cGpRwF5ZPjpJdtoqbrnta",
"sync": false
},
{
"__type__": "cc.Node",
"_name": "KnifeGirl",
"_objFlags": 0,
"_parent": {
"__id__": 11
},
"_children": [],
"_active": false,
"_components": [
{
"__id__": 17
},
{
"__id__": 18
}
],
"_prefab": {
"__id__": 19
},
"_opacity": 255,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_contentSize": {
"__type__": "cc.Size",
"width": 1425,
"height": 1024
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0
},
"_trs": {
"__type__": "TypedArray",
"ctor": "Float64Array",
"array": [
0,
-24,
0,
0,
0,
0,
1,
0.7,
0.5,
1
]
},
"_eulerAngles": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_skewX": 0,
"_skewY": 0,
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": ""
},
{
"__type__": "cc.Animation",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 16
},
"_enabled": true,
"_defaultClip": null,
"_clips": [
{
"__uuid__": "5afe50e0-d03f-4d48-a7c0-a350c36e14d4"
},
{
"__uuid__": "f7f60d3f-32c0-4b56-901e-6eec9f73fa83"
},
{
"__uuid__": "829b17c6-9365-4e97-b14f-fa266bd5ecbe"
},
{
"__uuid__": "2a50c853-000b-46f3-ae59-1dfb793de814"
},
{
"__uuid__": "bc461a02-4d1a-46a4-9f3c-7370adc6c1c8"
},
{
"__uuid__": "c4ddf3ea-2292-4511-a320-5486934ac361"
},
{
"__uuid__": "0e7468cc-b90d-4f68-91ce-0be126b406dd"
},
{
"__uuid__": "c86debde-118b-46b8-b483-f5ccec337315"
},
{
"__uuid__": "0892a3ea-9da1-4157-825b-0c8ef1c73eeb"
},
{
"__uuid__": "0a6478a6-f4cc-47cb-a1c6-761cf9141eb2"
},
{
"__uuid__": "7d69868f-d474-4d86-a262-01f1cdd49021"
},
{
"__uuid__": "52b8e47d-715c-4c12-a2c9-6f553e14dc42"
},
{
"__uuid__": "9b500cb0-8048-4715-81db-cc975c914225"
}
],
"playOnLoad": false,
"_id": ""
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 16
},
"_enabled": true,
"_materials": [
{
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
}
],
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
"_spriteFrame": null,
"_type": 0,
"_sizeMode": 1,
"_fillType": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_atlas": {
"__uuid__": "579bc0c1-f5e2-4a5d-889b-9d567e53b0e6"
},
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__uuid__": "59bff7a2-23e1-4d69-bce7-afb37eae196a"
},
"fileId": "bdCx1wrTtJ1KaGHUmgL7iA",
"sync": false "sync": false
}, },
{ {

View File

@ -440,7 +440,7 @@
"array": [ "array": [
0, 0,
0, 0,
216.6771387800957, 217.36724690689908,
0, 0,
0, 0,
0, 0,

View File

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

View File

@ -5,33 +5,17 @@ window.ATK_CHARACTER_STATE = {
Walking: [1, "Walking"], Walking: [1, "Walking"],
Atk1: [2, "Atk1"], Atk1: [2, "Atk1"],
Atked1: [3, "Atked1"], Atked1: [3, "Atked1"],
InAirIdle1: [4, "InAirIdle1"], InAirIdle1NoJump: [4, "InAirIdle1NoJump"],
InAirAtk1: [5, "Atk1"], InAirIdle1ByJump: [5, "InAirIdle1ByJump"], // The cycling part of it would be exactly "InAirIdle1NoJump"
InAirAtked1: [6, "Atked1"], InAirAtk1: [6, "InAirAtk1"],
InAirAtked1: [7, "InAirAtked1"],
BlownUp1: [8, "BlownUp1"],
LayDown1: [9, "LayDown1"], // The last frame of "LayDown1" should have a simliar boundingbox with the first frame of "GetUp1", otherwise the animation would seem odd
GetUp1: [10, "GetUp1"],
Atk2: [11, "Atk2"],
Atk3: [12, "Atk3"],
}; };
window.toInAirConjugate = function(foo) {
switch (foo) {
case window.ATK_CHARACTER_STATE.Idle1[0]:
case window.ATK_CHARACTER_STATE.Walking[0]:
return window.ATK_CHARACTER_STATE.InAirIdle1[0];
case window.ATK_CHARACTER_STATE.Atk1[0]:
return window.ATK_CHARACTER_STATE.InAirAtk1[0];
case window.ATK_CHARACTER_STATE.Atked1[0]:
return window.ATK_CHARACTER_STATE.InAirAtked1[0];
case window.ATK_CHARACTER_STATE.InAirIdle1[0]:
return window.ATK_CHARACTER_STATE.Idle1[0];
case window.ATK_CHARACTER_STATE.InAirAtk1[0]:
return window.ATK_CHARACTER_STATE.Atk1[0];
case window.ATK_CHARACTER_STATE.InAirAtked1[0]:
return window.ATK_CHARACTER_STATE.Atked1[0];
default:
console.warn(`Invalid characterState ${foo} received, no in air conjugate is available!`);
return null;
}
}
window.ATK_CHARACTER_STATE_ARR = []; window.ATK_CHARACTER_STATE_ARR = [];
for (let k in window.ATK_CHARACTER_STATE) { for (let k in window.ATK_CHARACTER_STATE) {
window.ATK_CHARACTER_STATE_ARR.push(window.ATK_CHARACTER_STATE[k]); window.ATK_CHARACTER_STATE_ARR.push(window.ATK_CHARACTER_STATE[k]);
@ -40,12 +24,18 @@ for (let k in window.ATK_CHARACTER_STATE) {
window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET = new Set(); window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET = new Set();
window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.add(window.ATK_CHARACTER_STATE.Idle1[0]); window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.add(window.ATK_CHARACTER_STATE.Idle1[0]);
window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.add(window.ATK_CHARACTER_STATE.Walking[0]); window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.add(window.ATK_CHARACTER_STATE.Walking[0]);
window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.add(window.ATK_CHARACTER_STATE.InAirIdle1[0]); window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.add(window.ATK_CHARACTER_STATE.InAirIdle1NoJump[0]);
window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.add(window.ATK_CHARACTER_STATE.InAirIdle1ByJump[0]);
window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.add(window.ATK_CHARACTER_STATE.BlownUp1[0]);
window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.add(window.ATK_CHARACTER_STATE.LayDown1[0]);
window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.add(window.ATK_CHARACTER_STATE.GetUp1[0]);
window.ATK_CHARACTER_STATE_IN_AIR_SET = new Set(); window.ATK_CHARACTER_STATE_IN_AIR_SET = new Set();
window.ATK_CHARACTER_STATE_IN_AIR_SET.add(window.ATK_CHARACTER_STATE.InAirIdle1[0]); window.ATK_CHARACTER_STATE_IN_AIR_SET.add(window.ATK_CHARACTER_STATE.InAirIdle1NoJump[0]);
window.ATK_CHARACTER_STATE_IN_AIR_SET.add(window.ATK_CHARACTER_STATE.InAirIdle1ByJump[0]);
window.ATK_CHARACTER_STATE_IN_AIR_SET.add(window.ATK_CHARACTER_STATE.InAirAtk1[0]); window.ATK_CHARACTER_STATE_IN_AIR_SET.add(window.ATK_CHARACTER_STATE.InAirAtk1[0]);
window.ATK_CHARACTER_STATE_IN_AIR_SET.add(window.ATK_CHARACTER_STATE.InAirAtked1[0]); window.ATK_CHARACTER_STATE_IN_AIR_SET.add(window.ATK_CHARACTER_STATE.InAirAtked1[0]);
window.ATK_CHARACTER_STATE_IN_AIR_SET.add(window.ATK_CHARACTER_STATE.BlownUp1[0]);
/* /*
Kindly note that the use of dragonBones anim is an informed choice for the feasibility of "gotoAndPlayByFrame", which is a required feature by "Map.rollbackAndChase". You might find that "cc.Animation" -- the traditional frame anim -- can also suffice this requirement, yet if we want to develop 3D frontend in the future, working with skeletal anim will make a smoother transition. Kindly note that the use of dragonBones anim is an informed choice for the feasibility of "gotoAndPlayByFrame", which is a required feature by "Map.rollbackAndChase". You might find that "cc.Animation" -- the traditional frame anim -- can also suffice this requirement, yet if we want to develop 3D frontend in the future, working with skeletal anim will make a smoother transition.
@ -80,19 +70,19 @@ cc.Class({
this.animComp = this.effAnimNode.getComponent(dragonBones.ArmatureDisplay); this.animComp = this.effAnimNode.getComponent(dragonBones.ArmatureDisplay);
if (!this.animComp) { if (!this.animComp) {
this.animComp = this.effAnimNode.getComponent(cc.Animation); this.animComp = this.effAnimNode.getComponent(cc.Animation);
} }
this.effAnimNode.active = true; this.effAnimNode.active = true;
}, },
updateCharacterAnim(rdfPlayer, prevRdfPlayer, forceAnimSwitch) { 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. // 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.
// Update directions // Update directions
if (this.animComp && this.animComp.node) { if (this.animComp && this.animComp.node) {
if (0 > rdfPlayer.DirX) { if (0 > rdfPlayer.DirX) {
this.animComp.node.scaleX = (-1.0); this.animNode.scaleX = (-1.0);
} else if (0 < rdfPlayer.DirX) { } else if (0 < rdfPlayer.DirX) {
this.animComp.node.scaleX = (1.0); this.animNode.scaleX = (1.0);
} }
} }
@ -117,29 +107,25 @@ cc.Class({
} }
if (this.animComp instanceof dragonBones.ArmatureDisplay) { if (this.animComp instanceof dragonBones.ArmatureDisplay) {
this._interruptPlayingAnimAndPlayNewAnimDragonBones(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl, playingAnimName); this._interruptPlayingAnimAndPlayNewAnimDragonBones(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl, playingAnimName, chConfig);
} else { } else {
this._interruptPlayingAnimAndPlayNewAnimFrameAnim(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, playingAnimName); this._interruptPlayingAnimAndPlayNewAnimFrameAnim(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, playingAnimName, chConfig);
} }
}, },
_interruptPlayingAnimAndPlayNewAnimDragonBones(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl, playingAnimName) { _interruptPlayingAnimAndPlayNewAnimDragonBones(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl, playingAnimName, chConfig) {
if (window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) { if (window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) {
// No "framesToRecover" // No "framesToRecover"
// console.warn(`#DragonBones JoinIndex=${rdfPlayer.joinIndex}, ${playingAnimName} -> ${newAnimName}`); // console.warn(`#DragonBones JoinIndex=${rdfPlayer.joinIndex}, ${playingAnimName} -> ${newAnimName}`);
underlyingAnimationCtrl.gotoAndPlayByFrame(newAnimName, 0, -1); underlyingAnimationCtrl.gotoAndPlayByFrame(newAnimName, 0, -1);
} else { } else {
const animationData = underlyingAnimationCtrl._animations[newAnimName]; const animationData = underlyingAnimationCtrl._animations[newAnimName];
let fromAnimFrame = (animationData.frameCount - rdfPlayer.FramesToRecover); let frameIdxInAnim = rdfPlayer.FramesInChState;
if (fromAnimFrame < 0) { underlyingAnimationCtrl.gotoAndPlayByFrame(newAnimName, frameIdxInAnim, 1);
// For Atk1 or Atk2, it's possible that the "meleeBullet.recoveryFrames" is configured to be slightly larger than corresponding animation duration frames
fromAnimFrame = 0;
}
underlyingAnimationCtrl.gotoAndPlayByFrame(newAnimName, fromAnimFrame, 1);
} }
}, },
_interruptPlayingAnimAndPlayNewAnimFrameAnim(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, playingAnimName) { _interruptPlayingAnimAndPlayNewAnimFrameAnim(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, playingAnimName, chConfig) {
if (window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) { if (window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) {
// No "framesToRecover" // No "framesToRecover"
//console.warn(`#FrameAnim JoinIndex=${rdfPlayer.joinIndex}, ${playingAnimName} -> ${newAnimName}`); //console.warn(`#FrameAnim JoinIndex=${rdfPlayer.joinIndex}, ${playingAnimName} -> ${newAnimName}`);
@ -148,11 +134,11 @@ cc.Class({
} }
// The "playTimes" counterpart is managed by each "cc.AnimationClip.wrapMode", already preset in the editor. // 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 const targetClip = this.animComp.getClips()[newCharacterState]; // The clips follow the exact order in ATK_CHARACTER_STATE
let fromTime = (targetClip.duration - rdfPlayer.FramesToRecover / targetClip.sample); // TODO: Anyway to avoid using division here? let frameIdxInAnim = rdfPlayer.FramesInChState;
if (fromTime < 0) { if (window.ATK_CHARACTER_STATE.InAirIdle1ByJump == newCharacterState && null != chConfig) {
// For Atk1 or Atk2, it's possible that the "meleeBullet.recoveryFrames" is configured to be slightly larger than corresponding animation duration frames frameIdxInAnim = chConfig.InAirIdleFrameIdxTurningPoint + (frameIdxInAnim - chConfig.InAirIdleFrameIdxTurningPoint) % chConfig.InAirIdleFrameIdxTurnedCycle; // TODO: Anyway to avoid using division here?
fromTime = 0; }
} let fromTime = (frameIdxInAnim / targetClip.sample); // TODO: Anyway to avoid using division here?
this.animComp.play(newAnimName, fromTime); this.animComp.play(newAnimName, fromTime);
}, },

View File

@ -107,23 +107,6 @@ cc.Class({
return (0 == inputFrameId % 10); return (0 == inputFrameId % 10);
}, },
_convertToInputFrameId(renderFrameId, inputDelayFrames) {
if (renderFrameId < inputDelayFrames) return 0;
return ((renderFrameId - inputDelayFrames) >> this.inputScaleFrames);
},
_convertToFirstUsedRenderFrameId(inputFrameId, inputDelayFrames) {
return ((inputFrameId << this.inputScaleFrames) + inputDelayFrames);
},
_convertToLastUsedRenderFrameId(inputFrameId, inputDelayFrames) {
return ((inputFrameId << this.inputScaleFrames) + inputDelayFrames + (1 << this.inputScaleFrames) - 1);
},
shouldGenerateInputFrameUpsync(renderFrameId) {
return ((renderFrameId & ((1 << this.inputScaleFrames) - 1)) == 0);
},
_allConfirmed(confirmedList) { _allConfirmed(confirmedList) {
return (confirmedList + 1) == (1 << this.playerRichInfoDict.size); return (confirmedList + 1) == (1 << this.playerRichInfoDict.size);
}, },
@ -296,7 +279,7 @@ cc.Class({
self.collisionPlayerIndexPrefix = (1 << 17); // For tracking the movements of players self.collisionPlayerIndexPrefix = (1 << 17); // For tracking the movements of players
if (null != self.playerRichInfoDict) { if (null != self.playerRichInfoDict) {
self.playerRichInfoDict.forEach((playerRichInfo, playerId) => { self.playerRichInfoDict.forEach((playerRichInfo, playerId) => {
if (playerRichInfo.node.parent) { if (playerRichInfo.node && playerRichInfo.node.parent) {
playerRichInfo.node.parent.removeChild(playerRichInfo.node); playerRichInfo.node.parent.removeChild(playerRichInfo.node);
} }
}); });
@ -315,11 +298,7 @@ cc.Class({
self.selfPlayerInfo = null; // This field is kept for distinguishing "self" and "others". self.selfPlayerInfo = null; // This field is kept for distinguishing "self" and "others".
self.recentInputCache = gopkgs.NewRingBufferJs((self.renderCacheSize >> 1) + 1); self.recentInputCache = gopkgs.NewRingBufferJs((self.renderCacheSize >> 1) + 1);
const spaceW = self.stageDiscreteW * self.stageTileW; self.gopkgsCollisionSys = gopkgs.NewCollisionSpaceJs((self.spaceOffsetX << 1), (self.spaceOffsetY << 1), self.collisionMinStep, self.collisionMinStep);
const spaceH = self.stageDiscreteH * self.stageTileH;
self.spaceOffsetX = (spaceW >> 1);
self.spaceOffsetY = (spaceH >> 1);
self.gopkgsCollisionSys = gopkgs.NewCollisionSpaceJs(spaceW, spaceH, self.collisionMinStep, self.collisionMinStep);
self.gopkgsCollisionSysMap = {}; // [WARNING] Don't use "JavaScript Map" which could cause loss of type information when passing through Golang transpiled functions! self.gopkgsCollisionSysMap = {}; // [WARNING] Don't use "JavaScript Map" which could cause loss of type information when passing through Golang transpiled functions!
self.collisionBarrierIndexPrefix = (1 << 16); // For tracking the movements of barriers, though not yet actually used self.collisionBarrierIndexPrefix = (1 << 16); // For tracking the movements of barriers, though not yet actually used
@ -350,6 +329,56 @@ cc.Class({
} }
}, },
initDebugDrawers() {
const self = this;
if (self.showCriticalCoordinateLabels) {
const drawer1 = new cc.Node();
drawer1.setPosition(cc.v2(0, 0))
safelyAddChild(self.node, drawer1);
setLocalZOrder(drawer1, 999);
const g1 = drawer1.addComponent(cc.Graphics);
g1.lineWidth = 2;
self.g1 = g1;
const collisionSpaceObjs = gopkgs.GetCollisionSpaceObjsJs(self.gopkgsCollisionSys); // This step is slow according to Chrome profiling, and we only need draw it once for those static barriers
for (let k in collisionSpaceObjs) {
const body = collisionSpaceObjs[k];
let padding = 0;
if (null != body.Data && null != body.Data.JoinIndex) {
// character
if (1 == body.Data.JoinIndex) {
g1.strokeColor = cc.Color.BLUE;
} else {
g1.strokeColor = cc.Color.RED;
}
padding = self.snapIntoPlatformOverlap;
} else {
// barrier
g1.strokeColor = cc.Color.WHITE;
}
const points = body.Shape.Points;
const wpos = [body.X - self.spaceOffsetX, body.Y - self.spaceOffsetY];
g1.moveTo(wpos[0], wpos[1]);
const cnt = points.length;
for (let j = 0; j < cnt; j += 1) {
const x = wpos[0] + points[j][0],
y = wpos[1] + points[j][1];
g1.lineTo(x, y);
}
g1.lineTo(wpos[0], wpos[1]);
g1.stroke();
}
const drawer2 = new cc.Node();
drawer2.setPosition(cc.v2(0, 0))
safelyAddChild(self.node, drawer2);
setLocalZOrder(drawer2, 999);
const g2 = drawer2.addComponent(cc.Graphics);
g2.lineWidth = 2;
self.g2 = g2;
}
},
onLoad() { onLoad() {
const self = this; const self = this;
window.mapIns = self; window.mapIns = self;
@ -439,16 +468,6 @@ cc.Class({
mapNode.removeAllChildren(); mapNode.removeAllChildren();
self._resetCurrentMatch(); self._resetCurrentMatch();
if (self.showCriticalCoordinateLabels) {
const drawer = new cc.Node();
drawer.setPosition(cc.v2(0, 0))
safelyAddChild(self.node, drawer);
setLocalZOrder(drawer, 999);
const g = drawer.addComponent(cc.Graphics);
g.lineWidth = 2;
self.g = g;
}
tiledMapIns.tmxAsset = tmxAsset; tiledMapIns.tmxAsset = tmxAsset;
const newMapSize = tiledMapIns.getMapSize(); const newMapSize = tiledMapIns.getMapSize();
const newTileSize = tiledMapIns.getTileSize(); const newTileSize = tiledMapIns.getTileSize();
@ -477,37 +496,6 @@ cc.Class({
const newBarrierCollider = gopkgs.GenerateConvexPolygonColliderJs(gopkgsBoundary, self.spaceOffsetX, self.spaceOffsetY, gopkgsBarrier, "Barrier"); const newBarrierCollider = gopkgs.GenerateConvexPolygonColliderJs(gopkgsBoundary, self.spaceOffsetX, self.spaceOffsetY, gopkgsBarrier, "Barrier");
self.gopkgsCollisionSys.Add(newBarrierCollider); self.gopkgsCollisionSys.Add(newBarrierCollider);
if (false && self.showCriticalCoordinateLabels) {
for (let i = 0; i < boundaryObj.length; ++i) {
const barrierVertLabelNode = new cc.Node();
switch (i % 4) {
case 0:
barrierVertLabelNode.color = cc.Color.RED;
break;
case 1:
barrierVertLabelNode.color = cc.Color.GRAY;
break;
case 2:
barrierVertLabelNode.color = cc.Color.BLACK;
break;
default:
barrierVertLabelNode.color = cc.Color.MAGENTA;
break;
}
const wx = boundaryObj.anchor.x + boundaryObj[i].x,
wy = boundaryObj.anchor.y + boundaryObj[i].y;
barrierVertLabelNode.setPosition(cc.v2(wx, wy));
const barrierVertLabel = barrierVertLabelNode.addComponent(cc.Label);
barrierVertLabel.fontSize = 12;
barrierVertLabel.lineHeight = barrierVertLabel.fontSize + 1;
barrierVertLabel.string = `(${wx.toFixed(1)}, ${wy.toFixed(1)})`;
safelyAddChild(self.node, barrierVertLabelNode);
setLocalZOrder(barrierVertLabelNode, 5);
barrierVertLabelNode.active = true;
}
}
// console.log("Created barrier: ", newBarrierCollider); // console.log("Created barrier: ", newBarrierCollider);
++barrierIdCounter; ++barrierIdCounter;
const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter); const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter);
@ -517,7 +505,7 @@ cc.Class({
Object.assign(self.selfPlayerInfo, { Object.assign(self.selfPlayerInfo, {
Id: self.selfPlayerInfo.playerId Id: self.selfPlayerInfo.playerId
}); });
self.initDebugDrawers();
const reqData = window.pb.protos.WsReq.encode({ const reqData = window.pb.protos.WsReq.encode({
msgId: Date.now(), msgId: Date.now(),
act: window.UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK, act: window.UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK,
@ -590,13 +578,13 @@ cc.Class({
const jsPlayersArr = new Array().fill(null); const jsPlayersArr = new Array().fill(null);
for (let k in pbRdf.playersArr) { for (let k in pbRdf.playersArr) {
const pbPlayer = pbRdf.playersArr[k]; const pbPlayer = pbRdf.playersArr[k];
const jsPlayer = gopkgs.NewPlayerDownsyncJs(pbPlayer.id, pbPlayer.virtualGridX, pbPlayer.virtualGridY, pbPlayer.dirX, pbPlayer.dirY, pbPlayer.velX, pbPlayer.velY, pbPlayer.framesToRecover, pbPlayer.speed, pbPlayer.battleState, pbPlayer.characterState, pbPlayer.joinIndex, pbPlayer.hp, pbPlayer.maxHp, pbPlayer.inAir, pbPlayer.colliderRadius); const jsPlayer = gopkgs.NewPlayerDownsyncJs(pbPlayer.id, pbPlayer.virtualGridX, pbPlayer.virtualGridY, pbPlayer.dirX, pbPlayer.dirY, pbPlayer.velX, pbPlayer.velY, pbPlayer.framesToRecover, pbPlayer.framesInChState, pbPlayer.activeSkillId, pbPlayer.activeSkillHit, pbPlayer.framesInvinsible, pbPlayer.speed, pbPlayer.battleState, pbPlayer.characterState, pbPlayer.joinIndex, pbPlayer.hp, pbPlayer.maxHp, pbPlayer.colliderRadius, pbPlayer.inAir);
jsPlayersArr[k] = jsPlayer; jsPlayersArr[k] = jsPlayer;
} }
const jsMeleeBulletsArr = []; const jsMeleeBulletsArr = [];
for (let k in pbRdf.meleeBullets) { for (let k in pbRdf.meleeBullets) {
const pbBullet = pbRdf.meleeBullets[k]; const pbBullet = pbRdf.meleeBullets[k];
const jsBullet = gopkgs.NewMeleeBulletJs(pbBullet.battleLocalId, pbBullet.startupFrames, pbBullet.activeFrames, pbBullet.recoveryFrames, pbBullet.recoveryFramesOnBlock, pbBullet.recoveryFramesOnHit, pbBullet.hitStunFrames, pbBullet.blockStunFrames, pbBullet.releaseTriggerType, pbBullet.damage, pbBullet.offenderJoinIndex, pbBullet.offenderPlayerId, pbBullet.pushback, pbBullet.hitboxOffset, pbBullet.selfMoveforwardX, pbBullet.selfMoveforwardY, pbBullet.hitboxSizeX, pbBullet.hitboxSizeY); const jsBullet = gopkgs.NewMeleeBulletJs(pbBullet.originatedRenderFrameId, pbBullet.offenderJoinIndex, pbBullet.startupFrames, pbBullet.cancellableStFrame, pbBullet.cancellableEdFrame, pbBullet.activeFrames, pbBullet.hitStunFrames, pbBullet.blockStunFrames, pbBullet.pushbackVelX, pbBullet.pushbackVelY, pbBullet.damage, pbBullet.selfLockVelX, pbBullet.selfLockVelY, pbBullet.hitboxOffsetX, pbBullet.hitboxOffsetY, pbBullet.hitboxSizeX, pbBullet.hitboxSizeY, pbBullet.blowUp);
jsMeleeBulletsArr.push(jsBullet); jsMeleeBulletsArr.push(jsBullet);
} }
@ -638,7 +626,10 @@ cc.Class({
} }
// 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 == rdf.id || window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet)
self.playerOpPatternToSkillId = pbRdf.playerOpPatternToSkillId; 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.PlayersArr);
// Show the top status indicators for IN_BATTLE // Show the top status indicators for IN_BATTLE
@ -694,18 +685,19 @@ cc.Class({
equalPlayers(lhs, rhs) { equalPlayers(lhs, rhs) {
if (null == lhs || null == rhs) return false; if (null == lhs || null == rhs) return false;
if (lhs.virtualGridX != rhs.virtualGridX) return false; if (lhs.VirtualGridX != rhs.VirtualGridX) return false;
if (lhs.virtualGridY != rhs.virtualGridY) return false; if (lhs.VirtualGridY != rhs.VirtualGridY) return false;
if (lhs.dirX != rhs.dirX) return false; if (lhs.DirX != rhs.DirX) return false;
if (lhs.dirY != rhs.dirY) return false; if (lhs.DirY != rhs.DirY) return false;
if (lhs.velX != rhs.velX) return false; if (lhs.VelX != rhs.VelX) return false;
if (lhs.velY != rhs.velY) return false; if (lhs.VelY != rhs.VelY) return false;
if (lhs.speed != rhs.speed) return false; if (lhs.Speed != rhs.Speed) return false;
if (lhs.framesToRecover != rhs.framesToRecover) return false; if (lhs.Hp != rhs.Hp) return false;
if (lhs.hp != rhs.hp) return false; if (lhs.MaxHp != rhs.MaxHp) return false;
if (lhs.maxHp != rhs.maxHp) return false; if (lhs.CharacterState != rhs.CharacterState) return false;
if (lhs.characterState != rhs.characterState) return false; if (lhs.InAir != rhs.InAir) return false;
if (lhs.inAir != rhs.inAir) return false; if (lhs.FramesToRecover != rhs.FramesToRecover) return false;
if (lhs.FramesInChState != rhs.FramesInChState) return false;
return true; return true;
}, },
@ -770,7 +762,7 @@ cc.Class({
} }
if (null == firstPredictedYetIncorrectInputFrameId) return; if (null == firstPredictedYetIncorrectInputFrameId) return;
const renderFrameId1 = self._convertToFirstUsedRenderFrameId(firstPredictedYetIncorrectInputFrameId, self.inputDelayFrames) - 1; const renderFrameId1 = gopkgs.ConvertToFirstUsedRenderFrameId(firstPredictedYetIncorrectInputFrameId) - 1;
if (renderFrameId1 >= self.chaserRenderFrameId) return; if (renderFrameId1 >= self.chaserRenderFrameId) return;
/* /*
@ -829,37 +821,20 @@ batchInputFrameIdRange=[${batch[0].inputFrameId}, ${batch[batch.length - 1].inpu
const self = this; const self = this;
const newPlayerNode = cc.instantiate(self.controlledCharacterPrefab) const newPlayerNode = cc.instantiate(self.controlledCharacterPrefab)
const playerScriptIns = newPlayerNode.getComponent("ControlledCharacter"); const playerScriptIns = newPlayerNode.getComponent("ControlledCharacter");
const chConfig = self.chConfigsOrderedByJoinIndex[joinIndex - 1];
playerScriptIns.setSpecies(chConfig.SpeciesName);
if (1 == joinIndex) { if (1 == joinIndex) {
playerScriptIns.setSpecies("SoldierWaterGhost"); newPlayerNode.color = cc.Color.RED;
} else if (2 == joinIndex) { } else {
playerScriptIns.setSpecies("UltramanTiga"); newPlayerNode.color = cc.Color.BLUE;
} }
const [wx, wy] = self.virtualGridToWorldPos(vx, vy); const [wx, wy] = gopkgs.VirtualGridToWorldPos(vx, vy);
newPlayerNode.setPosition(wx, wy); newPlayerNode.setPosition(wx, wy);
playerScriptIns.mapNode = self.node; playerScriptIns.mapNode = self.node;
const colliderRadius = playerDownsyncInfo.ColliderRadius;
const halfColliderWidth = colliderRadius,
halfColliderHeight = colliderRadius + colliderRadius; // avoid multiplying
const colliderWidth = halfColliderWidth + halfColliderWidth,
colliderHeight = halfColliderHeight + halfColliderHeight; // avoid multiplying
const [cx, cy] = gopkgs.WorldToPolygonColliderBLPos(wx, wy, halfColliderWidth, halfColliderHeight, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.spaceOffsetX, self.spaceOffsetY); console.log(`Created new player node: joinIndex=${joinIndex}`);
const gopkgsBoundaryAnchor = gopkgs.NewVec2DJs(cx, cy);
const gopkgsBoundaryPts = [
gopkgs.NewVec2DJs(0, 0),
gopkgs.NewVec2DJs(self.snapIntoPlatformOverlap + colliderWidth + self.snapIntoPlatformOverlap, 0),
gopkgs.NewVec2DJs(self.snapIntoPlatformOverlap + colliderWidth + self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap + colliderHeight + self.snapIntoPlatformOverlap),
gopkgs.NewVec2DJs(0, self.snapIntoPlatformOverlap + colliderHeight + self.snapIntoPlatformOverlap)
];
const gopkgsBoundary = gopkgs.NewPolygon2DJs(gopkgsBoundaryAnchor, gopkgsBoundaryPts);
const newPlayerCollider = gopkgs.GenerateConvexPolygonColliderJs(gopkgsBoundary, self.spaceOffsetX, self.spaceOffsetY, playerDownsyncInfo, "Player");
//const newPlayerCollider = gopkgs.GenerateRectColliderJs(wx, wy, colliderWidth, colliderHeight, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.spaceOffsetX, self.spaceOffsetY, playerDownsyncInfo, "Player");
self.gopkgsCollisionSys.Add(newPlayerCollider);
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
self.gopkgsCollisionSysMap[collisionPlayerIndex] = newPlayerCollider;
console.log(`Created new player collider: joinIndex=${joinIndex}, colliderRadius=${playerDownsyncInfo.ColliderRadius}`);
safelyAddChild(self.node, newPlayerNode); safelyAddChild(self.node, newPlayerNode);
setLocalZOrder(newPlayerNode, 5); setLocalZOrder(newPlayerNode, 5);
@ -883,8 +858,8 @@ batchInputFrameIdRange=[${batch[0].inputFrameId}, ${batch[batch.length - 1].inpu
let st = performance.now(); let st = performance.now();
let prevSelfInput = null, let prevSelfInput = null,
currSelfInput = null; currSelfInput = null;
const noDelayInputFrameId = self._convertToInputFrameId(self.renderFrameId, 0); // It's important that "inputDelayFrames == 0" here const noDelayInputFrameId = gopkgs.ConvertToNoDelayInputFrameId(self.renderFrameId);
if (self.shouldGenerateInputFrameUpsync(self.renderFrameId)) { if (gopkgs.ShouldGenerateInputFrameUpsync(self.renderFrameId)) {
[prevSelfInput, currSelfInput] = self.getOrPrefabInputFrameUpsync(noDelayInputFrameId); [prevSelfInput, currSelfInput] = self.getOrPrefabInputFrameUpsync(noDelayInputFrameId);
} }
@ -919,7 +894,7 @@ batchInputFrameIdRange=[${batch[0].inputFrameId}, ${batch[batch.length - 1].inpu
*/ */
// [WARNING] Don't try to get "prevRdf(i.e. renderFrameId == latest-1)" by "self.recentRenderCache.getByFrameId(...)" here, as the cache might have been updated by asynchronous "onRoomDownsyncFrame(...)" calls! // [WARNING] Don't try to get "prevRdf(i.e. renderFrameId == latest-1)" by "self.recentRenderCache.getByFrameId(...)" here, as the cache might have been updated by asynchronous "onRoomDownsyncFrame(...)" calls!
if (self.othersForcedDownsyncRenderFrameDict.has(rdf.id)) { if (self.othersForcedDownsyncRenderFrameDict.has(rdf.id)) {
const delayedInputFrameId = self._convertToInputFrameId(rdf.id, 0); const delayedInputFrameId = gopkgs.ConvertToDelayedInputFrameId(rdf.id);
const othersForcedDownsyncRenderFrame = self.othersForcedDownsyncRenderFrameDict.get(rdf.id); const othersForcedDownsyncRenderFrame = self.othersForcedDownsyncRenderFrameDict.get(rdf.id);
if (self.lastAllConfirmedInputFrameId >= delayedInputFrameId && !self.equalRoomDownsyncFrames(othersForcedDownsyncRenderFrame, rdf)) { 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.id} w/ inputFrameId=${delayedInputFrameId}:
@ -1063,12 +1038,13 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
const playersArr = rdf.PlayersArr; const playersArr = rdf.PlayersArr;
for (let k in playersArr) { for (let k in playersArr) {
const currPlayerDownsync = playersArr[k]; const currPlayerDownsync = playersArr[k];
const chConfig = self.chConfigsOrderedByJoinIndex[k];
const prevRdfPlayer = (null == prevRdf ? null : prevRdf.PlayersArr[k]); const prevRdfPlayer = (null == prevRdf ? null : prevRdf.PlayersArr[k]);
const [wx, wy] = self.virtualGridToWorldPos(currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY); const [wx, wy] = gopkgs.VirtualGridToWorldPos(currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY);
const playerRichInfo = self.playerRichInfoArr[k]; const playerRichInfo = self.playerRichInfoArr[k];
playerRichInfo.node.setPosition(wx, wy); playerRichInfo.node.setPosition(wx, wy);
playerRichInfo.scriptIns.updateSpeed(currPlayerDownsync.Speed); playerRichInfo.scriptIns.updateSpeed(currPlayerDownsync.Speed);
playerRichInfo.scriptIns.updateCharacterAnim(currPlayerDownsync, prevRdfPlayer, false); playerRichInfo.scriptIns.updateCharacterAnim(currPlayerDownsync, prevRdfPlayer, false, chConfig);
} }
// Update countdown // Update countdown
@ -1087,14 +1063,14 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
if (null == currRdf) { 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`; 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 = self._convertToInputFrameId(i, self.inputDelayFrames); const j = gopkgs.ConvertToDelayedInputFrameId(i);
const delayedInputFrame = self.recentInputCache.GetByFrameId(j); // Don't make prediction here, the inputFrameDownsyncs in recentInputCache was already predicted while prefabbing const delayedInputFrame = self.recentInputCache.GetByFrameId(j); // Don't make prediction here, the inputFrameDownsyncs in recentInputCache was already predicted while prefabbing
if (null == delayedInputFrame) { if (null == delayedInputFrame) {
// Shouldn't happen! // Shouldn't happen!
throw `Failed to get cached delayedInputFrame for i=${i}, j=${j}, renderFrameId=${self.renderFrameId}, lastUpsyncInputFrameId=${self.lastUpsyncInputFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, chaserRenderFrameId=${self.chaserRenderFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`; throw `Failed to get cached delayedInputFrame for i=${i}, j=${j}, renderFrameId=${self.renderFrameId}, lastUpsyncInputFrameId=${self.lastUpsyncInputFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, chaserRenderFrameId=${self.chaserRenderFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
} }
const jPrev = self._convertToInputFrameId(i - 1, self.inputDelayFrames); const jPrev = gopkgs.ConvertToDelayedInputFrameId(i - 1);
if (self.frameDataLoggingEnabled) { if (self.frameDataLoggingEnabled) {
const actuallyUsedInputClone = delayedInputFrame.InputList.slice(); const actuallyUsedInputClone = delayedInputFrame.InputList.slice();
const inputFrameDownsyncClone = { const inputFrameDownsyncClone = {
@ -1104,7 +1080,15 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
}; };
self.rdfIdToActuallyUsedInput.set(currRdf.Id, inputFrameDownsyncClone); self.rdfIdToActuallyUsedInput.set(currRdf.Id, inputFrameDownsyncClone);
} }
const nextRdf = gopkgs.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(self.recentInputCache, currRdf, collisionSys, collisionSysMap, self.gravityX, self.gravityY, self.jumpingInitVelY, self.inputDelayFrames, self.inputScaleFrames, self.spaceOffsetX, self.spaceOffsetY, self.snapIntoPlatformOverlap, self.snapIntoPlatformThreshold, self.worldToVirtualGridRatio, self.virtualGridToWorldRatio, self.playerOpPatternToSkillId); const nextRdf = gopkgs.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(self.recentInputCache, currRdf, collisionSys, collisionSysMap, self.spaceOffsetX, self.spaceOffsetY, self.chConfigsOrderedByJoinIndex);
if (3 == nextRdf.PlayersArr[0].ActiveSkillId && 3 != currRdf.PlayersArr[0].ActiveSkillId) {
console.log(`Started skill 3 at rdf.Id=${nextRdf.Id}`);
self.lastSkill3Started = nextRdf.Id;
}
if (3 != nextRdf.PlayersArr[0].ActiveSkillId && 3 == currRdf.PlayersArr[0].ActiveSkillId) {
console.log(`Stopped skill 3 at rdf.Id=${nextRdf.Id}, duration = ${nextRdf.Id-self.lastSkill3Started}`);
}
if (true == isChasing) { if (true == isChasing) {
// [WARNING] Move the cursor "self.chaserRenderFrameId" when "true == isChasing", keep in mind that "self.chaserRenderFrameId" is not monotonic! // [WARNING] Move the cursor "self.chaserRenderFrameId" when "true == isChasing", keep in mind that "self.chaserRenderFrameId" is not monotonic!
@ -1233,49 +1217,81 @@ actuallyUsedinputList:{${self.inputFrameDownsyncStr(actuallyUsedInputClone)}}`);
return `{${(playerCollider.x + leftPadding + halfBoundingW).toFixed(2)}, ${(playerCollider.y + bottomPadding + halfBoundingH).toFixed(2)}}`; return `{${(playerCollider.x + leftPadding + halfBoundingW).toFixed(2)}, ${(playerCollider.y + bottomPadding + halfBoundingH).toFixed(2)}}`;
}, },
virtualGridToWorldPos(vx, vy) {
// No loss of precision
const self = this;
return [vx * self.virtualGridToWorldRatio, vy * self.virtualGridToWorldRatio];
},
showDebugBoundaries(rdf) { showDebugBoundaries(rdf) {
const self = this; const self = this;
const leftPadding = self.snapIntoPlatformOverlap, // Hardcoded paddings for now
rightPadding = self.snapIntoPlatformOverlap, const leftPadding = 0.1,
topPadding = self.snapIntoPlatformOverlap, rightPadding = 0.1,
bottomPadding = self.snapIntoPlatformOverlap; topPadding = 0.1,
if (self.showCriticalCoordinateLabels) { bottomPadding = 0.1;
let g = self.g; if (self.showCriticalCoordinateLabels && self.g2) {
g.clear(); let g2 = self.g2;
g2.clear();
const collisionSpaceObjs = gopkgs.GetCollisionSpaceObjsJs(self.gopkgsCollisionSys); for (let k in rdf.PlayersArr) {
for (let k in collisionSpaceObjs) { const player = rdf.PlayersArr[k];
const body = collisionSpaceObjs[k]; if (1 == player.JoinIndex) {
let padding = 0; g2.strokeColor = cc.Color.BLUE;
if (null != body.Data && null != body.Data.JoinIndex) {
// character
if (1 == body.Data.JoinIndex) {
g.strokeColor = cc.Color.BLUE;
} else {
g.strokeColor = cc.Color.RED;
}
padding = self.snapIntoPlatformOverlap;
} else { } else {
// barrier g2.strokeColor = cc.Color.RED;
g.strokeColor = cc.Color.WHITE;
} }
const points = body.Shape.Points;
const wpos = [body.X - self.spaceOffsetX, body.Y - self.spaceOffsetY]; let [colliderWidth, colliderHeight] = [player.ColliderRadius * 2, player.ColliderRadius * 4];
g.moveTo(wpos[0], wpos[1]); switch (player.CharacterState) {
const cnt = points.length; case ATK_CHARACTER_STATE.LayDown1[0]:
for (let j = 0; j < cnt; j += 1) { [colliderWidth, colliderHeight] = [player.ColliderRadius * 4, player.ColliderRadius * 2];
const x = wpos[0] + points[j][0], break;
y = wpos[1] + points[j][1]; case ATK_CHARACTER_STATE.BlownUp1[0]:
g.lineTo(x, y); case ATK_CHARACTER_STATE.InAirIdle1NoJump[0]:
case ATK_CHARACTER_STATE.InAirIdle1ByJump[0]:
[colliderWidth, colliderHeight] = [player.ColliderRadius * 2, player.ColliderRadius * 2];
break;
}
const [halfColliderWidth, halfColliderHeight] = gopkgs.VirtualGridToWorldPos((colliderWidth >> 1), (colliderHeight >> 1));
const [wx, wy] = gopkgs.VirtualGridToWorldPos(player.VirtualGridX, player.VirtualGridY);
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]];
g2.moveTo(cx, cy);
for (let j = 0; j < pts.length; j += 1) {
g2.lineTo(pts[j][0] + cx, pts[j][1] + cy);
}
g2.lineTo(cx, cy);
g2.stroke();
}
for (let k in rdf.MeleeBullets) {
const meleeBullet = rdf.MeleeBullets[k];
if (
meleeBullet.Bullet.OriginatedRenderFrameId + meleeBullet.Bullet.StartupFrames <= rdf.Id
&&
meleeBullet.Bullet.OriginatedRenderFrameId + meleeBullet.Bullet.StartupFrames + meleeBullet.Bullet.ActiveFrames > rdf.Id
) {
const offender = rdf.PlayersArr[meleeBullet.Bullet.OffenderJoinIndex - 1];
if (1 == offender.JoinIndex) {
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) {
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 [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]];
g2.moveTo(bulletCx, bulletCy);
for (let j = 0; j < pts.length; j += 1) {
g2.lineTo(pts[j][0] + bulletCx, pts[j][1] + bulletCy);
}
g2.lineTo(bulletCx, bulletCy);
g2.stroke();
} }
g.lineTo(wpos[0], wpos[1]);
g.stroke();
} }
} }
}, },

View File

@ -28,8 +28,6 @@ cc.Class({
/** Init required prefab ended. */ /** Init required prefab ended. */
self.inputDelayFrames = 8;
self.inputScaleFrames = 2;
self.inputFrameUpsyncDelayTolerance = 2; self.inputFrameUpsyncDelayTolerance = 2;
self.collisionMinStep = 8; self.collisionMinStep = 8;
@ -40,24 +38,6 @@ cc.Class({
self.rollbackEstimatedDtNanos = 16666666; self.rollbackEstimatedDtNanos = 16666666;
self.tooFastDtIntervalMillis = 0.5 * self.rollbackEstimatedDtMillis; self.tooFastDtIntervalMillis = 0.5 * self.rollbackEstimatedDtMillis;
self.worldToVirtualGridRatio = 1000;
self.virtualGridToWorldRatio = 1.0 / self.worldToVirtualGridRatio;
const opJoinIndexPrefix1 = (1 << 8);
const opJoinIndexPrefix2 = (2 << 8);
self.playerOpPatternToSkillId = {};
self.playerOpPatternToSkillId[opJoinIndexPrefix1 + 0] = 1;
self.playerOpPatternToSkillId[opJoinIndexPrefix2 + 0] = 1;
/*
[WARNING] As when a character is standing on a barrier, if not carefully curated there MIGHT BE a bouncing sequence of "[(inAir -> dropIntoBarrier ->), (notInAir -> pushedOutOfBarrier ->)], [(inAir -> ..."
Moreover, "snapIntoPlatformOverlap" should be small enough such that the walking "velX" or jumping initial "velY" can escape from it by 1 renderFrame (when jumping is triggered, the character is waived from snappig for 1 renderFrame).
*/
self.snapIntoPlatformOverlap = 0.1;
self.snapIntoPlatformThreshold = 0.5; // a platform must be "horizontal enough" for a character to "stand on"
self.jumpingInitVelY = 7 * self.worldToVirtualGridRatio; // unit: (virtual grid length/renderFrame)
[self.gravityX, self.gravityY] = [0, -0.5 * self.worldToVirtualGridRatio]; // unit: (virtual grid length/renderFrame^2)
const tiledMapIns = self.node.getComponent(cc.TiledMap); const tiledMapIns = self.node.getComponent(cc.TiledMap);
const fullPathOfTmxFile = cc.js.formatStr("map/%s/map", "dungeon"); const fullPathOfTmxFile = cc.js.formatStr("map/%s/map", "dungeon");
@ -86,10 +66,8 @@ cc.Class({
self.node.setContentSize(newMapSize.width * newTileSize.width, newMapSize.height * newTileSize.height); self.node.setContentSize(newMapSize.width * newTileSize.width, newMapSize.height * newTileSize.height);
self.node.setPosition(cc.v2(0, 0)); self.node.setPosition(cc.v2(0, 0));
self.stageDiscreteW = newMapSize.width; self.spaceOffsetX = ((newMapSize.width * newTileSize.width) >> 1);
self.stageDiscreteH = newMapSize.height; self.spaceOffsetY = ((newMapSize.height * newTileSize.height) >> 1);
self.stageTileW = newTileSize.width;
self.stageTileH = newTileSize.height;
self._resetCurrentMatch(); self._resetCurrentMatch();
let barrierIdCounter = 0; let barrierIdCounter = 0;
@ -105,42 +83,17 @@ cc.Class({
const newBarrierCollider = gopkgs.GenerateConvexPolygonColliderJs(gopkgsBoundary, self.spaceOffsetX, self.spaceOffsetY, gopkgsBarrier, "Barrier"); const newBarrierCollider = gopkgs.GenerateConvexPolygonColliderJs(gopkgsBoundary, self.spaceOffsetX, self.spaceOffsetY, gopkgsBarrier, "Barrier");
self.gopkgsCollisionSys.Add(newBarrierCollider); self.gopkgsCollisionSys.Add(newBarrierCollider);
if (false && self.showCriticalCoordinateLabels) {
for (let i = 0; i < boundaryObj.length; ++i) {
const barrierVertLabelNode = new cc.Node();
switch (i % 4) {
case 0:
barrierVertLabelNode.color = cc.Color.RED;
break;
case 1:
barrierVertLabelNode.color = cc.Color.GRAY;
break;
case 2:
barrierVertLabelNode.color = cc.Color.BLACK;
break;
default:
barrierVertLabelNode.color = cc.Color.MAGENTA;
break;
}
const wx = boundaryObj.anchor.x + boundaryObj[i].x,
wy = boundaryObj.anchor.y + boundaryObj[i].y;
barrierVertLabelNode.setPosition(cc.v2(wx, wy));
const barrierVertLabel = barrierVertLabelNode.addComponent(cc.Label);
barrierVertLabel.fontSize = 12;
barrierVertLabel.lineHeight = barrierVertLabel.fontSize + 1;
barrierVertLabel.string = `(${wx.toFixed(1)}, ${wy.toFixed(1)})`;
safelyAddChild(self.node, barrierVertLabelNode);
setLocalZOrder(barrierVertLabelNode, 5);
barrierVertLabelNode.active = true;
}
}
// console.log("Created barrier: ", newBarrierCollider); // console.log("Created barrier: ", newBarrierCollider);
++barrierIdCounter; ++barrierIdCounter;
const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter); const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter);
self.gopkgsCollisionSysMap[collisionBarrierIndex] = newBarrierCollider; self.gopkgsCollisionSysMap[collisionBarrierIndex] = newBarrierCollider;
} }
self.initDebugDrawers();
const p1Vpos = gopkgs.WorldToVirtualGridPos(boundaryObjs.playerStartingPositions[0].x, boundaryObjs.playerStartingPositions[0].y);
const p2Vpos = gopkgs.WorldToVirtualGridPos(boundaryObjs.playerStartingPositions[1].x, boundaryObjs.playerStartingPositions[1].y);
const speedV = gopkgs.WorldToVirtualGridPos(1.0, 0);
const colliderRadiusV = gopkgs.WorldToVirtualGridPos(12.0, 0);
const startRdf = window.pb.protos.RoomDownsyncFrame.create({ const startRdf = window.pb.protos.RoomDownsyncFrame.create({
id: window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START, id: window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START,
@ -148,13 +101,13 @@ cc.Class({
window.pb.protos.PlayerDownsync.create({ window.pb.protos.PlayerDownsync.create({
id: 10, id: 10,
joinIndex: 1, joinIndex: 1,
virtualGridX: boundaryObjs.playerStartingPositions[0].x * self.worldToVirtualGridRatio, virtualGridX: p1Vpos[0],
virtualGridY: boundaryObjs.playerStartingPositions[0].y * self.worldToVirtualGridRatio, virtualGridY: p1Vpos[1],
speed: 1 * self.worldToVirtualGridRatio, speed: speedV[0],
colliderRadius: 12, colliderRadius: colliderRadiusV[0],
characterState: window.ATK_CHARACTER_STATE.InAirIdle1[0], characterState: window.ATK_CHARACTER_STATE.InAirIdle1NoJump[0],
framesToRecover: 0, framesToRecover: 0,
dirX: 0, dirX: +2,
dirY: 0, dirY: 0,
velX: 0, velX: 0,
velY: 0, velY: 0,
@ -163,19 +116,20 @@ cc.Class({
window.pb.protos.PlayerDownsync.create({ window.pb.protos.PlayerDownsync.create({
id: 11, id: 11,
joinIndex: 2, joinIndex: 2,
virtualGridX: boundaryObjs.playerStartingPositions[1].x * self.worldToVirtualGridRatio, virtualGridX: p2Vpos[0],
virtualGridY: boundaryObjs.playerStartingPositions[1].y * self.worldToVirtualGridRatio, virtualGridY: p2Vpos[1],
speed: 1 * self.worldToVirtualGridRatio, speed: speedV[0],
colliderRadius: 12, colliderRadius: colliderRadiusV[0],
characterState: window.ATK_CHARACTER_STATE.InAirIdle1[0], characterState: window.ATK_CHARACTER_STATE.InAirIdle1NoJump[0],
framesToRecover: 0, framesToRecover: 0,
dirX: 0, dirX: -2,
dirY: 0, dirY: 0,
velX: 0, velX: 0,
velY: 0, velY: 0,
inAir: true, inAir: true,
}), }),
] ],
speciesIdList: [1, 0],
}); });
self.selfPlayerInfo = { self.selfPlayerInfo = {
@ -202,8 +156,8 @@ cc.Class({
let st = performance.now(); let st = performance.now();
let prevSelfInput = null, let prevSelfInput = null,
currSelfInput = null; currSelfInput = null;
const noDelayInputFrameId = self._convertToInputFrameId(self.renderFrameId, 0); // It's important that "inputDelayFrames == 0" here const noDelayInputFrameId = gopkgs.ConvertToNoDelayInputFrameId(self.renderFrameId); // It's important that "inputDelayFrames == 0" here
if (self.shouldGenerateInputFrameUpsync(self.renderFrameId)) { if (gopkgs.ShouldGenerateInputFrameUpsync(self.renderFrameId)) {
const prevAndCurrInputs = self.getOrPrefabInputFrameUpsync(noDelayInputFrameId); const prevAndCurrInputs = self.getOrPrefabInputFrameUpsync(noDelayInputFrameId);
prevSelfInput = prevAndCurrInputs[0]; prevSelfInput = prevAndCurrInputs[0];
currSelfInput = prevAndCurrInputs[1]; currSelfInput = prevAndCurrInputs[1];

File diff suppressed because one or more lines are too long

View File

@ -1,9 +0,0 @@
{
"ver": "1.0.5",
"uuid": "da0a517f-5c74-4fc0-ba89-dbcee184b13e",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@ -7,12 +7,33 @@ import (
const ( const (
MAX_FLOAT64 = 1.7e+308 MAX_FLOAT64 = 1.7e+308
MAX_INT32 = int32(999999999)
COLLISION_PLAYER_INDEX_PREFIX = (1 << 17) COLLISION_PLAYER_INDEX_PREFIX = (1 << 17)
COLLISION_BARRIER_INDEX_PREFIX = (1 << 16) COLLISION_BARRIER_INDEX_PREFIX = (1 << 16)
COLLISION_BULLET_INDEX_PREFIX = (1 << 15) COLLISION_BULLET_INDEX_PREFIX = (1 << 15)
PATTERN_ID_UNABLE_TO_OP = -2 PATTERN_ID_UNABLE_TO_OP = -2
PATTERN_ID_NO_OP = -1 PATTERN_ID_NO_OP = -1
WORLD_TO_VIRTUAL_GRID_RATIO = float64(100)
VIRTUAL_GRID_TO_WORLD_RATIO = float64(1.0) / WORLD_TO_VIRTUAL_GRID_RATIO
GRAVITY_X = int32(0)
GRAVITY_Y = -int32(float64(0.5) * WORLD_TO_VIRTUAL_GRID_RATIO) // makes all "playerCollider.Y" a multiple of 0.5 in all cases
INPUT_DELAY_FRAMES = int32(8) // in the count of render frames
INPUT_SCALE_FRAMES = uint32(2) // inputDelayedAndScaledFrameId = ((originalFrameId - InputDelayFrames) >> InputScaleFrames)
NST_DELAY_FRAMES = int32(16) // network-single-trip delay in the count of render frames, proposed to be (InputDelayFrames >> 1) because we expect a round-trip delay to be exactly "InputDelayFrames"
SP_ATK_LOOKUP_FRAMES = int32(5)
SNAP_INTO_PLATFORM_OVERLAP = float64(0.1)
SNAP_INTO_PLATFORM_THRESHOLD = float64(0.5)
NO_SKILL = -1
NO_SKILL_HIT = -1
NO_LOCK_VEL = 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. // These directions are chosen such that when speed is changed to "(speedX+delta, speedY+delta)" for any of them, the direction is unchanged.
@ -28,47 +49,87 @@ var DIRECTION_DECODER = [][]int32{
{-1, +1}, {-1, +1},
} }
var skillIdToBullet = map[int]interface{}{
1: &MeleeBullet{
Bullet: Bullet{
// for offender
StartupFrames: int32(5),
ActiveFrames: int32(10),
RecoveryFrames: int32(34),
RecoveryFramesOnBlock: int32(34),
RecoveryFramesOnHit: int32(34),
HitboxOffset: float64(12.0), // should be about the radius of the PlayerCollider
// for defender
HitStunFrames: int32(18),
BlockStunFrames: int32(9),
Pushback: float64(8.0),
ReleaseTriggerType: int32(1), // 1: rising-edge, 2: falling-edge
Damage: int32(5),
SelfMoveforwardX: 0,
SelfMoveforwardY: 0,
HitboxSizeX: 24.0,
HitboxSizeY: 32.0,
},
},
}
const ( const (
ATK_CHARACTER_STATE_IDLE1 = int32(0) ATK_CHARACTER_STATE_IDLE1 = int32(0)
ATK_CHARACTER_STATE_WALKING = int32(1) ATK_CHARACTER_STATE_WALKING = int32(1)
ATK_CHARACTER_STATE_ATK1 = int32(2) ATK_CHARACTER_STATE_ATK1 = int32(2)
ATK_CHARACTER_STATE_ATKED1 = int32(3) ATK_CHARACTER_STATE_ATKED1 = int32(3)
ATK_CHARACTER_STATE_INAIR_IDLE1 = int32(4) ATK_CHARACTER_STATE_INAIR_IDLE1_NO_JUMP = int32(4)
ATK_CHARACTER_STATE_INAIR_ATK1 = int32(5) ATK_CHARACTER_STATE_INAIR_IDLE1_BY_JUMP = int32(5)
ATK_CHARACTER_STATE_INAIR_ATKED1 = int32(6) ATK_CHARACTER_STATE_INAIR_ATK1 = int32(6)
ATK_CHARACTER_STATE_INAIR_ATKED1 = int32(7)
ATK_CHARACTER_STATE_BLOWN_UP1 = int32(8)
ATK_CHARACTER_STATE_LAY_DOWN1 = int32(9)
ATK_CHARACTER_STATE_GET_UP1 = int32(10)
ATK_CHARACTER_STATE_ATK2 = int32(11)
ATK_CHARACTER_STATE_ATK3 = int32(12)
) )
func ConvertToInputFrameId(renderFrameId int32, inputDelayFrames int32, inputScaleFrames uint32) int32 { var inAirSet = map[int32]bool{
if renderFrameId < inputDelayFrames { ATK_CHARACTER_STATE_INAIR_IDLE1_NO_JUMP: true,
ATK_CHARACTER_STATE_INAIR_IDLE1_BY_JUMP: true,
ATK_CHARACTER_STATE_INAIR_ATK1: true,
ATK_CHARACTER_STATE_INAIR_ATKED1: true,
ATK_CHARACTER_STATE_BLOWN_UP1: true,
}
var noOpSet = map[int32]bool{
ATK_CHARACTER_STATE_ATKED1: true,
ATK_CHARACTER_STATE_INAIR_ATKED1: true,
ATK_CHARACTER_STATE_BLOWN_UP1: true,
ATK_CHARACTER_STATE_LAY_DOWN1: true,
// During the invinsible frames of GET_UP1, the player is allowed to take any action
}
var invinsibleSet = map[int32]bool{
ATK_CHARACTER_STATE_BLOWN_UP1: true,
ATK_CHARACTER_STATE_LAY_DOWN1: true,
ATK_CHARACTER_STATE_GET_UP1: true,
}
var nonAttackingSet = map[int32]bool{
ATK_CHARACTER_STATE_IDLE1: true,
ATK_CHARACTER_STATE_WALKING: true,
ATK_CHARACTER_STATE_INAIR_IDLE1_NO_JUMP: true,
ATK_CHARACTER_STATE_INAIR_IDLE1_BY_JUMP: true,
ATK_CHARACTER_STATE_ATKED1: true,
ATK_CHARACTER_STATE_INAIR_ATKED1: true,
ATK_CHARACTER_STATE_BLOWN_UP1: true,
ATK_CHARACTER_STATE_LAY_DOWN1: true,
ATK_CHARACTER_STATE_GET_UP1: true,
}
func ShouldPrefabInputFrameDownsync(prevRenderFrameId int32, renderFrameId int32) (bool, int32) {
for i := prevRenderFrameId + 1; i <= renderFrameId; i++ {
if (0 <= i) && (0 == (i & ((1 << INPUT_SCALE_FRAMES) - 1))) {
return true, i
}
}
return false, -1
}
func ShouldGenerateInputFrameUpsync(renderFrameId int32) bool {
return ((renderFrameId & ((1 << INPUT_SCALE_FRAMES) - 1)) == 0)
}
func ConvertToDelayedInputFrameId(renderFrameId int32) int32 {
if renderFrameId < INPUT_DELAY_FRAMES {
return 0 return 0
} }
return ((renderFrameId - inputDelayFrames) >> inputScaleFrames) return ((renderFrameId - INPUT_DELAY_FRAMES) >> INPUT_SCALE_FRAMES)
}
func ConvertToNoDelayInputFrameId(renderFrameId int32) int32 {
return (renderFrameId >> INPUT_SCALE_FRAMES)
}
func ConvertToFirstUsedRenderFrameId(inputFrameId int32) int32 {
return ((inputFrameId << INPUT_SCALE_FRAMES) + INPUT_DELAY_FRAMES)
}
func ConvertToLastUsedRenderFrameId(inputFrameId int32) int32 {
return ((inputFrameId << INPUT_SCALE_FRAMES) + INPUT_DELAY_FRAMES + (1 << INPUT_SCALE_FRAMES) - 1)
} }
func decodeInput(encodedInput uint64) *InputFrameDecoded { func decodeInput(encodedInput uint64) *InputFrameDecoded {
@ -250,18 +311,18 @@ func isPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e resolv.Vector, re
return false return false
} }
func WorldToVirtualGridPos(wx, wy, worldToVirtualGridRatio float64) (int32, int32) { func WorldToVirtualGridPos(wx, wy float64) (int32, int32) {
// [WARNING] Introduces loss of precision! // [WARNING] Introduces loss of precision!
// In JavaScript floating numbers suffer from seemingly non-deterministic arithmetics, and even if certain libs solved this issue by approaches such as fixed-point-number, they might not be used in other libs -- e.g. the "collision libs" we're interested in -- thus couldn't kill all pains. // In JavaScript floating numbers suffer from seemingly non-deterministic arithmetics, and even if certain libs solved this issue by approaches such as fixed-point-number, they might not be used in other libs -- e.g. the "collision libs" we're interested in -- thus couldn't kill all pains.
var virtualGridX int32 = int32(math.Floor(wx * worldToVirtualGridRatio)) var virtualGridX int32 = int32(math.Floor(wx * WORLD_TO_VIRTUAL_GRID_RATIO))
var virtualGridY int32 = int32(math.Floor(wy * worldToVirtualGridRatio)) var virtualGridY int32 = int32(math.Floor(wy * WORLD_TO_VIRTUAL_GRID_RATIO))
return virtualGridX, virtualGridY return virtualGridX, virtualGridY
} }
func VirtualGridToWorldPos(vx, vy int32, virtualGridToWorldRatio float64) (float64, float64) { func VirtualGridToWorldPos(vx, vy int32) (float64, float64) {
// No loss of precision // No loss of precision
var wx float64 = float64(vx) * virtualGridToWorldRatio var wx float64 = float64(vx) * VIRTUAL_GRID_TO_WORLD_RATIO
var wy float64 = float64(vy) * virtualGridToWorldRatio var wy float64 = float64(vy) * VIRTUAL_GRID_TO_WORLD_RATIO
return wx, wy return wx, wy
} }
@ -273,13 +334,13 @@ func PolygonColliderBLToWorldPos(cx, cy, halfBoundingW, halfBoundingH, topPaddin
return cx + halfBoundingW + leftPadding - collisionSpaceOffsetX, cy + halfBoundingH + bottomPadding - collisionSpaceOffsetY return cx + halfBoundingW + leftPadding - collisionSpaceOffsetX, cy + halfBoundingH + bottomPadding - collisionSpaceOffsetY
} }
func PolygonColliderBLToVirtualGridPos(cx, cy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY float64, worldToVirtualGridRatio float64) (int32, int32) { func PolygonColliderBLToVirtualGridPos(cx, cy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (int32, int32) {
wx, wy := PolygonColliderBLToWorldPos(cx, cy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY) wx, wy := PolygonColliderBLToWorldPos(cx, cy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY)
return WorldToVirtualGridPos(wx, wy, worldToVirtualGridRatio) return WorldToVirtualGridPos(wx, wy)
} }
func VirtualGridToPolygonColliderBLPos(vx, vy int32, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY float64, virtualGridToWorldRatio float64) (float64, float64) { func VirtualGridToPolygonColliderBLPos(vx, vy int32, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (float64, float64) {
wx, wy := VirtualGridToWorldPos(vx, vy, virtualGridToWorldRatio) wx, wy := VirtualGridToWorldPos(vx, vy)
return WorldToPolygonColliderBLPos(wx, wy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY) return WorldToPolygonColliderBLPos(wx, wy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY)
} }
@ -321,15 +382,19 @@ func calcHardPushbacksNorms(joinIndex int32, playerCollider *resolv.Object, play
return &ret return &ret
} }
func deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync, currRenderFrame *RoomDownsyncFrame, inputsBuffer *RingBuffer, inputDelayFrames int32, inputScaleFrames uint32) (int, bool, int32, int32) { func deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync, currRenderFrame *RoomDownsyncFrame, inputsBuffer *RingBuffer) (int, bool, int32, int32) {
// returns (patternId, jumpedOrNot, effectiveDx, effectiveDy) // returns (patternId, jumpedOrNot, effectiveDx, effectiveDy)
delayedInputFrameId := ConvertToInputFrameId(currRenderFrame.Id, inputDelayFrames, inputScaleFrames) delayedInputFrameId := ConvertToDelayedInputFrameId(currRenderFrame.Id)
delayedInputFrameIdForPrevRdf := ConvertToInputFrameId(currRenderFrame.Id-1, inputDelayFrames, inputScaleFrames) delayedInputFrameIdForPrevRdf := ConvertToDelayedInputFrameId(currRenderFrame.Id - 1)
if 0 >= delayedInputFrameId { if 0 >= delayedInputFrameId {
return PATTERN_ID_UNABLE_TO_OP, false, 0, 0 return PATTERN_ID_UNABLE_TO_OP, false, 0, 0
} }
if _, existent := noOpSet[currPlayerDownsync.CharacterState]; existent {
return PATTERN_ID_UNABLE_TO_OP, false, 0, 0
}
delayedInputList := inputsBuffer.GetByFrameId(delayedInputFrameId).(*InputFrameDownsync).InputList delayedInputList := inputsBuffer.GetByFrameId(delayedInputFrameId).(*InputFrameDownsync).InputList
var delayedInputListForPrevRdf []uint64 = nil var delayedInputListForPrevRdf []uint64 = nil
if 0 < delayedInputFrameIdForPrevRdf { if 0 < delayedInputFrameIdForPrevRdf {
@ -338,11 +403,8 @@ func deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync,
jumpedOrNot := false jumpedOrNot := false
joinIndex := currPlayerDownsync.JoinIndex joinIndex := currPlayerDownsync.JoinIndex
if 0 < currPlayerDownsync.FramesToRecover {
return PATTERN_ID_UNABLE_TO_OP, false, 0, 0
}
decodedInput := decodeInput(delayedInputList[joinIndex-1]) decodedInput := decodeInput(delayedInputList[joinIndex-1])
effDx, effDy := decodedInput.Dx, decodedInput.Dy effDx, effDy := int32(0), int32(0)
prevBtnALevel, prevBtnBLevel := int32(0), int32(0) prevBtnALevel, prevBtnBLevel := int32(0), int32(0)
if nil != delayedInputListForPrevRdf { if nil != delayedInputListForPrevRdf {
prevDecodedInput := decodeInput(delayedInputListForPrevRdf[joinIndex-1]) prevDecodedInput := decodeInput(delayedInputListForPrevRdf[joinIndex-1])
@ -350,121 +412,164 @@ func deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync,
prevBtnBLevel = prevDecodedInput.BtnBLevel prevBtnBLevel = prevDecodedInput.BtnBLevel
} }
if decodedInput.BtnBLevel > prevBtnBLevel { if 0 == currPlayerDownsync.FramesToRecover {
characStateAlreadyInAir := false // Jumping and moving are only allowed here
if ATK_CHARACTER_STATE_INAIR_IDLE1 == currPlayerDownsync.CharacterState || ATK_CHARACTER_STATE_INAIR_ATK1 == currPlayerDownsync.CharacterState || ATK_CHARACTER_STATE_INAIR_ATKED1 == currPlayerDownsync.CharacterState { effDx, effDy = decodedInput.Dx, decodedInput.Dy
characStateAlreadyInAir = true if decodedInput.BtnBLevel > prevBtnBLevel {
} if _, existent := inAirSet[currPlayerDownsync.CharacterState]; !existent {
characStateIsInterruptWaivable := false jumpedOrNot = true
if ATK_CHARACTER_STATE_IDLE1 == currPlayerDownsync.CharacterState || ATK_CHARACTER_STATE_WALKING == currPlayerDownsync.CharacterState || ATK_CHARACTER_STATE_INAIR_IDLE1 == currPlayerDownsync.CharacterState { }
characStateIsInterruptWaivable = true
}
if !characStateAlreadyInAir && characStateIsInterruptWaivable {
jumpedOrNot = true
} }
} }
patternId := PATTERN_ID_NO_OP patternId := PATTERN_ID_NO_OP
if decodedInput.BtnALevel > prevBtnALevel { if decodedInput.BtnALevel > prevBtnALevel {
patternId = 0 patternId = 1
effDx, effDy = 0, 0 // Most patterns/skills should not allow simultaneous movement
} }
return patternId, jumpedOrNot, effDx, effDy return patternId, jumpedOrNot, effDx, effDy
} }
// [WARNING] The params of this method is carefully tuned such that only "battle.RoomDownsyncFrame" is a necessary custom struct. // [WARNING] The params of this method is carefully tuned such that only "battle.RoomDownsyncFrame" is a necessary custom struct.
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer, currRenderFrame *RoomDownsyncFrame, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, gravityX, gravityY, jumpingInitVelY, inputDelayFrames int32, inputScaleFrames uint32, collisionSpaceOffsetX, collisionSpaceOffsetY, snapIntoPlatformOverlap, snapIntoPlatformThreshold, worldToVirtualGridRatio, virtualGridToWorldRatio float64, playerOpPatternToSkillId map[int]int) *RoomDownsyncFrame { 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! // [WARNING] On backend this function MUST BE called while "InputsBufferLock" is locked!
roomCapacity := len(currRenderFrame.PlayersArr) roomCapacity := len(currRenderFrame.PlayersArr)
nextRenderFramePlayers := make([]*PlayerDownsync, roomCapacity) nextRenderFramePlayers := make([]*PlayerDownsync, roomCapacity)
// Make a copy first // Make a copy first
for i, currPlayerDownsync := range currRenderFrame.PlayersArr { for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
nextRenderFramePlayers[i] = &PlayerDownsync{ nextRenderFramePlayers[i] = &PlayerDownsync{
Id: currPlayerDownsync.Id, Id: currPlayerDownsync.Id,
VirtualGridX: currPlayerDownsync.VirtualGridX, VirtualGridX: currPlayerDownsync.VirtualGridX,
VirtualGridY: currPlayerDownsync.VirtualGridY, VirtualGridY: currPlayerDownsync.VirtualGridY,
DirX: currPlayerDownsync.DirX, DirX: currPlayerDownsync.DirX,
DirY: currPlayerDownsync.DirY, DirY: currPlayerDownsync.DirY,
VelX: currPlayerDownsync.VelX, VelX: currPlayerDownsync.VelX,
VelY: currPlayerDownsync.VelY, VelY: currPlayerDownsync.VelY,
CharacterState: currPlayerDownsync.CharacterState, CharacterState: currPlayerDownsync.CharacterState,
InAir: true, InAir: true,
Speed: currPlayerDownsync.Speed, Speed: currPlayerDownsync.Speed,
BattleState: currPlayerDownsync.BattleState, BattleState: currPlayerDownsync.BattleState,
Score: currPlayerDownsync.Score, Score: currPlayerDownsync.Score,
Removed: currPlayerDownsync.Removed, Removed: currPlayerDownsync.Removed,
JoinIndex: currPlayerDownsync.JoinIndex, JoinIndex: currPlayerDownsync.JoinIndex,
FramesToRecover: currPlayerDownsync.FramesToRecover - 1, Hp: currPlayerDownsync.Hp,
Hp: currPlayerDownsync.Hp, MaxHp: currPlayerDownsync.MaxHp,
MaxHp: currPlayerDownsync.MaxHp, FramesToRecover: currPlayerDownsync.FramesToRecover - 1,
FramesInChState: currPlayerDownsync.FramesInChState + 1,
ActiveSkillId: currPlayerDownsync.ActiveSkillId,
ActiveSkillHit: currPlayerDownsync.ActiveSkillHit,
FramesInvinsible: currPlayerDownsync.FramesInvinsible - 1,
ColliderRadius: currPlayerDownsync.ColliderRadius,
} }
if nextRenderFramePlayers[i].FramesToRecover < 0 { if nextRenderFramePlayers[i].FramesToRecover < 0 {
nextRenderFramePlayers[i].FramesToRecover = 0 nextRenderFramePlayers[i].FramesToRecover = 0
} }
if nextRenderFramePlayers[i].FramesInvinsible < 0 {
nextRenderFramePlayers[i].FramesInvinsible = 0
}
} }
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? 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?
effPushbacks := make([]Vec2D, roomCapacity) effPushbacks := make([]Vec2D, roomCapacity)
hardPushbackNorms := make([]*[]Vec2D, roomCapacity) hardPushbackNorms := make([]*[]Vec2D, roomCapacity)
jumpedOrNotList := make([]bool, roomCapacity)
// 1. Process player inputs // 1. Process player inputs
for i, currPlayerDownsync := range currRenderFrame.PlayersArr { for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
jumpedOrNotList[i] = false
chConfig := chConfigsOrderedByJoinIndex[i]
thatPlayerInNextFrame := nextRenderFramePlayers[i] thatPlayerInNextFrame := nextRenderFramePlayers[i]
patternId, jumpedOrNot, effDx, effDy := deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame, currRenderFrame, inputsBuffer, inputDelayFrames, inputScaleFrames) patternId, jumpedOrNot, effDx, effDy := deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame, currRenderFrame, inputsBuffer)
if PATTERN_ID_UNABLE_TO_OP == patternId {
continue
}
if jumpedOrNot { if jumpedOrNot {
thatPlayerInNextFrame.VelY = jumpingInitVelY thatPlayerInNextFrame.VelY = int32(chConfig.JumpingInitVelY)
thatPlayerInNextFrame.VirtualGridY += jumpingInitVelY // Immediately gets out of any snapping jumpedOrNotList[i] = true
} }
joinIndex := currPlayerDownsync.JoinIndex joinIndex := currPlayerDownsync.JoinIndex
if PATTERN_ID_NO_OP != patternId { skillId := chConfig.SkillMapper(patternId, currPlayerDownsync)
if skillId, existent := playerOpPatternToSkillId[(int(joinIndex)<<uint(8))+patternId]; existent { if skillConfig, existent := skills[skillId]; existent {
skillConfig := skillIdToBullet[skillId].(*MeleeBullet) // Hardcoded type "MeleeBullet" for now thatPlayerInNextFrame.ActiveSkillId = int32(skillId)
var newMeleeBullet MeleeBullet = *skillConfig thatPlayerInNextFrame.ActiveSkillHit = 0
newMeleeBullet.OffenderJoinIndex = joinIndex
newMeleeBullet.OffenderPlayerId = currPlayerDownsync.Id // Hardcoded to use only the first hit for now
newMeleeBullet.OriginatedRenderFrameId = currRenderFrame.Id switch v := skillConfig.Hits[thatPlayerInNextFrame.ActiveSkillHit].(type) {
nextRenderFrameMeleeBullets = append(nextRenderFrameMeleeBullets, &newMeleeBullet) case *MeleeBullet:
thatPlayerInNextFrame.FramesToRecover = newMeleeBullet.RecoveryFrames var newBullet MeleeBullet = *v // Copied primitive fields into an onstack variable
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATK1 newBullet.OriginatedRenderFrameId = currRenderFrame.Id
if false == currPlayerDownsync.InAir { newBullet.OffenderJoinIndex = joinIndex
thatPlayerInNextFrame.VelX = 0 nextRenderFrameMeleeBullets = append(nextRenderFrameMeleeBullets, &newBullet)
thatPlayerInNextFrame.FramesToRecover = skillConfig.RecoveryFrames
hasLockVel := false
if NO_LOCK_VEL != v.SelfLockVelX {
hasLockVel = true
xfac := int32(1)
if 0 > thatPlayerInNextFrame.DirX {
xfac = -xfac
}
thatPlayerInNextFrame.VelX = xfac * v.SelfLockVelX
}
if NO_LOCK_VEL != v.SelfLockVelY {
hasLockVel = true
thatPlayerInNextFrame.VelY = v.SelfLockVelY
}
if false == hasLockVel {
if false == currPlayerDownsync.InAir {
thatPlayerInNextFrame.VelX = 0
}
} }
} }
continue
thatPlayerInNextFrame.CharacterState = skillConfig.BoundChState
continue // Don't allow movement if skill is used
} }
if 0 != effDx || 0 != effDy { if 0 == currPlayerDownsync.FramesToRecover {
thatPlayerInNextFrame.DirX, thatPlayerInNextFrame.DirY = effDx, effDy if 0 != effDx || 0 != effDy {
thatPlayerInNextFrame.VelX = effDx * currPlayerDownsync.Speed thatPlayerInNextFrame.DirX, thatPlayerInNextFrame.DirY = effDx, effDy
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_WALKING thatPlayerInNextFrame.VelX = effDx * currPlayerDownsync.Speed
} else { thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_WALKING
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1 } else {
thatPlayerInNextFrame.VelX = 0 thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1
thatPlayerInNextFrame.VelX = 0
}
} }
} }
// 2. Process player movement // 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 { for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
joinIndex := currPlayerDownsync.JoinIndex joinIndex := currPlayerDownsync.JoinIndex
effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y = float64(0), float64(0) effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y = float64(0), float64(0)
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
playerCollider := collisionSysMap[collisionPlayerIndex] chConfig := chConfigsOrderedByJoinIndex[i]
thatPlayerInNextFrame := nextRenderFramePlayers[i]
// Reset playerCollider position from the "virtual grid position" // Reset playerCollider position from the "virtual grid position"
newVx, newVy := currPlayerDownsync.VirtualGridX+currPlayerDownsync.VelX, currPlayerDownsync.VirtualGridY+currPlayerDownsync.VelY newVx, newVy := currPlayerDownsync.VirtualGridX+currPlayerDownsync.VelX, currPlayerDownsync.VirtualGridY+currPlayerDownsync.VelY
if jumpedOrNotList[i] {
newVy += chConfig.JumpingInitVelY // Immediately gets out of any snapping
}
playerCollider.X, playerCollider.Y = VirtualGridToPolygonColliderBLPos(newVx, newVy, playerCollider.W*0.5, playerCollider.H*0.5, 0, 0, 0, 0, collisionSpaceOffsetX, collisionSpaceOffsetY, virtualGridToWorldRatio) wx, wy := VirtualGridToWorldPos(newVx, newVy)
// Update in the collision system colliderWidth, colliderHeight := currPlayerDownsync.ColliderRadius*2, currPlayerDownsync.ColliderRadius*4
playerCollider.Update() switch currPlayerDownsync.CharacterState {
case ATK_CHARACTER_STATE_LAY_DOWN1:
colliderWidth, colliderHeight = currPlayerDownsync.ColliderRadius*4, currPlayerDownsync.ColliderRadius*2
case ATK_CHARACTER_STATE_BLOWN_UP1, ATK_CHARACTER_STATE_INAIR_IDLE1_NO_JUMP, ATK_CHARACTER_STATE_INAIR_IDLE1_BY_JUMP:
colliderWidth, colliderHeight = currPlayerDownsync.ColliderRadius*2, currPlayerDownsync.ColliderRadius*2
}
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
// Add to collision system
collisionSys.Add(playerCollider)
thatPlayerInNextFrame := nextRenderFramePlayers[i]
if currPlayerDownsync.InAir { if currPlayerDownsync.InAir {
thatPlayerInNextFrame.VelX += gravityX thatPlayerInNextFrame.VelX += GRAVITY_X
thatPlayerInNextFrame.VelY += gravityY thatPlayerInNextFrame.VelY += GRAVITY_Y
} }
} }
@ -474,13 +579,13 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
if (meleeBullet.OriginatedRenderFrameId+meleeBullet.StartupFrames <= currRenderFrame.Id) && (meleeBullet.OriginatedRenderFrameId+meleeBullet.StartupFrames+meleeBullet.ActiveFrames > currRenderFrame.Id) { if (meleeBullet.OriginatedRenderFrameId+meleeBullet.StartupFrames <= currRenderFrame.Id) && (meleeBullet.OriginatedRenderFrameId+meleeBullet.StartupFrames+meleeBullet.ActiveFrames > currRenderFrame.Id) {
offender := currRenderFrame.PlayersArr[meleeBullet.OffenderJoinIndex-1] offender := currRenderFrame.PlayersArr[meleeBullet.OffenderJoinIndex-1]
xfac := float64(1.0) // By now, straight Punch offset doesn't respect "y-axis" xfac := int32(1) // By now, straight Punch offset doesn't respect "y-axis"
if 0 > offender.DirX { if 0 > offender.DirX {
xfac = float64(-1.0) xfac = -xfac
} }
offenderWx, offenderWy := VirtualGridToWorldPos(offender.VirtualGridX, offender.VirtualGridY, virtualGridToWorldRatio) bulletWx, bulletWy := VirtualGridToWorldPos(offender.VirtualGridX+xfac*meleeBullet.HitboxOffsetX, offender.VirtualGridY)
bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy hitboxSizeWx, hitboxSizeWy := VirtualGridToWorldPos(meleeBullet.HitboxSizeX, meleeBullet.HitboxSizeY)
newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSizeX, meleeBullet.HitboxSizeY, snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap, collisionSpaceOffsetX, collisionSpaceOffsetY, meleeBullet, "MeleeBullet") 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) collisionSys.Add(newBulletCollider)
bulletColliders = append(bulletColliders, newBulletCollider) bulletColliders = append(bulletColliders, newBulletCollider)
} else { } else {
@ -491,12 +596,13 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
// 4. Calc pushbacks for each player (after its movement) w/o bullets // 4. Calc pushbacks for each player (after its movement) w/o bullets
for i, currPlayerDownsync := range currRenderFrame.PlayersArr { for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
joinIndex := currPlayerDownsync.JoinIndex joinIndex := currPlayerDownsync.JoinIndex
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex playerCollider := playerColliders[i]
playerCollider := collisionSysMap[collisionPlayerIndex]
playerShape := playerCollider.Shape.(*resolv.ConvexPolygon) playerShape := playerCollider.Shape.(*resolv.ConvexPolygon)
hardPushbackNorms[joinIndex-1] = calcHardPushbacksNorms(joinIndex, playerCollider, playerShape, snapIntoPlatformOverlap, &(effPushbacks[joinIndex-1])) hardPushbackNorms[joinIndex-1] = calcHardPushbacksNorms(joinIndex, playerCollider, playerShape, SNAP_INTO_PLATFORM_OVERLAP, &(effPushbacks[joinIndex-1]))
thatPlayerInNextFrame := nextRenderFramePlayers[i] thatPlayerInNextFrame := nextRenderFramePlayers[i]
chConfig := chConfigsOrderedByJoinIndex[i]
landedOnGravityPushback := false landedOnGravityPushback := false
if collision := playerCollider.Check(0, 0); nil != collision { if collision := playerCollider.Check(0, 0); nil != collision {
for _, obj := range collision.Objects { for _, obj := range collision.Objects {
isBarrier, isAnotherPlayer, isBullet := false, false, false isBarrier, isAnotherPlayer, isBullet := false, false, false
@ -521,7 +627,7 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
normAlignmentWithGravity := (overlapResult.OverlapX*float64(0) + overlapResult.OverlapY*float64(-1.0)) normAlignmentWithGravity := (overlapResult.OverlapX*float64(0) + overlapResult.OverlapY*float64(-1.0))
if isAnotherPlayer { if isAnotherPlayer {
// [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. // [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-snapIntoPlatformOverlap*2)*overlapResult.OverlapX, (overlapResult.Overlap-snapIntoPlatformOverlap*2)*overlapResult.OverlapY 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 _, hardPushbackNorm := range *hardPushbackNorms[joinIndex-1] {
projectedMagnitude := pushbackX*hardPushbackNorm.X + pushbackY*hardPushbackNorm.Y projectedMagnitude := pushbackX*hardPushbackNorm.X + pushbackY*hardPushbackNorm.Y
@ -533,7 +639,7 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
effPushbacks[joinIndex-1].X += pushbackX effPushbacks[joinIndex-1].X += pushbackX
effPushbacks[joinIndex-1].Y += pushbackY effPushbacks[joinIndex-1].Y += pushbackY
if snapIntoPlatformThreshold < normAlignmentWithGravity { if SNAP_INTO_PLATFORM_THRESHOLD < normAlignmentWithGravity {
landedOnGravityPushback = true landedOnGravityPushback = true
//playerColliderCenterX, playerColliderCenterY := playerCollider.Center() //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)
@ -544,75 +650,86 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
thatPlayerInNextFrame.InAir = false thatPlayerInNextFrame.InAir = false
if currPlayerDownsync.InAir && 0 >= currPlayerDownsync.VelY { if currPlayerDownsync.InAir && 0 >= currPlayerDownsync.VelY {
// fallStopping // fallStopping
thatPlayerInNextFrame.VelX = 0
thatPlayerInNextFrame.VelY = 0 thatPlayerInNextFrame.VelY = 0
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1 thatPlayerInNextFrame.VelX = 0
thatPlayerInNextFrame.FramesToRecover = 0 if _, existent := nonAttackingSet[thatPlayerInNextFrame.CharacterState]; existent {
} if ATK_CHARACTER_STATE_BLOWN_UP1 == thatPlayerInNextFrame.CharacterState {
} thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_LAY_DOWN1
if currPlayerDownsync.InAir { thatPlayerInNextFrame.FramesToRecover = chConfig.LayDownFramesToRecover
oldNextCharacterState := thatPlayerInNextFrame.CharacterState } else {
switch oldNextCharacterState { halfColliderWidthDiff, halfColliderHeightDiff := int32(0), currPlayerDownsync.ColliderRadius
case ATK_CHARACTER_STATE_IDLE1, ATK_CHARACTER_STATE_WALKING: _, halfColliderWorldHeightDiff := VirtualGridToWorldPos(halfColliderWidthDiff, halfColliderHeightDiff)
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_IDLE1 effPushbacks[joinIndex-1].Y -= halfColliderWorldHeightDiff // To prevent bouncing due to abrupt change of collider shape
case ATK_CHARACTER_STATE_ATK1: thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATK1 }
case ATK_CHARACTER_STATE_ATKED1: }
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATKED1 } else {
if _, existent := nonAttackingSet[thatPlayerInNextFrame.CharacterState]; existent {
// not fallStopping, could be in LayDown or GetUp
if ATK_CHARACTER_STATE_LAY_DOWN1 == thatPlayerInNextFrame.CharacterState {
if 0 == thatPlayerInNextFrame.FramesToRecover {
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_GET_UP1
thatPlayerInNextFrame.FramesToRecover = chConfig.GetUpFramesToRecover
}
} else if ATK_CHARACTER_STATE_GET_UP1 == thatPlayerInNextFrame.CharacterState {
if 0 == thatPlayerInNextFrame.FramesToRecover {
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1
thatPlayerInNextFrame.FramesInvinsible = chConfig.GetUpInvinsibleFrames
}
}
}
} }
} }
} }
// 5. Check bullet-anything collisions // 5. Check bullet-anything collisions
for _, bulletCollider := range bulletColliders { for _, bulletCollider := range bulletColliders {
meleeBullet := bulletCollider.Data.(*MeleeBullet)
bulletShape := bulletCollider.Shape.(*resolv.ConvexPolygon)
collision := bulletCollider.Check(0, 0) collision := bulletCollider.Check(0, 0)
bulletCollider.Space.Remove(bulletCollider) // Make sure that the bulletCollider is always removed for each renderFrame bulletCollider.Space.Remove(bulletCollider) // Make sure that the bulletCollider is always removed for each renderFrame
if nil == collision { switch v := bulletCollider.Data.(type) {
nextRenderFrameMeleeBullets = append(nextRenderFrameMeleeBullets, meleeBullet) case *MeleeBullet:
continue if nil == collision {
} nextRenderFrameMeleeBullets = append(nextRenderFrameMeleeBullets, v)
offender := currRenderFrame.PlayersArr[meleeBullet.OffenderJoinIndex-1] continue
for _, obj := range collision.Objects { }
defenderShape := obj.Shape.(*resolv.ConvexPolygon) bulletShape := bulletCollider.Shape.(*resolv.ConvexPolygon)
switch t := obj.Data.(type) { offender := currRenderFrame.PlayersArr[v.OffenderJoinIndex-1]
case *PlayerDownsync: for _, obj := range collision.Objects {
if meleeBullet.OffenderPlayerId == t.Id { defenderShape := obj.Shape.(*resolv.ConvexPolygon)
continue switch t := obj.Data.(type) {
} case *PlayerDownsync:
overlapped, _, _, _ := CalcPushbacks(0, 0, bulletShape, defenderShape) if v.OffenderJoinIndex == t.JoinIndex {
if !overlapped { continue
continue
}
joinIndex := t.JoinIndex
xfac := float64(1.0) // By now, straight Punch offset doesn't respect "y-axis"
if 0 > offender.DirX {
xfac = float64(-1.0)
}
pushbackX, pushbackY := -xfac*meleeBullet.Pushback, float64(0)
for _, hardPushbackNorm := range *hardPushbackNorms[joinIndex-1] {
projectedMagnitude := pushbackX*hardPushbackNorm.X + pushbackY*hardPushbackNorm.Y
if 0 > projectedMagnitude {
//fmt.Printf("defenderPlayerId=%d, joinIndex=%d reducing bullet pushback={%.3f, %.3f} by {%.3f, %.3f} where hardPushbackNorm={%.3f, %.3f}, projectedMagnitude=%.3f at renderFrame.id=%d", t.Id, joinIndex, pushbackX, pushbackY, projectedMagnitude*hardPushbackNorm.X, projectedMagnitude*hardPushbackNorm.Y, hardPushbackNorm.X, hardPushbackNorm.Y, projectedMagnitude, currRenderFrame.Id)
pushbackX -= projectedMagnitude * hardPushbackNorm.X
pushbackY -= projectedMagnitude * hardPushbackNorm.Y
} }
if _, existent := invinsibleSet[t.CharacterState]; existent {
continue
}
if 0 < t.FramesInvinsible {
continue
}
overlapped, _, _, _ := CalcPushbacks(0, 0, bulletShape, defenderShape)
if !overlapped {
continue
}
xfac := int32(1) // By now, straight Punch offset doesn't respect "y-axis"
if 0 > offender.DirX {
xfac = -xfac
}
pushbackVelX, pushbackVelY := xfac*v.PushbackVelX, v.PushbackVelY
atkedPlayerInNextFrame := nextRenderFramePlayers[t.JoinIndex-1]
atkedPlayerInNextFrame.VelX = pushbackVelX
atkedPlayerInNextFrame.VelY = pushbackVelY
if v.BlowUp {
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_BLOWN_UP1
} else {
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATKED1
}
oldFramesToRecover := nextRenderFramePlayers[t.JoinIndex-1].FramesToRecover
if v.HitStunFrames > oldFramesToRecover {
atkedPlayerInNextFrame.FramesToRecover = v.HitStunFrames
}
default:
} }
effPushbacks[joinIndex-1].X += pushbackX
effPushbacks[joinIndex-1].Y += pushbackY
atkedPlayerInCurFrame, atkedPlayerInNextFrame := currRenderFrame.PlayersArr[t.JoinIndex-1], nextRenderFramePlayers[t.JoinIndex-1]
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATKED1
if atkedPlayerInCurFrame.InAir {
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATKED1
}
oldFramesToRecover := nextRenderFramePlayers[t.JoinIndex-1].FramesToRecover
if meleeBullet.HitStunFrames > oldFramesToRecover {
atkedPlayerInNextFrame.FramesToRecover = meleeBullet.HitStunFrames
}
default:
} }
} }
} }
@ -620,11 +737,43 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
// 6. Get players out of stuck barriers if there's any // 6. Get players out of stuck barriers if there's any
for i, currPlayerDownsync := range currRenderFrame.PlayersArr { for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
joinIndex := currPlayerDownsync.JoinIndex joinIndex := currPlayerDownsync.JoinIndex
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex playerCollider := playerColliders[i]
playerCollider := collisionSysMap[collisionPlayerIndex]
// Update "virtual grid position" // Update "virtual grid position"
thatPlayerInNextFrame := nextRenderFramePlayers[i] 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, worldToVirtualGridRatio) 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)
// Update "CharacterState"
if thatPlayerInNextFrame.InAir {
oldNextCharacterState := thatPlayerInNextFrame.CharacterState
switch oldNextCharacterState {
case ATK_CHARACTER_STATE_IDLE1, ATK_CHARACTER_STATE_WALKING:
if jumpedOrNotList[i] || ATK_CHARACTER_STATE_INAIR_IDLE1_BY_JUMP == currPlayerDownsync.CharacterState {
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_IDLE1_BY_JUMP
} else {
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_IDLE1_NO_JUMP
}
case ATK_CHARACTER_STATE_ATK1:
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATK1
// No inAir transition for ATK2/ATK3 for now
case ATK_CHARACTER_STATE_ATKED1:
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATKED1
}
}
// Reset "FramesInChState" if "CharacterState" is changed
if thatPlayerInNextFrame.CharacterState != currPlayerDownsync.CharacterState {
thatPlayerInNextFrame.FramesInChState = 0
}
// Remove any active skill if not attacking
if _, existent := nonAttackingSet[thatPlayerInNextFrame.CharacterState]; existent {
thatPlayerInNextFrame.ActiveSkillId = int32(NO_SKILL)
thatPlayerInNextFrame.ActiveSkillHit = int32(NO_SKILL_HIT)
}
}
for _, playerCollider := range playerColliders {
playerCollider.Space.Remove(playerCollider)
} }
return &RoomDownsyncFrame{ return &RoomDownsyncFrame{

View File

@ -0,0 +1,348 @@
package battle
type SkillMapperType func(patternId int, currPlayerDownsync *PlayerDownsync) int
type CharacterConfig struct {
SpeciesId int
SpeciesName string
InAirIdleFrameIdxTurningPoint int
InAirIdleFrameIdxTurnedCycle int
LayDownFrames int32
LayDownFramesToRecover int32
GetUpInvinsibleFrames int32
GetUpFramesToRecover int32
Speed int32
JumpingInitVelY int32
SkillMapper SkillMapperType
}
var Characters = map[int]*CharacterConfig{
0: &CharacterConfig{
SpeciesId: 0,
SpeciesName: "MonkGirl",
InAirIdleFrameIdxTurningPoint: 11,
InAirIdleFrameIdxTurnedCycle: 1,
LayDownFrames: int32(16),
LayDownFramesToRecover: int32(16),
GetUpInvinsibleFrames: int32(10),
GetUpFramesToRecover: int32(27),
Speed: int32(float64(1.2) * WORLD_TO_VIRTUAL_GRID_RATIO),
JumpingInitVelY: int32(float64(8) * WORLD_TO_VIRTUAL_GRID_RATIO),
SkillMapper: func(patternId int, currPlayerDownsync *PlayerDownsync) int {
if 1 == patternId {
if 0 == currPlayerDownsync.FramesToRecover {
if currPlayerDownsync.InAir {
return 255
} else {
return 1
}
} else {
// Now that "0 < FramesToRecover", we're only able to fire any skill if it's a cancellation
if skillConfig, existent1 := skills[int(currPlayerDownsync.ActiveSkillId)]; existent1 {
switch v := skillConfig.Hits[currPlayerDownsync.ActiveSkillHit].(type) {
case *MeleeBullet:
if v.CancellableStFrame <= currPlayerDownsync.FramesInChState && currPlayerDownsync.FramesInChState < v.CancellableEdFrame {
if nextSkillId, existent2 := v.CancelTransit[patternId]; existent2 {
return nextSkillId
}
}
}
}
}
}
// By default no skill can be fired
return NO_SKILL
},
},
1: &CharacterConfig{
SpeciesId: 1,
SpeciesName: "KnifeGirl",
InAirIdleFrameIdxTurningPoint: 9,
InAirIdleFrameIdxTurnedCycle: 1,
LayDownFrames: int32(16),
LayDownFramesToRecover: int32(16),
GetUpInvinsibleFrames: int32(10),
GetUpFramesToRecover: int32(27),
Speed: int32(float64(1.4) * WORLD_TO_VIRTUAL_GRID_RATIO),
JumpingInitVelY: int32(float64(7.5) * WORLD_TO_VIRTUAL_GRID_RATIO),
SkillMapper: func(patternId int, currPlayerDownsync *PlayerDownsync) int {
if 1 == patternId {
if 0 == currPlayerDownsync.FramesToRecover {
if currPlayerDownsync.InAir {
return 256
} else {
return 4
}
} else {
// Now that "0 < FramesToRecover", we're only able to fire any skill if it's a cancellation
if skillConfig, existent1 := skills[int(currPlayerDownsync.ActiveSkillId)]; existent1 {
switch v := skillConfig.Hits[currPlayerDownsync.ActiveSkillHit].(type) {
case *MeleeBullet:
if v.CancellableStFrame <= currPlayerDownsync.FramesInChState && currPlayerDownsync.FramesInChState < v.CancellableEdFrame {
if nextSkillId, existent2 := v.CancelTransit[patternId]; existent2 {
return nextSkillId
}
}
}
}
}
}
// By default no skill can be fired
return NO_SKILL
},
},
}
var skills = map[int]*Skill{
1: &Skill{
RecoveryFrames: int32(30),
RecoveryFramesOnBlock: int32(30),
RecoveryFramesOnHit: int32(30),
ReleaseTriggerType: int32(1),
BoundChState: ATK_CHARACTER_STATE_ATK1,
Hits: []interface{}{
&MeleeBullet{
Bullet: Bullet{
StartupFrames: int32(7),
ActiveFrames: int32(22),
HitStunFrames: int32(13),
BlockStunFrames: int32(9),
Damage: int32(5),
SelfLockVelX: int32(float64(0.05) * WORLD_TO_VIRTUAL_GRID_RATIO),
SelfLockVelY: NO_LOCK_VEL,
PushbackVelX: int32(float64(0.5) * WORLD_TO_VIRTUAL_GRID_RATIO),
PushbackVelY: int32(0),
HitboxOffsetX: int32(float64(12) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxOffsetY: int32(0),
HitboxSizeX: int32(float64(24) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxSizeY: int32(float64(32) * WORLD_TO_VIRTUAL_GRID_RATIO),
CancellableStFrame: int32(13),
CancellableEdFrame: int32(30),
CancelTransit: map[int]int{
1: 2,
},
// TODO: Use non-zero "selfLockVel"
},
},
},
},
2: &Skill{
RecoveryFrames: int32(36),
RecoveryFramesOnBlock: int32(36),
RecoveryFramesOnHit: int32(36),
ReleaseTriggerType: int32(1),
BoundChState: ATK_CHARACTER_STATE_ATK2,
Hits: []interface{}{
&MeleeBullet{
Bullet: Bullet{
StartupFrames: int32(18),
ActiveFrames: int32(18),
HitStunFrames: int32(18),
BlockStunFrames: int32(9),
Damage: int32(5),
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),
HitboxOffsetX: int32(float64(18) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxOffsetY: int32(0),
HitboxSizeX: int32(float64(24) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxSizeY: int32(float64(32) * WORLD_TO_VIRTUAL_GRID_RATIO),
CancellableStFrame: int32(22),
CancellableEdFrame: int32(36),
CancelTransit: map[int]int{
1: 3,
},
},
},
},
},
3: &Skill{
RecoveryFrames: int32(50),
RecoveryFramesOnBlock: int32(50),
RecoveryFramesOnHit: int32(50),
ReleaseTriggerType: int32(1),
BoundChState: ATK_CHARACTER_STATE_ATK3,
Hits: []interface{}{
&MeleeBullet{
Bullet: Bullet{
StartupFrames: int32(15),
ActiveFrames: int32(30),
HitStunFrames: MAX_INT32,
BlockStunFrames: int32(9),
Damage: int32(10),
SelfLockVelX: int32(float64(0.5) * WORLD_TO_VIRTUAL_GRID_RATIO),
SelfLockVelY: int32(float64(5) * WORLD_TO_VIRTUAL_GRID_RATIO),
PushbackVelX: int32(float64(2) * WORLD_TO_VIRTUAL_GRID_RATIO),
PushbackVelY: int32(float64(7) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxOffsetX: int32(float64(32) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxOffsetY: int32(0),
HitboxSizeX: int32(float64(48) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxSizeY: int32(float64(32) * WORLD_TO_VIRTUAL_GRID_RATIO),
BlowUp: true,
},
},
},
},
4: &Skill{
RecoveryFrames: int32(30),
RecoveryFramesOnBlock: int32(30),
RecoveryFramesOnHit: int32(30),
ReleaseTriggerType: int32(1),
BoundChState: ATK_CHARACTER_STATE_ATK1,
Hits: []interface{}{
&MeleeBullet{
Bullet: Bullet{
StartupFrames: int32(7),
ActiveFrames: int32(22),
HitStunFrames: int32(13),
BlockStunFrames: int32(9),
Damage: int32(5),
SelfLockVelX: int32(float64(0.05) * WORLD_TO_VIRTUAL_GRID_RATIO),
SelfLockVelY: NO_LOCK_VEL,
PushbackVelX: int32(float64(0.5) * WORLD_TO_VIRTUAL_GRID_RATIO),
PushbackVelY: int32(0),
HitboxOffsetX: int32(float64(12) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxOffsetY: int32(0),
HitboxSizeX: int32(float64(24) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxSizeY: int32(float64(32) * WORLD_TO_VIRTUAL_GRID_RATIO),
CancellableStFrame: int32(13),
CancellableEdFrame: int32(30),
CancelTransit: map[int]int{
1: 5,
},
// TODO: Use non-zero "selfLockVel"
},
},
},
},
5: &Skill{
RecoveryFrames: int32(36),
RecoveryFramesOnBlock: int32(36),
RecoveryFramesOnHit: int32(36),
ReleaseTriggerType: int32(1),
BoundChState: ATK_CHARACTER_STATE_ATK2,
Hits: []interface{}{
&MeleeBullet{
Bullet: Bullet{
StartupFrames: int32(18),
ActiveFrames: int32(18),
HitStunFrames: int32(18),
BlockStunFrames: int32(9),
Damage: int32(5),
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),
HitboxOffsetX: int32(float64(18) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxOffsetY: int32(0),
HitboxSizeX: int32(float64(24) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxSizeY: int32(float64(32) * WORLD_TO_VIRTUAL_GRID_RATIO),
CancellableStFrame: int32(22),
CancellableEdFrame: int32(36),
CancelTransit: map[int]int{
1: 6,
},
},
},
},
},
6: &Skill{
RecoveryFrames: int32(45),
RecoveryFramesOnBlock: int32(45),
RecoveryFramesOnHit: int32(45),
ReleaseTriggerType: int32(1),
BoundChState: ATK_CHARACTER_STATE_ATK3,
Hits: []interface{}{
&MeleeBullet{
Bullet: Bullet{
StartupFrames: int32(15),
ActiveFrames: int32(28),
HitStunFrames: MAX_INT32,
BlockStunFrames: int32(9),
Damage: int32(10),
SelfLockVelX: int32(float64(-0.1) * WORLD_TO_VIRTUAL_GRID_RATIO),
SelfLockVelY: NO_LOCK_VEL,
PushbackVelX: int32(float64(2) * WORLD_TO_VIRTUAL_GRID_RATIO),
PushbackVelY: int32(float64(7) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxOffsetX: int32(float64(24) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxOffsetY: int32(0),
HitboxSizeX: int32(float64(32) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxSizeY: int32(float64(32) * WORLD_TO_VIRTUAL_GRID_RATIO),
BlowUp: true,
},
},
},
},
255: &Skill{
RecoveryFrames: int32(30),
RecoveryFramesOnBlock: int32(30),
RecoveryFramesOnHit: int32(30),
ReleaseTriggerType: int32(1),
BoundChState: ATK_CHARACTER_STATE_INAIR_ATK1,
Hits: []interface{}{
&MeleeBullet{
Bullet: Bullet{
StartupFrames: int32(3),
ActiveFrames: int32(20),
HitStunFrames: int32(18),
BlockStunFrames: int32(9),
Damage: int32(5),
SelfLockVelX: NO_LOCK_VEL,
SelfLockVelY: NO_LOCK_VEL,
PushbackVelX: int32(float64(0.5) * WORLD_TO_VIRTUAL_GRID_RATIO),
PushbackVelY: int32(0),
HitboxOffsetX: int32(float64(12) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxOffsetY: int32(0),
HitboxSizeX: int32(float64(32) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxSizeY: int32(float64(24) * WORLD_TO_VIRTUAL_GRID_RATIO),
},
},
},
},
256: &Skill{
RecoveryFrames: int32(20),
RecoveryFramesOnBlock: int32(20),
RecoveryFramesOnHit: int32(20),
ReleaseTriggerType: int32(1),
BoundChState: ATK_CHARACTER_STATE_INAIR_ATK1,
Hits: []interface{}{
&MeleeBullet{
Bullet: Bullet{
StartupFrames: int32(3),
ActiveFrames: int32(10),
HitStunFrames: int32(15),
BlockStunFrames: int32(9),
Damage: int32(5),
SelfLockVelX: NO_LOCK_VEL,
SelfLockVelY: NO_LOCK_VEL,
PushbackVelX: int32(float64(0.5) * WORLD_TO_VIRTUAL_GRID_RATIO),
PushbackVelY: int32(0),
HitboxOffsetX: int32(float64(12) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxOffsetY: int32(0),
HitboxSizeX: int32(float64(32) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxSizeY: int32(float64(24) * WORLD_TO_VIRTUAL_GRID_RATIO),
},
},
},
},
}

View File

@ -23,15 +23,21 @@ type PlayerDownsync struct {
Speed int32 Speed int32
BattleState int32 BattleState int32
JoinIndex int32 JoinIndex int32
ColliderRadius float64 ColliderRadius int32
Removed bool Removed bool
Score int32 Score int32
LastMoveGmtMillis int32 LastMoveGmtMillis int32
FramesToRecover int32 FramesToRecover int32
FramesInChState int32
Hp int32 Hp int32
MaxHp int32 MaxHp int32
CharacterState int32 CharacterState int32
InAir bool InAir bool
ActiveSkillId int32
ActiveSkillHit int32
FramesInvinsible int32
} }
type InputFrameDecoded struct { type InputFrameDecoded struct {
@ -52,27 +58,31 @@ type Barrier struct {
type Bullet struct { type Bullet struct {
// for offender // for offender
BattleLocalId int32 OriginatedRenderFrameId int32 // Copied from the first bullet for all subsequent bullets
StartupFrames int32 OffenderJoinIndex int32 // Copied to favor collision handling of the dispatched bullet
StartupFrames int32 // from "OriginatedRenderFrameId"
CancellableStFrame int32 // from "OriginatedRenderFrameId"
CancellableEdFrame int32 // from "OriginatedRenderFrameId"
ActiveFrames int32 ActiveFrames int32
RecoveryFrames int32
RecoveryFramesOnBlock int32
RecoveryFramesOnHit int32
HitboxOffset float64
OriginatedRenderFrameId int32
// for defender
HitStunFrames int32
BlockStunFrames int32
Pushback float64
ReleaseTriggerType int32
Damage int32
OffenderJoinIndex int32
OffenderPlayerId int32
SelfMoveforwardX float64 // for defender
SelfMoveforwardY float64 HitStunFrames int32
HitboxSizeX float64 BlockStunFrames int32
HitboxSizeY float64 PushbackVelX int32
PushbackVelY int32
Damage int32
SelfLockVelX int32
SelfLockVelY int32
HitboxOffsetX int32
HitboxOffsetY int32
HitboxSizeX int32
HitboxSizeY int32
BlowUp bool
CancelTransit map[int]int
} }
type MeleeBullet struct { type MeleeBullet struct {
@ -90,6 +100,16 @@ type FireballBullet struct {
Bullet Bullet
} }
type Skill struct {
BattleLocalId int32
RecoveryFrames int32
RecoveryFramesOnBlock int32
RecoveryFramesOnHit int32
ReleaseTriggerType int32 // 1: rising-edge, 2: falling-edge
BoundChState int32
Hits []interface{} // Hits within a "Skill" are automatically triggered
}
type RoomDownsyncFrame struct { type RoomDownsyncFrame struct {
Id int32 Id int32
PlayersArr []*PlayerDownsync PlayersArr []*PlayerDownsync

View File

@ -42,50 +42,57 @@ func NewBarrierJs(boundary *Polygon2D) *js.Object {
}) })
} }
func NewPlayerDownsyncJs(id, virtualGridX, virtualGridY, dirX, dirY, velX, velY, framesToRecover, speed, battleState, characterState, joinIndex, hp, maxHp int32, inAir bool, colliderRadius float64) *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 bool) *js.Object {
return js.MakeWrapper(&PlayerDownsync{ return js.MakeWrapper(&PlayerDownsync{
Id: id, Id: id,
VirtualGridX: virtualGridX, VirtualGridX: virtualGridX,
VirtualGridY: virtualGridY, VirtualGridY: virtualGridY,
DirX: dirX, DirX: dirX,
DirY: dirY, DirY: dirY,
VelX: velX, VelX: velX,
VelY: velY, VelY: velY,
FramesToRecover: framesToRecover, FramesToRecover: framesToRecover,
Speed: speed, FramesInChState: framesInChState,
BattleState: battleState, ActiveSkillId: activeSkillId,
JoinIndex: joinIndex, ActiveSkillHit: activeSkillHit,
ColliderRadius: colliderRadius, FramesInvinsible: framesInvinsible,
Hp: hp, Speed: speed,
MaxHp: maxHp, BattleState: battleState,
CharacterState: characterState, CharacterState: characterState,
InAir: inAir, JoinIndex: joinIndex,
Hp: hp,
MaxHp: maxHp,
ColliderRadius: colliderRadius,
InAir: inAir,
}) })
} }
func NewMeleeBulletJs(battleLocalId, startupFrames, activeFrames, recoveryFrames, recoveryFramesOnBlock, recoveryFramesOnHit, hitStunFrames, blockStunFrames, releaseTriggerType, damage, offenderJoinIndex, offenderPlayerId int32, pushback, hitboxOffset, selfMoveforwardX, selfMoveforwardY, hitboxSizeX, hitboxSizeY float64) *js.Object { func NewMeleeBulletJs(originatedRenderFrameId, offenderJoinIndex, startupFrames, cancellableStFrame, cancellableEdFrame, activeFrames, hitStunFrames, blockStunFrames, pushbackVelX, pushbackVelY, damage, selfLockVelX, selfLockVelY, hitboxOffsetX, hitboxOffsetY, hitboxSizeX, hitboxSizeY int32, blowUp bool) *js.Object {
return js.MakeWrapper(&MeleeBullet{ return js.MakeWrapper(&MeleeBullet{
Bullet: Bullet{ Bullet: Bullet{
BattleLocalId: battleLocalId, OriginatedRenderFrameId: originatedRenderFrameId,
StartupFrames: startupFrames, OffenderJoinIndex: offenderJoinIndex,
ActiveFrames: activeFrames,
RecoveryFrames: recoveryFrames,
RecoveryFramesOnBlock: recoveryFramesOnBlock,
RecoveryFramesOnHit: recoveryFramesOnHit,
HitboxOffset: hitboxOffset,
HitStunFrames: hitStunFrames,
BlockStunFrames: blockStunFrames,
Pushback: pushback,
ReleaseTriggerType: releaseTriggerType,
Damage: damage,
SelfMoveforwardX: selfMoveforwardX, StartupFrames: startupFrames,
SelfMoveforwardY: selfMoveforwardY, CancellableStFrame: cancellableStFrame,
HitboxSizeX: hitboxSizeX, CancellableEdFrame: cancellableEdFrame,
HitboxSizeY: hitboxSizeY, ActiveFrames: activeFrames,
OffenderJoinIndex: offenderJoinIndex, HitStunFrames: hitStunFrames,
OffenderPlayerId: offenderPlayerId, BlockStunFrames: blockStunFrames,
PushbackVelX: pushbackVelX,
PushbackVelY: pushbackVelY,
Damage: damage,
SelfLockVelX: selfLockVelX,
SelfLockVelY: selfLockVelY,
HitboxOffsetX: hitboxOffsetX,
HitboxOffsetY: hitboxOffsetY,
HitboxSizeX: hitboxSizeX,
HitboxSizeY: hitboxSizeY,
BlowUp: blowUp,
}, },
}) })
} }
@ -109,18 +116,19 @@ func GetCollisionSpaceObjsJs(space *resolv.Space) []*js.Object {
return ret return ret
} }
func GenerateRectColliderJs(wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) *js.Object { 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. [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 space = gopkgs.NewCollisionSpaceJs(2048, 2048, 8, 8);
var a = gopkgs.GenerateRectColliderJs(189, 497, 48, 48, snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap, spaceOffsetX, spaceOffsetY, "Player"); var a = gopkgs.GenerateRectColliderJs(189, 497, 48, 48, spaceOffsetX, spaceOffsetY, "Player");
space.Add(a); 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. 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. 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)) return js.MakeFullWrapper(GenerateRectCollider(wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, data, tag))
} }
@ -129,27 +137,43 @@ func GenerateConvexPolygonColliderJs(unalignedSrc *Polygon2D, spaceOffsetX, spac
return js.MakeFullWrapper(GenerateConvexPolygonCollider(unalignedSrc, spaceOffsetX, spaceOffsetY, data, tag)) return js.MakeFullWrapper(GenerateConvexPolygonCollider(unalignedSrc, spaceOffsetX, spaceOffsetY, data, tag))
} }
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(inputsBuffer *RingBuffer, currRenderFrame *RoomDownsyncFrame, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, gravityX, gravityY, jumpingInitVelY, inputDelayFrames int32, inputScaleFrames uint32, collisionSpaceOffsetX, collisionSpaceOffsetY, snapIntoPlatformOverlap, snapIntoPlatformThreshold, worldToVirtualGridRatio, virtualGridToWorldRatio float64, playerOpPatternToSkillId map[int]int) *js.Object { func GetCharacterConfigsOrderedByJoinIndex(speciesIdList []int) []*js.Object {
ret := make([]*js.Object, len(speciesIdList), len(speciesIdList))
for i, speciesId := range speciesIdList {
ret[i] = js.MakeFullWrapper(Characters[speciesId])
}
return ret
}
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(inputsBuffer *RingBuffer, currRenderFrame *RoomDownsyncFrame, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, collisionSpaceOffsetX, collisionSpaceOffsetY float64, chConfigsOrderedByJoinIndex []*CharacterConfig) *js.Object {
// We need access to all fields of RoomDownsyncFrame for displaying in frontend // We need access to all fields of RoomDownsyncFrame for displaying in frontend
return js.MakeFullWrapper(ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer, currRenderFrame, collisionSys, collisionSysMap, gravityX, gravityY, jumpingInitVelY, inputDelayFrames, inputScaleFrames, collisionSpaceOffsetX, collisionSpaceOffsetY, snapIntoPlatformOverlap, snapIntoPlatformThreshold, worldToVirtualGridRatio, virtualGridToWorldRatio, playerOpPatternToSkillId)) return js.MakeFullWrapper(ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer, currRenderFrame, collisionSys, collisionSysMap, collisionSpaceOffsetX, collisionSpaceOffsetY, chConfigsOrderedByJoinIndex))
} }
func main() { func main() {
js.Global.Set("gopkgs", map[string]interface{}{ js.Global.Set("gopkgs", map[string]interface{}{
"NewVec2DJs": NewVec2DJs, "NewVec2DJs": NewVec2DJs,
"NewPolygon2DJs": NewPolygon2DJs, "NewPolygon2DJs": NewPolygon2DJs,
"NewBarrierJs": NewBarrierJs, "NewBarrierJs": NewBarrierJs,
"NewPlayerDownsyncJs": NewPlayerDownsyncJs, "NewPlayerDownsyncJs": NewPlayerDownsyncJs,
"NewMeleeBulletJs": NewMeleeBulletJs, "NewMeleeBulletJs": NewMeleeBulletJs,
"NewRoomDownsyncFrameJs": NewRoomDownsyncFrameJs, "NewRoomDownsyncFrameJs": NewRoomDownsyncFrameJs,
"NewCollisionSpaceJs": NewCollisionSpaceJs, "NewCollisionSpaceJs": NewCollisionSpaceJs,
"NewInputFrameDownsync": NewInputFrameDownsync, "NewInputFrameDownsync": NewInputFrameDownsync,
"NewRingBufferJs": NewRingBufferJs, "NewRingBufferJs": NewRingBufferJs,
"GenerateRectColliderJs": GenerateRectColliderJs, "GenerateRectColliderJs": GenerateRectColliderJs,
"GenerateConvexPolygonColliderJs": GenerateConvexPolygonColliderJs, "GenerateConvexPolygonColliderJs": GenerateConvexPolygonColliderJs,
"GetCollisionSpaceObjsJs": GetCollisionSpaceObjsJs, "GetCollisionSpaceObjsJs": GetCollisionSpaceObjsJs,
"ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs": ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs,
"WorldToPolygonColliderBLPos": WorldToPolygonColliderBLPos, // No need to wrap primitive return types "WorldToPolygonColliderBLPos": WorldToPolygonColliderBLPos, // No need to wrap primitive return types
"PolygonColliderBLToWorldPos": PolygonColliderBLToWorldPos, "PolygonColliderBLToWorldPos": PolygonColliderBLToWorldPos,
"WorldToVirtualGridPos": WorldToVirtualGridPos,
"VirtualGridToWorldPos": VirtualGridToWorldPos,
"GetCharacterConfigsOrderedByJoinIndex": GetCharacterConfigsOrderedByJoinIndex,
"ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs": ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs,
"ConvertToDelayedInputFrameId": ConvertToDelayedInputFrameId,
"ConvertToNoDelayInputFrameId": ConvertToNoDelayInputFrameId,
"ConvertToFirstUsedRenderFrameId": ConvertToFirstUsedRenderFrameId,
"ConvertToLastUsedRenderFrameId": ConvertToLastUsedRenderFrameId,
"ShouldGenerateInputFrameUpsync": ShouldGenerateInputFrameUpsync,
}) })
} }

View File

@ -285,27 +285,27 @@ func (obj *Object) Check(dx, dy float64, tags ...string) *Collision {
} }
/* /*
// 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`. // 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() ox, oy := cc.checkingObject.Center()
oc := Vector{ox, oy} oc := Vector{ox, oy}
sort.Slice(cc.Objects, func(i, j int) bool { sort.Slice(cc.Objects, func(i, j int) bool {
ix, iy := cc.Objects[i].Center() ix, iy := cc.Objects[i].Center()
jx, jy := cc.Objects[j].Center() jx, jy := cc.Objects[j].Center()
return Vector{ix, iy}.Sub(oc).Magnitude2() < Vector{jx, jy}.Sub(oc).Magnitude2() return Vector{ix, iy}.Sub(oc).Magnitude2() < Vector{jx, jy}.Sub(oc).Magnitude2()
}) })
cw := cc.checkingObject.Space.CellWidth cw := cc.checkingObject.Space.CellWidth
ch := cc.checkingObject.Space.CellHeight ch := cc.checkingObject.Space.CellHeight
sort.Slice(cc.Cells, func(i, j int) bool { 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() < 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() Vector{float64(cc.Cells[j].X*cw + (cw / 2)), float64(cc.Cells[j].Y*ch + (ch / 2))}.Sub(oc).Magnitude2()
}) })
*/ */
return cc return cc